/* * File: mon-util.cc * Summary: Misc monster related functions. * Written by: Linley Henzell */ // $pellbinder: (c) D.G.S.E 1998 // some routines snatched from former monsstat.cc #include "AppHdr.h" #include "mon-util.h" #include "beam.h" #include "colour.h" #include "database.h" #include "directn.h" #include "fprop.h" #include "fight.h" #include "ghost.h" #include "goditem.h" #include "itemname.h" #include "kills.h" #include "mislead.h" #include "mon-behv.h" #include "mon-iter.h" #include "mon-place.h" #include "coord.h" #include "mon-stuff.h" #include "options.h" #include "random.h" #include "religion.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "env.h" #include "terrain.h" #include "viewchar.h" //jmf: moved from inside function static FixedVector < int, NUM_MONSTERS > mon_entry; mon_display monster_symbols[NUM_MONSTERS]; static bool initialised_randmons = false; static std::vector monsters_by_habitat[NUM_HABITATS]; #include "mon-data.h" #define MONDATASIZE ARRAYSZ(mondata) 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 (feat_is_watery(grid)) return (HT_WATER); if (feat_is_rock(grid) && !feat_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_AMPHIBIOUS_WATER: case HT_WATER: return (DNGN_DEEP_WATER); case HT_LAVA: return (DNGN_LAVA); case HT_ROCK: return (DNGN_ROCK_WALL); case HT_LAND: case HT_AMPHIBIOUS_LAND: default: return (DNGN_FLOOR); } } static void _initialise_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) { monster_type mt = static_cast(m); if (invalid_monster_type(mt)) continue; if (monster_habitable_grid(mt, grid)) monsters_by_habitat[i].push_back(mt); } } initialised_randmons = true; } monster_type random_monster_at_grid(const coord_def& p) { return (random_monster_at_grid(grd(p))); } monster_type random_monster_at_grid(dungeon_feature_type grid) { if (!initialised_randmons) _initialise_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_MARA_FAKE) { // 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() { // First, fill static array with dummy values. {dlb} mon_entry.init(-1); // Next, fill static array with location of entry in mondata[]. {dlb}: for (unsigned int i = 0; i < MONDATASIZE; ++i) mon_entry[mondata[i].mc] = i; // Finally, monsters yet with dummy entries point to TTTSNB(tm). {dlb}: for (unsigned int i = 0; i < NUM_MONSTERS; ++i) if (mon_entry[i] == -1) mon_entry[i] = mon_entry[MONS_PROGRAM_BUG]; init_monster_symbols(); } void init_monster_symbols() { std::map base_mons; std::map::iterator it; 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; it = base_mons.find(md.glyph); if (it == base_mons.end() || it->first == MONS_PROGRAM_BUG) base_mons[md.glyph] = static_cast(i); md.detected = base_mons[md.glyph]; } } 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; } monster_symbols[MONS_GOLD_MIMIC].glyph = dchar_glyph(DCHAR_ITEM_GOLD); monster_symbols[MONS_WEAPON_MIMIC].glyph = dchar_glyph(DCHAR_ITEM_WEAPON); monster_symbols[MONS_ARMOUR_MIMIC].glyph = dchar_glyph(DCHAR_ITEM_ARMOUR); monster_symbols[MONS_SCROLL_MIMIC].glyph = dchar_glyph(DCHAR_ITEM_SCROLL); monster_symbols[MONS_POTION_MIMIC].glyph = dchar_glyph(DCHAR_ITEM_POTION); } 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 (mons_is_ghost_demon(mon->type)) 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); } short mon_resist_def::get_resist_level(mon_resist_flags res_type) const { switch (res_type) { case MR_RES_ELEC: return elec; case MR_RES_POISON: return poison; case MR_RES_FIRE: return fire; case MR_RES_STEAM: return steam; case MR_RES_COLD: return cold; case MR_RES_ACID: return acid; case MR_RES_ROTTING: return rotting; default: return (0); } } 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); } int scan_mon_inv_randarts(const monsters *mon, artefact_prop_type ra_prop) { int ret = 0; if (mons_itemuse(mon) >= MONUSE_STARTING_EQUIPMENT) { const int weapon = mon->inv[MSLOT_WEAPON]; const int second = mon->inv[MSLOT_ALT_WEAPON]; // Two-headed ogres, etc. const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; if (weapon != NON_ITEM && mitm[weapon].base_type == OBJ_WEAPONS && is_artefact(mitm[weapon])) { ret += artefact_wpn_property(mitm[weapon], ra_prop); } if (second != NON_ITEM && mitm[second].base_type == OBJ_WEAPONS && is_artefact(mitm[second])) { ret += artefact_wpn_property(mitm[second], ra_prop); } if (armour != NON_ITEM && mitm[armour].base_type == OBJ_ARMOUR && is_artefact(mitm[armour])) { ret += artefact_wpn_property(mitm[armour], ra_prop); } if (shield != NON_ITEM && mitm[shield].base_type == OBJ_ARMOUR && is_artefact(mitm[shield])) { ret += artefact_wpn_property(mitm[shield], ra_prop); } } return (ret); } static int _scan_mon_inv_items(const monsters *mon, bool (*item_type)(const item_def&)) { int ret = 0; if (mons_itemuse(mon) >= MONUSE_STARTING_EQUIPMENT) { const int weapon = mon->inv[MSLOT_WEAPON]; const int second = mon->inv[MSLOT_ALT_WEAPON]; // Two-headed ogres, etc. const int misc = mon->inv[MSLOT_MISCELLANY]; const int potion = mon->inv[MSLOT_POTION]; const int wand = mon->inv[MSLOT_WAND]; const int scroll = mon->inv[MSLOT_SCROLL]; if (weapon != NON_ITEM && mitm[weapon].base_type == OBJ_WEAPONS && item_type(mitm[weapon])) { ret++; } if (second != NON_ITEM && mitm[second].base_type == OBJ_WEAPONS && item_type(mitm[second])) { ret++; } if (misc != NON_ITEM && mitm[misc].base_type == OBJ_MISCELLANY && item_type(mitm[misc])) { ret++; } if (potion != NON_ITEM && mitm[potion].base_type == OBJ_POTIONS && item_type(mitm[potion])) { ret++; } if (wand != NON_ITEM && mitm[misc].base_type == OBJ_WANDS && item_type(mitm[wand])) { ret++; } if (scroll != NON_ITEM && mitm[scroll].base_type == OBJ_SCROLLS && item_type(mitm[scroll])) { ret++; } } return (ret); } static bool _mons_has_undrinkable_potion(const monsters *mon) { if (mons_itemuse(mon) >= MONUSE_STARTING_EQUIPMENT) { const int potion = mon->inv[MSLOT_POTION]; if (potion != NON_ITEM && mitm[potion].base_type == OBJ_POTIONS) { const potion_type ptype = static_cast(mitm[potion].sub_type); if (!mon->can_drink_potion(ptype)) return (true); } } return (false); } int mons_unusable_items(const monsters *mon) { int ret = 0; if (mon->is_holy()) ret += _scan_mon_inv_items(mon, is_evil_item) > 0; else if (mon->undead_or_demonic()) { ret += _scan_mon_inv_items(mon, is_holy_item) > 0; if (mon->holiness() == MH_UNDEAD && _mons_has_undrinkable_potion(mon)) ret++; } return (ret); } mon_holy_type mons_class_holiness(int mc) { ASSERT(smc); return (smc->holiness); } bool mons_class_is_confusable(int mc) { ASSERT(smc); 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) { ASSERT(smc); return (smc->resist_magic < MAG_IMMUNE); } 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_fast(const monsters *mon) { int pspeed = 1000/player_movement_speed()/player_speed(); #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Your delay: %d, your speed: %d, mon speed: %d", player_movement_speed(), pspeed, mon->speed); #endif return (mon->speed > pspeed); } bool mons_is_projectile(int mc) { return (mc == MONS_ORB_OF_DESTRUCTION); } 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 && mon->type != MONS_BALLISTOMYCETE); } // Returns true for monsters that obviously (to the player) feel // "thematically at home" in a branch. Currently used for native // monsters recognising 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_genus(monster->type) == MONS_ELF); case BRANCH_ORCISH_MINES: return (mons_genus(monster->type) == MONS_ORC); case BRANCH_SHOALS: return (mons_species(monster->type) == MONS_CYCLOPS || mons_species(monster->type) == MONS_MERFOLK || mons_genus(monster->type) == MONS_MERMAID || monster->type == MONS_HARPY); case BRANCH_SLIME_PITS: return (mons_genus(monster->type) == MONS_JELLY); case BRANCH_SNAKE_PIT: return (mons_genus(monster->type) == MONS_NAGA || mons_genus(monster->type) == MONS_SNAKE); case BRANCH_HALL_OF_ZOT: return (mons_genus(monster->type) == MONS_DRACONIAN || monster->type == MONS_ORB_GUARDIAN || monster->type == MONS_ORB_OF_FIRE || monster->type == MONS_DEATH_COB); case BRANCH_CRYPT: return (monster->holiness() == MH_UNDEAD); case BRANCH_TOMB: return (mons_genus(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_poisoner(const monsters *mon) { if (mons_corpse_effect(mon->type) == CE_POISONOUS) return (true); if (mon->has_attack_flavour(AF_POISON) || mon->has_attack_flavour(AF_POISON_NASTY) || mon->has_attack_flavour(AF_POISON_MEDIUM) || mon->has_attack_flavour(AF_POISON_STRONG) || mon->has_attack_flavour(AF_POISON_STR)) { return (true); } return (false); } // Monsters considered as "slime" for Jiyva. bool mons_class_is_slime(int mc) { return (mons_genus(mc) == MONS_JELLY || mons_genus(mc) == MONS_GIANT_EYEBALL || mons_genus(mc) == MONS_GIANT_ORANGE_BRAIN); } bool mons_is_slime(const monsters *mon) { return (mons_class_is_slime(mon->type)); } // Plant or fungus really bool mons_class_is_plant(int mc) { return (mons_genus(mc) == MONS_PLANT || mons_genus(mc) == MONS_BUSH || mons_genus(mc) == MONS_FUNGUS); } bool mons_is_plant(const monsters *mon) { return (mons_class_is_plant(mon->type)); } bool mons_eats_items(const monsters *mon) { return (mons_itemeat(mon) == MONEAT_ITEMS || mon->has_ench(ENCH_EAT_ITEMS)); } bool mons_eats_corpses(const monsters *mon) { return (mons_itemeat(mon) == MONEAT_CORPSES); } bool mons_eats_food(const monsters *mon) { return (mons_itemeat(mon) == MONEAT_FOOD); } bool mons_is_skeletal(int mc) { return (mc == MONS_SKELETON_SMALL || mc == MONS_SKELETON_LARGE || mc == MONS_SKELETAL_DRAGON || mc == MONS_SKELETAL_WARRIOR); } bool invalid_monster(const monsters *mon) { return (!mon || invalid_monster_type(mon->type)); } bool invalid_monster_type(monster_type mt) { return (mt < 0 || mt >= NUM_MONSTERS || mon_entry[mt] == mon_entry[MONS_PROGRAM_BUG]); } bool invalid_monster_index(int i) { return (i < 0 || i >= MAX_MONSTERS); } bool mons_is_statue(int mc, bool allow_disintegrate) { if (mc == MONS_ORANGE_STATUE || mc == MONS_SILVER_STATUE) return (true); if (!allow_disintegrate) return (mc == MONS_ICE_STATUE || mc == MONS_ROXANNE); return (false); } 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); } // Returns true if the given monster's foe is also a monster. bool mons_foe_is_mons(const monsters *mons) { const actor *foe = mons->get_foe(); return foe && foe->atype() == ACT_MONSTER; } int mons_weight(int mc) { ASSERT(smc); return (smc->weight); } corpse_effect_type mons_corpse_effect(int mc) { ASSERT(smc); 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) { if (mc == RANDOM_DRACONIAN || mc == RANDOM_BASE_DRACONIAN || mc == RANDOM_NONBASE_DRACONIAN) { return (MONS_DRACONIAN); } ASSERT(smc); return (smc->genus); } monster_type mons_detected_base(monster_type mc) { return (monster_symbols[mc].detected); } 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 case DARKGREY: 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_GURGLE: case S_LOUD: return 10; case S_SHOUT2: case S_ROAR: case S_VERY_LOUD: return 12; default: return 8; } } // Only beasts and chaos spawns use S_RANDOM for noise type. // Pandemonium lords can also get here, but this is 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 || shout == S_SILENT) return (false); // Chaos spawns can do anything but demon taunts, since they're // not coherent enough to actually say words. if (type == MONS_CHAOS_SPAWN) return (shout != S_DEMON_TAUNT); // For Pandemonium lords, almost everything is fair game. It's only // used for the shouting verb ("say", "bellow", "roar", etc.) 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: 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) { ASSERT(smc); 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_ghost_demon(int mc) { return (mc == MONS_UGLY_THING || mc == MONS_VERY_UGLY_THING || mc == MONS_PLAYER_GHOST || mc == MONS_DANCING_WEAPON || mc == MONS_PANDEMONIUM_DEMON); } 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)); } unsigned mons_char(int mc) { return monster_symbols[mc].glyph; } mon_itemuse_type mons_class_itemuse(int mc) { ASSERT(smc); return (smc->gmon_use); } mon_itemuse_type mons_itemuse(const monsters *mon) { if (mons_enslaved_twisted_soul(mon)) return (MONUSE_OPEN_DOORS); else if (mons_enslaved_intact_soul(mon)) return (mons_class_itemuse(mons_zombie_base(mon))); return (mons_class_itemuse(mon->type)); } mon_itemeat_type mons_class_itemeat(int mc) { ASSERT(smc); return (smc->gmon_eat); } mon_itemeat_type mons_itemeat(const monsters *mon) { if (mons_enslaved_twisted_soul(mon)) return (MONEAT_NOTHING); else if (mons_enslaved_intact_soul(mon)) return (mons_class_itemeat(mons_zombie_base(mon))); return (mons_class_itemeat(mon->type)); } int mons_class_colour(int mc) { return (monster_symbols[mc].colour); } int mons_colour(const monsters *mon) { return (mon->colour); } bool mons_class_can_regenerate(int mc) { return (!mons_class_flag(mc, M_NO_REGEN)); } bool mons_can_regenerate(const monsters *mon) { if (mon->type == MONS_PLAYER_GHOST && mon->ghost->species == SP_DEEP_DWARF) return (false); return (mons_class_can_regenerate(mon->type)); } int mons_zombie_size(int mc) { ASSERT(smc); return (smc->zombie_size); } monster_type mons_zombie_base(const monsters *mon) { return (mon->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 *mon) { return (mons_class_is_zombified(mon->type)); } monster_type mons_base_type(const monsters *mon) { return mons_is_zombified(mon) ? mon->base_monster : mon->type; } bool mons_class_can_be_zombified(int mc) { int ms = mons_species(mc); return (mons_zombie_size(ms) != Z_NOZOMBIE && mons_weight(ms) > 0); } bool mons_can_be_zombified(const monsters *mon) { return (mons_class_can_be_zombified(mon->type) && !mon->is_summoned() && !mons_enslaved_body_and_soul(mon)); } bool mons_class_can_use_stairs(int mc) { return ((!mons_class_is_zombified(mc) || mc == MONS_SPECTRAL_THING) && mc != MONS_KRAKEN_TENTACLE); } bool mons_can_use_stairs(const monsters *mon) { return (mons_class_can_use_stairs(mon->type)); } bool mons_enslaved_body_and_soul(const monsters *mon) { return (mon->has_ench(ENCH_SOUL_RIPE)); } bool mons_enslaved_twisted_soul(const monsters *mon) { return (testbits(mon->flags, MF_ENSLAVED_SOUL) && (mon->type == MONS_ABOMINATION_SMALL || mon->type == MONS_ABOMINATION_LARGE)); } bool mons_enslaved_intact_soul(const monsters *mon) { return (testbits(mon->flags, MF_ENSLAVED_SOUL) && mon->type == MONS_SPECTRAL_THING); } bool mons_enslaved_soul(const monsters *mon) { return (mons_enslaved_twisted_soul(mon) || mons_enslaved_intact_soul(mon)); } bool name_zombie(monsters *mon, int mc, const std::string mon_name) { mon->mname = mon_name; // Special case for Blork the orc: shorten his name to "Blork" // to avoid mentions of e.g "Blork the orc the orc zombie". if (mc == MONS_BLORK_THE_ORC) mon->mname = "Blork"; // Also for the Lernaean hydra. else if (mc == MONS_LERNAEAN_HYDRA) mon->mname = "Lernaean hydra"; if (starts_with(mon->mname, "shaped ")) mon->flags |= MF_NAME_SUFFIX; return (true); } bool name_zombie(monsters *mon, const monsters* orig) { if (!mons_is_unique(orig->type) && orig->mname.empty()) return (false); std::string name; if (!orig->mname.empty()) name = orig->mname; else name = mons_type_name(orig->type, DESC_PLAIN); return (name_zombie(mon, orig->type, name)); } 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 if (mons->type == MONS_SPECTRAL_THING && coinflip()) attk.flavour = AF_DRAIN_XP; else if (attk.flavour != AF_REACH) attk.flavour = AF_PLAIN; attk.damage = downscale_zombie_damage(attk.damage); return (attk); } mon_attack_def mons_attack_spec(const monsters *mon, int attk_number) { int mc = mon->type; if (mc == MONS_KRAKEN_TENTACLE && !invalid_monster_index(mon->number)) { // Use the zombie, etc info from the kraken mon = &menv[mon->number]; } const bool zombified = mons_is_zombified(mon); if (attk_number < 0 || attk_number > 3 || mon->has_hydra_multi_attack()) attk_number = 0; if (mons_is_ghost_demon(mc)) { if (attk_number == 0) { return (mon_attack_def::attk(mon->ghost->damage, mon->ghost->att_type, mon->ghost->att_flav)); } return (mon_attack_def::attk(0, AT_NONE)); } if (zombified && mc != MONS_KRAKEN_TENTACLE) mc = mons_zombie_base(mon); ASSERT(smc); mon_attack_def attk = smc->attack[attk_number]; if (attk.type == AT_RANDOM) attk.type = static_cast(random_range(AT_HIT, AT_GORE)); if (attk.flavour == AF_KLOWN) { mon_attack_flavour flavours[] = {AF_POISON_NASTY, AF_ROT, AF_DRAIN_XP, AF_FIRE, AF_COLD, AF_BLINK}; attk.flavour = RANDOM_ELEMENT(flavours); } // Slime creature attacks are multiplied by the number merged. if (mon->type == MONS_SLIME_CREATURE && mon->number > 1) attk.damage *= mon->number; return (zombified ? downscale_zombie_attack(mon, attk) : attk); } int mons_damage(int mc, int rt) { if (rt < 0 || rt > 3) rt = 0; ASSERT(smc); return smc->attack[rt].damage; } bool mons_immune_magic(const monsters *mon) { return (get_monster_data(mon->type)->resist_magic == MAG_IMMUNE); } const char* mons_resist_string(const monsters *mon) { if ( mons_immune_magic(mon) ) return "is unaffected"; else return "resists"; } bool mons_has_lifeforce(const monsters *mon) { const mon_holy_type holiness = mon->holiness(); return (holiness == MH_NATURAL || holiness == MH_PLANT); } bool mons_skeleton(int mc) { return (!mons_class_flag(mc, M_NO_SKELETON)); } flight_type mons_class_flies(int mc) { const monsterentry *me = get_monster_data(mc); return (me ? me->fly : FL_NONE); } flight_type mons_flies(const monsters *mon, bool randarts) { if (mons_enslaved_twisted_soul(mon)) return (FL_LEVITATE); if (mons_is_ghost_demon(mon->type)) return (mon->ghost->fly); const int montype = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; flight_type ret = mons_class_flies(montype); // Handle the case where the zombified base monster can't fly, but // the zombified monster can (e.g. spectral things). if (ret == FL_NONE && mons_is_zombified(mon)) ret = mons_class_flies(mon->type); if (randarts && ret == FL_NONE && scan_mon_inv_randarts(mon, ARTP_LEVITATE) > 0) { ret = FL_LEVITATE; } return (ret); } bool mons_class_amphibious(int mc) { habitat_type ht = mons_class_habitat(mc); return (ht == HT_AMPHIBIOUS_LAND || ht == HT_AMPHIBIOUS_WATER); } bool mons_amphibious(const monsters *mon) { const int montype = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; return (mons_class_amphibious(montype)); } bool mons_class_wall_shielded(int mc) { return (mons_class_habitat(mc) == HT_ROCK); } bool mons_wall_shielded(const monsters *mon) { const int montype = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; return (mons_class_wall_shielded(montype)); } // 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 mons_difficulty(int mtype) { // Currently, difficulty is defined as "average hp". Leaks too much info? const monsterentry* me = get_monster_data(mtype); // [ds] XXX: Use monster experience value as a better indicator of diff.? return (me->hpdice[0] * (me->hpdice[1] + (me->hpdice[2]/2)) + me->hpdice[3]); } int exper_value(const monsters *monster) { long x_val = 0; // These four are the original arguments. const int mclass = monster->type; const int mHD = monster->hit_dice; int maxhp = monster->max_hit_points; // Hacks to make merged slime creatures not worth so much exp. We // will calculate the experience we would get for 1 blob, and then // just multiply it so that exp is linear with blobs merged. -cao if (monster->type == MONS_SLIME_CREATURE && monster->number > 1) maxhp /= monster->number; // These are some values we care about. const int speed = mons_base_speed(monster); const int modifier = _mons_exp_mod(mclass); const int item_usage = mons_itemuse(monster); // XXX: Shapeshifters can qualify here, even though they can't cast. const bool spellcaster = monster->can_use_spells(); // Early out for no XP monsters. if (mons_class_flag(mclass, M_NO_EXP_GAIN)) return (0); // The beta26 statues have non-spell-like abilities that the experience // code can't see, so inflate their XP a bit. Ice statues and Roxanne // get plenty of XP for their spells. if (mclass == MONS_ORANGE_STATUE || mclass == MONS_SILVER_STATUE) return (mHD * 15); 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_HAUNT: case SPELL_HELLFIRE_BURST: case SPELL_HELLFIRE: case SPELL_SYMBOL_OF_TORMENT: case SPELL_ICE_STORM: case SPELL_FIRE_STORM: 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_IRON_SHOT: case SPELL_TELEPORT_SELF: case SPELL_TELEPORT_OTHER: case SPELL_PORKALATOR: 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) 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; } // Slime creature exp hack part 2: Scale exp back up by the number // of blobs merged. -cao if (monster->type == MONS_SLIME_CREATURE && monster->number > 1) x_val *= monster->number; // 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); } monster_type random_draconian_monster_species() { const int num_drac = MONS_PALE_DRACONIAN - MONS_BLACK_DRACONIAN + 1; return static_cast(MONS_BLACK_DRACONIAN + random2(num_drac)); } // Note: For consistent behavior in player_will_anger_monster(), all // spellbooks a given monster can get here should produce the same // return values in the following: // // is_holy_spell() // // (is_unholy_spell() || is_evil_spell()) // // (is_unclean_spell() || is_chaotic_spell()) // // FIXME: This is not true for one set of spellbooks; MST_WIZARD_IV // contains the unholy Banishment spell, but the other MST_WIZARD-type // spellbooks contain no unholy or evil spells. static bool _get_spellbook_list(mon_spellbook_type book[6], monster_type mon_type) { bool retval = true; book[0] = MST_NO_SPELLS; book[1] = MST_NO_SPELLS; book[2] = MST_NO_SPELLS; book[3] = MST_NO_SPELLS; book[4] = MST_NO_SPELLS; book[5] = MST_NO_SPELLS; switch (mon_type) { case MONS_DEEP_ELF_CONJURER: book[0] = MST_DEEP_ELF_CONJURER_I; book[1] = MST_DEEP_ELF_CONJURER_II; break; case MONS_HELL_KNIGHT: book[0] = MST_HELL_KNIGHT_I; book[1] = MST_HELL_KNIGHT_II; break; case MONS_LICH: case MONS_ANCIENT_LICH: book[0] = MST_LICH_I; book[1] = MST_LICH_II; book[2] = MST_LICH_III; book[3] = MST_LICH_IV; break; case MONS_NECROMANCER: book[0] = MST_NECROMANCER_I; book[1] = MST_NECROMANCER_II; break; case MONS_ORC_WIZARD: case MONS_DEEP_ELF_SOLDIER: case MONS_DEEP_ELF_FIGHTER: case MONS_DEEP_ELF_KNIGHT: book[0] = MST_ORC_WIZARD_I; book[1] = MST_ORC_WIZARD_II; book[2] = MST_ORC_WIZARD_III; break; case MONS_WIZARD: case MONS_OGRE_MAGE: case MONS_EROLCHA: case MONS_DEEP_ELF_MAGE: book[0] = MST_WIZARD_I; book[1] = MST_WIZARD_II; book[2] = MST_WIZARD_III; book[3] = MST_WIZARD_IV; book[4] = MST_WIZARD_V; break; case MONS_DRACONIAN_KNIGHT: book[0] = MST_DEEP_ELF_CONJURER_I; book[1] = MST_DEEP_ELF_CONJURER_II; book[2] = MST_HELL_KNIGHT_I; book[3] = MST_HELL_KNIGHT_II; book[4] = MST_NECROMANCER_I; book[5] = MST_NECROMANCER_II; break; default: retval = false; break; } return (retval); } static void _get_spells(mon_spellbook_type& book, monsters *mon) { if (book == MST_NO_SPELLS && mons_class_flag(mon->type, M_SPELLCASTER)) { mon_spellbook_type multi_book[6]; if (_get_spellbook_list(multi_book, mon->type)) { do book = multi_book[random2(6)]; while (book == MST_NO_SPELLS); } } mon->load_spells(book); // (Dumb) special casing to give ogre mages Haste Other. -cao if (mon->type == MONS_OGRE_MAGE) mon->spells[0] = SPELL_HASTE_OTHER; mon->bind_spell_flags(); } void define_monster(int midx) { define_monster(menv[midx]); } // Generate a shiny new and unscarred monster. void define_monster(monsters &mons) { 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 = mons_real_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_ABOMINATION_LARGE: hd = 8 + random2(4); ac = 5 + random2avg(9, 2); ev = 3 + random2(5); col = random_colour(); break; case MONS_ZOMBIE_LARGE: hd = 3 + random2(5); break; case MONS_BEAST: hd = 4 + random2(4); ac = 2 + random2(5); ev = 7 + random2(5); break; case MONS_MANTICORE: // Manticores start off with 8 to 16 spike volleys. monnumber = 8 + random2(9); break; case MONS_SLIME_CREATURE: // Slime creatures start off as only single un-merged blobs. monnumber = 1; break; case MONS_HYDRA: // Hydras start off with 4 to 8 heads. monnumber = random_range(4, 8); break; case MONS_LERNAEAN_HYDRA: // The Lernaean hydra starts off with 27 heads. monnumber = 27; break; case MONS_GILA_MONSTER: if (col != BLACK) // May be overwritten by the mon_glyph option. break; col = element_colour(ETC_GILA); break; case MONS_KRAKEN: if (col != BLACK) // May be overwritten by the mon_glyph option. break; col = element_colour(ETC_KRAKEN); 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: case MONS_DRACONIAN_KNIGHT: { // Professional draconians still have a base draconian type. // White draconians will never be draconian scorchers, but // apart from that, anything goes. do monbase = random_draconian_monster_species(); while (drac_colour_incompatible(mcls, monbase)); break; } case MONS_DRACONIAN: case MONS_ELF: case MONS_HUMAN: // These are supposed to only be created by polymorph. hd += random2(10) - 4; ac += random2(5) - 2; ev += random2(5) - 2; break; default: if (mons_is_mimic(mcls)) col = get_mimic_colour(&mons); break; } if (col == BLACK) col = random_colour(); // 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_NO_MONSTER) mons.base_monster = monbase; if (mons.number == 0) mons.number = monnumber; mons.flags = 0L; mons.experience = 0L; mons.colour = col; spells = m->sec; _get_spells(spells, &mons); // Reset monster enchantments. mons.enchantments.clear(); mons.ench_countdown = 0; // NOTE: For player ghosts and (very) ugly things this just ensures // that the monster instance is valid and won't crash when used, // though the (very) ugly thing generated should actually work. The // player ghost and (very) ugly thing code is currently only used // for generating a monster for MonsterMenuEntry in // _find_description() in command.cc. switch (mcls) { case MONS_PANDEMONIUM_DEMON: { ghost_demon ghost; ghost.init_random_demon(); mons.set_ghost(ghost); mons.pandemon_init(); mons.bind_spell_flags(); break; } case MONS_PLAYER_GHOST: { ghost_demon ghost; ghost.init_player_ghost(); mons.set_ghost(ghost); mons.ghost_init(); mons.bind_spell_flags(); break; } case MONS_UGLY_THING: case MONS_VERY_UGLY_THING: { ghost_demon ghost; ghost.init_ugly_thing(mcls == MONS_VERY_UGLY_THING); mons.set_ghost(ghost, false); mons.uglything_init(); break; } default: break; } } static const unsigned char ugly_colour_values[] = { RED, BROWN, GREEN, CYAN, MAGENTA, LIGHTGREY }; unsigned char ugly_thing_random_colour() { return (RANDOM_ELEMENT(ugly_colour_values)); } int ugly_thing_colour_offset(const unsigned char colour) { for (unsigned i = 0; i < ARRAYSZ(ugly_colour_values); ++i) { if (make_low_colour(colour) == ugly_colour_values[i]) return (i); } return (-1); } static const char *drac_colour_names[] = { "black", "mottled", "yellow", "green", "purple", "red", "white", "pale" }; std::string draconian_colour_name(monster_type mon_type) { COMPILE_CHECK(ARRAYSZ(drac_colour_names) == MONS_PALE_DRACONIAN - MONS_DRACONIAN, c1); if (mon_type < MONS_BLACK_DRACONIAN || mon_type > MONS_PALE_DRACONIAN) return ("buggy"); return (drac_colour_names[mon_type - 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); } 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; } } switch (type) { case RANDOM_MONSTER: result += "random monster"; return (result); case RANDOM_DRACONIAN: result += "random draconian"; return (result); case RANDOM_BASE_DRACONIAN: result += "random base draconian"; return (result); case RANDOM_NONBASE_DRACONIAN: result += "random nonbase draconian"; return (result); case WANDERING_MONSTER: result += "wandering monster"; return (result); } const monsterentry *me = get_monster_data(type); ASSERT(me != NULL); if (me == NULL) { result += make_stringf("invalid type %d", type); return (result); } result += me->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]) // XXX: Hack && !starts_with(&result[2], "one-")) { 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) { if (p_monsterid >= 0 && p_monsterid < NUM_MONSTERS) return (&mondata[mon_entry[p_monsterid]]); else return (NULL); } static int _mons_exp_mod(int mc) { ASSERT(smc); return (smc->exp_mod); } int mons_class_base_speed(int mc) { ASSERT(smc); return (smc->speed); } int mons_real_base_speed(int mc) { ASSERT(smc); int speed = smc->speed; switch (mc) { 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); } int mons_class_zombie_base_speed(int zombie_base_mc) { return (std::max(3, mons_class_base_speed(zombie_base_mc) - 2)); } int mons_base_speed(const monsters *mon) { if (mons_enslaved_intact_soul(mon)) return (mons_class_base_speed(mons_zombie_base(mon))); return (mons_is_zombified(mon) ? mons_class_zombie_base_speed(mons_zombie_base(mon)) : mons_class_base_speed(mon->type)); } mon_intel_type mons_class_intel(int mc) { ASSERT(smc); return (smc->intel); } mon_intel_type mons_intel(const monsters *mon) { if (mons_enslaved_twisted_soul(mon)) return (I_NORMAL); else if (mons_enslaved_intact_soul(mon)) return (mons_class_intel(mons_zombie_base(mon))); return (mons_class_intel(mon->type)); } habitat_type mons_class_habitat(int mc) { const monsterentry *me = get_monster_data(mc); return (me ? me->habitat : HT_LAND); } habitat_type mons_habitat(const monsters *mon) { return (mons_class_habitat(mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type)); } habitat_type mons_class_primary_habitat(int mc) { habitat_type ht = mons_class_habitat(mc); if (ht == HT_AMPHIBIOUS_LAND) ht = HT_LAND; else if (ht == HT_AMPHIBIOUS_WATER) ht = HT_WATER; return (ht); } habitat_type mons_primary_habitat(const monsters *mon) { return (mons_class_primary_habitat(mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type)); } habitat_type mons_class_secondary_habitat(int mc) { habitat_type ht = mons_class_habitat(mc); if (ht == HT_AMPHIBIOUS_LAND) ht = HT_WATER; else if (ht == HT_AMPHIBIOUS_WATER || ht == HT_ROCK) ht = HT_LAND; return (ht); } habitat_type mons_secondary_habitat(const monsters *mon) { return (mons_class_secondary_habitat(mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type)); } bool intelligent_ally(const monsters *mon) { return (mon->attitude == ATT_FRIENDLY && mons_intel(mon) >= I_NORMAL); } int mons_power(int mc) { // For now, just return monster hit dice. ASSERT(smc); 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_class_wields_two_weapons(int mc) { return (mons_class_flag(mc, M_TWOWEAPON)); } bool mons_wields_two_weapons(const monsters *mon) { const int montype = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; return (mons_class_wields_two_weapons(montype)); } bool mons_self_destructs(const monsters *m) { return (m->type == MONS_GIANT_SPORE || m->type == MONS_BALL_LIGHTNING || m->type == MONS_ORB_OF_DESTRUCTION); } int mons_base_damage_brand(const monsters *m) { if (mons_is_ghost_demon(m->type)) return (m->ghost->brand); return (SPWPN_NORMAL); } bool mons_att_wont_attack(mon_attitude_type fr) { return (fr == ATT_FRIENDLY || fr == ATT_GOOD_NEUTRAL || fr == ATT_STRICT_NEUTRAL); } mon_attitude_type mons_attitude(const monsters *m) { if (m->friendly()) return ATT_FRIENDLY; else if (m->good_neutral()) return ATT_GOOD_NEUTRAL; else if (m->strict_neutral()) return ATT_STRICT_NEUTRAL; else if (m->neutral()) return ATT_NEUTRAL; else return ATT_HOSTILE; } bool mons_is_confused(const monsters *m, bool class_too) { return (m->has_ench(ENCH_CONFUSION) && (class_too || !mons_class_flag(m->type, M_CONFUSED))); } 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_influenced_by_sanctuary(const monsters *m) { return (!m->wont_attack() && (m->holiness() != MH_PLANT || mons_is_stationary(m))); } bool mons_is_fleeing_sanctuary(const monsters *m) { return (mons_is_influenced_by_sanctuary(m) && in_bounds(env.sanctuary_pos) && (m->flags & MF_FLEEING_FROM_SANCTUARY)); } bool mons_is_batty(const monsters *m) { return mons_class_flag(m->type, M_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_is_unknown_mimic(const monsters *m) { return mons_is_mimic(m->type) && !mons_is_known_mimic(m); } bool mons_looks_stabbable(const monsters *m) { const unchivalric_attack_type uat = is_unchivalric_attack(&you, m); return (mons_behaviour_perceptible(m) && !m->friendly() && (uat == UCAT_PARALYSED || uat == UCAT_SLEEPING)); } bool mons_looks_distracted(const monsters *m) { const unchivalric_attack_type uat = is_unchivalric_attack(&you, m); return (mons_behaviour_perceptible(m) && !m->friendly() && uat != UCAT_NO_ATTACK && uat != UCAT_PARALYSED && uat != UCAT_SLEEPING); } void mons_start_fleeing_from_sanctuary(monsters *monster) { monster->flags |= MF_FLEEING_FROM_SANCTUARY; monster->target = env.sanctuary_pos; behaviour_event(monster, ME_SCARE, MHITNOT, env.sanctuary_pos); } void mons_stop_fleeing_from_sanctuary(monsters *monster) { const bool had_flag = (monster->flags & MF_FLEEING_FROM_SANCTUARY); monster->flags &= (~MF_FLEEING_FROM_SANCTUARY); if (had_flag) behaviour_event(monster, ME_EVAL, MHITYOU); } void mons_pacify(monsters *mon, mon_attitude_type att) { // Make the monster permanently neutral. mon->attitude = att; mon->flags |= MF_WAS_NEUTRAL; if (!testbits(mon->flags, MF_GOT_HALF_XP) && !mon->is_summoned()) { // 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; } // Cancel fleeing and such. mon->behaviour = BEH_WANDER; // Make the monster begin leaving the level. behaviour_event(mon, ME_EVAL); // XXX: Needs a better way to track Pikel's turning neutral. if (mon->type == MONS_PIKEL) pikel_band_neutralise(); } static bool _mons_should_fire_beneficial(bolt &beam) { // Should monster haste other be able to target the player? // Saying no for now. -cao if (beam.target == you.pos()) return false; // Assuming all beneficial beams are enchantments if a foe is in // the path the beam will definitely hit them so we shouldn't fire // in that case. if (beam.friend_info.count == 0 || beam.foe_info.count != 0) { return (false); } // Should beneficial monster enchantment beams be allowed in a // sanctuary? -cao if (is_sanctuary(you.pos()) || is_sanctuary(beam.source)) return (false); return (true); } static bool _beneficial_beam_flavour(beam_type flavour) { switch(flavour) { case BEAM_HASTE: case BEAM_HEALING: case BEAM_INVISIBILITY: return (true); default: return (false); } } // For SUMMON_PLAYER_GHOST. bool _find_players_ghost () { bool found = false; for (monster_iterator mi; mi; ++mi) if (mi->type == MONS_PLAYER_GHOST && mi->mname == you.your_name) found = true; return found; } bool mons_should_fire(struct bolt &beam) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "tracer: foes %d (pow: %d), friends %d (pow: %d), " "foe_ratio: %d, smart: %s", beam.foe_info.count, beam.foe_info.power, beam.friend_info.count, beam.friend_info.power, beam.foe_ratio, beam.smart_monster ? "yes" : "no"); #endif // Use different evaluation criteria if the beam is a beneficial // enchantment (haste other). if (_beneficial_beam_flavour(beam.flavour)) return (_mons_should_fire_beneficial(beam)); // Friendly monsters shouldn't be targetting you: this will happen // often because the default behaviour for charmed monsters is to // have you as a target. While foe_ratio will handle this, we // don't want a situation where a friendly dragon breathes through // you to hit other creatures... it should target the other // creatures, and coincidentally hit you. // // FIXME: this can cause problems with reflection, bounces, etc. // It would be better to have the monster fire logic never reach // this point for friendlies. if (!invalid_monster_index(beam.beam_source)) { monsters& m = menv[beam.beam_source]; if (m.alive() && m.friendly() && beam.target == you.pos()) return (false); } // 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_info.count == 0) return (false); if (is_sanctuary(you.pos()) || is_sanctuary(beam.source)) return (false); // If we hit no friends, fire away. if (beam.friend_info.count == 0) return (true); // Only fire if they do acceptably low collateral damage. return (beam.foe_info.power >= div_round_up(beam.foe_ratio * (beam.foe_info.power + beam.friend_info.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_SWIFTNESS: case SPELL_INVISIBILITY: case SPELL_MINOR_HEALING: case SPELL_MAJOR_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 targ_adj = false; bool targ_sanct = false; if (mon->foe == MHITYOU || mon->foe == MHITNOT) { if (adjacent(you.pos(), mon->pos())) targ_adj = true; if (is_sanctuary(you.pos())) targ_sanct = true; } else { if (adjacent(menv[mon->foe].pos(), mon->pos())) targ_adj = true; if (is_sanctuary(menv[mon->foe].pos())) targ_sanct = true; } switch (monspell) { case SPELL_TELEPORT_OTHER: return !targ_sanct; case SPELL_TELEPORT_SELF: // Don't cast again if already about to teleport. return !mon->has_ench(ENCH_TP); case SPELL_MINOR_HEALING: case SPELL_MAJOR_HEALING: return true; case SPELL_BLINK_AWAY: case SPELL_BLINK_RANGE: return true; case SPELL_BLINK_OTHER: return !targ_sanct && targ_adj; case SPELL_BLINK: return targ_adj; case SPELL_TOMB_OF_DOROKLOHE: return true; case SPELL_NO_SPELL: return false; default: return !targ_adj && spell_typematch(monspell, SPTYP_SUMMONING); } } // 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; const actor *foe = mon->get_foe(); // Keep friendly summoners from spamming summons constantly. if (mon->friendly() && (!foe || foe == &you) && spell_typematch(monspell, SPTYP_SUMMONING)) { return (true); } if (!mon->wont_attack()) { if (spell_harms_area(monspell) && env.sanctuary_time > 0) return (true); if (spell_harms_target(monspell) && is_sanctuary(mon->target)) 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_CALL_TIDE: return (!player_in_branch(BRANCH_SHOALS) || mon->has_ench(ENCH_TIDE) || !foe || (grd(mon->pos()) == DNGN_DEEP_WATER && grd(foe->pos()) == DNGN_DEEP_WATER)); case SPELL_BRAIN_FEED: ret = (foe != &you); break; case SPELL_BOLT_OF_DRAINING: case SPELL_AGONY: case SPELL_SYMBOL_OF_TORMENT: { if (!foe) { ret = true; break; } // 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(); ret = ((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); break; } case SPELL_MIASMA: ret = (!foe || foe->res_rotting()); break; case SPELL_DISPEL_UNDEAD: // [ds] How is dispel undead intended to interact with vampires? ret = (!foe || foe->holiness() != MH_UNDEAD); break; case SPELL_CORONA: 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_SWIFTNESS: if (mon->has_ench(ENCH_SWIFT)) ret = true; break; case SPELL_INVISIBILITY: if (mon->has_ench(ENCH_INVIS) || mon->friendly() && !you.can_see_invisible(false)) { ret = true; } break; case SPELL_MINOR_HEALING: case SPELL_MAJOR_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) { ret = you.duration[DUR_TELEPORT]; break; } else if (mon->foe != MHITNOT) { ret = (menv[mon->foe].has_ench(ENCH_TP)); break; } // intentional fall-through case SPELL_SLOW: case SPELL_CONFUSE: case SPELL_PAIN: case SPELL_BANISHMENT: case SPELL_DISINTEGRATE: case SPELL_PARALYSE: case SPELL_SLEEP: case SPELL_HIBERNATION: { if (monspell == SPELL_HIBERNATION && (!foe || foe->asleep())) { ret = true; break; } // Occasionally we don't estimate... just fire and see. if (one_chance_in(5)) { ret = false; break; } // Only intelligent monsters estimate. int intel = mons_intel(mon); if (intel < I_NORMAL) { ret = false; break; } // We'll estimate the target's resistance to magic, by first getting // the actual value and then randomising it. int est_magic_resist = (mon->foe == MHITNOT) ? 10000 : 0; if (mon->foe != MHITNOT) { if (mon->foe == MHITYOU) est_magic_resist = you.res_magic(); else est_magic_resist = menv[mon->foe].res_magic(); // now randomise (normal intels less accurate than high): if (intel == I_NORMAL) est_magic_resist += random2(80) - 40; else est_magic_resist += random2(30) - 15; } int 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 int diff = (monspell == SPELL_PAIN || monspell == SPELL_SLOW || monspell == SPELL_CONFUSE) ? 0 : 50; if (est_magic_resist - power > diff) ret = true; break; } case SPELL_MISLEAD: if (you.duration[DUR_MISLED] > 10 && one_chance_in(3)) ret = true; break; case SPELL_SUMMON_PLAYER_GHOST: // Only ever want one at a time. if (_find_players_ghost()) ret = true; break; case SPELL_FAKE_MARA_SUMMON: if (count_mara_fakes() == 2) ret = true; break; case SPELL_NO_SPELL: ret = true; break; default: break; } return (ret); } static bool _ms_los_spell(spell_type monspell) { // True, the tentacles _are_ summoned, but they are restricted to // water just like the kraken is, so it makes more sense not to // count them here. if (monspell == SPELL_KRAKEN_TENTACLES) return (false); if (monspell == SPELL_SMITING || monspell == SPELL_AIRSTRIKE || monspell == SPELL_HAUNT || monspell == SPELL_MISLEAD || spell_typematch(monspell, SPTYP_SUMMONING)) { return (true); } return (false); } static bool _ms_ranged_spell(spell_type monspell, bool attack_only = false, bool ench_too = true) { // Check for Smiting specially, so it's not filtered along // with the summon spells. if (attack_only && (monspell == SPELL_SMITING || monspell == SPELL_AIRSTRIKE || monspell == SPELL_MISLEAD)) { return (true); } // These spells are ranged, but aren't direct attack spells. if (_ms_los_spell(monspell)) return (!attack_only); switch (monspell) { case SPELL_NO_SPELL: case SPELL_CANTRIP: case SPELL_HASTE: case SPELL_MINOR_HEALING: case SPELL_MAJOR_HEALING: case SPELL_TELEPORT_SELF: case SPELL_INVISIBILITY: case SPELL_BLINK: case SPELL_BERSERKER_RAGE: case SPELL_SWIFTNESS: return (false); // The animation spells don't work through transparent walls and // thus are listed here instead of above. case SPELL_ANIMATE_DEAD: case SPELL_ANIMATE_SKELETON: return (!attack_only); case SPELL_CONFUSE: case SPELL_SLOW: case SPELL_PARALYSE: case SPELL_SLEEP: case SPELL_TELEPORT_OTHER: return (ench_too); default: // All conjurations count as ranged spells. return (true); } } // Returns true if the monster has an ability that only needs LOS to // affect the target. bool mons_has_los_ability(monster_type mon_type) { // These two have Torment, but are handled specially. if (mon_type == MONS_FIEND || mon_type == MONS_PIT_FIEND) return (true); // These eyes only need LOS, as well. (The other eyes use spells.) if (mon_type == MONS_GIANT_EYEBALL || mon_type == MONS_EYE_OF_DRAINING || mon_type == MONS_GOLDEN_EYE) { return (true); } // Although not using spells, these are exceedingly dangerous. if (mon_type == MONS_SILVER_STATUE || mon_type == MONS_ORANGE_STATUE) return (true); // Beholding just needs LOS. if (mons_genus(mon_type) == MONS_MERMAID) return (true); return (false); } bool mons_has_los_attack(const monsters *mon) { // Monsters may have spell like abilities. if (mons_has_los_ability(mon->type)) return (true); if (mon->can_use_spells()) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) if (_ms_los_spell(mon->spells[i])) return (true); } return (false); } bool mons_has_ranged_spell(const monsters *mon, bool attack_only, bool ench_too) { // Monsters may have spell-like abilities. if (mons_has_los_ability(mon->type)) return (true); if (mon->can_use_spells()) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) if (_ms_ranged_spell(mon->spells[i], attack_only, ench_too)) return (true); } return (false); } bool mons_has_ranged_ability(const monsters *mon) { if (mon->type == MONS_ELECTRIC_EEL || mon->type == MONS_LAVA_SNAKE) 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, bool visible) { gender_type gender = GENDER_NEUTER; if (mons_genus(mon_type) == MONS_MERMAID || mon_type == MONS_HARPY || mon_type == MONS_SPHINX) { gender = GENDER_FEMALE; } // Mara's fakes aren't a unique, but should still be classified // as male. else if (mon_type == MONS_MARA_FAKE) gender = GENDER_MALE; else if (mons_is_unique(mon_type) && mon_type != MONS_PLAYER_GHOST) { 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_ROXANNE: case MONS_SONJA: case MONS_ILSUIW: case MONS_NERGALLE: case MONS_KIRKE: case MONS_DUVESSA: gender = GENDER_FEMALE; break; case MONS_ROYAL_JELLY: case MONS_LERNAEAN_HYDRA: gender = GENDER_NEUTER; break; default: gender = GENDER_MALE; break; } } if (!visible) gender = GENDER_NEUTER; 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"); case PRONOUN_OBJECTIVE: // Awkward at start of sentence, always lower. return ((gender == 0) ? "it" : (gender == 1) ? "him" : "her"); } return (""); } // Checks if the monster can use smiting/torment to attack without // unimpeded LOS to the player. bool mons_has_smite_attack(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 || hspell_pass[i] == SPELL_FIRE_STORM || hspell_pass[i] == SPELL_AIRSTRIKE || hspell_pass[i] == SPELL_MISLEAD) { 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 are disqualified because they 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); } // Monsters too stupid to use stairs (e.g. non-spectral zombified undead) // are also disqualified. if (!mons_can_use_stairs(m)) return (false); // Smiters profit from staying back and smiting. if (mons_has_smite_attack(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. Worms and elementals are on the list because // all 'w' are currently unrelated. return (mchar != 'C' && mchar != 'B' && mchar != '(' && mchar != 'D' && mchar != 'G' && 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, bool fleeing) { const monsterentry *me1 = get_monster_data(m1->type), *me2 = get_monster_data(m2->type); if (!me1 || !me2) return (false); int mchar1 = me1->showchar; int mchar2 = me2->showchar; // If both are demons, the smaller number is the nastier demon. if (isdigit(mchar1) && isdigit(mchar2)) return (fleeing || 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 (fleeing || m1->hit_dice > m2->hit_dice); // If they're the same holiness, monsters smart enough to use stairs can // push past monsters too stupid to use stairs (so that e.g. non-zombified // or spectral zombified undead can push past non-spectral zombified // undead). if (m1->holiness() == m2->holiness() && mons_can_use_stairs(m1) && !mons_can_use_stairs(m2)) { return (true); } if (mons_genus(m1->type) == MONS_KILLER_BEE && mons_genus(m2->type) == MONS_KILLER_BEE) { if (fleeing) return (true); if (m1->type == MONS_QUEEN_BEE && m2->type != MONS_QUEEN_BEE) 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 && (fleeing || m1->hit_dice > m2->hit_dice)); } bool mons_class_can_pass(int mc, const dungeon_feature_type grid) { if (mons_class_wall_shielded(mc)) { // Permanent walls can't be passed through. return (!feat_is_solid(grid) || feat_is_rock(grid) && !feat_is_permarock(grid)); } return !feat_is_solid(grid); } bool mons_can_pass(const monsters *mon, dungeon_feature_type grid) { const int montype = mons_is_zombified(mon) ? mons_zombie_base(mon) : mon->type; return (mons_class_can_pass(montype, grid)); } 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); } } mon_inv_type item_to_mslot(const item_def &item) { switch (item.base_type) { case OBJ_WEAPONS: return MSLOT_WEAPON; case OBJ_MISSILES: return MSLOT_MISSILE; case OBJ_ARMOUR: return equip_slot_to_mslot(get_armour_slot(item)); case OBJ_WANDS: return MSLOT_WAND; case OBJ_SCROLLS: return MSLOT_SCROLL; case OBJ_POTIONS: return MSLOT_POTION; case OBJ_MISCELLANY: return MSLOT_MISCELLANY; case OBJ_GOLD: return MSLOT_GOLD; default: return NUM_MONSTER_SLOTS; } } monster_type royal_jelly_ejectable_monster() { return static_cast( random_choose(MONS_ACID_BLOB, MONS_AZURE_JELLY, MONS_DEATH_OOZE, -1)); } // Replaces @foe_god@ and @god_is@ with foe's god name. // // Atheists get "You"/"you", and worshippers of nameless gods get "Your // god"/"your god". static std::string _replace_god_name(god_type god, bool need_verb = false, bool capital = false) { std::string result = ((god == GOD_NO_GOD) ? (capital ? "You" : "you") : (god == GOD_NAMELESS) ? (capital ? "Your god" : "your god") : god_name(god, false)); if (need_verb) result += (god == GOD_NO_GOD) ? " are" : " is"; return (result); } static std::string _get_species_insult(const std::string &species, const std::string &type) { std::string insult; std::string lookup; // Get species genus. if (!species.empty()) { lookup = "insult "; lookup += species; lookup += " "; lookup += type; 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); 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; const actor* foe = (monster->wont_attack() && invalid_monster_index(monster->foe)) ? &you : monster->get_foe(); if (s_type < 0 || s_type >= NUM_LOUDNESS || s_type == NUM_SHOUTS) s_type = mons_shouts(monster->type); std::string foe_species; if (foe == NULL) ; else if (foe->atype() == ACT_PLAYER) { foe_species = species_name(you.species, 1, true); msg = replace_all(msg, " @to_foe@", ""); msg = replace_all(msg, " @at_foe@", ""); msg = replace_all(msg, "@player_only@", ""); msg = replace_all(msg, " @foe,@", ","); msg = replace_all(msg, "@player", "@foe"); msg = replace_all(msg, "@Player", "@Foe"); msg = replace_all(msg, "@foe_possessive@", "your"); msg = replace_all(msg, "@foe@", "you"); msg = replace_all(msg, "@Foe@", "You"); msg = replace_all(msg, "@foe_name@", you.your_name); msg = replace_all(msg, "@foe_species@", species_name(you.species, 1)); msg = replace_all(msg, "@foe_genus@", foe_species); msg = replace_all(msg, "@foe_genus_plural@", _pluralise_player_genus()); } else { std::string foe_name; const monsters* m_foe = dynamic_cast(foe); if (you.can_see(foe) || crawl_state.arena) { if (m_foe->attitude == ATT_FRIENDLY && !mons_is_unique(m_foe->type) && !crawl_state.arena) { foe_name = foe->name(DESC_NOCAP_YOUR); const std::string::size_type pos = foe_name.find("'"); if (pos != std::string::npos) foe_name = foe_name.substr(0, pos); } else foe_name = foe->name(DESC_NOCAP_THE); } else foe_name = "something"; std::string prep = "at"; if (s_type == S_SILENT || s_type == S_SHOUT || s_type == S_NORMAL) prep = "to"; msg = replace_all(msg, "@says@ @to_foe@", "@says@ " + prep + " @foe@"); msg = replace_all(msg, " @to_foe@", " to @foe@"); msg = replace_all(msg, " @at_foe@", " at @foe@"); msg = replace_all(msg, "@foe,@", "@foe@,"); msg = replace_all(msg, "@foe_possessive@", "@foe@'s"); msg = replace_all(msg, "@foe@", foe_name); msg = replace_all(msg, "@Foe@", upcase_first(foe_name)); if (m_foe->is_named()) msg = replace_all(msg, "@foe_name@", foe->name(DESC_PLAIN, true)); std::string species = mons_type_name(mons_species(m_foe->type), DESC_PLAIN); msg = replace_all(msg, "@foe_species@", species); std::string genus = mons_type_name(mons_genus(m_foe->type), DESC_PLAIN); msg = replace_all(msg, "@foe_genus@", genus); msg = replace_all(msg, "@foe_genus_plural@", pluralise(genus)); foe_species = genus; } description_level_type nocap = DESC_NOCAP_THE, cap = DESC_CAP_THE; if (monster->is_named() && you.can_see(monster)) { const std::string name = monster->name(DESC_CAP_THE); 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) && !crawl_state.arena && you.can_see(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 (you.see_cell(monster->pos())) { dungeon_feature_type feat = grd(monster->pos()); if (feat < DNGN_MINMOVE || feat >= NUM_FEATURES) msg = replace_all(msg, "@surface@", "buggy surface"); else if (feat == DNGN_LAVA) msg = replace_all(msg, "@surface@", "lava"); else if (feat_is_water(feat)) 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"); } if (you.can_see(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"); } // Player name. msg = replace_all(msg, "@player_name@", you.your_name); 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)); msg = replace_all(msg, "@objective@", monster->pronoun(PRONOUN_OBJECTIVE)); // Body parts. bool can_plural = false; std::string part_str = monster->hand_name(false, &can_plural); msg = replace_all(msg, "@hand@", part_str); msg = replace_all(msg, "@Hand@", upcase_first(part_str)); if (!can_plural) part_str = "NO PLURAL HANDS"; else part_str = monster->hand_name(true); msg = replace_all(msg, "@hands@", part_str); msg = replace_all(msg, "@Hands@", upcase_first(part_str)); can_plural = false; part_str = monster->arm_name(false, &can_plural); msg = replace_all(msg, "@arm@", part_str); msg = replace_all(msg, "@Arm@", upcase_first(part_str)); if (!can_plural) part_str = "NO PLURAL ARMS"; else part_str = monster->arm_name(true); msg = replace_all(msg, "@arms@", part_str); msg = replace_all(msg, "@Arms@", upcase_first(part_str)); can_plural = false; part_str = monster->foot_name(false, &can_plural); msg = replace_all(msg, "@foot@", part_str); msg = replace_all(msg, "@Foot@", upcase_first(part_str)); if (!can_plural) part_str = "NO PLURAL FOOT"; else part_str = monster->foot_name(true); msg = replace_all(msg, "@feet@", part_str); msg = replace_all(msg, "@Feet@", upcase_first(part_str)); if (foe != NULL) { const god_type god = foe->deity(); // Replace with "you are" for atheists. msg = replace_all(msg, "@god_is@", _replace_god_name(god, true, false)); msg = replace_all(msg, "@God_is@", _replace_god_name(god, true, true)); // No verb needed. msg = replace_all(msg, "@foe_god@", _replace_god_name(god, false, false)); msg = replace_all(msg, "@foe_god@", _replace_god_name(god, false, true)); } // The monster's god, not the player's. Atheists get // "NO GOD"/"NO GOD", and worshippers of nameless gods get // "a god"/"its god". if (monster->god == GOD_NO_GOD) { msg = replace_all(msg, "@God@", "NO GOD"); msg = replace_all(msg, "@possessive_God@", "NO GOD"); } else if (monster->god == GOD_NAMELESS) { msg = replace_all(msg, "@God@", "a god"); std::string possessive = monster->pronoun(PRONOUN_NOCAP_POSSESSIVE); possessive += " god"; msg = replace_all(msg, "@possessive_God@", possessive.c_str()); } else { msg = replace_all(msg, "@God@", god_name(monster->god)); msg = replace_all(msg, "@possessive_God@", god_name(monster->god)); } // Replace with species specific insults. if (msg.find("@species_insult_") != std::string::npos) { msg = replace_all(msg, "@species_insult_adj1@", _get_species_insult(foe_species, "adj1")); msg = replace_all(msg, "@species_insult_adj2@", _get_species_insult(foe_species, "adj2")); msg = replace_all(msg, "@species_insult_noun@", _get_species_insult(foe_species, "noun")); } static const char * sound_list[] = { "says", // actually S_SILENT "shouts", "barks", "shouts", "roars", "screams", "bellows", "screeches", "buzzes", "moans", "gurgles", "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) { mpr("Invalid @says@ type.", MSGCH_DIAGNOSTICS); msg = replace_all(msg, "@says@", "buggily says"); } else msg = replace_all(msg, "@says@", sound_list[s_type]); msg = apostrophise_fixup(msg); 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) { if (type == MONS_CHAOS_SPAWN) return (static_cast(random2(MON_SHAPE_MISC + 1))); const bool flies = (mons_class_flies(type) == FL_FLY); 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 (flies) return (MON_SHAPE_HUMANOID_WINGED_TAILED); else return (MON_SHAPE_HUMANOID_TAILED); } else if (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': // quasits 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': // mutated type, not enough info to determine shape return (MON_SHAPE_MISC); 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 (flies) return (MON_SHAPE_QUADRUPED_WINGED); else return (MON_SHAPE_QUADRUPED); case 'E': // efreeti 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 if (mons_genus(type) == MONS_GUARDIAN_SERPENT) return (MON_SHAPE_SNAKE); else 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_ELECTRIC_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 that a demon has wings if it can fly, and that it has // a tail if it has a sting or 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), acid(0), asphyx(false), sticky_flame(false), rotting(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), acid(0), asphyx(false), sticky_flame(false), rotting(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 = true; 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; case MR_RES_ROTTING: rotting = 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 = asphyx || o.asphyx; acid += o.acid; pierce += o.pierce; slice += o.slice; bludgeon += o.bludgeon; sticky_flame = sticky_flame || o.sticky_flame; rotting = rotting || o.rotting; return (*this); } mon_resist_def mon_resist_def::operator | (const mon_resist_def &o) const { mon_resist_def c(*this); return (c |= o); } bool player_or_mon_in_sanct(const monsters* monster) { return (is_sanctuary(you.pos()) || is_sanctuary(monster->pos())); }