/* * File: mon-util.cc * Summary: Misc monster related functions. * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <2> 11/04/99 cdl added a break to spell selection * for kobold Summoners * print just "[Ii]t" for invisible undead * renamed params to monam() * <1> -/--/-- LRH Created */ // $pellbinder: (c) D.G.S.E 1998 // some routines snatched from former monsstat.cc #include "AppHdr.h" #include "enum.h" #include "mon-util.h" #include "monstuff.h" #include #include #include #include #include #include #include "externs.h" #include "beam.h" #include "database.h" #include "debug.h" #include "delay.h" #include "dgnevent.h" #include "fight.h" #include "ghost.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "it_use2.h" #include "Kills.h" #include "message.h" #include "misc.h" #include "monplace.h" #include "mstuff2.h" #include "mtransit.h" #include "player.h" #include "randart.h" #include "religion.h" #include "shopping.h" // for item values #include "spells3.h" #include "spl-util.h" #include "stuff.h" #include "terrain.h" #include "tiles.h" #include "traps.h" #include "tutorial.h" #include "view.h" //jmf: moved from inside function static FixedVector < int, NUM_MONSTERS > mon_entry; struct mon_spellbook { mon_spellbook_type type; spell_type spells[NUM_MONSTER_SPELL_SLOTS]; }; mon_display monster_symbols[NUM_MONSTERS]; // Really important extern -- screen redraws suck w/o it! {dlb} FixedVector < unsigned short, 1000 > mcolour; static bool initialized_randmons = false; static std::vector monsters_by_habitat[NUM_HABITATS]; static monsterentry mondata[] = { #include "mon-data.h" }; #define MONDATASIZE ARRAYSZ(mondata) static mon_spellbook mspell_list[] = { #include "mon-spll.h" }; static int _mons_exp_mod(int mclass); // Macro that saves some typing, nothing more. #define smc get_monster_data(mc) /* ******************** BEGIN PUBLIC FUNCTIONS ******************** */ habitat_type grid2habitat(dungeon_feature_type grid) { if (grid_is_watery(grid)) return (HT_WATER); if (grid_is_rock(grid) && !grid_is_permarock(grid)) return (HT_ROCK); switch (grid) { case DNGN_LAVA: return (HT_LAVA); case DNGN_FLOOR: default: return (HT_LAND); } } dungeon_feature_type habitat2grid(habitat_type ht) { switch (ht) { case HT_WATER: return (DNGN_DEEP_WATER); case HT_LAVA: return (DNGN_LAVA); case HT_ROCK: return (DNGN_ROCK_WALL); case HT_LAND: default: return (DNGN_FLOOR); } } static void _initialize_randmons() { for (int i = 0; i < NUM_HABITATS; ++i) { const dungeon_feature_type grid = habitat2grid( habitat_type(i) ); for (int m = 0; m < NUM_MONSTERS; ++m) { if (invalid_monster_class(m)) continue; if (monster_habitable_grid(m, grid)) monsters_by_habitat[i].push_back(static_cast(m)); } } initialized_randmons = true; } monster_type random_monster_at_grid(int x, int y) { return (random_monster_at_grid(grd[x][y])); } monster_type random_monster_at_grid(dungeon_feature_type grid) { if (!initialized_randmons) _initialize_randmons(); const habitat_type ht = grid2habitat(grid); const std::vector &valid_mons = monsters_by_habitat[ht]; ASSERT(!valid_mons.empty()); return (valid_mons.empty() ? MONS_PROGRAM_BUG : valid_mons[ random2(valid_mons.size()) ]); } typedef std::map mon_name_map; static mon_name_map Mon_Name_Cache; void init_mon_name_cache() { for (unsigned i = 0; i < sizeof(mondata) / sizeof(*mondata); ++i) { std::string name = mondata[i].name; lowercase(name); const int mtype = mondata[i].mc; const monster_type mon = monster_type(mtype); // Deal sensibly with duplicate entries; refuse or allow the insert, // depending on which should take precedence. Mostly we don't care, // except looking up "rakshasa" and getting _FAKE breaks ?/M rakshasa. if (Mon_Name_Cache.find(name) != Mon_Name_Cache.end()) { if (mon == MONS_RAKSHASA_FAKE || mon == MONS_ARMOUR_MIMIC || mon == MONS_SCROLL_MIMIC || mon == MONS_POTION_MIMIC || mon == MONS_ABOMINATION_LARGE) { // Keep previous entry. continue; } else { DEBUGSTR("Un-handled duplicate monster name: %s", name.c_str()); ASSERT(false); } } Mon_Name_Cache[name] = mon; } } monster_type get_monster_by_name(std::string name, bool exact) { lowercase(name); if (exact) { mon_name_map::iterator i = Mon_Name_Cache.find(name); if (i != Mon_Name_Cache.end()) return static_cast(i->second); return MONS_PROGRAM_BUG; } monster_type mon = MONS_PROGRAM_BUG; for (unsigned i = 0; i < sizeof(mondata) / sizeof(*mondata); ++i) { std::string candidate = mondata[i].name; lowercase(candidate); const int mtype = mondata[i].mc; const std::string::size_type match = candidate.find(name); if (match == std::string::npos) continue; mon = monster_type(mtype); // We prefer prefixes over partial matches. if (match == 0) break; } return (mon); } void init_monsters(FixedVector < unsigned short, 1000 > &colour) { unsigned int x; // must be unsigned to match size_t {dlb} for (x = 0; x < MONDATASIZE; x++) colour[mondata[x].mc] = mondata[x].colour; // First, fill static array with dummy values. {dlb} mon_entry.init(-1); // Next, fill static array with location of entry in mondata[]. {dlb}: for (x = 0; x < MONDATASIZE; x++) mon_entry[mondata[x].mc] = x; // Finally, monsters yet with dummy entries point to TTTSNB(tm). {dlb}: for (x = 0; x < NUM_MONSTERS; x++) if (mon_entry[x] == -1) mon_entry[x] = mon_entry[MONS_PROGRAM_BUG]; init_monster_symbols(); } void init_monster_symbols() { for (int i = 0; i < NUM_MONSTERS; ++i) { mon_display &md = monster_symbols[i]; const monsterentry *me = get_monster_data(i); if (me) { md.glyph = me->showchar; md.colour = me->colour; } } for (int i = 0, size = Options.mon_glyph_overrides.size(); i < size; ++i) { const mon_display &md = Options.mon_glyph_overrides[i]; if (md.type == MONS_PROGRAM_BUG) continue; if (md.glyph) monster_symbols[md.type].glyph = md.glyph; if (md.colour) monster_symbols[md.type].colour = md.colour; } } const mon_resist_def &get_mons_class_resists(int mc) { const monsterentry *me = get_monster_data(mc); return (me? me->resists : get_monster_data(MONS_PROGRAM_BUG)->resists); } mon_resist_def get_mons_resists(const monsters *mon) { mon_resist_def resists; if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON) resists = (mon->ghost->resists); else resists = mon_resist_def(); resists |= get_mons_class_resists(mon->type); if (mons_genus(mon->type) == MONS_DRACONIAN && mon->type != MONS_DRACONIAN || mon->type == MONS_TIAMAT) { monster_type draco_species = draco_subspecies(mon); if (draco_species != mon->type) resists |= get_mons_class_resists(draco_species); } return (resists); } monsters *monster_at(const coord_def &pos) { const int mindex = mgrd(pos); return (mindex != NON_MONSTER? &menv[mindex] : NULL); } int mons_piety(const monsters *mon) { if (mon->god == GOD_NO_GOD) return (0); // We're open to fine-tuning. return (mon->hit_dice * 14); } bool mons_class_flag(int mc, int bf) { const monsterentry *me = smc; if (!me) return (false); return ((me->bitfields & bf) != 0); } static int _scan_mon_inv_randarts( const monsters *mon, randart_prop_type ra_prop) { int ret = 0; if (mons_itemuse( mon->type ) >= MONUSE_STARTING_EQUIPMENT) { const int weapon = mon->inv[MSLOT_WEAPON]; const int second = mon->inv[MSLOT_MISSILE]; // Two-headed ogres, etc. const int armour = mon->inv[MSLOT_ARMOUR]; if (weapon != NON_ITEM && mitm[weapon].base_type == OBJ_WEAPONS && is_random_artefact( mitm[weapon] )) { ret += randart_wpn_property( mitm[weapon], ra_prop ); } if (second != NON_ITEM && mitm[second].base_type == OBJ_WEAPONS && is_random_artefact( mitm[second] )) { ret += randart_wpn_property( mitm[second], ra_prop ); } if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR && is_random_artefact( mitm[armour] )) { ret += randart_wpn_property( mitm[armour], ra_prop ); } } return (ret); } // Returns true if a given monster is an abomination created by // Twisted Resurrection. static bool _mons_your_abomination(const monsters *mon) { return ((mon->type == MONS_ABOMINATION_SMALL || mon->type == MONS_ABOMINATION_LARGE) && testbits(mon->flags, MF_CREATED_FRIENDLY)); } mon_holy_type mons_holiness(const monsters *mon) { if (_mons_your_abomination(mon)) return (MH_UNDEAD); return (mons_class_holiness(mon->type)); } mon_holy_type mons_class_holiness(int mc) { return (smc->holiness); } bool mons_class_is_confusable(int mc) { return (smc->resist_magic < MAG_IMMUNE && mons_class_holiness(mc) != MH_NONLIVING && mons_class_holiness(mc) != MH_PLANT); } bool mons_class_is_slowable(int mc) { return (smc->resist_magic < MAG_IMMUNE); } bool mons_is_wall_shielded(int mc) { return (mons_habitat_by_type(mc) == HT_ROCK); } bool mons_class_is_stationary(int mc) { return mons_class_flag(mc, M_STATIONARY); } bool mons_is_stationary(const monsters *mon) { return mons_class_is_stationary(mon->type); } bool mons_is_insubstantial(int mc) { return mons_class_flag(mc, M_INSUBSTANTIAL); } bool mons_has_blood(int mc) { return (mons_class_flag(mc, M_COLD_BLOOD) || mons_class_flag(mc, M_WARM_BLOOD)); } bool mons_behaviour_perceptible(const monsters *mon) { return (!mons_class_flag(mon->type, M_NO_EXP_GAIN) && !mons_is_mimic(mon->type) && !mons_is_statue(mon->type) && mon->type != MONS_OKLOB_PLANT); } // Returns true for monsters that obviously (to the player) feel // "thematically at home" in a branch. Currently used for native // monsters recognizing traps and patrolling branch entrances. bool mons_is_native_in_branch(const monsters *monster, const branch_type branch) { switch (branch) { case BRANCH_ELVEN_HALLS: return (mons_species(monster->type) == MONS_ELF); case BRANCH_ORCISH_MINES: return (mons_species(monster->type) == MONS_ORC); case BRANCH_SHOALS: return (mons_species(monster->type) == MONS_CYCLOPS || mons_species(monster->type) == MONS_MERFOLK || mons_species(monster->type) == MONS_MERMAID); case BRANCH_SLIME_PITS: return (mons_species(monster->type) == MONS_JELLY); case BRANCH_SNAKE_PIT: return (mons_species(monster->type) == MONS_NAGA || mons_species(monster->type) == MONS_SNAKE); case BRANCH_HALL_OF_ZOT: return (mons_species(monster->type) == MONS_DRACONIAN || monster->type == MONS_ORB_GUARDIAN); case BRANCH_TOMB: return (mons_species(monster->type) == MONS_MUMMY); case BRANCH_HIVE: return (monster->type == MONS_KILLER_BEE_LARVA || monster->type == MONS_KILLER_BEE || monster->type == MONS_QUEEN_BEE); case BRANCH_HALL_OF_BLADES: return (monster->type == MONS_DANCING_WEAPON); default: return (false); } } bool mons_is_chaotic(const monsters *mon) { if (mon->has_ench(ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER)) return (true); if (mon->has_spell(SPELL_POLYMORPH_OTHER)) return (true); const int attk_flavour = mons_attack_spec(mon, 0).flavour; return (attk_flavour == AF_MUTATE || attk_flavour == AF_ROT); } bool mons_is_poisoner(const monsters *mon) { if (mons_corpse_effect(mon->type) == CE_POISONOUS) return (true); const int attk_flavour = mons_attack_spec(mon, 0).flavour; return (attk_flavour == AF_POISON || attk_flavour == AF_POISON_NASTY || attk_flavour == AF_POISON_MEDIUM || attk_flavour == AF_POISON_STRONG || attk_flavour == AF_POISON_STR); } bool mons_is_icy(const monsters *mon) { return (mons_is_icy(mon->type)); } bool mons_is_icy(int mtype) { return (mtype == MONS_ICE_BEAST || mtype == MONS_SIMULACRUM_SMALL || mtype == MONS_SIMULACRUM_LARGE || mtype == MONS_ICE_STATUE); } bool invalid_monster(const monsters *mon) { return (!mon || mon->type == -1); } bool invalid_monster_class(int mclass) { return (mclass < 0 || mclass >= NUM_MONSTERS || mon_entry[mclass] == -1 || mon_entry[mclass] == MONS_PROGRAM_BUG); } bool invalid_monster_index(int i) { return (i < 0 || i >= MAX_MONSTERS); } bool mons_is_statue(int mc) { return (mc == MONS_ORANGE_STATUE || mc == MONS_SILVER_STATUE || mc == MONS_ICE_STATUE); } bool mons_is_mimic(int mc) { return (mons_species(mc) == MONS_GOLD_MIMIC); } bool mons_is_demon(int mc) { const int show_char = mons_char(mc); // Not every demonic monster is a demon (hell hog, hell hound, etc.) if (mons_class_holiness(mc) == MH_DEMONIC && (isdigit(show_char) || show_char == '&')) { return (true); } return (false); } int mons_zombie_size(int mc) { return (smc->zombie_size); } int mons_weight(int mc) { return (smc->weight); } corpse_effect_type mons_corpse_effect(int mc) { return (smc->corpse_thingy); } monster_type mons_species(int mc) { const monsterentry *me = get_monster_data(mc); return (me? me->species : MONS_PROGRAM_BUG); } monster_type mons_genus(int mc) { return (smc->genus); } monster_type draco_subspecies(const monsters *mon) { ASSERT( mons_genus( mon->type ) == MONS_DRACONIAN ); if (mon->type == MONS_TIAMAT) { switch (mon->colour) { case RED: return MONS_RED_DRACONIAN; case WHITE: return MONS_WHITE_DRACONIAN; case BLUE: // black return MONS_BLACK_DRACONIAN; case GREEN: return MONS_GREEN_DRACONIAN; case MAGENTA: return MONS_PURPLE_DRACONIAN; default: break; } } monster_type ret = mons_species(mon->type); if (ret == MONS_DRACONIAN && mon->type != MONS_DRACONIAN) ret = static_cast( mon->base_monster ); return (ret); } int get_shout_noise_level(const shout_type shout) { switch (shout) { case S_SILENT: return 0; case S_HISS: case S_VERY_SOFT: return 4; case S_SOFT: return 6; case S_LOUD: return 10; case S_SHOUT2: case S_ROAR: case S_VERY_LOUD: return 12; default: return 8; } } // Only the beast uses S_RANDOM for noise type. // Pandemonium lords can also get here but this mostly used for the // "says" verb used for insults. static bool _shout_fits_monster(int type, int shout) { if (shout == NUM_SHOUTS || shout >= NUM_LOUDNESS) return (false); // For demon lords almost everything is fair game. // It's only used for the shouting verb ("say", "bellow", "roar", ...) // anyway. if (type != MONS_BEAST) return (shout != S_BUZZ && shout != S_WHINE && shout != S_CROAK); switch (shout) { // 2-headed ogres, bees or mosquitos never fit. case S_SHOUT2: case S_BUZZ: case S_WHINE: // The beast cannot speak. case S_DEMON_TAUNT: // Silent is boring. case S_SILENT: return (false); default: return (true); } } // If demon_shout is true, we're trying to find a random verb and loudness // for a pandemonium lord trying to shout. shout_type mons_shouts(int mc, bool demon_shout) { shout_type u = smc->shouts; // Pandemonium lords use this to get the noises. if (u == S_RANDOM || demon_shout && u == S_DEMON_TAUNT) { const int max_shout = (u == S_RANDOM ? NUM_SHOUTS : NUM_LOUDNESS); do u = static_cast(random2(max_shout)); while (!_shout_fits_monster(mc, u)); } return (u); } bool mons_is_unique(int mc) { return (mons_class_flag(mc, M_UNIQUE)); } bool mons_sense_invis(const monsters *mon) { return (mons_class_flag(mon->type, M_SENSE_INVIS)); } bool mons_see_invis(const monsters *mon) { if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON) return (mon->ghost->see_invis); else if (mons_class_flag(mon->type, M_SEE_INVIS)) return (true); else if (_scan_mon_inv_randarts( mon, RAP_EYESIGHT ) > 0) return (true); return (false); } bool mon_can_see_monster( const monsters *mon, const monsters *targ ) { if (!mon->mon_see_grid(targ->x, targ->y)) return (false); return (mons_monster_visible(mon, targ)); } // This does NOT do line of sight! It checks the targ's visibility // with respect to mon's perception, but doesn't do walls or range. bool mons_monster_visible( const monsters *mon, const monsters *targ ) { if (targ->has_ench(ENCH_SUBMERGED) || targ->invisible() && !mons_see_invis(mon)) { return (false); } return (true); } // This does NOT do line of sight! It checks the player's visibility // with respect to mon's perception, but doesn't do walls or range. bool mons_player_visible( const monsters *mon ) { if (you.invisible()) { if (player_in_water()) return (true); if (mons_see_invis(mon) || mons_sense_invis(mon)) return (true); return (false); } return (true); } unsigned mons_char(int mc) { return monster_symbols[mc].glyph; } int mons_class_colour(int mc) { return monster_symbols[mc].colour; } mon_itemuse_type mons_itemuse(int mc) { return (smc->gmon_use); } int mons_colour(const monsters *monster) { return (monster->colour); } monster_type mons_zombie_base(const monsters *monster) { return (monster->base_monster); } bool mons_class_is_zombified(int mc) { return (mc == MONS_ZOMBIE_SMALL || mc == MONS_ZOMBIE_LARGE || mc == MONS_SKELETON_SMALL || mc == MONS_SKELETON_LARGE || mc == MONS_SIMULACRUM_SMALL || mc == MONS_SIMULACRUM_LARGE || mc == MONS_SPECTRAL_THING); } bool mons_is_zombified(const monsters *monster) { return mons_class_is_zombified(monster->type); } int downscale_zombie_damage(int damage) { // These are cumulative, of course: {dlb} if (damage > 1) damage--; if (damage > 4) damage--; if (damage > 11) damage--; if (damage > 14) damage--; return (damage); } mon_attack_def downscale_zombie_attack(const monsters *mons, mon_attack_def attk) { switch (attk.type) { case AT_STING: case AT_SPORE: case AT_TOUCH: case AT_ENGULF: attk.type = AT_HIT; break; default: break; } if (mons->type == MONS_SIMULACRUM_LARGE || mons->type == MONS_SIMULACRUM_SMALL) { attk.flavour = AF_COLD; } else { attk.flavour = AF_PLAIN; } attk.damage = downscale_zombie_damage(attk.damage); return (attk); } mon_attack_def mons_attack_spec(const monsters *mons, int attk_number) { int mc = mons->type; const bool zombified = mons_is_zombified(mons); if (attk_number < 0 || attk_number > 3 || mons->has_hydra_multi_attack()) attk_number = 0; if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) { if (attk_number == 0) return mon_attack_def::attk(mons->ghost->damage); else return mon_attack_def::attk(0, AT_NONE); } if (zombified) mc = mons_zombie_base(mons); mon_attack_def attk = smc->attack[attk_number]; if (attk.flavour == AF_KLOWN) { switch (random2(6)) { case 0: attk.flavour = AF_POISON_NASTY; break; case 1: attk.flavour = AF_ROT; break; case 2: attk.flavour = AF_DRAIN_XP; break; case 3: attk.flavour = AF_FIRE; break; case 4: attk.flavour = AF_COLD; break; case 5: attk.flavour = AF_BLINK; break; } } return (zombified ? downscale_zombie_attack(mons, attk) : attk); } int mons_damage(int mc, int rt) { if (rt < 0 || rt > 3) rt = 0; return smc->attack[rt].damage; } bool mons_immune_magic(const monsters *mon) { return get_monster_data(mon->type)->resist_magic == MAG_IMMUNE; } int mons_resist_magic( const monsters *mon ) { if (mons_immune_magic(mon)) return MAG_IMMUNE; int u = (get_monster_data(mon->type))->resist_magic; // Negative values get multiplied with monster hit dice. if (u < 0) u = mon->hit_dice * -u * 4 / 3; // Randarts have a multiplicative effect. u *= (_scan_mon_inv_randarts( mon, RAP_MAGIC ) + 100); u /= 100; // ego armour resistance const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; if (armour != NON_ITEM && get_armour_ego_type( mitm[armour] ) == SPARM_MAGIC_RESISTANCE ) { u += 30; } if (shield != NON_ITEM && get_armour_ego_type( mitm[shield] ) == SPARM_MAGIC_RESISTANCE ) { u += 30; } return (u); } const char* mons_resist_string(const monsters *mon) { if ( mons_immune_magic(mon) ) return "is unaffected"; else return "resists"; } // Returns true if the monster made its save against hostile // enchantments/some other magics. bool check_mons_resist_magic( const monsters *monster, int pow ) { int mrs = mons_resist_magic(monster); if (mrs == MAG_IMMUNE) return (true); // Evil, evil hack to make weak one hd monsters easier for first // level characters who have resistable 1st level spells. Six is // a very special value because mrs = hd * 2 * 3 for most monsters, // and the weak, low level monsters have been adjusted so that the // "3" is typically a 1. There are some notable one hd monsters // that shouldn't fall under this, so we do < 6, instead of <= 6... // or checking monster->hit_dice. The goal here is to make the // first level easier for these classes and give them a better // shot at getting to level two or three and spells that can help // them out (or building a level or two of their base skill so they // aren't resisted as often). -- bwr if (mrs < 6 && coinflip()) return (false); pow = stepdown_value( pow, 30, 40, 100, 120 ); const int mrchance = (100 + mrs) - pow; const int mrch2 = random2(100) + random2(101); #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Power: %d, monster's MR: %d, target: %d, roll: %d", pow, mrs, mrchance, mrch2 ); #endif return (mrch2 < mrchance); } int mons_res_elec( const monsters *mon ) { int mc = mon->type; // This is a variable, not a player_xx() function, so can be above 1. int u = 0; u += get_mons_resists(mon).elec; // Don't bother checking equipment if the monster can't use it. if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) { u += _scan_mon_inv_randarts( mon, RAP_ELECTRICITY ); // No ego armour, but storm dragon. const int armour = mon->inv[MSLOT_ARMOUR]; if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR && mitm[armour].sub_type == ARM_STORM_DRAGON_ARMOUR) { u += 1; } } // Monsters can legitimately get multiple levels of electricity resistance. return (u); } bool mons_res_asphyx(const monsters *mon) { const mon_holy_type holiness = mons_holiness(mon); return (mons_is_unholy(mon) || holiness == MH_NONLIVING || holiness == MH_PLANT || get_mons_resists(mon).asphyx > 0); } int mons_res_acid( const monsters *mon ) { return get_mons_resists(mon).acid; } int mons_res_poison( const monsters *mon ) { int mc = mon->type; int u = get_mons_resists(mon).poison; if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) { u += _scan_mon_inv_randarts( mon, RAP_POISON ); const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR) { // intrinsic armour abilities switch (mitm[armour].sub_type) { case ARM_SWAMP_DRAGON_ARMOUR: u += 1; break; case ARM_GOLD_DRAGON_ARMOUR: u += 1; break; default: break; } // ego armour resistance if (get_armour_ego_type( mitm[armour] ) == SPARM_POISON_RESISTANCE) u += 1; } if (shield != NON_ITEM && mitm[shield].base_type == OBJ_ARMOUR) { // ego armour resistance if (get_armour_ego_type( mitm[shield] ) == SPARM_POISON_RESISTANCE) u += 1; } } // Monsters can legitimately get multiple levels of poison resistance. return (u); } bool mons_res_sticky_flame( const monsters *mon ) { return (get_mons_resists(mon).sticky_flame || mon->has_equipped(EQ_BODY_ARMOUR, ARM_MOTTLED_DRAGON_ARMOUR)); } int mons_res_steam( const monsters *mon ) { int res = get_mons_resists(mon).steam; if (mon->has_equipped(EQ_BODY_ARMOUR, ARM_STEAM_DRAGON_ARMOUR)) res += 3; return (res + mons_res_fire(mon) / 2); } int mons_res_fire( const monsters *mon ) { int mc = mon->type; const mon_resist_def res = get_mons_resists(mon); int u = std::min(res.fire + res.hellfire * 3, 3); if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) { u += _scan_mon_inv_randarts( mon, RAP_FIRE ); const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR) { // intrinsic armour abilities switch (mitm[armour].sub_type) { case ARM_DRAGON_ARMOUR: u += 2; break; case ARM_GOLD_DRAGON_ARMOUR: u += 1; break; case ARM_ICE_DRAGON_ARMOUR: u -= 1; break; default: break; } // check ego resistance const int ego = get_armour_ego_type( mitm[armour] ); if (ego == SPARM_FIRE_RESISTANCE || ego == SPARM_RESISTANCE) u += 1; } if (shield != NON_ITEM && mitm[shield].base_type == OBJ_ARMOUR) { // check ego resistance const int ego = get_armour_ego_type( mitm[shield] ); if (ego == SPARM_FIRE_RESISTANCE || ego == SPARM_RESISTANCE) u += 1; } } if (u < -3) u = -3; else if (u > 3) u = 3; return (u); } int mons_res_cold( const monsters *mon ) { int mc = mon->type; int u = get_mons_resists(mon).cold; if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) { u += _scan_mon_inv_randarts( mon, RAP_COLD ); const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR) { // intrinsic armour abilities switch (mitm[armour].sub_type) { case ARM_ICE_DRAGON_ARMOUR: u += 2; break; case ARM_GOLD_DRAGON_ARMOUR: u += 1; break; case ARM_DRAGON_ARMOUR: u -= 1; break; default: break; } // check ego resistance const int ego = get_armour_ego_type( mitm[armour] ); if (ego == SPARM_COLD_RESISTANCE || ego == SPARM_RESISTANCE) u += 1; } if (shield != NON_ITEM && mitm[shield].base_type == OBJ_ARMOUR) { // check ego resistance const int ego = get_armour_ego_type( mitm[shield] ); if (ego == SPARM_COLD_RESISTANCE || ego == SPARM_RESISTANCE) u += 1; } } if (u < -3) u = -3; else if (u > 3) u = 3; return (u); } int mons_res_miasma( const monsters *mon ) { return (mons_holiness(mon) != MH_NATURAL || mon->type == MONS_DEATH_DRAKE ? 3 : 0); } int mons_res_negative_energy( const monsters *mon ) { int mc = mon->type; const mon_holy_type holiness = mons_holiness(mon); if (mons_is_unholy(mon) || holiness == MH_NONLIVING || holiness == MH_PLANT || mon->type == MONS_SHADOW_DRAGON || mon->type == MONS_DEATH_DRAKE // TSO protects his warriors' life force || mon->type == MONS_DAEVA) { return (3); // to match the value for players } int u = 0; if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) { u += _scan_mon_inv_randarts( mon, RAP_NEGATIVE_ENERGY ); const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR) { // check for ego resistance if (get_armour_ego_type( mitm[armour] ) == SPARM_POSITIVE_ENERGY) u += 1; } if (shield != NON_ITEM && mitm[shield].base_type == OBJ_ARMOUR) { // check for ego resistance if (get_armour_ego_type( mitm[shield] ) == SPARM_POSITIVE_ENERGY) u += 1; } } if (u > 3) u = 3; return (u); } // end mons_res_negative_energy() bool mons_is_holy(const monsters *mon) { return (mons_holiness(mon) == MH_HOLY); } bool mons_is_evil(const monsters *mon) { return (mons_class_flag(mon->type, M_EVIL)); } bool mons_is_unholy(const monsters *mon) { const mon_holy_type holiness = mons_holiness(mon); return (holiness == MH_UNDEAD || holiness == MH_DEMONIC); } bool mons_is_evil_or_unholy(const monsters *mon) { return (mons_is_evil(mon) || mons_is_unholy(mon)); } bool mons_has_lifeforce(const monsters *mon) { const mon_holy_type holiness = mons_holiness(mon); return (holiness == MH_NATURAL || holiness == MH_PLANT); } bool mons_skeleton(int mc) { if (mons_zombie_size(mc) == Z_NOZOMBIE || mons_weight(mc) == 0 || (mons_class_flag(mc, M_NO_SKELETON))) { return (false); } return (true); } // end mons_skeleton() flight_type mons_class_flies(int mc) { if (mons_class_flag(mc, M_FLIES)) return (FL_FLY); if (mons_class_flag(mc, M_LEVITATE)) return (FL_LEVITATE); return (FL_NONE); } flight_type mons_flies(const monsters *mon) { if (mon->type == MONS_PANDEMONIUM_DEMON && mon->ghost->fly) { return (mon->ghost->fly); } const int type = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; const flight_type ret = mons_class_flies( type ); return (ret ? ret : (_scan_mon_inv_randarts(mon, RAP_LEVITATE) > 0) ? FL_LEVITATE : FL_NONE); } bool mons_class_amphibious(int mc) { return mons_class_flag(mc, M_AMPHIBIOUS); } bool mons_amphibious(const monsters *mon) { const int type = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; return (mons_class_amphibious(type)); } // This nice routine we keep in exactly the way it was. int hit_points(int hit_dice, int min_hp, int rand_hp) { int hrolled = 0; for (int hroll = 0; hroll < hit_dice; hroll++) { hrolled += random2(1 + rand_hp); hrolled += min_hp; } return (hrolled); } // This function returns the standard number of hit dice for a type // of monster, not a pacticular monsters current hit dice. -- bwr // XXX: Rename to mons_class_* to be like the rest. int mons_type_hit_dice( int type ) { struct monsterentry *mon_class = get_monster_data( type ); if (mon_class) return (mon_class->hpdice[0]); return (0); } int exper_value( const struct monsters *monster ) { long x_val = 0; // These three are the original arguments. const int mclass = monster->type; const int mHD = monster->hit_dice; const int maxhp = monster->max_hit_points; // These are some values we care about. const int speed = mons_speed(mclass); const int modifier = _mons_exp_mod(mclass); const int item_usage = mons_itemuse(mclass); // XXX: Shapeshifters can qualify here, even though they can't cast. const bool spellcaster = mons_class_flag( mclass, M_SPELLCASTER ); // Early out for no XP monsters. if (mons_class_flag(mclass, M_NO_EXP_GAIN)) return (0); if (mons_is_statue(mclass)) return (mHD * 15); // These undead take damage to maxhp, so we use only HD. -- bwr if (mclass == MONS_ZOMBIE_SMALL || mclass == MONS_ZOMBIE_LARGE || mclass == MONS_SIMULACRUM_SMALL || mclass == MONS_SIMULACRUM_LARGE || mclass == MONS_SKELETON_SMALL || mclass == MONS_SKELETON_LARGE) { x_val = (16 + mHD * 4) * (mHD * mHD) / 10; } else { x_val = (16 + maxhp) * (mHD * mHD) / 10; } // Let's calculate a simple difficulty modifier. -- bwr int diff = 0; // Let's look for big spells. if (spellcaster) { const monster_spells &hspell_pass = monster->spells; for (int i = 0; i < 6; i++) { switch (hspell_pass[i]) { case SPELL_PARALYSE: case SPELL_SMITING: case SPELL_HELLFIRE_BURST: case SPELL_HELLFIRE: case SPELL_SYMBOL_OF_TORMENT: diff += 25; break; case SPELL_LIGHTNING_BOLT: case SPELL_BOLT_OF_DRAINING: case SPELL_VENOM_BOLT: case SPELL_STICKY_FLAME: case SPELL_DISINTEGRATE: case SPELL_SUMMON_GREATER_DEMON: case SPELL_BANISHMENT: case SPELL_LEHUDIBS_CRYSTAL_SPEAR: case SPELL_BOLT_OF_IRON: case SPELL_TELEPORT_SELF: case SPELL_TELEPORT_OTHER: diff += 10; break; default: break; } } } // Let's look at regeneration. if (monster_descriptor( mclass, MDSC_REGENERATES )) diff += 15; // Monsters at normal or fast speed with big melee damage. if (speed >= 10) { int max_melee = 0; for (int i = 0; i < 4; i++) max_melee += mons_damage( mclass, i ); if (max_melee > 30) diff += (max_melee / ((speed == 10) ? 2 : 1)); } // Monsters who can use equipment (even if only the equipment // they are given) can be considerably enhanced because of // the way weapons work for monsters. -- bwr if (item_usage == MONUSE_STARTING_EQUIPMENT || item_usage == MONUSE_WEAPONS_ARMOUR) { diff += 30; } // Set a reasonable range on the difficulty modifier... // Currently 70% - 200%. -- bwr if (diff > 100) diff = 100; else if (diff < -30) diff = -30; // Apply difficulty. x_val *= (100 + diff); x_val /= 100; // Basic speed modification. if (speed > 0) { x_val *= speed; x_val /= 10; } // Slow monsters without spells and items often have big HD which // cause the experience value to be overly large... this tries // to reduce the inappropriate amount of XP that results. -- bwr if (speed < 10 && !spellcaster && item_usage < MONUSE_STARTING_EQUIPMENT) x_val /= 2; // Apply the modifier in the monster's definition. if (modifier > 0) { x_val *= modifier; x_val /= 10; } // Reductions for big values. -- bwr if (x_val > 100) x_val = 100 + ((x_val - 100) * 3) / 4; if (x_val > 1000) x_val = 1000 + (x_val - 1000) / 2; // Guarantee the value is within limits. if (x_val <= 0) x_val = 1; else if (x_val > 15000) x_val = 15000; return (x_val); } // end exper_value() void mons_load_spells( monsters *mon, mon_spellbook_type book ) { mon->load_spells(book); } void define_monster(int index) { define_monster(menv[index]); } // Generate a shiny new and unscarred monster. void define_monster(monsters &mons) { int temp_rand = 0; // probability determination {dlb} int mcls = mons.type; int hd, hp, hp_max, ac, ev, speed; int monnumber = mons.number; monster_type monbase = mons.base_monster; const monsterentry *m = get_monster_data(mcls); int col = mons_class_colour(mons.type); mon_spellbook_type spells = MST_NO_SPELLS; mons.mname.clear(); hd = m->hpdice[0]; // misc ac = m->AC; ev = m->ev; speed = monsters::base_speed(mcls); mons.god = GOD_NO_GOD; switch (mcls) { case MONS_ABOMINATION_SMALL: hd = 4 + random2(4); ac = 3 + random2(7); ev = 7 + random2(6); col = random_colour(); break; case MONS_ZOMBIE_SMALL: hd = (coinflip() ? 1 : 2); break; case MONS_ZOMBIE_LARGE: hd = 3 + random2(5); break; case MONS_ABOMINATION_LARGE: hd = 8 + random2(4); ac = 5 + random2avg(9, 2); ev = 3 + random2(5); col = random_colour(); break; case MONS_BEAST: hd = 4 + random2(4); ac = 2 + random2(5); ev = 7 + random2(5); break; case MONS_HYDRA: monnumber = random_range(4, 8); break; case MONS_DEEP_ELF_FIGHTER: case MONS_DEEP_ELF_KNIGHT: case MONS_DEEP_ELF_SOLDIER: case MONS_ORC_WIZARD: spells = static_cast(MST_ORC_WIZARD_I + random2(3)); break; case MONS_LICH: case MONS_ANCIENT_LICH: spells = static_cast(MST_LICH_I + random2(4)); break; case MONS_HELL_KNIGHT: spells = (coinflip() ? MST_HELL_KNIGHT_I : MST_HELL_KNIGHT_II); break; case MONS_NECROMANCER: spells = (coinflip() ? MST_NECROMANCER_I : MST_NECROMANCER_II); break; case MONS_WIZARD: case MONS_OGRE_MAGE: case MONS_EROLCHA: case MONS_DEEP_ELF_MAGE: spells = static_cast(MST_WIZARD_I + random2(5)); break; case MONS_DEEP_ELF_CONJURER: spells = (coinflip()? MST_DEEP_ELF_CONJURER_I : MST_DEEP_ELF_CONJURER_II); break; case MONS_BUTTERFLY: case MONS_SPATIAL_VORTEX: case MONS_KILLER_KLOWN: if (col != BLACK) // May be overwritten by the mon_glyph option. break; col = random_colour(); break; case MONS_GILA_MONSTER: if (col != BLACK) // May be overwritten by the mon_glyph option. break; temp_rand = random2(7); col = (temp_rand >= 5 ? LIGHTRED : // 2/7 temp_rand >= 3 ? LIGHTMAGENTA : // 2/7 temp_rand >= 1 ? MAGENTA // 1/7 : YELLOW); // 1/7 break; case MONS_DRACONIAN: // These are supposed to only be created by polymorph. hd += random2(10); ac += random2(5); ev += random2(5); break; case MONS_DRACONIAN_CALLER: case MONS_DRACONIAN_MONK: case MONS_DRACONIAN_ZEALOT: case MONS_DRACONIAN_SHIFTER: case MONS_DRACONIAN_ANNIHILATOR: case MONS_DRACONIAN_SCORCHER: { // Professional draconians still have a base draconian type. // White draconians will never be draconian scorchers, but // apart from that, anything goes. do monbase = static_cast(MONS_BLACK_DRACONIAN + random2(8)); while (drac_colour_incompatible(mcls, monbase)); break; } case MONS_DRACONIAN_KNIGHT: { temp_rand = random2(10); // hell knight, death knight, chaos knight... if (temp_rand < 6) spells = (coinflip() ? MST_HELL_KNIGHT_I : MST_HELL_KNIGHT_II); else if (temp_rand < 9) spells = (coinflip() ? MST_NECROMANCER_I : MST_NECROMANCER_II); else { spells = (coinflip() ? MST_DEEP_ELF_CONJURER_I : MST_DEEP_ELF_CONJURER_II); } monbase = static_cast(MONS_BLACK_DRACONIAN + random2(8)); break; } case MONS_HUMAN: case MONS_ELF: // These are supposed to only be created by polymorph. hd += random2(10); ac += random2(5); ev += random2(5); break; default: if (mons_is_mimic( mcls )) col = get_mimic_colour( &mons ); break; } if (col == BLACK) col = random_colour(); if (spells == MST_NO_SPELLS && mons_class_flag(mons.type, M_SPELLCASTER)) spells = m->sec; // Some calculations. hp = hit_points(hd, m->hpdice[1], m->hpdice[2]); hp += m->hpdice[3]; hp_max = hp; // So let it be written, so let it be done. mons.hit_dice = hd; mons.hit_points = hp; mons.max_hit_points = hp_max; mons.ac = ac; mons.ev = ev; mons.speed = speed; mons.speed_increment = 70; if (mons.base_monster == MONS_PROGRAM_BUG) mons.base_monster = monbase; if (mons.number == 0) mons.number = monnumber; mons.flags = 0L; mons.experience = 0L; mons.colour = col; mons_load_spells( &mons, spells ); // Reset monster enchantments. mons.enchantments.clear(); mons.ench_countdown = 0; } // end define_monster() static const char *drac_colour_names[] = { "black", "mottled", "yellow", "green", "purple", "red", "white", "pale" }; std::string draconian_colour_name(monster_type mtype) { COMPILE_CHECK(ARRAYSZ(drac_colour_names) == MONS_PALE_DRACONIAN - MONS_DRACONIAN, c1); if (mtype < MONS_BLACK_DRACONIAN || mtype > MONS_PALE_DRACONIAN) return "buggy"; return (drac_colour_names[mtype - MONS_BLACK_DRACONIAN]); } monster_type draconian_colour_by_name(const std::string &name) { COMPILE_CHECK(ARRAYSZ(drac_colour_names) == (MONS_PALE_DRACONIAN - MONS_DRACONIAN), c1); for (unsigned i = 0; i < ARRAYSZ(drac_colour_names); ++i) if (name == drac_colour_names[i]) return static_cast(i + MONS_BLACK_DRACONIAN); return (MONS_PROGRAM_BUG); } static std::string _str_monam(const monsters& mon, description_level_type desc, bool force_seen) { if (desc == DESC_NONE) return (""); // Handle non-visible case first. if (!force_seen && !player_monster_visible(&mon)) { switch (desc) { case DESC_CAP_THE: case DESC_CAP_A: return "It"; case DESC_NOCAP_THE: case DESC_NOCAP_A: case DESC_PLAIN: return "it"; default: return "it (buggy)"; } } // Assumed visible from now on. // Various special cases: // non-gold mimics, dancing weapons, ghosts, Pan demons if (mons_is_mimic(mon.type) && mon.type != MONS_GOLD_MIMIC) { item_def item; get_mimic_item( &mon, item ); return item.name(desc); } if (mon.type == MONS_DANCING_WEAPON && mon.inv[MSLOT_WEAPON] != NON_ITEM) { unsigned long ignore_flags = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES; bool use_inscrip = true; if (desc == DESC_BASENAME || desc == DESC_QUALNAME || desc == DESC_DBNAME) { use_inscrip = false; } item_def item = mitm[mon.inv[MSLOT_WEAPON]]; return item.name(desc, false, false, use_inscrip, false, ignore_flags); } if (desc == DESC_DBNAME) return get_monster_data(mon.type)->name; if (mon.type == MONS_PLAYER_GHOST) return apostrophise(mon.mname) + " ghost"; // If the monster has an explicit name, return that, handling it like // a unique's name. if (desc != DESC_BASENAME && !mon.mname.empty()) return mon.mname; std::string result; // Start building the name string. // Start with the prefix. // (Uniques don't get this, because their names are proper nouns.) if (!mons_is_unique(mon.type)) { switch (desc) { case DESC_CAP_THE: result = (mons_friendly(&mon) ? "Your " : "The "); break; case DESC_NOCAP_THE: result = (mons_friendly(&mon) ? "your " : "the "); break; case DESC_CAP_A: result = "A "; break; case DESC_NOCAP_A: result = "a "; break; case DESC_PLAIN: default: break; } } // Some monsters might want the name of a different creature. int nametype = mon.type; // Tack on other prefixes. switch (mon.type) { case MONS_SPECTRAL_THING: result += "spectral "; nametype = mon.base_monster; break; case MONS_ZOMBIE_SMALL: case MONS_ZOMBIE_LARGE: case MONS_SKELETON_SMALL: case MONS_SKELETON_LARGE: case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE: nametype = mon.base_monster; break; case MONS_DRACONIAN_CALLER: case MONS_DRACONIAN_MONK: case MONS_DRACONIAN_ZEALOT: case MONS_DRACONIAN_SHIFTER: case MONS_DRACONIAN_ANNIHILATOR: case MONS_DRACONIAN_KNIGHT: case MONS_DRACONIAN_SCORCHER: if (mon.base_monster != MONS_PROGRAM_BUG) // database search result += draconian_colour_name(mon.base_monster) + " "; break; default: break; } // Done here to cover cases of undead versions of hydras. if (nametype == MONS_HYDRA && mon.number > 0 && desc != DESC_DBNAME) { if (mon.number < 11) { result += (mon.number == 1) ? "one" : (mon.number == 2) ? "two" : (mon.number == 3) ? "three" : (mon.number == 4) ? "four" : (mon.number == 5) ? "five" : (mon.number == 6) ? "six" : (mon.number == 7) ? "seven" : (mon.number == 8) ? "eight" : (mon.number == 9) ? "nine" : "ten"; } else { snprintf(info, INFO_SIZE, "%d", mon.number); result += info; } result += "-headed "; } // Add the base name. result += get_monster_data(nametype)->name; // Add suffixes. switch (mon.type) { case MONS_ZOMBIE_SMALL: case MONS_ZOMBIE_LARGE: result += " zombie"; break; case MONS_SKELETON_SMALL: case MONS_SKELETON_LARGE: result += " skeleton"; break; case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE: result += " simulacrum"; break; default: break; } // Vowel fix: Change 'a orc' to 'an orc' if (result.length() >= 3 && (result[0] == 'a' || result[0] == 'A') && result[1] == ' ' && is_vowel(result[2])) { result.insert(1, "n"); } // All done. return result; } std::string mons_type_name(int type, description_level_type desc ) { std::string result; if (!mons_is_unique(type)) { switch (desc) { case DESC_CAP_THE: result = "The "; break; case DESC_NOCAP_THE: result = "the "; break; case DESC_CAP_A: result = "A "; break; case DESC_NOCAP_A: result = "a "; break; case DESC_PLAIN: default: break; } } result += get_monster_data(type)->name; // Vowel fix: Change 'a orc' to 'an orc'. if (result.length() >= 3 && (result[0] == 'a' || result[0] == 'A') && result[1] == ' ' && is_vowel(result[2]) ) { result.insert(1, "n"); } return result; } static std::string _get_proper_monster_name(const monsters *mon) { const monsterentry *me = mon->find_monsterentry(); if (!me) return (""); std::string name = getRandNameString(me->name, " name"); if (!name.empty()) return name; name = getRandNameString(get_monster_data(mons_genus(mon->type))->name, " name"); if (!name.empty()) return name; name = getRandNameString("generic_monster_name"); return name; } // Names a previously unnamed monster. bool give_monster_proper_name(monsters *mon, bool orcs_only) { // Already has a unique name. if (mon->is_named()) return (false); // Since this is called from the various divine blessing routines, // don't bless non-orcs, and normally don't bless plain orcs, either. if (orcs_only) { if (mons_species(mon->type) != MONS_ORC || mon->type == MONS_ORC && !one_chance_in(8)) { return (false); } } mon->mname = _get_proper_monster_name(mon); return (mon->is_named()); } // See mons_init for initialization of mon_entry array. monsterentry *get_monster_data(int p_monsterid) { const int me = p_monsterid != -1 && p_monsterid < NUM_MONSTERS? mon_entry[p_monsterid] : -1; if (me >= 0) // PARANOIA return (&mondata[me]); else return (NULL); } static int _mons_exp_mod(int mc) { return (smc->exp_mod); } int mons_speed(int mc) { return (smc->speed); } mon_intel_type mons_intel(int mc) { return (smc->intel); } habitat_type mons_habitat_by_type(int mc) { const monsterentry *me = get_monster_data(mc); return (me ? me->habitat : HT_LAND); } habitat_type mons_habitat(const monsters *m) { return mons_habitat_by_type( mons_is_zombified(m) ? mons_zombie_base(m) : m->type ); } bool intelligent_ally(const monsters *monster) { return (monster->attitude == ATT_FRIENDLY && mons_intel(monster->type) >= I_NORMAL); } int mons_power(int mc) { // For now, just return monster hit dice. return (smc->hpdice[0]); } bool mons_aligned(int m1, int m2) { mon_attitude_type fr1, fr2; monsters *mon1, *mon2; if (m1 == MHITNOT || m2 == MHITNOT) return (true); if (m1 == MHITYOU) fr1 = ATT_FRIENDLY; else { mon1 = &menv[m1]; fr1 = mons_attitude(mon1); } if (m2 == MHITYOU) fr2 = ATT_FRIENDLY; else { mon2 = &menv[m2]; fr2 = mons_attitude(mon2); } return (mons_atts_aligned(fr1, fr2)); } bool mons_atts_aligned(mon_attitude_type fr1, mon_attitude_type fr2) { if (mons_att_wont_attack(fr1) && mons_att_wont_attack(fr2)) return (true); return (fr1 == fr2); } bool mons_wields_two_weapons(monster_type m) { return mons_class_flag(m, M_TWOWEAPON); } bool mons_wields_two_weapons(const monsters *m) { return (mons_wields_two_weapons(static_cast(m->type))); } bool mons_eats_corpses(const monsters *m) { return (m->type == MONS_NECROPHAGE || m->type == MONS_GHOUL); } bool mons_is_summoned(const monsters *m) { return (m->has_ench(ENCH_ABJ)); } // Does not check whether the monster can dual-wield - that is the // caller's responsibility. int mons_offhand_weapon_index(const monsters *m) { return (m->inv[MSLOT_ALT_WEAPON]); } int mons_base_damage_brand(const monsters *m) { if (m->type == MONS_PLAYER_GHOST || m->type == MONS_PANDEMONIUM_DEMON) return m->ghost->brand; return (SPWPN_NORMAL); } size_type mons_size(const monsters *m) { return m->body_size(); } bool mons_friendly(const monsters *m) { return (m->attitude == ATT_FRIENDLY || m->has_ench(ENCH_CHARM)); } bool mons_neutral(const monsters *m) { return (m->attitude == ATT_NEUTRAL || m->has_ench(ENCH_NEUTRAL) || m->attitude == ATT_GOOD_NEUTRAL); } bool mons_good_neutral(const monsters *m) { return (m->attitude == ATT_GOOD_NEUTRAL); } bool mons_is_pacified(const monsters *m) { return (m->attitude == ATT_NEUTRAL && testbits(m->flags, MF_GOT_HALF_XP)); } bool mons_wont_attack(const monsters *m) { return (mons_friendly(m) || mons_good_neutral(m)); } bool mons_att_wont_attack(mon_attitude_type fr) { return (fr == ATT_FRIENDLY || fr == ATT_GOOD_NEUTRAL); } mon_attitude_type mons_attitude(const monsters *m) { if (mons_friendly(m)) return ATT_FRIENDLY; else if (mons_good_neutral(m)) return ATT_GOOD_NEUTRAL; else if (mons_neutral(m)) return ATT_NEUTRAL; else return ATT_HOSTILE; } bool mons_is_submerged(const monsters *m) { // FIXME, switch to 4.1's MF_SUBMERGED system which is much cleaner. return (m->has_ench(ENCH_SUBMERGED)); } bool mons_is_paralysed(const monsters *m) { return (m->has_ench(ENCH_PARALYSIS)); } bool mons_is_petrified(const monsters *m) { return (m->has_ench(ENCH_PETRIFIED)); } bool mons_is_petrifying(const monsters *m) { return (m->has_ench(ENCH_PETRIFYING)); } bool mons_cannot_act(const monsters *m) { return (mons_is_paralysed(m) || mons_is_petrified(m) && !mons_is_petrifying(m)); } bool mons_cannot_move(const monsters *m) { return (mons_cannot_act(m) || mons_is_petrifying(m)); } bool mons_is_confused(const monsters *m) { return (m->has_ench(ENCH_CONFUSION) && !mons_class_flag(m->type, M_CONFUSED)); } bool mons_is_caught(const monsters *m) { return (m->has_ench(ENCH_HELD)); } bool mons_is_sleeping(const monsters *m) { return (m->behaviour == BEH_SLEEP); } bool mons_is_wandering(const monsters *m) { return (m->behaviour == BEH_WANDER); } bool mons_is_seeking(const monsters *m) { return (m->behaviour == BEH_SEEK); } bool mons_is_fleeing(const monsters *m) { return (m->behaviour == BEH_FLEE); } bool mons_is_panicking(const monsters *m) { return (m->behaviour == BEH_PANIC); } bool mons_is_cornered(const monsters *m) { return (m->behaviour == BEH_CORNERED); } bool mons_is_lurking(const monsters *m) { return (m->behaviour == BEH_LURK); } bool mons_is_batty(const monsters *m) { return testbits(m->flags, MF_BATTY); } bool mons_was_seen(const monsters *m) { return testbits(m->flags, MF_SEEN); } bool mons_is_known_mimic(const monsters *m) { return mons_is_mimic(m->type) && testbits(m->flags, MF_KNOWN_MIMIC); } bool mons_looks_stabbable(const monsters *m) { return (mons_behaviour_perceptible(m) && !mons_friendly(m) && (mons_is_sleeping(m) || mons_cannot_act(m))); } bool mons_looks_distracted(const monsters *m) { return (mons_behaviour_perceptible(m) && !mons_friendly(m) && (m->foe != MHITYOU && !mons_is_batty(m) && !mons_neutral(m) || mons_is_confused(m) || mons_is_fleeing(m) || mons_is_caught(m) || mons_is_petrifying(m))); } void mons_pacify(monsters *mon) { // Make the monster permanently neutral. mon->attitude = ATT_NEUTRAL; mon->flags |= MF_WAS_NEUTRAL; if (!testbits(mon->flags, MF_GOT_HALF_XP)) { // Give the player half of the monster's XP. unsigned int exp_gain = 0, avail_gain = 0; gain_exp(exper_value(mon) / 2 + 1, &exp_gain, &avail_gain); mon->flags |= MF_GOT_HALF_XP; } // Make the monster begin leaving the level. behaviour_event(mon, ME_EVAL); } bool mons_should_fire(struct bolt &beam) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "tracer: foes %d, friends %d, foe_ratio: %d, smart: %s", beam.foe_count, beam.fr_count, beam.foe_ratio, beam.smart_monster ? "yes" : "no"); #endif // Use of foeRatio: // The higher this number, the more monsters will _avoid_ collateral // damage to their friends. // Setting this to zero will in fact have all monsters ignore their // friends when considering collateral damage. // Quick check - did we in fact get any foes? if (beam.foe_count == 0) return (false); if (is_sanctuary(you.x_pos, you.y_pos) || is_sanctuary(beam.source_x, beam.source_y)) { return (false); } // If we either hit no friends, or monster too dumb to care. if (beam.fr_count == 0 || !beam.smart_monster) return (true); // Only fire if they do acceptably low collateral damage. return (beam.foe_power >= div_round_up(beam.foe_ratio * (beam.foe_power + beam.fr_power), 100)); } // Returns true if the spell is something you wouldn't want done if // you had a friendly target... only returns a meaningful value for // non-beam spells. bool ms_direct_nasty(spell_type monspell) { return (spell_needs_foe(monspell) && !spell_typematch(monspell, SPTYP_SUMMONING)); } // Spells a monster may want to cast if fleeing from the player, and // the player is not in sight. bool ms_useful_fleeing_out_of_sight( const monsters *mon, spell_type monspell ) { if (monspell == SPELL_NO_SPELL || ms_waste_of_time( mon, monspell )) return (false); switch (monspell) { case SPELL_HASTE: case SPELL_INVISIBILITY: case SPELL_LESSER_HEALING: case SPELL_GREATER_HEALING: case SPELL_ANIMATE_DEAD: return (true); default: if (spell_typematch(monspell, SPTYP_SUMMONING) && one_chance_in(4)) return (true); break; } return (false); } bool ms_low_hitpoint_cast( const monsters *mon, spell_type monspell ) { bool ret = false; bool targ_adj = false; bool targ_sanct = false; if (mon->foe == MHITYOU || mon->foe == MHITNOT) { if (adjacent(you.x_pos, you.y_pos, mon->x, mon->y)) targ_adj = true; if (is_sanctuary(you.x_pos, you.y_pos)) targ_sanct = true; } else { if (adjacent( menv[mon->foe].x, menv[mon->foe].y, mon->x, mon->y )) targ_adj = true; if (is_sanctuary( menv[mon->foe].x, menv[mon->foe].y )) targ_sanct = true; } switch (monspell) { case SPELL_TELEPORT_OTHER: ret = !targ_sanct; break; case SPELL_TELEPORT_SELF: // Don't cast again if already about to teleport. if (mon->has_ench(ENCH_TP)) return (false); // intentional fall-through case SPELL_LESSER_HEALING: case SPELL_GREATER_HEALING: ret = true; break; case SPELL_BLINK_OTHER: if (targ_sanct) return (false); // intentional fall-through case SPELL_BLINK: if (targ_adj) ret = true; break; case SPELL_NO_SPELL: ret = false; break; default: if (!targ_adj && spell_typematch(monspell, SPTYP_SUMMONING)) ret = true; break; } return (ret); } // Spells for a quick get-away. // Currently only used to get out of a net. bool ms_quick_get_away( const monsters *mon /*unused*/, spell_type monspell ) { switch (monspell) { case SPELL_TELEPORT_SELF: // Don't cast again if already about to teleport. if (mon->has_ench(ENCH_TP)) return (false); // intentional fall-through case SPELL_BLINK: return (true); default: return (false); } } // Checks to see if a particular spell is worth casting in the first place. bool ms_waste_of_time( const monsters *mon, spell_type monspell ) { bool ret = false; int intel, est_magic_resist, power, diff; const actor *foe = mon->get_foe(); // Keep friendly summoners from spamming summons constantly. if (mons_friendly(mon) && (!foe || foe == &you) && spell_typematch(monspell, SPTYP_SUMMONING)) { return (true); } if (!mons_wont_attack(mon)) { if (spell_harms_area(monspell) && env.sanctuary_time > 0) return (true); if (spell_harms_target(monspell) && is_sanctuary(mon->target_x, mon->target_y)) { return (true); } } // Eventually, we'll probably want to be able to have monsters // learn which of their elemental bolts were resisted and have those // handled here as well. -- bwr switch (monspell) { case SPELL_BOLT_OF_DRAINING: case SPELL_AGONY: case SPELL_SYMBOL_OF_TORMENT: { if (!foe) return (true); // Check if the foe *appears* to be immune to negative energy. // We can't just use foe->res_negative_energy() because // that'll mean monsters can just "know" the player is fully // life-protected if he has triple life protection. const mon_holy_type holiness = foe->holiness(); return ((holiness == MH_UNDEAD // If the claimed undead is the player, it must be // a non-vampire, or a bloodless vampire. && (foe != &you || you.is_undead != US_SEMI_UNDEAD || you.hunger_state == HS_STARVING)) // Demons, but not demonspawn - demonspawn will show // up as demonic for purposes of things like holy // wrath, but are still (usually) susceptible to // torment and draining. || (holiness == MH_DEMONIC && foe != &you) || holiness == MH_NONLIVING || holiness == MH_PLANT); } case SPELL_DISPEL_UNDEAD: // [ds] How is dispel undead intended to interact with vampires? return (!foe || foe->holiness() != MH_UNDEAD); case SPELL_BACKLIGHT: { ret = (!foe || foe->backlit()); break; } case SPELL_BERSERKER_RAGE: if (!mon->needs_berserk(false)) ret = true; break; case SPELL_HASTE: if (mon->has_ench(ENCH_HASTE)) ret = true; break; case SPELL_INVISIBILITY: if (mon->has_ench(ENCH_INVIS) || mons_friendly(mon) && !player_see_invis(false)) { ret = true; } break; case SPELL_LESSER_HEALING: case SPELL_GREATER_HEALING: if (mon->hit_points > mon->max_hit_points / 2) ret = true; break; case SPELL_TELEPORT_SELF: // Monsters aren't smart enough to know when to cancel teleport. if (mon->has_ench(ENCH_TP)) ret = true; break; case SPELL_TELEPORT_OTHER: // Monsters aren't smart enough to know when to cancel teleport. if (mon->foe == MHITYOU) { if (you.duration[DUR_TELEPORT]) return (true); } else if (mon->foe != MHITNOT) { if (menv[mon->foe].has_ench(ENCH_TP)) return (true); } // intentional fall-through case SPELL_SLOW: case SPELL_CONFUSE: case SPELL_PAIN: case SPELL_BANISHMENT: case SPELL_DISINTEGRATE: case SPELL_PARALYSE: case SPELL_SLEEP: if (monspell == SPELL_SLEEP && (!foe || foe->asleep())) return (true); // Occasionally we don't estimate... just fire and see. if (one_chance_in(5)) return (false); // Only intelligent monsters estimate. intel = mons_intel( mon->type ); if (intel != I_NORMAL && intel != I_HIGH) return (false); // We'll estimate the target's resistance to magic, by first getting // the actual value and then randomizing it. est_magic_resist = (mon->foe == MHITNOT) ? 10000 : 0; if (mon->foe != MHITNOT) { if (mon->foe == MHITYOU) est_magic_resist = player_res_magic(); else est_magic_resist = mons_resist_magic(&menv[mon->foe]); // now randomize (normal intels less accurate than high): if (intel == I_NORMAL) est_magic_resist += random2(80) - 40; else est_magic_resist += random2(30) - 15; } power = 12 * mon->hit_dice * (monspell == SPELL_PAIN ? 2 : 1); power = stepdown_value( power, 30, 40, 100, 120 ); // Determine the amount of chance allowed by the benefit from // the spell. The estimated difficulty is the probability // of rolling over 100 + diff on 2d100. -- bwr diff = (monspell == SPELL_PAIN || monspell == SPELL_SLOW || monspell == SPELL_CONFUSE) ? 0 : 50; if (est_magic_resist - power > diff) ret = true; break; case SPELL_NO_SPELL: ret = true; break; default: break; } return (ret); } static bool _ms_ranged_spell( spell_type monspell ) { switch (monspell) { case SPELL_HASTE: case SPELL_LESSER_HEALING: case SPELL_GREATER_HEALING: case SPELL_TELEPORT_SELF: case SPELL_INVISIBILITY: case SPELL_BLINK: return (false); default: break; } return (true); } bool mons_is_magic_user( const monsters *mon ) { if (mons_class_flag(mon->type, M_ACTUAL_SPELLS)) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) if (mon->spells[i] != SPELL_NO_SPELL) return (true); } return (false); } // Returns true if the monster has an ability that only needs LOS to // affect the target. bool mons_has_los_ability( int mclass ) { // These two have Torment, but are handled specially. if (mclass == MONS_FIEND || mclass == MONS_PIT_FIEND) return (true); // These eyes only need LOS, as well. (The other eyes use spells.) if (mclass == MONS_GIANT_EYEBALL || mclass == MONS_EYE_OF_DRAINING) return (true); // Although not using spells, these are exceedingly dangerous. if (mclass == MONS_SILVER_STATUE || mclass == MONS_ORANGE_STATUE) return (true); // Beholding just needs LOS. if (mclass == MONS_MERMAID) return (true); return (false); } bool mons_has_ranged_spell( const monsters *mon ) { const int mclass = mon->type; // Monsters may have spell like abilities. if (mons_has_los_ability(mclass)) return (true); if (mons_class_flag( mclass, M_SPELLCASTER )) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; i++) if (_ms_ranged_spell( mon->spells[i] )) return (true); } return (false); } bool mons_has_ranged_attack( const monsters *mon ) { // Ugh. monsters *mnc = const_cast(mon); const item_def *weapon = mnc->launcher(); const item_def *primary = mnc->mslot_item(MSLOT_WEAPON); const item_def *missile = mnc->missiles(); if (!missile && weapon != primary && primary && get_weapon_brand(*primary) == SPWPN_RETURNING) { return (true); } if (!missile) return (false); return is_launched(mnc, weapon, *missile); } // Use of variant: // 0 : She is tap dancing. // 1 : It seems she is tap dancing. (lower case pronoun) // 2 : Her sword explodes! (upper case possessive) // 3 : It sticks to her sword! (lower case possessive) // ... as needed const char *mons_pronoun(monster_type mon_type, pronoun_type variant) { gender_type gender = GENDER_NEUTER; if (mons_is_unique( mon_type ) && mon_type != MONS_PLAYER_GHOST || mon_type == MONS_MERMAID) { switch(mon_type) { case MONS_JESSICA: case MONS_PSYCHE: case MONS_JOSEPHINE: case MONS_AGNES: case MONS_MAUD: case MONS_LOUISE: case MONS_FRANCES: case MONS_MARGERY: case MONS_EROLCHA: case MONS_ERICA: case MONS_TIAMAT: case MONS_ERESHKIGAL: case MONS_MERMAID: gender = GENDER_FEMALE; break; default: gender = GENDER_MALE; break; } } switch(variant) { case PRONOUN_CAP: return ((gender == 0) ? "It" : (gender == 1) ? "He" : "She"); case PRONOUN_NOCAP: return ((gender == 0) ? "it" : (gender == 1) ? "he" : "she"); case PRONOUN_CAP_POSSESSIVE: return ((gender == 0) ? "Its" : (gender == 1) ? "His" : "Her"); case PRONOUN_NOCAP_POSSESSIVE: return ((gender == 0) ? "its" : (gender == 1) ? "his" : "her"); case PRONOUN_REFLEXIVE: // Awkward at start of sentence, always lower. return ((gender == 0) ? "itself" : (gender == 1) ? "himself" : "herself"); } return (""); } // Checks if the monster can use smiting/torment to attack without // unimpeded LOS to the player. static bool _mons_can_smite(const monsters *monster) { if (monster->type == MONS_FIEND) return (true); const monster_spells &hspell_pass = monster->spells; for (unsigned i = 0; i < hspell_pass.size(); ++i) if (hspell_pass[i] == SPELL_SYMBOL_OF_TORMENT || hspell_pass[i] == SPELL_SMITING || hspell_pass[i] == SPELL_HELLFIRE_BURST) { return (true); } return (false); } // Determines if a monster is smart and pushy enough to displace other // monsters. A shover should not cause damage to the shovee by // displacing it, so monsters that trail clouds of badness are // ineligible. The shover should also benefit from shoving, so monsters // that can smite/torment are ineligible. // // (Smiters would be eligible for shoving when fleeing if the AI allowed // for smart monsters to flee.) bool monster_shover(const monsters *m) { const monsterentry *me = get_monster_data(m->type); if (!me) return (false); // Efreet and fire elementals are disqualified because they leave behind // clouds of flame. Rotting devils trail clouds of miasma. if (m->type == MONS_EFREET || m->type == MONS_FIRE_ELEMENTAL || m->type == MONS_ROTTING_DEVIL || m->type == MONS_CURSE_TOE) { return (false); } // Smiters profit from staying back and smiting. if (_mons_can_smite(m)) return (false); int mchar = me->showchar; // Somewhat arbitrary: giants and dragons are too big to get past anything, // beetles are too dumb (arguable), dancing weapons can't communicate, eyes // aren't pushers and shovers, zombies are zombies. Worms and elementals // are on the list because all 'w' are currently unrelated. return (mchar != 'C' && mchar != 'B' && mchar != '(' && mchar != 'D' && mchar != 'G' && mchar != 'Z' && mchar != 'z' && mchar != 'w' && mchar != '#'); } // Returns true if m1 and m2 are related, and m1 is higher up the totem pole // than m2. The criteria for being related are somewhat loose, as you can see // below. bool monster_senior(const monsters *m1, const monsters *m2) { const monsterentry *me1 = get_monster_data(m1->type), *me2 = get_monster_data(m2->type); if (!me1 || !me2) return (false); int mchar1 = me1->showchar, mchar2 = me2->showchar; // If both are demons, the smaller number is the nastier demon. if (isdigit(mchar1) && isdigit(mchar2)) return (mchar1 < mchar2); // &s are the evillest demons of all, well apart from Geryon, who really // profits from *not* pushing past beasts. if (mchar1 == '&' && isdigit(mchar2) && m1->type != MONS_GERYON) return (m1->hit_dice > m2->hit_dice); // Skeletal warriors can push past zombies large and small. if (m1->type == MONS_SKELETAL_WARRIOR && (mchar2 == 'z' || mchar2 == 'Z')) return (m1->hit_dice > m2->hit_dice); if (m1->type == MONS_QUEEN_BEE && (m2->type == MONS_KILLER_BEE || m2->type == MONS_KILLER_BEE_LARVA)) { return (true); } if (m1->type == MONS_KILLER_BEE && m2->type == MONS_KILLER_BEE_LARVA) return (true); // Special-case gnolls, so they can't get past (hob)goblins. if (m1->type == MONS_GNOLL && m2->type != MONS_GNOLL) return (false); return (mchar1 == mchar2 && m1->hit_dice > m2->hit_dice); } /////////////////////////////////////////////////////////////////////////////// // monsters methods monsters::monsters() : type(-1), hit_points(0), max_hit_points(0), hit_dice(0), ac(0), ev(0), speed(0), speed_increment(0), x(0), y(0), target_x(0), target_y(0), patrol_point(0, 0), travel_target(MTRAV_NONE), inv(NON_ITEM), spells(), attitude(ATT_HOSTILE), behaviour(BEH_WANDER), foe(MHITYOU), enchantments(), flags(0L), experience(0), number(0), colour(BLACK), foe_memory(0), shield_blocks(0), god(GOD_NO_GOD), ghost(), seen_context("") { travel_path.clear(); } // Empty destructor to keep auto_ptr happy with incomplete ghost_demon type. monsters::~monsters() { } monsters::monsters(const monsters &mon) { init_with(mon); } monsters &monsters::operator = (const monsters &mon) { if (this != &mon) init_with(mon); return (*this); } void monsters::reset() { mname.clear(); enchantments.clear(); ench_countdown = 0; inv.init(NON_ITEM); flags = 0; experience = 0L; type = -1; base_monster = MONS_PROGRAM_BUG; hit_points = 0; max_hit_points = 0; hit_dice = 0; ac = 0; ev = 0; speed_increment = 0; attitude = ATT_HOSTILE; behaviour = BEH_SLEEP; foe = MHITNOT; number = 0; if (in_bounds(x, y)) mgrd[x][y] = NON_MONSTER; x = y = 0; patrol_point = coord_def(0, 0); travel_target = MTRAV_NONE; travel_path.clear(); ghost.reset(NULL); } void monsters::init_with(const monsters &mon) { mname = mon.mname; type = mon.type; base_monster = mon.base_monster; hit_points = mon.hit_points; max_hit_points = mon.max_hit_points; hit_dice = mon.hit_dice; ac = mon.ac; ev = mon.ev; speed = mon.speed; speed_increment = mon.speed_increment; x = mon.x; y = mon.y; target_x = mon.target_x; target_y = mon.target_y; patrol_point = mon.patrol_point; travel_target = mon.travel_target; travel_path = mon.travel_path; inv = mon.inv; spells = mon.spells; attitude = mon.attitude; behaviour = mon.behaviour; foe = mon.foe; enchantments = mon.enchantments; flags = mon.flags; experience = mon.experience; number = mon.number; colour = mon.colour; foe_memory = mon.foe_memory; god = mon.god; if (mon.ghost.get()) ghost.reset(new ghost_demon( *mon.ghost )); else ghost.reset(NULL); } coord_def monsters::pos() const { return coord_def(x, y); } coord_def monsters::target_pos() const { return coord_def(target_x, target_y); } bool monsters::swimming() const { const dungeon_feature_type grid = grd[x][y]; return (grid_is_watery(grid) && mons_habitat(this) == HT_WATER); } bool monsters::submerged() const { return (mons_is_submerged(this)); } bool monsters::floundering() const { const dungeon_feature_type grid = grd[x][y]; return (grid_is_water(grid) // Can't use monster_habitable_grid because that'll return true // for non-water monsters in shallow water. && mons_habitat(this) != HT_WATER && !mons_amphibious(this) && !mons_flies(this)); } bool mons_class_can_pass(const int mclass, const dungeon_feature_type grid) { if (mons_is_wall_shielded(mclass)) { // Permanent walls can't be passed through. return (!grid_is_solid(grid) || grid_is_rock(grid) && !grid_is_permarock(grid)); } return !grid_is_solid(grid); } bool monsters::can_pass_through_feat(dungeon_feature_type grid) const { return mons_class_can_pass(type, grid); } bool monsters::can_drown() const { // Mummies can fall apart in water; ghouls, vampires, and demons can // drown in water/lava. // Other undead just "sink like a rock", to be never seen again. return (!mons_res_asphyx(this) || mons_genus(type) == MONS_MUMMY || mons_genus(type) == MONS_GHOUL || mons_genus(type) == MONS_VAMPIRE || mons_is_zombified(this) || holiness() == MH_DEMONIC); } size_type monsters::body_size(int /* psize */, bool /* base */) const { const monsterentry *e = get_monster_data(type); return (e? e->size : SIZE_MEDIUM); } int monsters::body_weight() const { int mclass = type; switch(mclass) { case MONS_SPECTRAL_THING: case MONS_SPECTRAL_WARRIOR: case MONS_ELECTRIC_GOLEM: case MONS_RAKSHASA_FAKE: return 0; case MONS_ZOMBIE_SMALL: case MONS_ZOMBIE_LARGE: case MONS_SKELETON_SMALL: case MONS_SKELETON_LARGE: case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE: mclass = number; break; default: break; } int weight = mons_weight(mclass); // weight == 0 in the monster entry indicates "no corpse". Can't // use CE_NOCORPSE, because the corpse-effect field is used for // corpseless monsters to indicate what happens if their blood // is sucked. Grrrr. if (weight == 0 && !mons_is_insubstantial(type)) { const monsterentry *entry = get_monster_data(mclass); switch(entry->size) { case SIZE_TINY: weight = 150; break; case SIZE_LITTLE: weight = 300; break; case SIZE_SMALL: weight = 425; break; case SIZE_MEDIUM: weight = 550; break; case SIZE_LARGE: weight = 1300; break; case SIZE_BIG: weight = 1500; break; case SIZE_GIANT: weight = 1800; break; case SIZE_HUGE: weight = 2200; break; default: mpr("ERROR: invalid monster body weight"); perror("monsters::body_weight(): invalid monster body weight"); end(0); } switch(mclass) { case MONS_IRON_DEVIL: weight += 550; break; case MONS_STONE_GOLEM: case MONS_EARTH_ELEMENTAL: case MONS_CRYSTAL_GOLEM: weight *= 2; break; case MONS_IRON_DRAGON: case MONS_IRON_GOLEM: weight *= 3; break; case MONS_QUICKSILVER_DRAGON: case MONS_SILVER_STATUE: weight *= 4; break; case MONS_WOOD_GOLEM: weight *= 2; weight /= 3; break; case MONS_FLYING_SKULL: case MONS_CURSE_SKULL: case MONS_SKELETAL_DRAGON: case MONS_SKELETAL_WARRIOR: weight /= 2; break; case MONS_SHADOW_FIEND: case MONS_SHADOW_IMP: case MONS_SHADOW_DEMON: weight /= 3; break; } switch(monster_symbols[mclass].glyph) { case 'L': weight /= 2; break; case 'p': weight = 0; break; } } if (type == MONS_SKELETON_SMALL || type == MONS_SKELETON_LARGE) weight /= 2; return (weight); } int monsters::total_weight() const { int burden = 0; for (int i = 0; i < NUM_MONSTER_SLOTS; i++) if (inv[i] != NON_ITEM) burden += item_mass(mitm[inv[i]]) * mitm[inv[i]].quantity; return (body_weight() + burden); } int monsters::damage_type(int which_attack) { const item_def *mweap = weapon(which_attack); if (!mweap) { const mon_attack_def atk = mons_attack_spec(this, which_attack); return (atk.type == AT_CLAW) ? DVORP_CLAWING : DVORP_CRUSHING; } return (get_vorpal_type(*mweap)); } int monsters::damage_brand(int which_attack) { const item_def *mweap = weapon(which_attack); if (!mweap) { if (type == MONS_PLAYER_GHOST || type == MONS_PANDEMONIUM_DEMON) return (ghost->brand); else return (SPWPN_NORMAL); } return (!is_range_weapon(*mweap) ? get_weapon_brand(*mweap) : SPWPN_NORMAL); } item_def *monsters::missiles() { return (inv[MSLOT_MISSILE] != NON_ITEM ? &mitm[inv[MSLOT_MISSILE]] : NULL); } int monsters::missile_count() { if (const item_def *missile = missiles()) return (missile->quantity); return (0); } item_def *monsters::launcher() { item_def *weap = mslot_item(MSLOT_WEAPON); if (weap && is_range_weapon(*weap)) return (weap); weap = mslot_item(MSLOT_ALT_WEAPON); return (weap && is_range_weapon(*weap)? weap : NULL); } item_def *monsters::weapon(int which_attack) { const mon_attack_def attk = mons_attack_spec(this, which_attack); if ( attk.type != AT_HIT ) return NULL; // Even/odd attacks use main/offhand weapon if ( which_attack > 1 ) which_attack &= 1; // This randomly picks one of the wielded weapons for monsters that can use // two weapons. Not ideal, but better than nothing. fight.cc does it right, // for various values of right. int weap = inv[MSLOT_WEAPON]; if (which_attack && mons_wields_two_weapons(this)) { const int offhand = mons_offhand_weapon_index(this); if (offhand != NON_ITEM && (weap == NON_ITEM || which_attack == 1 || coinflip())) { weap = offhand; } } return (weap == NON_ITEM) ? NULL : &mitm[weap]; } bool monsters::can_throw_rocks() const { return (type == MONS_STONE_GIANT || type == MONS_CYCLOPS || type == MONS_OGRE || type == MONS_POLYPHEMUS); } bool monsters::has_spell_of_type(unsigned disciplines) const { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) { if (spells[i] == SPELL_NO_SPELL) continue; if (spell_typematch(spells[i], disciplines)) return (true); } return (false); } bool monsters::can_use_missile(const item_def &item) const { // Don't allow monsters to pick up missiles without the corresponding // launcher. The opposite is okay, and sufficient wandering will // hopefully take the monster to a stack of appropriate missiles. // Prevent monsters that have conjurations / summonings from // grabbing missiles. if (has_spell_of_type(SPTYP_CONJURATION | SPTYP_SUMMONING)) return (false); // Blademasters don't want to throw stuff. if (type == MONS_DEEP_ELF_BLADEMASTER) return (false); if (item.base_type == OBJ_WEAPONS) return (is_throwable(item)); if (item.base_type != OBJ_MISSILES) return (false); if ((item.sub_type == MI_THROWING_NET || item.sub_type == MI_JAVELIN) && body_size(PSIZE_BODY) < SIZE_MEDIUM) { return (false); } if (item.sub_type == MI_LARGE_ROCK && !can_throw_rocks()) return (false); // Stones and darts don't need any launcher, and are always okay. // Other missile types that don't need any launcher should be okay // if we've gotten this far. if (item.sub_type == MI_STONE || item.sub_type == MI_DART || !has_launcher(item)) { return (true); } item_def *launch; for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) { launch = mslot_item(static_cast(i)); if (launch && fires_ammo_type(*launch) == item.sub_type) return (true); } // No fitting launcher in inventory. return (false); } void monsters::swap_slots(mon_inv_type a, mon_inv_type b) { const int swap = inv[a]; inv[a] = inv[b]; inv[b] = swap; } void monsters::equip_weapon(item_def &item, int near, bool msg) { if (msg && !need_message(near)) msg = false; if (msg) { snprintf(info, INFO_SIZE, " wields %s.", item.name(DESC_NOCAP_A, false, false, true, false, ISFLAG_CURSED).c_str()); msg = simple_monster_message(this, info); } const int brand = get_weapon_brand(item); if (brand == SPWPN_PROTECTION) ac += 5; if (msg) { bool message_given = true; switch (brand) { case SPWPN_FLAMING: mpr("It bursts into flame!"); break; case SPWPN_FREEZING: mpr("It glows with a cold blue light!"); break; case SPWPN_HOLY_WRATH: mpr("It softly glows with a divine radiance!"); break; case SPWPN_ELECTROCUTION: mpr("You hear the crackle of electricity.", MSGCH_SOUND); break; case SPWPN_VENOM: mpr("It begins to drip with poison!"); break; case SPWPN_DRAINING: mpr("You sense an unholy aura."); break; case SPWPN_FLAME: mpr("It bursts into flame!"); break; case SPWPN_FROST: mpr("It is covered in frost."); break; case SPWPN_RETURNING: mpr("It wiggles slightly."); break; case SPWPN_DISTORTION: mpr("Its appearance distorts for a moment."); break; default: // A ranged weapon without special message is known to be unbranded. if (brand != SPWPN_NORMAL || !is_range_weapon(item)) message_given = false; } if (message_given) set_ident_flags(item, ISFLAG_KNOW_TYPE); } } void monsters::equip_armour(item_def &item, int near) { if (need_message(near)) { snprintf(info, INFO_SIZE, " wears %s.", item.name(DESC_NOCAP_A).c_str()); simple_monster_message(this, info); } const equipment_type eq = get_armour_slot(item); if (eq != EQ_SHIELD) { ac += property( item, PARM_AC ); const int armour_plus = item.plus; ASSERT(abs(armour_plus) < 20); if (abs(armour_plus) < 20) ac += armour_plus; } // Shields can affect evasion. ev += property( item, PARM_EVASION ) / 2; if (ev < 1) ev = 1; // This *shouldn't* happen. } void monsters::equip(item_def &item, int slot, int near) { switch (item.base_type) { case OBJ_WEAPONS: { bool give_msg = (slot == MSLOT_WEAPON || mons_wields_two_weapons(this)); equip_weapon(item, near, give_msg); break; } case OBJ_ARMOUR: equip_armour(item, near); break; default: break; } } void monsters::unequip_weapon(item_def &item, int near, bool msg) { if (msg && !need_message(near)) msg = false; if (msg) { snprintf(info, INFO_SIZE, " unwields %s.", item.name(DESC_NOCAP_A, false, false, true, false, ISFLAG_CURSED).c_str()); msg = simple_monster_message(this, info); } const int brand = get_weapon_brand(item); if (brand == SPWPN_PROTECTION) ac -= 5; if (msg && brand != SPWPN_NORMAL) { bool message_given = true; switch (brand) { case SPWPN_FLAMING: mpr("It stops flaming."); break; case SPWPN_HOLY_WRATH: mpr("It stops glowing."); break; case SPWPN_ELECTROCUTION: mpr("It stops crackling."); break; case SPWPN_VENOM: mpr("It stops dripping with poison."); break; case SPWPN_DISTORTION: mpr("Its appearance distorts for a moment."); break; default: message_given = false; } if (message_given) set_ident_flags(item, ISFLAG_KNOW_TYPE); } } void monsters::unequip_armour(item_def &item, int near) { if (need_message(near)) { snprintf(info, INFO_SIZE, " takes off %s.", item.name(DESC_NOCAP_A).c_str()); simple_monster_message(this, info); } const equipment_type eq = get_armour_slot(item); if (eq != EQ_SHIELD) { ac -= property( item, PARM_AC ); const int armour_plus = item.plus; ASSERT(abs(armour_plus) < 20); if (abs(armour_plus) < 20) ac -= armour_plus; } ev -= property( item, PARM_EVASION ) / 2; if (ev < 1) ev = 1; // This *shouldn't* happen. } bool monsters::unequip(item_def &item, int slot, int near, bool force) { if (!force && item.cursed()) return (false); if (!force && mons_near(this) && player_monster_visible(this)) set_ident_flags(item, ISFLAG_KNOW_CURSE); switch (item.base_type) { case OBJ_WEAPONS: { bool give_msg = (slot == MSLOT_WEAPON || mons_wields_two_weapons(this)); unequip_weapon(item, near, give_msg); break; } case OBJ_ARMOUR: unequip_armour(item, near); break; default: break; } return (true); } void monsters::lose_pickup_energy() { if (const monsterentry* entry = find_monsterentry()) { const int delta = speed * entry->energy_usage.pickup_percent / 100; if (speed_increment > 25 && delta < speed_increment) speed_increment -= delta; } } void monsters::pickup_message(const item_def &item, int near) { if (need_message(near)) { mprf("%s picks up %s.", name(DESC_CAP_THE).c_str(), item.base_type == OBJ_GOLD ? "some gold" : item.name(DESC_NOCAP_A).c_str()); } } bool monsters::pickup(item_def &item, int slot, int near, bool force_merge) { // If a monster chooses a two-handed weapon as main weapon, it will // first have to drop any shield it might wear. // (Monsters will always favour damage over protection.) if (slot == MSLOT_WEAPON && inv[MSLOT_SHIELD] != NON_ITEM && hands_reqd(item, body_size(PSIZE_BODY)) == HANDS_TWO) { if (!drop_item(MSLOT_SHIELD, near)) return (false); } if (inv[slot] != NON_ITEM) { if (items_stack(item, mitm[inv[slot]], force_merge)) { dungeon_events.fire_position_event( dgn_event(DET_ITEM_PICKUP, pos(), 0, item.index(), monster_index(this)), pos()); pickup_message(item, near); inc_mitm_item_quantity( inv[slot], item.quantity ); destroy_item(item.index()); equip(item, slot, near); lose_pickup_energy(); return (true); } return (false); } dungeon_events.fire_position_event( dgn_event(DET_ITEM_PICKUP, pos(), 0, item.index(), monster_index(this)), pos()); const int index = item.index(); unlink_item(index); inv[slot] = index; pickup_message(item, near); equip(item, slot, near); lose_pickup_energy(); return (true); } bool monsters::drop_item(int eslot, int near) { if (eslot < 0 || eslot >= NUM_MONSTER_SLOTS) return (false); int index = inv[eslot]; if (index == NON_ITEM) return (true); item_def &item(mitm[index]); // Unequip equipped items before dropping them; unequip() prevents // cursed items from being removed. bool was_unequipped = false; if (eslot == MSLOT_WEAPON || eslot == MSLOT_ARMOUR || eslot == MSLOT_ALT_WEAPON && mons_wields_two_weapons(this)) { if (!unequip(item, eslot, near)) return (false); was_unequipped = true; } bool on_floor = true; if (item.flags & ISFLAG_SUMMONED) { on_floor = false; if (need_message(near)) mprf("%s disappear%s in a puff of smoke as %s drops it!", item.name(DESC_CAP_THE).c_str(), item.quantity == 1 ? "s" : "", name(DESC_NOCAP_THE).c_str()); item_was_destroyed(item, monster_index(this)); } else if (!move_item_to_grid(&index, x, y)) { // Re-equip item if we somehow failed to drop it. if (was_unequipped) equip(item, eslot, near); return (false); } if (on_floor) { if (mons_friendly(this)) item.flags |= ISFLAG_DROPPED_BY_ALLY; if (need_message(near)) mprf("%s drops %s.", name(DESC_CAP_THE).c_str(), item.name(DESC_NOCAP_A).c_str()); dungeon_feature_type feat = grd(pos()); if (grid_destroys_items(feat)) { if ( player_can_hear(pos()) ) mprf(MSGCH_SOUND, grid_item_destruction_message(feat)); item_was_destroyed(item, monster_index(this)); unlink_item(index); } } inv[eslot] = NON_ITEM; return (true); } bool monsters::pickup_launcher(item_def &launch, int near) { const int mdam_rating = mons_weapon_damage_rating(launch); const missile_type mt = fires_ammo_type(launch); int eslot = -1; for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) { if (const item_def *elaunch = mslot_item(static_cast(i))) { if (!is_range_weapon(*elaunch)) continue; return (fires_ammo_type(*elaunch) == mt && mons_weapon_damage_rating(*elaunch) < mdam_rating && drop_item(i, near) && pickup(launch, i, near)); } else eslot = i; } return (eslot == -1? false : pickup(launch, eslot, near)); } static bool _is_signature_weapon(monsters *monster, const item_def &weapon) { if (weapon.base_type != OBJ_WEAPONS) return (false); if (monster->type == MONS_DAEVA) return (weapon.sub_type == WPN_BLESSED_EUDEMON_BLADE); // We might allow Sigmund to pick up a better scythe if he finds one... if (monster->type == MONS_SIGMUND) return (weapon.sub_type == WPN_SCYTHE); if (is_fixed_artefact(weapon)) { switch (weapon.special) { case SPWPN_SCEPTRE_OF_ASMODEUS: return (monster->type == MONS_ASMODEUS); case SPWPN_STAFF_OF_DISPATER: return (monster->type == MONS_DISPATER); case SPWPN_SWORD_OF_CEREBOV: return (monster->type == MONS_CEREBOV); } } return (false); } bool monsters::pickup_melee_weapon(item_def &item, int near) { const bool dual_wielding = mons_wields_two_weapons(this); if (dual_wielding) { // If we have either weapon slot free, pick up the weapon. if (inv[MSLOT_WEAPON] == NON_ITEM) return pickup(item, MSLOT_WEAPON, near); if (inv[MSLOT_ALT_WEAPON] == NON_ITEM) return pickup(item, MSLOT_ALT_WEAPON, near); } const int mdam_rating = mons_weapon_damage_rating(item); int eslot = -1; item_def *weap; // Monsters have two weapon slots, one of which can be a ranged, and // the other a melee weapon. (The exception being dual-wielders who can // wield two melee weapons). The weapon in MSLOT_WEAPON is the one // currently wielded (can be empty). for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) { weap = mslot_item(static_cast(i)); if (!weap) { // If no weapon in this slot, mark this one. if (eslot == -1) eslot = i; } else { if (is_range_weapon(*weap)) continue; // Don't drop weapons specific to the monster. if (_is_signature_weapon(this, *weap) && !dual_wielding) return (false); // If we get here, the weapon is a melee weapon. // If the new weapon is better than the current one and not cursed, // replace it. Otherwise, give up. if (mons_weapon_damage_rating(*weap) < mdam_rating && !weap->cursed()) { if (!dual_wielding || i == MSLOT_WEAPON || mons_weapon_damage_rating(*weap) < mons_weapon_damage_rating(*mslot_item(MSLOT_WEAPON))) { eslot = i; if (!dual_wielding) break; } } else if (!dual_wielding) { // We've got a good melee weapon, that's enough. return (false); } } } // No slot found to place this item. if (eslot == -1) return (false); // Current item cannot be dropped. if (inv[eslot] != NON_ITEM && !drop_item(eslot, near)) return (false); return (pickup(item, eslot, near)); } // Arbitrary damage adjustment for quantity of missiles. So sue me. static int _q_adj_damage(int damage, int qty) { return (damage * std::min(qty, 8)); } bool monsters::pickup_throwable_weapon(item_def &item, int near) { // If occupied, don't pick up a throwable weapons if it would just // stack with an existing one. (Upgrading is possible.) if (mslot_item(MSLOT_MISSILE) && (mons_is_wandering(this) || mons_friendly(this) && foe == MHITYOU) && pickup(item, MSLOT_MISSILE, near, true)) { return (true); } item_def *launch = NULL; const int exist_missile = mons_pick_best_missile(this, &launch, true); if (exist_missile == NON_ITEM || (_q_adj_damage(mons_missile_damage(launch, &mitm[exist_missile]), mitm[exist_missile].quantity) < _q_adj_damage(mons_thrown_weapon_damage(&item), item.quantity))) { if (inv[MSLOT_MISSILE] != NON_ITEM && !drop_item(MSLOT_MISSILE, near)) return (false); return pickup(item, MSLOT_MISSILE, near); } return (false); } bool monsters::wants_weapon(const item_def &weap) const { // monsters can't use fixed artefacts if (is_fixed_artefact( weap )) return (false); // Blademasters and master archers like their starting weapon and // don't want another, thank you. if (type == MONS_DEEP_ELF_BLADEMASTER || type == MONS_DEEP_ELF_MASTER_ARCHER) { return (false); } // Wimpy monsters (e.g. kobold, goblin) shouldn't pick up halberds etc. if (!check_weapon_wieldable_size( weap, body_size(PSIZE_BODY) )) return (false); // Monsters capable of dual-wielding will always prefer two weapons // to a single two-handed one, however strong. if (mons_wields_two_weapons(this) && hands_reqd(weap, body_size(PSIZE_BODY)) == HANDS_TWO) { return (false); } // Nobody picks up giant clubs. // Starting equipment is okay, of course. if (weap.sub_type == WPN_GIANT_CLUB || weap.sub_type == WPN_GIANT_SPIKED_CLUB) { return (false); } // Demonic/undead monsters won't pick up holy wrath. if (get_weapon_brand(weap) == SPWPN_HOLY_WRATH && mons_is_unholy(this)) return (false); // Holy monsters won't pick up demonic weapons. if (mons_holiness(this) == MH_HOLY && is_evil_item(weap)) return (false); return (true); } static bool _item_race_matches_monster(const item_def &item, monsters *mons) { return (get_equip_race(item) == ISFLAG_ELVEN && mons_genus(mons->type) == MONS_ELF || get_equip_race(item) == ISFLAG_ORCISH && mons_genus(mons->type) == MONS_ORC); } bool monsters::wants_armour(const item_def &item) const { // Monsters that are capable of dual wielding won't pick up shields. // Neither will monsters that are already wielding a two-hander. if (is_shield(item) && (mons_wields_two_weapons(this) || mslot_item(MSLOT_WEAPON) && hands_reqd(*mslot_item(MSLOT_WEAPON), body_size(PSIZE_BODY)) == HANDS_TWO)) { return (false); } // Returns whether this armour is the monster's size. return (check_armour_size(item, mons_size(this))); } mon_inv_type equip_slot_to_mslot(equipment_type eq) { switch (eq) { case EQ_WEAPON: return MSLOT_WEAPON; case EQ_BODY_ARMOUR: return MSLOT_ARMOUR; case EQ_SHIELD: return MSLOT_SHIELD; default: return (NUM_MONSTER_SLOTS); } } bool monsters::pickup_armour(item_def &item, int near, bool force) { ASSERT(item.base_type == OBJ_ARMOUR); if (!force && !wants_armour(item)) return (false); equipment_type eq = EQ_NONE; // Hack to allow nagas/centaurs to wear bardings. (jpeg) switch (item.sub_type) { case ARM_NAGA_BARDING: if (::mons_species(this->type) == MONS_NAGA) eq = EQ_BODY_ARMOUR; break; case ARM_CENTAUR_BARDING: if (::mons_species(this->type) == MONS_CENTAUR || ::mons_species(this->type) == MONS_YAKTAUR) { eq = EQ_BODY_ARMOUR; } break; default: eq = get_armour_slot(item); } // Bardings are only wearable by the appropriate monster. if (eq == EQ_NONE) return (false); // XXX: Monsters can only equip body armour and shields (as of 0.4). if (!force && eq != EQ_BODY_ARMOUR && eq != EQ_SHIELD) return (false); const mon_inv_type mslot = equip_slot_to_mslot(eq); if (mslot == NUM_MONSTER_SLOTS) return (false); int newAC = item.armour_rating(); // No armour yet -> get this one. if (!mslot_item(mslot) && newAC > 0) return pickup(item, mslot, near); // Very simplistic armour evaluation (AC comparison). if (const item_def *existing_armour = slot_item(eq)) { if (!force) { int oldAC = existing_armour->armour_rating(); if (oldAC > newAC) return (false); if (oldAC == newAC) { // Use shopping value as a crude estimate of resistances etc. // XXX: This is not really logical as many properties don't // apply to monsters (e.g. levitation, blink, berserk). int oldval = item_value(*existing_armour); int newval = item_value(item); // Vastly prefer matching racial type. if (_item_race_matches_monster(*existing_armour, this)) oldval *= 2; if (_item_race_matches_monster(item, this)) newval *= 2; if (oldval >= newval) return (false); } } if (!drop_item(mslot, near)) return (false); } return pickup(item, mslot, near); } bool monsters::pickup_weapon(item_def &item, int near, bool force) { if (!force && !wants_weapon(item)) return (false); // Weapon pickup involves: // - If we have no weapons, always pick this up. // - If this is a melee weapon and we already have a melee weapon, pick // it up if it is superior to the one we're carrying (and drop the // one we have). // - If it is a ranged weapon, and we already have a ranged weapon, // pick it up if it is better than the one we have. // - If it is a throwable weapon, and we're carrying no missiles (or our // missiles are the same type), pick it up. if (is_range_weapon(item)) return (pickup_launcher(item, near)); if (pickup_melee_weapon(item, near)) return (true); return (can_use_missile(item) && pickup_throwable_weapon(item, near)); } bool monsters::pickup_missile(item_def &item, int near, bool force) { const item_def *miss = missiles(); if (!force) { if (item.sub_type == MI_THROWING_NET) { // Monster may not pick up trapping net. if (mons_is_caught(this) && item_is_stationary(item)) return (false); } else // None of these exceptions hold for throwing nets. { // Spellcasters should not waste time with ammunition. if (mons_has_ranged_spell(this)) return (false); // Monsters in a fight will only pick up missiles if doing so // is worthwhile. if (!mons_is_wandering(this) && (!mons_friendly(this) || foe != MHITYOU) && (item.quantity < 5 || miss && miss->quantity >= 7)) { return (false); } } } if (miss && items_stack(*miss, item)) return (pickup(item, MSLOT_MISSILE, near)); if (!force && !can_use_missile(item)) return (false); if (miss) { item_def *launch; for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) { launch = mslot_item(static_cast(i)); if (launch) { const int bow_brand = get_weapon_brand(*launch); const int item_brand = get_ammo_brand(item); // If this ammunition is better, drop the old ones. // Don't upgrade to ammunition whose brand cancels the // launcher brand or doesn't improve it further. if (fires_ammo_type(*launch) == item.sub_type && (fires_ammo_type(*launch) != miss->sub_type || item.plus > miss->plus && get_ammo_brand(*miss) == item_brand || item.plus >= miss->plus && get_ammo_brand(*miss) == SPMSL_NORMAL && item_brand != SPMSL_NORMAL && (bow_brand != SPWPN_FLAME || item_brand == SPMSL_POISONED) && bow_brand != SPWPN_FROST)) { if (!drop_item(MSLOT_MISSILE, near)) return (false); break; } } } // Darts don't absolutely need a launcher - still allow upgrading. if (item.sub_type == miss->sub_type && item.sub_type == MI_DART && (item.plus > miss->plus || item.plus == miss->plus && get_ammo_brand(*miss) == SPMSL_NORMAL && get_ammo_brand(item) != SPMSL_NORMAL)) { if (!drop_item(MSLOT_MISSILE, near)) return (false); } } return pickup(item, MSLOT_MISSILE, near); } bool monsters::pickup_wand(item_def &item, int near) { // Don't pick up empty wands. if (item.plus == 0) return (false); // Only low-HD monsters bother with wands. if (hit_dice >= 14) return (false); // This is the only type based restriction I can think of: // Holy monsters won't pick up draining items. if (mons_holiness(this) == MH_HOLY && item.sub_type == WAND_DRAINING) return (false); // If you already have a charged wand, don't bother. // Otherwise, replace with a charged one. if (item_def *wand = mslot_item(MSLOT_WAND)) { if (wand->plus > 0) return (false); if (!drop_item(MSLOT_WAND, near)) return (false); } return (pickup(item, MSLOT_WAND, near)); } bool monsters::pickup_scroll(item_def &item, int near) { if (item.sub_type != SCR_TELEPORTATION && item.sub_type != SCR_BLINKING && item.sub_type != SCR_SUMMONING) { return (false); } return pickup(item, MSLOT_SCROLL, near); } bool monsters::pickup_potion(item_def &item, int near) { // Only allow monsters to pick up potions if they can actually use them. switch (item.sub_type) { case POT_HEALING: case POT_HEAL_WOUNDS: if (mons_holiness(this) == MH_UNDEAD || mons_holiness(this) == MH_NONLIVING || mons_holiness(this) == MH_PLANT) { return (false); } break; case POT_BLOOD: case POT_BLOOD_COAGULATED: if (::mons_species(this->type) != MONS_VAMPIRE) return (false); break; case POT_SPEED: case POT_INVISIBILITY: // If there are any item using monsters that are permanently invisible // this might have to be restricted. break; default: return (false); } return pickup(item, MSLOT_POTION, near); } bool monsters::pickup_gold(item_def &item, int near) { return pickup(item, MSLOT_GOLD, near); } bool monsters::eat_corpse(item_def &carrion, int near) { if (!mons_eats_corpses(this)) return (false); hit_points += 1 + random2(mons_weight(carrion.plus)) / 100; // Limited growth factor here -- should 77 really be the cap? {dlb}: if (hit_points > 100) hit_points = 100; if (hit_points > max_hit_points) max_hit_points = hit_points; if (need_message(near)) { mprf("%s eats %s.", name(DESC_CAP_THE).c_str(), carrion.name(DESC_NOCAP_THE).c_str()); } destroy_item( carrion.index() ); return (true); } bool monsters::pickup_misc(item_def &item, int near) { // Never pick up runes. if (item.sub_type == MISC_RUNE_OF_ZOT) return (false); // Holy monsters won't pick up unholy misc. items. if (mons_holiness(this) == MH_HOLY && is_evil_item(item)) return (false); return pickup(item, MSLOT_MISCELLANY, near); } // Jellies are handled elsewhere, in _handle_pickup() in monstuff.cc. bool monsters::pickup_item(item_def &item, int near, bool force) { // Equipping stuff can be forced when initially equipping monsters. if (!force) { // If a monster isn't otherwise occupied (has a foe, is fleeing, etc.) // it is considered wandering. bool wandering = (mons_is_wandering(this) || mons_friendly(this) && foe == MHITYOU); const int itype = item.base_type; // Weak(ened) monsters won't stop to pick up things as long as they // feel unsafe. if (!wandering && (hit_points * 10 < max_hit_points || hit_points < 10) && mon_enemies_around(this)) { return (false); } if (mons_friendly(this)) { // Never pick up gold or misc. items, it'd only annoy the player. if (itype == OBJ_MISCELLANY || itype == OBJ_GOLD) return (false); // Depending on the friendly pickup toggle, your allies may not // pick up anything, or only stuff dropped by (other) allies. if (you.friendly_pickup == FRIENDLY_PICKUP_NONE || you.friendly_pickup == FRIENDLY_PICKUP_FRIEND && !testbits(item.flags, ISFLAG_DROPPED_BY_ALLY)) { return (false); } } if (!wandering) { // These are not important enough for pickup when // seeking, fleeing etc. if (itype == OBJ_ARMOUR || itype == OBJ_CORPSES || itype == OBJ_MISCELLANY || itype == OBJ_GOLD) { return (false); } if (itype == OBJ_WEAPONS || itype == OBJ_MISSILES) { // Fleeing monsters only pick up emergency equipment. if (mons_is_fleeing(this)) return (false); // While occupied, hostile monsters won't pick up items // dropped or thrown by you. (You might have done that to // distract them.) if (!mons_friendly(this) && (testbits(item.flags, ISFLAG_DROPPED) || testbits(item.flags, ISFLAG_THROWN))) { return (false); } } } } switch (item.base_type) { // Pickup some stuff only if WANDERING. case OBJ_ARMOUR: return pickup_armour(item, near, force); case OBJ_CORPSES: return eat_corpse(item, near); case OBJ_MISCELLANY: return pickup_misc(item, near); case OBJ_GOLD: return pickup_gold(item, near); // Fleeing monsters won't pick up these. // Hostiles won't pick them up if they were ever dropped/thrown by you. case OBJ_WEAPONS: return pickup_weapon(item, near, force); case OBJ_MISSILES: return pickup_missile(item, near, force); // Other types can always be picked up // (barring other checks depending on subtype, of course). case OBJ_WANDS: return pickup_wand(item, near); case OBJ_SCROLLS: return pickup_scroll(item, near); case OBJ_POTIONS: return pickup_potion(item, near); default: return (false); } } bool monsters::need_message(int &near) const { return (near != -1 ? near : (near = visible()) ); } void monsters::swap_weapons(int near) { item_def *weap = mslot_item(MSLOT_WEAPON); item_def *alt = mslot_item(MSLOT_ALT_WEAPON); if (weap && !unequip(*weap, MSLOT_WEAPON, near)) { // Item was cursed. return; } swap_slots(MSLOT_WEAPON, MSLOT_ALT_WEAPON); if (alt) equip(*alt, MSLOT_WEAPON, near); // Monsters can swap weapons really fast. :-) if ((weap || alt) && speed_increment >= 2) { if (const monsterentry *entry = find_monsterentry()) speed_increment -= div_rand_round(entry->energy_usage.attack, 5); } } void monsters::wield_melee_weapon(int near) { const item_def *weap = mslot_item(MSLOT_WEAPON); if (!weap || (!weap->cursed() && is_range_weapon(*weap))) { const item_def *alt = mslot_item(MSLOT_ALT_WEAPON); // Switch to the alternate weapon if it's not a ranged weapon, too, // or switch away from our main weapon if it's a ranged weapon. if (alt && !is_range_weapon(*alt) || weap && !alt) swap_weapons(near); } } item_def *monsters::slot_item(equipment_type eq) { return mslot_item(equip_slot_to_mslot(eq)); } item_def *monsters::mslot_item(mon_inv_type mslot) const { const int mindex = (mslot == NUM_MONSTER_SLOTS) ? NON_ITEM : inv[mslot]; return (mindex == NON_ITEM ? NULL : &mitm[mindex]); } item_def *monsters::shield() { return (mslot_item(MSLOT_SHIELD)); } bool monsters::is_named() const { return (!mname.empty() || mons_is_unique(type)); } bool monsters::has_base_name() const { // Any non-ghost, non-Pandemonium demon that has an explicitly set name // has a base name. return (!mname.empty() && !ghost.get()); } std::string monsters::name(description_level_type desc) const { return this->name(desc, false); } std::string monsters::name(description_level_type desc, bool force_vis) const { const bool possessive = (desc == DESC_NOCAP_YOUR || desc == DESC_NOCAP_ITS); if (possessive) desc = DESC_NOCAP_THE; std::string monnam = _str_monam(*this, desc, force_vis); return (possessive? apostrophise(monnam) : monnam); } std::string monsters::base_name(description_level_type desc, bool force_vis) const { if (ghost.get() || mons_is_unique(type)) return (name(desc, force_vis)); else { unwind_var tmname( const_cast(this)->mname, ""); return (name(desc, force_vis)); } } std::string monsters::pronoun(pronoun_type pro) const { return (mons_pronoun(static_cast(type), pro)); } std::string monsters::conj_verb(const std::string &verb) const { if (!verb.empty() && verb[0] == '!') return (verb.substr(1)); if (verb == "are") return ("is"); if (ends_with(verb, "f") || ends_with(verb, "fe") || ends_with(verb, "y")) { return (verb + "s"); } return (pluralise(verb)); } int monsters::id() const { return (type); } int monsters::get_experience_level() const { return (hit_dice); } bool monsters::fumbles_attack(bool verbose) { if (floundering() && one_chance_in(4)) { if (verbose) { if (you.can_see(this)) { mprf("%s splashes around in the water.", this->name(DESC_CAP_THE).c_str()); } else if (!silenced(you.x_pos, you.y_pos) && !silenced(x, y)) mpr("You hear a splashing noise.", MSGCH_SOUND); } return (true); } if (mons_is_submerged(this)) return (true); return (false); } bool monsters::cannot_fight() const { return (mons_class_flag(type, M_NO_EXP_GAIN) || mons_is_statue(type)); } void monsters::attacking(actor * /* other */) { } void monsters::go_berserk(bool /* intentional */) { if (!this->can_go_berserk()) return; if (has_ench(ENCH_SLOW)) { del_ench(ENCH_SLOW, true); // Give no additional message. simple_monster_message( this, make_stringf(" shakes off %s lethargy.", pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str()).c_str()); } del_ench(ENCH_HASTE); del_ench(ENCH_FATIGUE, true); // Give no additional message. const int duration = 16 + random2avg(13, 2); add_ench(mon_enchant(ENCH_BERSERK, 0, KC_OTHER, duration * 10)); add_ench(mon_enchant(ENCH_HASTE, 0, KC_OTHER, duration * 10)); simple_monster_message( this, " goes berserk!" ); } void monsters::expose_to_element(beam_type, int) { } void monsters::banish(const std::string &) { monster_die(this, KILL_RESET, 0); } int monsters::holy_aura() const { return ((type == MONS_DAEVA || type == MONS_ANGEL) ? hit_dice : 0); } bool monsters::has_spell(spell_type spell) const { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) if (spells[i] == spell) return (true); return (false); } bool monsters::visible() const { return (mons_near(this) && player_monster_visible(this)); } bool monsters::confused() const { return (mons_is_confused(this)); } bool monsters::paralysed() const { return (mons_is_paralysed(this)); } bool monsters::cannot_act() const { return (mons_cannot_act(this)); } bool monsters::cannot_move() const { return (mons_cannot_move(this)); } bool monsters::asleep() const { return (mons_is_sleeping(this)); } bool monsters::backlit(bool check_haloed) const { return (has_ench(ENCH_BACKLIGHT) || ((check_haloed) ? haloed() : false)); } bool monsters::haloed() const { return (inside_halo(x, y)); } bool monsters::caught() const { return (mons_is_caught(this)); } int monsters::shield_bonus() const { const item_def *shld = const_cast(this)->shield(); if (shld) { // Note that 0 is not quite no-blocking. if (incapacitated()) return (0); int shld_c = property(*shld, PARM_AC); return (random2avg(shld_c + hit_dice * 2 / 3, 2)); } return (-100); } int monsters::shield_block_penalty() const { return (4 * shield_blocks * shield_blocks); } void monsters::shield_block_succeeded() { ++shield_blocks; } int monsters::shield_bypass_ability(int) const { return (15 + hit_dice * 2 / 3); } int monsters::armour_class() const { return (ac); } int monsters::melee_evasion(const actor *act) const { int evasion = ev; if (paralysed() || asleep() || one_chance_in(20)) evasion = 0; else if (caught()) evasion /= (body_size(PSIZE_BODY) + 2); else if (confused()) evasion /= 2; return (evasion); } void monsters::heal(int amount, bool max_too) { hit_points += amount; if (max_too) max_hit_points += amount; if (hit_points > max_hit_points) hit_points = max_hit_points; } mon_holy_type monsters::holiness() const { return (mons_holiness(this)); } int monsters::res_fire() const { return (mons_res_fire(this)); } int monsters::res_steam() const { return (mons_res_steam(this)); } int monsters::res_cold() const { return (mons_res_cold(this)); } int monsters::res_elec() const { return (mons_res_elec(this)); } int monsters::res_poison() const { return (mons_res_poison(this)); } int monsters::res_negative_energy() const { return (mons_res_negative_energy(this)); } int monsters::res_rotting() const { return (mons_holiness(this) == MH_NATURAL ? 0 : 1); } flight_type monsters::flight_mode() const { return (mons_flies(this)); } bool monsters::is_levitating() const { // Checking class flags is not enough - see mons_flies. return (flight_mode() == FL_LEVITATE); } int monsters::mons_species() const { return ::mons_species(type); } void monsters::poison(actor *agent, int amount) { if (amount <= 0) return; // Scale poison down for monsters. if (!(amount /= 2)) amount = 1; poison_monster(this, agent->kill_alignment(), amount); } int monsters::skill(skill_type sk, bool) const { switch (sk) { case SK_NECROMANCY: return (holiness() == MH_UNDEAD? hit_dice / 2 : hit_dice / 3); default: return (0); } } void monsters::blink() { monster_blink(this); } void monsters::teleport(bool now, bool) { monster_teleport(this, now, false); } bool monsters::alive() const { return (hit_points > 0 && type != -1); } god_type monsters::deity() const { return (god); } int monsters::hurt(const actor *agent, int amount) { if (amount <= 0) return (0); amount = std::min(amount, hit_points); hit_points -= amount; // Allow the victim to exhibit passive damage behaviour (royal jelly). react_to_damage(amount); if (agent && (hit_points < 1 || hit_dice < 1) && type != -1) { if (agent->atype() == ACT_PLAYER) monster_die(this, KILL_YOU, 0); else { monster_die(this, KILL_MON, monster_index( dynamic_cast(agent) )); } } return (amount); } void monsters::rot(actor *agent, int rotlevel, int immed_rot) { if (res_rotting() > 0) return; // Apply immediate damage because we can't handle rotting for monsters yet. const int damage = immed_rot; if (damage > 0) { if (mons_near(this) && player_monster_visible(this)) { mprf("%s %s!", name(DESC_CAP_THE).c_str(), rotlevel == 0? "looks less resilient" : "rots"); } hurt(agent, damage); if (alive()) { max_hit_points -= immed_rot * 2; if (hit_points > max_hit_points) hit_points = max_hit_points; } } if (rotlevel > 0) { add_ench( mon_enchant(ENCH_ROT, std::min(rotlevel, 4), agent->kill_alignment()) ); } } void monsters::confuse(int strength) { bolt beam_temp; beam_temp.flavour = BEAM_CONFUSION; mons_ench_f2( this, beam_temp ); } void monsters::paralyse(int strength) { bolt paralysis; paralysis.flavour = BEAM_PARALYSIS; mons_ench_f2(this, paralysis); } void monsters::petrify(int strength) { if (mons_is_insubstantial(type)) return; bolt petrif; petrif.flavour = BEAM_PETRIFY; mons_ench_f2(this, petrif); } void monsters::slow_down(int strength) { bolt slow; slow.flavour = BEAM_SLOW; mons_ench_f2(this, slow); } void monsters::set_ghost(const ghost_demon &g) { #ifdef USE_TILE TileGhostInit(g); #endif ghost.reset( new ghost_demon(g) ); mname = ghost->name; } void monsters::pandemon_init() { hit_dice = ghost->xl; hit_points = ghost->max_hp; max_hit_points = ghost->max_hp; ac = ghost->ac; ev = ghost->ev; // Don't make greased-lightning Pandemonium demons in the dungeon // max speed = 17). Demons in Pandemonium can be up to speed 24. if (you.level_type == LEVEL_DUNGEON) speed = (one_chance_in(3)? 10 : 7 + roll_dice(2, 5)); else speed = (one_chance_in(3) ? 10 : 10 + roll_dice(2, 7)); speed_increment = 70; if (you.char_direction == GDT_ASCENDING && you.level_type == LEVEL_DUNGEON) colour = LIGHTRED; else colour = random_colour(); // demon's colour load_spells(MST_GHOST); } void monsters::ghost_init() { type = MONS_PLAYER_GHOST; hit_dice = ghost->xl; hit_points = ghost->max_hp; max_hit_points = ghost->max_hp; ac = ghost->ac; ev = ghost->ev; speed = ghost->speed; speed_increment = 70; attitude = ATT_HOSTILE; behaviour = BEH_WANDER; flags = 0; foe = MHITNOT; foe_memory = 0; colour = mons_class_colour(MONS_PLAYER_GHOST); number = MONS_PROGRAM_BUG; load_spells(MST_GHOST); inv.init(NON_ITEM); enchantments.clear(); ench_countdown = 0; find_place_to_live(); } bool monsters::check_set_valid_home(const coord_def &place, coord_def &chosen, int &nvalid) const { if (!in_bounds(place)) return (false); if (place == you.pos()) return (false); if (mgrd(place) != NON_MONSTER || grd(place) < DNGN_FLOOR) return (false); if (one_chance_in(++nvalid)) chosen = place; return (true); } bool monsters::find_home_around(const coord_def &c, int radius) { coord_def place(-1, -1); int nvalid = 0; for (int yi = -radius; yi <= radius; ++yi) { const coord_def c1(c.x - radius, c.y + yi); const coord_def c2(c.x + radius, c.y + yi); check_set_valid_home(c1, place, nvalid); check_set_valid_home(c2, place, nvalid); } for (int xi = -radius + 1; xi < radius; ++xi) { const coord_def c1(c.x + xi, c.y - radius); const coord_def c2(c.x + xi, c.y + radius); check_set_valid_home(c1, place, nvalid); check_set_valid_home(c2, place, nvalid); } if (nvalid) { x = place.x; y = place.y; return (true); } return (false); } bool monsters::find_place_near_player() { for (int radius = 1; radius < 7; ++radius) if (find_home_around(you.pos(), radius)) return (true); return (false); } bool monsters::find_home_anywhere() { int tries = 600; do { x = random_range(6, GXM - 7); y = random_range(6, GYM - 7); } while ((grd[x][y] != DNGN_FLOOR || mgrd[x][y] != NON_MONSTER) && tries-- > 0); return (tries >= 0); } bool monsters::find_place_to_live(bool near_player) { if ((near_player && find_place_near_player()) || find_home_anywhere()) { mgrd[x][y] = monster_index(this); return (true); } return (false); } void monsters::destroy_inventory() { for (int j = 0; j < NUM_MONSTER_SLOTS; j++) { if (inv[j] != NON_ITEM) { destroy_item( inv[j] ); inv[j] = NON_ITEM; } } } bool monsters::is_travelling() const { return (!travel_path.empty()); } bool monsters::is_patrolling() const { return (patrol_point != coord_def(0, 0)); } bool monsters::needs_transit() const { return ((mons_is_unique(type) || (flags & MF_BANISHED) || type == MONS_ROYAL_JELLY || you.level_type == LEVEL_DUNGEON && hit_dice > 8 + random2(25)) && !mons_is_summoned(this)); } void monsters::set_transit(const level_id &dest) { add_monster_to_transit(dest, *this); } void monsters::load_spells(mon_spellbook_type book) { spells.init(SPELL_NO_SPELL); if (book == MST_NO_SPELLS || book == MST_GHOST && !ghost.get()) return; #if DEBUG_DIAGNOSTICS mprf( MSGCH_DIAGNOSTICS, "%s: loading spellbook #%d", name(DESC_PLAIN).c_str(), static_cast(book) ); #endif if (book == MST_GHOST) spells = ghost->spells; else { for (unsigned int i = 0; i < ARRAYSZ(mspell_list); ++i) { if (mspell_list[i].type == book) { for (int j = 0; j < NUM_MONSTER_SPELL_SLOTS; ++j) spells[j] = mspell_list[i].spells[j]; break; } } } #if DEBUG_DIAGNOSTICS // Only for ghosts, too spammy to use for all monsters. if (book == MST_GHOST) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; i++) { mprf( MSGCH_DIAGNOSTICS, "Spell #%d: %d (%s)", i, spells[i], spell_title(spells[i]) ); } } #endif } bool monsters::has_hydra_multi_attack() const { return (type == MONS_HYDRA || mons_is_zombified(this) && base_monster == MONS_HYDRA); } bool monsters::has_ench(enchant_type ench) const { return (enchantments.find(ench) != enchantments.end()); } bool monsters::has_ench(enchant_type ench, enchant_type ench2) const { if (ench2 == ENCH_NONE) ench2 = ench; for (int i = ench; i <= ench2; ++i) if (has_ench(static_cast(i))) return (true); return (false); } mon_enchant monsters::get_ench(enchant_type ench1, enchant_type ench2) const { if (ench2 == ENCH_NONE) ench2 = ench1; for (int e = ench1; e <= ench2; ++e) { mon_enchant_list::const_iterator i = enchantments.find(static_cast(e)); if (i != enchantments.end()) return (i->second); } return mon_enchant(); } void monsters::update_ench(const mon_enchant &ench) { if (ench.ench != ENCH_NONE) { mon_enchant_list::iterator i = enchantments.find(ench.ench); if (i != enchantments.end()) i->second = ench; } } bool monsters::add_ench(const mon_enchant &ench) { // silliness if (ench.ench == ENCH_NONE) return (false); mon_enchant_list::iterator i = enchantments.find(ench.ench); bool new_enchantment = false; mon_enchant *added = NULL; if (i == enchantments.end()) { new_enchantment = true; added = &(enchantments[ench.ench] = ench); } else { i->second += ench; added = &i->second; } // If the duration is not set, we must calculate it (depending on the // enchantment). if (!ench.duration) added->set_duration(this, new_enchantment ? NULL : &ench); if (new_enchantment) add_enchantment_effect(ench); return (true); } void monsters::add_enchantment_effect(const mon_enchant &ench, bool quiet) { // Check for slow/haste. switch (ench.ench) { case ENCH_BERSERK: // Inflate hp. scale_hp(3, 2); if (has_ench(ENCH_SUBMERGED)) del_ench(ENCH_SUBMERGED); if (mons_is_lurking(this)) { behaviour = BEH_WANDER; behaviour_event(this, ME_EVAL); } break; case ENCH_HASTE: if (speed >= 100) speed = 100 + ((speed - 100) * 2); else speed *= 2; break; case ENCH_SLOW: if (speed >= 100) speed = 100 + ((speed - 100) / 2); else speed /= 2; break; case ENCH_SUBMERGED: // XXX: What if the monster was invisible before submerging? if (mons_near(this) && !quiet) { if (type == MONS_AIR_ELEMENTAL) { mprf("%s merges itself into the air.", name(DESC_CAP_A, true).c_str() ); } else if (type == MONS_TRAPDOOR_SPIDER) { mprf("%s hides itself under the floor.", name(DESC_CAP_A, true).c_str() ); } } break; case ENCH_CONFUSION: if (type == MONS_TRAPDOOR_SPIDER && has_ench(ENCH_SUBMERGED)) del_ench(ENCH_SUBMERGED); if (mons_is_lurking(this)) { behaviour = BEH_WANDER; behaviour_event(this, ME_EVAL); } break; case ENCH_CHARM: behaviour = BEH_SEEK; target_x = you.x_pos; target_y = you.y_pos; foe = MHITYOU; if (is_patrolling()) { // Enslaved monsters stop patrolling and forget their patrol point, // they're supposed to follow you now. patrol_point = coord_def(0, 0); } if (you.can_see(this)) learned_something_new(TUT_MONSTER_FRIENDLY); break; default: break; } } static bool _prepare_del_ench(monsters* mon, const mon_enchant &me) { if (me.ench != ENCH_SUBMERGED) return (true); if (mon->pos() != you.pos()) return (true); // Monster un-submerging while under player. Try to move to an // adjacent square in which the monster could have been submerged // and have it unusbmerge from there. coord_def pos, target_square; int okay_squares = 0; for (pos.x = you.x_pos - 1; pos.x <= you.x_pos + 1; ++pos.x) for (pos.y = you.y_pos - 1; pos.y <= you.y_pos + 1; ++pos.y) { if (pos == you.pos()) continue; if (in_bounds(pos) && mgrd(pos) == NON_MONSTER && monster_can_submerge(mon, grd(pos))) { if (one_chance_in(++okay_squares)) target_square = pos; } } if (okay_squares > 0) { int mnum = mgrd(mon->pos()); mgrd(mon->pos()) = NON_MONSTER; mgrd(target_square) = mnum; mon->x = target_square.x; mon->y = target_square.y; return (true); } // No available adjacent squares from which the monster could also // have unsubmerged. Can it just stay submerged where it is? if (monster_can_submerge(mon, grd(mon->pos()))) return (false); // The terrain changed and the monster can't remain submerged. // Try to move to an adjacent square where it would be happy. for (pos.x = you.x_pos - 1; pos.x <= you.x_pos + 1; ++pos.x) for (pos.y = you.y_pos - 1; pos.y <= you.y_pos + 1; ++pos.y) { if (pos == you.pos()) continue; if (in_bounds(pos) && mgrd(pos) == NON_MONSTER && monster_habitable_grid(mon, grd(pos)) && trap_type_at_xy(pos.x, pos.y) == NUM_TRAPS) { if (one_chance_in(++okay_squares)) target_square = pos; } } if (okay_squares > 0) { int mnum = mgrd(mon->pos()); mgrd(mon->pos()) = NON_MONSTER; mgrd(target_square) = mnum; mon->x = target_square.x; mon->y = target_square.y; } return (true); } bool monsters::del_ench(enchant_type ench, bool quiet, bool effect) { mon_enchant_list::iterator i = enchantments.find(ench); if (i == enchantments.end()) return (false); const mon_enchant me = i->second; const enchant_type et = i->first; if (!_prepare_del_ench(this, me)) return (false); enchantments.erase(et); if (effect) remove_enchantment_effect(me, quiet); return (true); } void monsters::remove_enchantment_effect(const mon_enchant &me, bool quiet) { switch (me.ench) { case ENCH_BERSERK: scale_hp(2, 3); break; case ENCH_HASTE: if (speed >= 100) speed = 100 + ((speed - 100) / 2); else speed /= 2; break; case ENCH_SLOW: if (!quiet) simple_monster_message(this, " is no longer moving slowly."); if (speed >= 100) speed = 100 + ((speed - 100) * 2); else speed *= 2; break; case ENCH_PARALYSIS: if (!quiet) simple_monster_message(this, " is no longer paralysed."); behaviour_event(this, ME_EVAL); break; case ENCH_NEUTRAL: if (!quiet) simple_monster_message(this, " is no longer neutral."); behaviour_event(this, ME_EVAL); break; case ENCH_PETRIFIED: if (!quiet) simple_monster_message(this, " is no longer petrified."); del_ench(ENCH_PETRIFYING); behaviour_event(this, ME_EVAL); break; case ENCH_PETRIFYING: if (!has_ench(ENCH_PETRIFIED)) break; if (!quiet) simple_monster_message(this, " stops moving altogether!"); behaviour_event(this, ME_EVAL); break; case ENCH_FEAR: if (holiness() == MH_NONLIVING || has_ench(ENCH_BERSERK)) { // This should only happen because of fleeing sanctuary snprintf( info, INFO_SIZE, " stops retreating."); } else { snprintf( info, INFO_SIZE, " seems to regain %s courage.", mons_pronoun(static_cast(this->type), PRONOUN_NOCAP_POSSESSIVE)); } if (!quiet) simple_monster_message(this, info); // Reevaluate behaviour. behaviour_event(this, ME_EVAL); break; case ENCH_CONFUSION: if (!quiet) simple_monster_message(this, " seems less confused."); // Reevaluate behaviour. behaviour_event(this, ME_EVAL); break; case ENCH_INVIS: // Invisible monsters stay invisible. if (mons_class_flag(type, M_INVIS)) add_ench( mon_enchant(ENCH_INVIS) ); else if (mons_near(this) && !player_see_invis() && !has_ench( ENCH_SUBMERGED )) { if (!quiet) mprf("%s appears from thin air!", name(DESC_CAP_A, true).c_str() ); seen_monster(this); } break; case ENCH_CHARM: if (!quiet) simple_monster_message(this, " is no longer charmed."); if (mons_near(this) && player_monster_visible(this)) { // and fire activity interrupts interrupt_activity(AI_SEE_MONSTER, activity_interrupt_data(this, "uncharm")); } if (is_patrolling()) { // Enslaved monsters stop patrolling and forget their patrol point, // in case they were on order to wait. patrol_point = coord_def(0, 0); } // Reevaluate behaviour. behaviour_event(this, ME_EVAL); break; case ENCH_BACKLIGHT: if (!quiet) { if (player_monster_visible(this)) simple_monster_message(this, " stops glowing."); else if (has_ench(ENCH_INVIS) && mons_near(this)) { mprf("%s stops glowing and disappears.", name(DESC_CAP_THE, true).c_str()); } } break; case ENCH_STICKY_FLAME: if (!quiet) simple_monster_message(this, " stops burning."); break; case ENCH_POISON: if (!quiet) simple_monster_message(this, " looks more healthy."); break; case ENCH_ROT: if (!quiet) simple_monster_message(this, " is no longer rotting."); break; case ENCH_HELD: { int net = get_trapping_net(x,y); if (net != NON_ITEM) remove_item_stationary(mitm[net]); if (!quiet) simple_monster_message(this, " breaks free."); break; } case ENCH_ABJ: case ENCH_SHORT_LIVED: add_ench(mon_enchant(ENCH_ABJ)); if (this->has_ench(ENCH_BERSERK)) simple_monster_message(this, " is no longer berserk."); monster_die(this, quiet ? KILL_DISMISSED : KILL_RESET, 0); break; case ENCH_SUBMERGED: if (mons_is_wandering(this)) { behaviour = BEH_SEEK; behaviour_event(this, ME_EVAL); } if (you.pos() == this->pos()) { mprf(MSGCH_ERROR, "%s is on the same square as you!", name(DESC_CAP_A).c_str()); } if (you.can_see(this)) { if (!mons_is_safe( static_cast(this)) && is_run_delay(current_delay_action())) { // Already set somewhere else. if (!seen_context.empty()) return; if (type == MONS_AIR_ELEMENTAL) seen_context = "thin air"; else if (type == MONS_TRAPDOOR_SPIDER) seen_context = "leaps out"; else if (monster_habitable_grid(this, DNGN_FLOOR)) seen_context = "bursts forth"; else seen_context = "surfaces"; } else if (!quiet) { if (type == MONS_AIR_ELEMENTAL) { mprf("%s forms itself from the air!", name(DESC_CAP_A, true).c_str() ); } else if (type == MONS_TRAPDOOR_SPIDER) { mprf("%s leaps out from its hiding place under the floor!", name(DESC_CAP_A, true).c_str() ); } } } else if (mons_near(this) && grid_compatible(grd[this->x][this->y], DNGN_DEEP_WATER)) { mpr("Something invisible bursts forth from the water."); interrupt_activity( AI_FORCE_INTERRUPT ); } break; default: break; } } bool monsters::lose_ench_levels(const mon_enchant &e, int lev) { if (!lev) return (false); if (e.degree <= lev) { del_ench(e.ench); return (true); } else { mon_enchant newe(e); newe.degree -= lev; update_ench(newe); return (false); } } bool monsters::lose_ench_duration(const mon_enchant &e, int dur) { if (!dur) return (false); if (e.duration <= dur) { del_ench(e.ench); return (true); } else { mon_enchant newe(e); newe.duration -= dur; update_ench(newe); return (false); } } //--------------------------------------------------------------- // // timeout_enchantments // // Update a monster's enchantments when the player returns // to the level. // // Management for enchantments... problems with this are the oddities // (monster dying from poison several thousands of turns later), and // game balance. // // Consider: Poison/Sticky Flame a monster at range and leave, monster // dies but can't leave level to get to player (implied game balance of // the delayed damage is that the monster could be a danger before // it dies). This could be fixed by keeping some monsters active // off level and allowing them to take stairs (a very serious change). // // Compare this to the current abuse where the player gets // effectively extended duration of these effects (although only // the actual effects only occur on level, the player can leave // and heal up without having the effect disappear). // // This is a simple compromise between the two... the enchantments // go away, but the effects don't happen off level. -- bwr // //--------------------------------------------------------------- void monsters::timeout_enchantments(int levels) { if (enchantments.empty()) return; const mon_enchant_list ec = enchantments; for (mon_enchant_list::const_iterator i = ec.begin(); i != ec.end(); ++i) { switch (i->first) { case ENCH_POISON: case ENCH_ROT: case ENCH_BACKLIGHT: case ENCH_STICKY_FLAME: case ENCH_ABJ: case ENCH_SHORT_LIVED: case ENCH_SLOW: case ENCH_HASTE: case ENCH_FEAR: case ENCH_INVIS: case ENCH_CHARM: case ENCH_SLEEP_WARY: case ENCH_SICK: case ENCH_SLEEPY: case ENCH_PARALYSIS: case ENCH_PETRIFYING: case ENCH_PETRIFIED: case ENCH_BATTLE_FRENZY: case ENCH_NEUTRAL: lose_ench_levels(i->second, levels); break; case ENCH_BERSERK: del_ench(i->first); del_ench(ENCH_HASTE); break; case ENCH_FATIGUE: del_ench(i->first); del_ench(ENCH_SLOW); break; case ENCH_TP: del_ench(i->first); teleport(true); break; case ENCH_CONFUSION: if (!mons_class_flag(type, M_CONFUSED)) del_ench(i->first); blink(); break; case ENCH_HELD: del_ench(i->first); break; default: break; } if (!alive()) break; } } std::string monsters::describe_enchantments() const { std::ostringstream oss; for (mon_enchant_list::const_iterator i = enchantments.begin(); i != enchantments.end(); ++i) { if (i != enchantments.begin()) oss << ", "; oss << std::string(i->second); } return (oss.str()); } // Used to adjust time durations in calc_duration() for monster speed. static inline int _mod_speed( int val, int speed ) { if (!speed) speed = 10; const int modded = (speed ? (val * 10) / speed : val); return (modded? modded : 1); } bool monsters::decay_enchantment(const mon_enchant &me, bool decay_degree) { // Faster monsters can wiggle out of the net more quickly. const int spd = (me.ench == ENCH_HELD) ? speed : 10; const int actdur = speed_to_duration(spd); if (lose_ench_duration(me, actdur)) return (true); if (!decay_degree) return (false); // Decay degree so that higher degrees decay faster than lower // degrees, and a degree of 1 does not decay (it expires when the // duration runs out). const int level = me.degree; if (level <= 1) return (false); const int decay_factor = level * (level + 1) / 2; if (me.duration < me.maxduration * (decay_factor - 1) / decay_factor) { mon_enchant newme = me; --newme.degree; newme.maxduration = newme.duration; if (newme.degree <= 0) { del_ench(me.ench); return (true); } else update_ench(newme); } return (false); } void monsters::apply_enchantment(const mon_enchant &me) { const int spd = 10; switch (me.ench) { case ENCH_BERSERK: if (decay_enchantment(me)) { simple_monster_message(this, " is no longer berserk."); del_ench(ENCH_HASTE); const int duration = random_range(70, 130); add_ench(mon_enchant(ENCH_FATIGUE, 0, KC_OTHER, duration)); add_ench(mon_enchant(ENCH_SLOW, 0, KC_OTHER, duration)); } break; case ENCH_FATIGUE: if (decay_enchantment(me)) { simple_monster_message(this, " looks more energetic."); del_ench(ENCH_SLOW, true); } break; case ENCH_SLOW: case ENCH_HASTE: case ENCH_FEAR: case ENCH_PARALYSIS: case ENCH_NEUTRAL: case ENCH_PETRIFYING: case ENCH_PETRIFIED: case ENCH_SICK: case ENCH_BACKLIGHT: case ENCH_ABJ: case ENCH_CHARM: case ENCH_SLEEP_WARY: decay_enchantment(me); break; case ENCH_BATTLE_FRENZY: decay_enchantment(me, false); break; case ENCH_HELD: { if (mons_is_stationary(this) || mons_cannot_act(this) || mons_is_sleeping(this)) { break; } int net = get_trapping_net(x,y,true); if (net == NON_ITEM) // Really shouldn't happen! { del_ench(ENCH_HELD); break; } // Handled in handle_pickup. if (mons_itemuse(type) == MONUSE_EATS_ITEMS) break; // The enchantment doubles as the durability of a net // the more corroded it gets, the more easily it will break. const int hold = mitm[net].plus; // This will usually be negative. const int mon_size = body_size(PSIZE_BODY); // Smaller monsters can escape more quickly. if (mon_size < random2(SIZE_BIG) // BIG = 5 && !has_ench(ENCH_BERSERK) && type != MONS_DANCING_WEAPON) { if (mons_near(this) && !player_monster_visible(this)) mpr("Something wriggles in the net."); else simple_monster_message(this, " struggles to escape the net."); // Confused monsters have trouble finding the exit. if (has_ench(ENCH_CONFUSION) && !one_chance_in(5)) break; decay_enchantment(me, 2*(NUM_SIZE_LEVELS - mon_size) - hold); // Frayed nets are easier to escape. if (mon_size <= -(hold-1)/2) decay_enchantment(me, (NUM_SIZE_LEVELS - mon_size)); } else // Large (and above) monsters always thrash the net and destroy it { // e.g. ogre, large zombie (large); centaur, naga, hydra (big). if (mons_near(this) && !player_monster_visible(this)) mpr("Something wriggles in the net."); else simple_monster_message(this, " struggles against the net."); // Confused monsters more likely to struggle without result. if (has_ench(ENCH_CONFUSION) && one_chance_in(3)) break; // Nets get destroyed more quickly for larger monsters // and if already strongly frayed. int damage = 0; // tiny: 1/6, little: 2/5, small: 3/4, medium and above: always if (x_chance_in_y(mon_size + 1, SIZE_GIANT - mon_size)) damage++; // Handled specially to make up for its small size. if (type == MONS_DANCING_WEAPON) { damage += one_chance_in(3); if (can_cut_meat(mitm[inv[MSLOT_WEAPON]])) damage++; } // Extra damage for large (50%) and big (always). if (mon_size == SIZE_BIG || mon_size == SIZE_LARGE && coinflip()) damage++; // overall damage per struggle: // tiny -> 1/6 // little -> 2/5 // small -> 3/4 // medium -> 1 // large -> 1,5 // big -> 2 // extra damage if already damaged if (random2(body_size(PSIZE_BODY) - hold + 1) >= 4) damage++; // Berserking doubles damage dealt. if (has_ench(ENCH_BERSERK)) damage *= 2; // Faster monsters can damage the net more often per // time period. if (speed != 0) damage = div_rand_round(damage * speed, spd); mitm[net].plus -= damage; if (mitm[net].plus < -7) { if (mons_near(this)) { if (player_monster_visible(this)) { mprf("The net rips apart, and %s comes free!", name(DESC_NOCAP_THE).c_str()); } else { mpr("All of a sudden the net rips apart!"); } } destroy_item(net); del_ench(ENCH_HELD, true); } } break; } case ENCH_CONFUSION: if (!mons_class_flag(type, M_CONFUSED)) decay_enchantment(me); break; case ENCH_INVIS: if (!mons_class_flag( type, M_INVIS )) decay_enchantment(me); break; case ENCH_SUBMERGED: { // Not even air elementals unsubmerge into clouds. if (env.cgrid[x][y] != EMPTY_CLOUD) break; // Air elementals are a special case, as their // submerging in air isn't up to choice. -- bwr if (type == MONS_AIR_ELEMENTAL) { heal_monster( this, 1, one_chance_in(5) ); if (one_chance_in(5)) del_ench(ENCH_SUBMERGED); break; } // Now we handle the others: const dungeon_feature_type grid = grd[x][y]; // Badly injured monsters prefer to stay submerged... // electrical eels and lava snakes have ranged attacks // and are more likely to surface. -- bwr if (!monster_can_submerge(this, grid)) del_ench(ENCH_SUBMERGED); // forced to surface else if (type == MONS_TRAPDOOR_SPIDER) { // This should probably never happen. if (!mons_is_lurking(this)) del_ench(ENCH_SUBMERGED); break; } else if (hit_points <= max_hit_points / 2) break; else if (((type == MONS_ELECTRICAL_EEL || type == MONS_LAVA_SNAKE) && (one_chance_in(50) || (mons_near(this) && hit_points == max_hit_points && !one_chance_in(10)))) || one_chance_in(200) || (mons_near(this) && hit_points == max_hit_points && !one_chance_in(5))) { del_ench(ENCH_SUBMERGED); } break; } case ENCH_POISON: { const int poisonval = me.degree; int dam = (poisonval >= 4) ? 1 : 0; if (coinflip()) dam += roll_dice(1, poisonval + 1); if (mons_res_poison(this) < 0) dam += roll_dice(2, poisonval) - 1; if (dam > 0) { hurt_monster(this, dam); #if DEBUG_DIAGNOSTICS // For debugging, we don't have this silent. simple_monster_message( this, " takes poison damage.", MSGCH_DIAGNOSTICS ); mprf(MSGCH_DIAGNOSTICS, "poison damage: %d", dam ); #endif if (hit_points < 1) { monster_die(this, me.killer(), me.kill_agent()); break; } } decay_enchantment(me, true); break; } case ENCH_ROT: { if (hit_points > 1 && one_chance_in(3)) { hurt_monster(this, 1); if (hit_points < max_hit_points && coinflip()) --max_hit_points; } decay_enchantment(me, true); break; } // Assumption: mons_res_fire has already been checked. case ENCH_STICKY_FLAME: { int dam = resist_adjust_damage(this, BEAM_FIRE, res_fire(), roll_dice( 2, 4 ) - 1); if (dam > 0) { hurt_monster(this, dam); simple_monster_message(this, " burns!"); #if DEBUG_DIAGNOSTICS mprf( MSGCH_DIAGNOSTICS, "sticky flame damage: %d", dam ); #endif if (hit_points < 1) { monster_die(this, me.killer(), me.kill_agent()); break; } } decay_enchantment(me, true); break; } case ENCH_SHORT_LIVED: // This should only be used for ball lightning -- bwr if (decay_enchantment(me)) hit_points = -1; break; case ENCH_GLOWING_SHAPESHIFTER: // This ench never runs out! // Number of actions is fine for shapeshifters. if (type == MONS_GLOWING_SHAPESHIFTER || one_chance_in(4)) monster_polymorph(this, RANDOM_MONSTER, PPT_SAME); break; case ENCH_SHAPESHIFTER: // This ench never runs out! if (type == MONS_SHAPESHIFTER || x_chance_in_y(1000 / (15 * hit_dice / 5), 1000)) { monster_polymorph(this, RANDOM_MONSTER, PPT_SAME); } break; case ENCH_TP: if (decay_enchantment(me, true)) monster_teleport( this, true ); break; case ENCH_SLEEPY: del_ench(ENCH_SLEEPY); break; default: break; } } void monsters::mark_summoned(int longevity, bool mark_items) { add_ench( mon_enchant(ENCH_ABJ, longevity) ); if (mark_items) { for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) { const int item = inv[i]; if (item != NON_ITEM) mitm[item].flags |= ISFLAG_SUMMONED; } } } void monsters::apply_enchantments() { if (enchantments.empty()) return; const mon_enchant_list ec = enchantments; for (mon_enchant_list::const_iterator i = ec.begin(); i != ec.end(); ++i) { apply_enchantment(i->second); if (!alive()) break; } } void monsters::scale_hp(int num, int den) { hit_points = hit_points * num / den; max_hit_points = max_hit_points * num / den; if (hit_points < 1) hit_points = 1; if (max_hit_points < 1) max_hit_points = 1; if (hit_points > max_hit_points) hit_points = max_hit_points; } kill_category monsters::kill_alignment() const { return (mons_friendly(this)? KC_FRIENDLY : KC_OTHER); } bool monsters::sicken(int amount) { if (holiness() != MH_NATURAL || (amount /= 2) < 1) return (false); if (!has_ench(ENCH_SICK) && mons_near(this) && player_monster_visible(this)) { // Yes, could be confused with poisoning. mprf("%s looks sick.", name(DESC_CAP_THE).c_str()); } add_ench(mon_enchant(ENCH_SICK, 0, KC_OTHER, amount * 10)); return (true); } int monsters::base_speed(int mcls) { const monsterentry *m = get_monster_data(mcls); if (!m) return (10); int speed = m->speed; switch (mcls) { case MONS_ABOMINATION_SMALL: speed = 7 + random2avg(9, 2); break; case MONS_ABOMINATION_LARGE: speed = 6 + random2avg(7, 2); break; case MONS_BEAST: speed = 10 + random2(8); break; } return (speed); } // Recalculate movement speed. void monsters::fix_speed() { if (mons_is_zombified(this)) speed = base_speed(number) - 2; else speed = base_speed(type); if (has_ench(ENCH_HASTE)) speed *= 2; else if (has_ench(ENCH_SLOW)) speed /= 2; } // Check speed and speed_increment sanity. void monsters::check_speed() { // FIXME: If speed is borked, recalculate. Need to figure out how speed // is getting borked. if (speed < 0 || speed > 130) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Bad speed: %s, spd: %d, spi: %d, hd: %d, ench: %s", name(DESC_PLAIN).c_str(), speed, speed_increment, hit_dice, describe_enchantments().c_str()); #endif fix_speed(); #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Fixed speed for %s to %d", name(DESC_PLAIN).c_str(), speed); #endif } if (speed_increment < 0) speed_increment = 0; if (speed_increment > 200) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Clamping speed increment on %s: %d", name(DESC_PLAIN).c_str(), speed_increment); #endif speed_increment = 140; } } actor *monsters::get_foe() const { if (foe == MHITNOT) return (NULL); else if (foe == MHITYOU) return (&you); // Must be a monster! monsters *my_foe = &menv[foe]; return (my_foe->alive()? my_foe : NULL); } int monsters::foe_distance() const { const actor *afoe = get_foe(); return (afoe ? pos().distance_from(afoe->pos()) : INFINITE_DISTANCE); } bool monsters::can_go_berserk() const { if (holiness() != MH_NATURAL) return (false); if (has_ench(ENCH_FATIGUE) || has_ench(ENCH_BERSERK)) return (false); // If we have no melee attack, going berserk is pointless. const mon_attack_def attk = mons_attack_spec(this, 0); if (attk.type == AT_NONE || attk.damage == 0) return (false); return (true); } bool monsters::needs_berserk(bool check_spells) const { if (!can_go_berserk()) return (false); if (has_ench(ENCH_HASTE) || has_ench(ENCH_TP)) return (false); if (foe_distance() > 3) return (false); if (check_spells) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) { const int spell = spells[i]; if (spell != SPELL_NO_SPELL && spell != SPELL_BERSERKER_RAGE) return (false); } } return (true); } bool monsters::can_see_invisible() const { return (mons_see_invis(this)); } bool monsters::invisible() const { return (has_ench(ENCH_INVIS) && !backlit()); } bool monsters::visible_to(const actor *looker) const { if (this == looker) return (!invisible() || can_see_invisible()); if (looker->atype() == ACT_PLAYER) return player_monster_visible(this); else { const monsters* mon = dynamic_cast(looker); return mons_monster_visible(mon, this); } } bool monsters::mon_see_grid(int tx, int ty, bool reach) const { if (distance(x, y, tx, ty) > LOS_RADIUS * LOS_RADIUS) return (false); dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE; if (reach) max_disallowed = DNGN_MAX_NONREACH; // XXX: Ignoring clouds for now. return (!num_feats_between(x, y, tx, ty, DNGN_UNSEEN, max_disallowed, true, true)); } bool monsters::can_see(const actor *target) const { if (this == target) return visible_to(target); if (!target->visible_to(this)) return (false); if (target->atype() == ACT_PLAYER) return mons_near(this); const monsters* mon = dynamic_cast(target); return mon_see_grid(mon->x, mon->y); } bool monsters::can_mutate() const { return (holiness() == MH_NATURAL); } bool monsters::can_safely_mutate() const { return (can_mutate()); } bool monsters::mutate() { if (!can_mutate()) return (false); return (monster_polymorph(this, RANDOM_MONSTER)); } bool monsters::is_icy() const { return (mons_is_icy(type)); } bool monsters::has_action_energy() const { return (speed_increment >= 80); } void monsters::check_redraw(const coord_def &old) const { const bool see_new = see_grid(pos()); const bool see_old = see_grid(old); if ((see_new || see_old) && !view_update()) { if (see_new) view_update_at(pos()); if (see_old) view_update_at(old); update_screen(); } } void monsters::apply_location_effects() { dungeon_events.fire_position_event(DET_MONSTER_MOVED, pos()); // monsters stepping on traps: mons_trap(this); if (alive()) mons_check_pool(this); if (alive() && has_ench(ENCH_SUBMERGED) && (!monster_can_submerge(this, grd[x][y]) || type == MONS_TRAPDOOR_SPIDER)) { del_ench(ENCH_SUBMERGED); if (type == MONS_TRAPDOOR_SPIDER) behaviour_event(this, ME_EVAL); } } bool monsters::move_to_pos(const coord_def &newpos) { if (mgrd[newpos.x][newpos.y] != NON_MONSTER || you.x_pos == newpos.x && you.y_pos == newpos.y) { return (false); } // Clear old cell pointer. mgrd[x][y] = NON_MONSTER; // Set monster x,y to new value. x = newpos.x; y = newpos.y; // set new monster grid pointer to this monster. mgrd[x][y] = monster_index(this); return (true); } // Returns true if the trap should be revealed to the player. bool monsters::do_shaft() { if (!is_valid_shaft_level()) return (false); // Handle instances of do_shaft() being invoked magically when // the monster isn't standing over a shaft. if (trap_type_at_xy(x, y) != TRAP_SHAFT) { switch(grd[x][y]) { case DNGN_FLOOR: case DNGN_OPEN_DOOR: case DNGN_TRAP_MECHANICAL: case DNGN_TRAP_MAGICAL: case DNGN_TRAP_NATURAL: case DNGN_UNDISCOVERED_TRAP: case DNGN_ENTER_SHOP: break; default: return (false); } if (airborne() || total_weight() == 0) { if (mons_near(this)) { if (player_monster_visible(this)) { mprf("A shaft briefly opens up underneath %s!", name(DESC_NOCAP_THE).c_str()); } else mpr("A shaft briefly opens up in the floor!"); } handle_items_on_shaft(this->x, this->y, false); return (false); } } level_id lev = shaft_dest(); if (lev == level_id::current()) return (false); // If a pacified monster is leaving the level via a shaft trap, and // has reached its goal, handle it here. if (!mons_is_pacified(this)) set_transit(lev); const bool reveal = simple_monster_message(this, " falls through a shaft!"); handle_items_on_shaft(this->x, this->y, false); // Monster is no longer on this level. destroy_inventory(); monster_cleanup(this); return (reveal); } void monsters::put_to_sleep(int) { if (has_ench(ENCH_BERSERK)) return; behaviour = BEH_SLEEP; add_ench(ENCH_SLEEPY); add_ench(ENCH_SLEEP_WARY); } void monsters::check_awaken(int) { // XXX } const monsterentry *monsters::find_monsterentry() const { return (type == -1 || type == MONS_PROGRAM_BUG) ? NULL : get_monster_data(type); } int monsters::action_energy(energy_use_type et) const { if (const monsterentry *me = find_monsterentry()) { const mon_energy_usage &mu = me->energy_usage; switch (et) { case EUT_MOVE: return mu.move; case EUT_SWIM: return mu.swim; case EUT_MISSILE: return mu.missile; case EUT_ITEM: return mu.item; case EUT_SPECIAL: return mu.special; case EUT_SPELL: return mu.spell; case EUT_ATTACK: return mu.attack; case EUT_PICKUP: return mu.pickup_percent; } } return 10; } void monsters::lose_energy(energy_use_type et, int div, int mult) { int energy_loss = div_round_up(mult * action_energy(et), div); if (has_ench(ENCH_PETRIFYING)) { energy_loss *= 3; energy_loss /= 2; } speed_increment -= energy_loss; } static inline monster_type _royal_jelly_ejectable_monster() { return static_cast( random_choose( MONS_ACID_BLOB, MONS_AZURE_JELLY, MONS_DEATH_OOZE, -1 ) ); } void monsters::react_to_damage(int damage) { // The royal jelly objects to taking damage and will SULK. :-) if (type == MONS_ROYAL_JELLY && alive() && damage > 8 && x_chance_in_y(damage, 50)) { const int tospawn = 1 + random2avg(1 + std::min((damage - 8) / 8, 5), 2); #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Trying to spawn %d jellies.", tospawn); #endif const beh_type sbehaviour = SAME_ATTITUDE(this); int spawned = 0; for (int i = 0; i < tospawn; ++i) { const monster_type jelly = _royal_jelly_ejectable_monster(); coord_def jpos = find_newmons_square_contiguous(jelly, pos()); if (!in_bounds(jpos)) continue; const int nmons = mons_place( mgen_data( jelly, sbehaviour, 0, jpos, foe, true ) ); if (nmons != -1 && nmons != NON_MONSTER) { // Don't allow milking the royal jelly. menv[nmons].flags |= MF_CREATED_FRIENDLY; spawned++; } } const bool needs_message = spawned && mons_near(this) && player_monster_visible(this); if (needs_message) { const std::string monnam = name(DESC_CAP_THE); mprf("%s shudders%s.", monnam.c_str(), spawned >= 5 ? " alarmingly" : spawned >= 3 ? " violently" : spawned > 1 ? " vigorously" : ""); if (spawned == 1) mprf("%s spits out another jelly.", monnam.c_str()); else { mprf("%s spits out %s more jellies.", monnam.c_str(), number_in_words(spawned).c_str()); } } } } ///////////////////////////////////////////////////////////////////////// // mon_enchant static const char *enchant_names[] = { "none", "slow", "haste", "fear", "conf", "inv", "pois", "bers", "rot", "summon", "abj", "backlit", "charm", "fire", "gloshifter", "shifter", "tp", "wary", "submerged", "short lived", "paralysis", "sick", "sleep", "fatigue", "held", "blood-lust", "neutral", "petrifying", "petrified", "bug" }; static const char *_mons_enchantment_name(enchant_type ench) { COMPILE_CHECK(ARRAYSZ(enchant_names) == NUM_ENCHANTMENTS+1, c1); if (ench > NUM_ENCHANTMENTS) ench = NUM_ENCHANTMENTS; return (enchant_names[ench]); } mon_enchant::mon_enchant(enchant_type e, int deg, kill_category whose, int dur) : ench(e), degree(deg), duration(dur), maxduration(0), who(whose) { } mon_enchant::operator std::string () const { return make_stringf("%s (%d:%d%s)", _mons_enchantment_name(ench), degree, duration, kill_category_desc(who)); } const char *mon_enchant::kill_category_desc(kill_category k) const { return (k == KC_YOU? " you" : k == KC_FRIENDLY? " pet" : ""); } void mon_enchant::merge_killer(kill_category k) { who = who < k? who : k; } void mon_enchant::cap_degree() { // Sickness is not capped. if (ench == ENCH_SICK) return; // Hard cap to simulate old enum behaviour, we should really throw this // out entirely. const int max = ench == ENCH_ABJ? 6 : 4; if (degree > max) degree = max; } mon_enchant &mon_enchant::operator += (const mon_enchant &other) { if (ench == other.ench) { degree += other.degree; cap_degree(); duration += other.duration; merge_killer(other.who); } return (*this); } mon_enchant mon_enchant::operator + (const mon_enchant &other) const { mon_enchant tmp(*this); tmp += other; return (tmp); } killer_type mon_enchant::killer() const { return (who == KC_YOU? KILL_YOU : who == KC_FRIENDLY? KILL_MON : KILL_MISC); } int mon_enchant::kill_agent() const { return (who == KC_FRIENDLY? ANON_FRIENDLY_MONSTER : 0); } int mon_enchant::modded_speed(const monsters *mons, int hdplus) const { return (_mod_speed(mons->hit_dice + hdplus, mons->speed)); } int mon_enchant::calc_duration(const monsters *mons, const mon_enchant *added) const { int cturn = 0; const int newdegree = added? added->degree : degree; const int deg = newdegree? newdegree : 1; // Beneficial enchantments (like Haste) should not be throttled by // monster HD via modded_speed(). Use mod_speed instead! switch (ench) { case ENCH_HASTE: cturn = 1000 / _mod_speed(25, mons->speed); break; case ENCH_INVIS: cturn = 1000 / _mod_speed(25, mons->speed); break; case ENCH_SLOW: cturn = 250 / (1 + modded_speed(mons, 10)); break; case ENCH_FEAR: cturn = 150 / (1 + modded_speed(mons, 5)); break; case ENCH_PARALYSIS: cturn = std::max(90 / modded_speed(mons, 5), 3); break; case ENCH_PETRIFIED: cturn = std::max(8, 150 / (1 + modded_speed(mons, 5))); break; case ENCH_PETRIFYING: cturn = 50 / _mod_speed(10, mons->speed); break; case ENCH_CONFUSION: cturn = std::max(100 / modded_speed(mons, 5), 3); break; case ENCH_HELD: cturn = 120 / _mod_speed(25, mons->speed); break; case ENCH_POISON: cturn = 1000 * deg / _mod_speed(125, mons->speed); break; case ENCH_STICKY_FLAME: cturn = 1000 * deg / _mod_speed(200, mons->speed); break; case ENCH_ROT: if (deg > 1) cturn = 1000 * (deg - 1) / _mod_speed(333, mons->speed); cturn += 1000 / _mod_speed(250, mons->speed); break; case ENCH_BACKLIGHT: if (deg > 1) cturn = 1000 * (deg - 1) / _mod_speed(200, mons->speed); cturn += 1000 / _mod_speed(100, mons->speed); break; case ENCH_SHORT_LIVED: cturn = 1000 / _mod_speed(200, mons->speed); break; case ENCH_ABJ: if (deg >= 6) cturn = 1000 / _mod_speed(10, mons->speed); if (deg >= 5) cturn += 1000 / _mod_speed(20, mons->speed); cturn += 1000 * std::min(4, deg) / _mod_speed(100, mons->speed); break; case ENCH_CHARM: cturn = 500 / modded_speed(mons, 10); break; case ENCH_TP: cturn = 1000 * deg / _mod_speed(1000, mons->speed); break; case ENCH_SLEEP_WARY: cturn = 1000 / _mod_speed(50, mons->speed); break; default: break; } if (cturn < 2) cturn = 2; int raw_duration = (cturn * speed_to_duration(mons->speed)); raw_duration = fuzz_value(raw_duration, 60, 40); if (raw_duration < 15) raw_duration = 15; return (raw_duration); } // Calculate the effective duration (in terms of normal player time - 10 // duration units being one normal player action) of this enchantment. void mon_enchant::set_duration(const monsters *mons, const mon_enchant *added) { if (duration && !added) return; if (added && added->duration) duration += added->duration; else duration += calc_duration(mons, added); if (duration > maxduration) maxduration = duration; } // Replaces @player_god@ and @god_is@ with player's god name // special handling for atheists: use "you"/"You" instead. static std::string _replace_god_name(bool need_verb = false, bool capital = false) { std::string result = (you.religion == GOD_NO_GOD ? (capital ? "You" : "you") : god_name(you.religion, false)); if (need_verb) { result += (you.religion == GOD_NO_GOD ? " are" : " is"); } return (result); } static std::string _get_species_insult(const std::string type) { std::string lookup = "insult "; // Get species genus. lookup += species_name(you.species, 1, true); lookup += " "; lookup += type; std::string insult = getSpeakString(lowercase(lookup)); if (insult.empty()) // Species too specific? { lookup = "insult general "; lookup += type; insult = getSpeakString(lookup); } return (insult); } static std::string _pluralise_player_genus() { std::string sp = species_name(you.species, 1, true, false, true); if (player_genus(GENPC_ELVEN, you.species) || player_genus(GENPC_DWARVEN, you.species)) { sp = sp.substr(0, sp.find("f")); sp += "ves"; } else if (you.species != SP_DEMONSPAWN) sp += "s"; return (sp); } // Replaces the "@foo@" strings in monster shout and monster speak // definitions. std::string do_mon_str_replacements(const std::string &in_msg, const monsters* monster, int s_type) { std::string msg = in_msg; description_level_type nocap = DESC_NOCAP_THE, cap = DESC_CAP_THE; std::string name = monster->is_named()? monster->name(DESC_CAP_THE) : ""; if (!name.empty() && player_monster_visible(monster)) { msg = replace_all(msg, "@the_something@", name); msg = replace_all(msg, "@The_something@", name); msg = replace_all(msg, "@the_monster@", name); msg = replace_all(msg, "@The_monster@", name); } else if (monster->attitude == ATT_FRIENDLY && !mons_is_unique(monster->type) && player_monster_visible(monster)) { nocap = DESC_PLAIN; cap = DESC_PLAIN; msg = replace_all(msg, "@the_something@", "your @the_something@"); msg = replace_all(msg, "@The_something@", "Your @The_something@"); msg = replace_all(msg, "@the_monster@", "your @the_monster@"); msg = replace_all(msg, "@The_monster@", "Your @the_monster@"); } if (see_grid(monster->x, monster->y)) { dungeon_feature_type feat = grd[monster->x][monster->y]; if (feat < DNGN_MINMOVE || feat >= NUM_REAL_FEATURES) msg = replace_all(msg, "@surface@", "buggy surface"); else if (feat == DNGN_LAVA) msg = replace_all(msg, "@surface@", "lava"); else if (feat == DNGN_DEEP_WATER || feat == DNGN_SHALLOW_WATER) msg = replace_all(msg, "@surface@", "water"); else if (feat >= DNGN_ALTAR_FIRST_GOD && feat <= DNGN_ALTAR_LAST_GOD) msg = replace_all(msg, "@surface@", "altar"); else msg = replace_all(msg, "@surface@", "ground"); msg = replace_all(msg, "@feature@", raw_feature_description(feat)); } else { msg = replace_all(msg, "@surface@", "buggy unseen surface"); msg = replace_all(msg, "@feature@", "buggy unseen feature"); } msg = replace_all(msg, "@player_name@", you.your_name); msg = replace_all(msg, "@player_species@", species_name(you.species, 1).c_str()); msg = replace_all(msg, "@player_genus@", species_name(you.species, 1, true, false, true).c_str()); msg = replace_all(msg, "@player_genus_plural@", _pluralise_player_genus().c_str()); if (player_monster_visible(monster)) { std::string something = monster->name(DESC_PLAIN); msg = replace_all(msg, "@something@", something); msg = replace_all(msg, "@a_something@", monster->name(DESC_NOCAP_A)); msg = replace_all(msg, "@the_something@", monster->name(nocap)); something[0] = toupper(something[0]); msg = replace_all(msg, "@Something@", something); msg = replace_all(msg, "@A_something@", monster->name(DESC_CAP_A)); msg = replace_all(msg, "@The_something@", monster->name(cap)); } else { msg = replace_all(msg, "@something@", "something"); msg = replace_all(msg, "@a_something@", "something"); msg = replace_all(msg, "@the_something@", "something"); msg = replace_all(msg, "@Something@", "Something"); msg = replace_all(msg, "@A_something@", "Something"); msg = replace_all(msg, "@The_something@", "Something"); } std::string plain = monster->name(DESC_PLAIN); msg = replace_all(msg, "@monster@", plain); msg = replace_all(msg, "@a_monster@", monster->name(DESC_NOCAP_A)); msg = replace_all(msg, "@the_monster@", monster->name(nocap)); plain[0] = toupper(plain[0]); msg = replace_all(msg, "@Monster@", plain); msg = replace_all(msg, "@A_monster@", monster->name(DESC_CAP_A)); msg = replace_all(msg, "@The_monster@", monster->name(cap)); msg = replace_all(msg, "@Pronoun@", monster->pronoun(PRONOUN_CAP)); msg = replace_all(msg, "@pronoun@", monster->pronoun(PRONOUN_NOCAP)); msg = replace_all(msg, "@Possessive@", monster->pronoun(PRONOUN_CAP_POSSESSIVE)); msg = replace_all(msg, "@possessive@", monster->pronoun(PRONOUN_NOCAP_POSSESSIVE)); // Replace with "you are" for atheists. msg = replace_all(msg, "@god_is@", _replace_god_name(true, false)); msg = replace_all(msg, "@God_is@", _replace_god_name(true, true)); // No verb needed, msg = replace_all(msg, "@player_god@", _replace_god_name(false, false)); msg = replace_all(msg, "@Player_god@", _replace_god_name(false, true)); // Replace with species specific insults. if (msg.find("@species_insult_") != std::string::npos) { msg = replace_all(msg, "@species_insult_adj1@", _get_species_insult("adj1")); msg = replace_all(msg, "@species_insult_adj2@", _get_species_insult("adj2")); msg = replace_all(msg, "@species_insult_noun@", _get_species_insult("noun")); } static const char * sound_list[] = { "says", // actually S_SILENT "shouts", "barks", "shouts", "roars", "screams", "bellows", "screeches", "buzzes", "moans", "whines", "croaks", "growls", "hisses", "sneers", // S_DEMON_TAUNT "buggily says", // NUM_SHOUTS "breathes", // S_VERY_SOFT "whispers", // S_SOFT "says", // S_NORMAL "shouts", // S_LOUD "screams" // S_VERY_LOUD }; if (s_type < 0 || s_type >= NUM_LOUDNESS || s_type == NUM_SHOUTS) s_type = mons_shouts(monster->type); if (s_type < 0 || s_type >= NUM_LOUDNESS || s_type == NUM_SHOUTS) { mpr("Invalid @says@ type.", MSGCH_DIAGNOSTICS); msg = replace_all(msg, "@says@", "buggily says"); } else { msg = replace_all(msg, "@says@", sound_list[s_type]); } // The proper possessive for a word ending in an "s" is to // put an apostrophe after the "s": "Chris" -> "Chris'", // not "Chris" -> "Chris's". Stupid English language... msg = replace_all(msg, "s's", "s'"); return msg; } static mon_body_shape _get_ghost_shape(const monsters *mon) { const ghost_demon &ghost = *(mon->ghost); switch (ghost.species) { case SP_NAGA: return (MON_SHAPE_NAGA); case SP_CENTAUR: return (MON_SHAPE_CENTAUR); case SP_KENKU: return (MON_SHAPE_HUMANOID_WINGED); case SP_RED_DRACONIAN: case SP_WHITE_DRACONIAN: case SP_GREEN_DRACONIAN: case SP_YELLOW_DRACONIAN: case SP_GREY_DRACONIAN: case SP_BLACK_DRACONIAN: case SP_PURPLE_DRACONIAN: case SP_MOTTLED_DRACONIAN: case SP_PALE_DRACONIAN: case SP_BASE_DRACONIAN: return (MON_SHAPE_HUMANOID_TAILED); default: return (MON_SHAPE_HUMANOID); } } mon_body_shape get_mon_shape(const monsters *mon) { if (mon->type == MONS_PLAYER_GHOST) return _get_ghost_shape(mon); else if (mons_is_zombified(mon)) return get_mon_shape(mon->base_monster); else return get_mon_shape(mon->type); } mon_body_shape get_mon_shape(const int type) { switch(mons_char(type)) { case 'a': // ants and cockroaches return(MON_SHAPE_INSECT); case 'b': // bats and butterflys if (type == MONS_BUTTERFLY) return(MON_SHAPE_INSECT_WINGED); else return(MON_SHAPE_BAT); case 'c': // centaurs return(MON_SHAPE_CENTAUR); case 'd': // draconions and drakes if (mons_genus(type) == MONS_DRACONIAN) { if (mons_class_flag(type, M_FLIES)) return(MON_SHAPE_HUMANOID_WINGED_TAILED); else return(MON_SHAPE_HUMANOID_TAILED); } else if (mons_class_flag(type, M_FLIES)) return(MON_SHAPE_QUADRUPED_WINGED); else return(MON_SHAPE_QUADRUPED); case 'e': // elves return(MON_SHAPE_HUMANOID); case 'f': // fungi return(MON_SHAPE_FUNGUS); case 'g': // gargoyles, gnolls, goblins and hobgoblins if (type == MONS_GARGOYLE) return(MON_SHAPE_HUMANOID_WINGED_TAILED); else return(MON_SHAPE_HUMANOID); case 'h': // hounds return(MON_SHAPE_QUADRUPED); case 'j': // snails return(MON_SHAPE_SNAIL); case 'k': // killer bees return(MON_SHAPE_INSECT_WINGED); case 'l': // lizards return(MON_SHAPE_QUADRUPED); case 'm': // merfolk return(MON_SHAPE_HUMANOID); case 'n': // necrophages and ghouls return(MON_SHAPE_HUMANOID); case 'o': // orcs return(MON_SHAPE_HUMANOID); case 'p': // ghosts if (type != MONS_INSUBSTANTIAL_WISP && type != MONS_PLAYER_GHOST) // handled elsewhere { return(MON_SHAPE_HUMANOID); } break; case 'q': // quasists return(MON_SHAPE_HUMANOID_TAILED); case 'r': // rodents return(MON_SHAPE_QUADRUPED); case 's': // arachnids and centipedes if (type == MONS_GIANT_CENTIPEDE) return(MON_SHAPE_CENTIPEDE); else return(MON_SHAPE_ARACHNID); case 'u': // ugly things are humanoid??? return(MON_SHAPE_HUMANOID); case 't': // minotaurs return(MON_SHAPE_HUMANOID); case 'v': // vortices and elementals return(MON_SHAPE_MISC); case 'w': // worms return(MON_SHAPE_SNAKE); case 'x': // small abominations return(MON_SHAPE_MISC); case 'y': // winged insects return(MON_SHAPE_INSECT_WINGED); case 'z': // small skeletons if (type == MONS_SKELETAL_WARRIOR) return(MON_SHAPE_HUMANOID); else { // constructed type, not enough info to determine shape return(MON_SHAPE_MISC); } case 'A': // angelic beings return(MON_SHAPE_HUMANOID_WINGED); case 'B': // beetles return(MON_SHAPE_INSECT); case 'C': // giants return(MON_SHAPE_HUMANOID); case 'D': // dragons if (mons_class_flag(type, M_FLIES)) return(MON_SHAPE_QUADRUPED_WINGED); else return(MON_SHAPE_QUADRUPED); case 'E': // effreets return(MON_SHAPE_HUMANOID); case 'F': // frogs return(MON_SHAPE_QUADRUPED_TAILLESS); case 'G': // floating eyeballs and orbs return(MON_SHAPE_ORB); case 'H': // manticores, hippogriffs and griffins if (type == MONS_MANTICORE) return(MON_SHAPE_QUADRUPED); else return(MON_SHAPE_QUADRUPED_WINGED); case 'I': // ice beasts return(MON_SHAPE_QUADRUPED); case 'J': // jellies and jellyfish return(MON_SHAPE_BLOB); case 'K': // kobolds return(MON_SHAPE_HUMANOID); case 'L': // liches return(MON_SHAPE_HUMANOID); case 'M': // mummies return(MON_SHAPE_HUMANOID); case 'N': // nagas return(MON_SHAPE_NAGA); case 'O': // ogres return(MON_SHAPE_HUMANOID); case 'P': // plants return(MON_SHAPE_PLANT); case 'Q': // queen insects if (type == MONS_QUEEN_BEE) return(MON_SHAPE_INSECT_WINGED); else return(MON_SHAPE_INSECT); case 'R': // rakshasa; humanoid? return(MON_SHAPE_HUMANOID); case 'S': // snakes return(MON_SHAPE_SNAKE); case 'T': // trolls return(MON_SHAPE_HUMANOID); case 'U': // bears return(MON_SHAPE_QUADRUPED_TAILLESS); case 'V': // vampires return(MON_SHAPE_HUMANOID); case 'W': // wraiths, humanoid if not a spectral thing if (type == MONS_SPECTRAL_THING) // constructed type, not enough info to determine shape return(MON_SHAPE_MISC); else return(MON_SHAPE_HUMANOID); case 'X': // large abominations return(MON_SHAPE_MISC); case 'Y': // yaks and sheep if (type == MONS_SHEEP) return(MON_SHAPE_QUADRUPED_TAILLESS); else return(MON_SHAPE_QUADRUPED); case 'Z': // constructed type, not enough info to determine shape return(MON_SHAPE_MISC); case ';': // Fish and eels if (type == MONS_ELECTRICAL_EEL) return(MON_SHAPE_SNAKE); else return (MON_SHAPE_FISH); // The various demons, plus some golems and statues. And humanoids. case '1': case '2': case '3': case '4': case '5': case '&': case '8': case '@': // Assume demon has wings if it can fly. bool flies = mons_class_flag(type, M_FLIES); // Assume demon has a tail if it has a sting attack or a // tail slap attack. monsterentry *mon_data = get_monster_data(type); bool tailed = false; for (int i = 0; i < 4; i++) if (mon_data->attack[i].type == AT_STING || mon_data->attack[i].type == AT_TAIL_SLAP) { tailed = true; break; } if (flies && tailed) return(MON_SHAPE_HUMANOID_WINGED_TAILED); else if (flies && !tailed) return(MON_SHAPE_HUMANOID_WINGED); else if (!flies && tailed) return(MON_SHAPE_HUMANOID_TAILED); else return(MON_SHAPE_HUMANOID); } return(MON_SHAPE_MISC); } std::string get_mon_shape_str(const monsters *mon) { return get_mon_shape_str(get_mon_shape(mon)); } std::string get_mon_shape_str(const int type) { return get_mon_shape_str(get_mon_shape(type)); } std::string get_mon_shape_str(const mon_body_shape shape) { ASSERT(shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_MISC); if (shape < MON_SHAPE_HUMANOID || shape > MON_SHAPE_MISC) return("buggy shape"); static const char *shape_names[] = { "humanoid", "winged humanoid", "tailed humanoid", "winged tailed humanoid", "centaur", "naga", "quadruped", "tailless quadruped", "winged quadruped", "bat", "snake", "fish", "insect", "winged insect", "arachnid", "centipede", "snail", "plant", "fungus", "orb", "blob", "misc" }; return (shape_names[shape]); } ///////////////////////////////////////////////////////////////////////// // mon_resist_def mon_resist_def::mon_resist_def() : elec(0), poison(0), fire(0), steam(0), cold(0), hellfire(0), asphyx(0), acid(0), sticky_flame(false), pierce(0), slice(0), bludgeon(0) { } short mon_resist_def::get_default_res_level(int resist, short level) const { if (level != -100) return level; switch (resist) { case MR_RES_STEAM: case MR_RES_ACID: return 3; case MR_RES_ELEC: return 2; default: return 1; } } mon_resist_def::mon_resist_def(int flags, short level) : elec(0), poison(0), fire(0), steam(0), cold(0), hellfire(0), asphyx(0), acid(0), sticky_flame(false), pierce(0), slice(0), bludgeon(0) { for (int i = 0; i < 32; ++i) { const short nl = get_default_res_level(1 << i, level); switch (flags & (1 << i)) { // resistances case MR_RES_STEAM: steam = 3; break; case MR_RES_ELEC: elec = nl; break; case MR_RES_POISON: poison = nl; break; case MR_RES_FIRE: fire = nl; break; case MR_RES_HELLFIRE: hellfire = nl; break; case MR_RES_COLD: cold = nl; break; case MR_RES_ASPHYX: asphyx = nl; break; case MR_RES_ACID: acid = nl; break; // vulnerabilities case MR_VUL_ELEC: elec = -nl; break; case MR_VUL_POISON: poison = -nl; break; case MR_VUL_FIRE: fire = -nl; break; case MR_VUL_COLD: cold = -nl; break; // resistance to certain damage types case MR_RES_PIERCE: pierce = nl; break; case MR_RES_SLICE: slice = nl; break; case MR_RES_BLUDGEON: bludgeon = nl; break; // vulnerability to certain damage types case MR_VUL_PIERCE: pierce = -nl; break; case MR_VUL_SLICE: slice = -nl; break; case MR_VUL_BLUDGEON: bludgeon = -nl; break; case MR_RES_STICKY_FLAME: sticky_flame = true; break; default: break; } } } const mon_resist_def &mon_resist_def::operator |= (const mon_resist_def &o) { elec += o.elec; poison += o.poison; fire += o.fire; cold += o.cold; hellfire += o.hellfire; asphyx += o.asphyx; acid += o.acid; pierce += o.pierce; slice += o.slice; bludgeon += o.bludgeon; sticky_flame = sticky_flame || o.sticky_flame; return (*this); } mon_resist_def mon_resist_def::operator | (const mon_resist_def &o) const { mon_resist_def c(*this); return (c |= o); }