From 020b3d8b5ccc12e63f38d0f81c828ff4569a7933 Mon Sep 17 00:00:00 2001 From: Matthew Cline Date: Sat, 31 Oct 2009 19:33:56 -0700 Subject: monster.cc: Split of monsters clasxs methods First step in splitting up mon-util.cc: move monsters class methods out into monster.cc --- crawl-ref/source/mon-util.cc | 5715 +----------------------------------------- 1 file changed, 54 insertions(+), 5661 deletions(-) (limited to 'crawl-ref/source/mon-util.cc') diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 14ec525994..381fce5e8e 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -9,66 +9,31 @@ #include "AppHdr.h" -#include "enum.h" #include "mon-util.h" -#include "monstuff.h" - -#include -#include -#include -#include -#include -#include -#include "externs.h" -#include "species.h" - -#include "artefact.h" #include "beam.h" -#include "cloud.h" #include "colour.h" #include "database.h" -#include "debug.h" -#include "delay.h" -#include "dgnevent.h" #include "directn.h" #include "fight.h" #include "ghost.h" #include "goditem.h" #include "itemname.h" -#include "itemprop.h" -#include "items.h" -#include "it_use2.h" #include "kills.h" #include "los.h" -#include "message.h" -#include "misc.h" #include "monplace.h" -#include "mstuff2.h" -#include "mtransit.h" -#include "player.h" +#include "monstuff.h" #include "random.h" #include "religion.h" -#include "shopping.h" // for item values -#include "spells3.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "terrain.h" -#include "traps.h" -#include "tutorial.h" #include "view.h" -#include "xom.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]; static bool initialised_randmons = false; @@ -78,14 +43,8 @@ static std::vector monsters_by_habitat[NUM_HABITATS]; #define MONDATASIZE ARRAYSZ(mondata) -static mon_spellbook mspell_list[] = { -#include "mon-spll.h" -}; - static int _mons_exp_mod(int mclass); -static int _mons_real_base_speed(int mc); - // Macro that saves some typing, nothing more. #define smc get_monster_data(mc) @@ -345,8 +304,8 @@ bool mons_class_flag(int mc, int bf) return ((me->bitfields & bf) != 0); } -static int _scan_mon_inv_randarts(const monsters *mon, - artefact_prop_type ra_prop) +int scan_mon_inv_randarts(const monsters *mon, + artefact_prop_type ra_prop) { int ret = 0; @@ -1167,7 +1126,7 @@ int mons_resist_magic( const monsters *mon ) u = mon->hit_dice * -u * 4 / 3; // Randarts have a multiplicative effect. - u *= (_scan_mon_inv_randarts( mon, ARTP_MAGIC ) + 100); + u *= (scan_mon_inv_randarts( mon, ARTP_MAGIC ) + 100); u /= 100; // ego armour resistance @@ -1294,7 +1253,7 @@ flight_type mons_flies(const monsters *mon, bool randarts) ret = mons_class_flies(mon->type); if (randarts && ret == FL_NONE - && _scan_mon_inv_randarts(mon, ARTP_LEVITATE) > 0) + && scan_mon_inv_randarts(mon, ARTP_LEVITATE) > 0) { ret = FL_LEVITATE; } @@ -1549,7 +1508,7 @@ void define_monster(monsters &mons) ac = m->AC; ev = m->ev; - speed = _mons_real_base_speed(mcls); + speed = mons_real_base_speed(mcls); mons.god = GOD_NO_GOD; @@ -1739,10 +1698,6 @@ static const unsigned char ugly_colour_values[] = { RED, BROWN, GREEN, CYAN, MAGENTA, LIGHTGREY }; -static const char *ugly_colour_names[] = { - "red", "brown", "green", "cyan", "purple", "white" -}; - unsigned char ugly_thing_random_colour() { return (RANDOM_ELEMENT(ugly_colour_values)); @@ -1762,21 +1717,11 @@ int ugly_thing_colour_offset(const monsters *mon) return (-1); } -static std::string _ugly_thing_colour_name(const monsters *mon) -{ - const int colour_offset = ugly_thing_colour_offset(mon); - - if (colour_offset == -1) - return ("buggy"); - - return (ugly_colour_names[colour_offset]); -} - static const char *drac_colour_names[] = { "black", "mottled", "yellow", "green", "purple", "red", "white", "pale" }; -static std::string _draconian_colour_name(monster_type mtype) +std::string draconian_colour_name(monster_type mtype) { COMPILE_CHECK(ARRAYSZ(drac_colour_names) == MONS_PALE_DRACONIAN - MONS_DRACONIAN, c1); @@ -1801,251 +1746,6 @@ monster_type draconian_colour_by_name(const std::string &name) return (MONS_PROGRAM_BUG); } -static std::string _str_monam(const monsters& mon, description_level_type desc, - bool force_seen) -{ - if (mon.type == MONS_NO_MONSTER) - return ("DEAD MONSTER"); - else if (invalid_monster_type(mon.type) && mon.type != MONS_PROGRAM_BUG) - return make_stringf("INVALID MONSTER (#%d)", mon.type); - - const bool arena_submerged = crawl_state.arena && !force_seen - && mon.submerged(); - - // Handle non-visible case first. - if (!force_seen && !you.can_see(&mon) && !arena_submerged) - { - 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; - } - - const 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"); - - // Some monsters might want the name of a different creature. - monster_type nametype = mon.type; - - // Tack on other prefixes. - switch (mon.type) - { - case MONS_ZOMBIE_SMALL: case MONS_ZOMBIE_LARGE: - case MONS_SKELETON_SMALL: case MONS_SKELETON_LARGE: - case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE: - case MONS_SPECTRAL_THING: - nametype = mon.base_monster; - break; - - default: - break; - } - - // If the monster has an explicit name, return that, handling it like - // a unique's name. Special handling for named hydras. - if (desc != DESC_BASENAME && !mon.mname.empty() - && mons_genus(nametype) != MONS_HYDRA) - { - 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(nametype) - && (mon.mname.empty() || mons_genus(nametype) == MONS_HYDRA)) - { - const bool use_your = mons_friendly(&mon); - switch (desc) - { - case DESC_CAP_THE: - result = (use_your ? "Your " : "The "); - break; - case DESC_NOCAP_THE: - result = (use_your ? "your " : "the "); - break; - case DESC_CAP_A: - if (mon.mname.empty()) - result = "A "; - else - result = "The "; - break; - case DESC_NOCAP_A: - if (mon.mname.empty()) - result = "a "; - else - result = "the "; - break; - case DESC_PLAIN: - default: - break; - } - } - - if (arena_submerged) - result += "submerged "; - - // Tack on other prefixes. - switch (mon.type) - { - case MONS_UGLY_THING: - case MONS_VERY_UGLY_THING: - result += _ugly_thing_colour_name(&mon) + " "; - break; - - case MONS_SPECTRAL_THING: - result += "spectral "; - 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_NO_MONSTER) // database search - result += _draconian_colour_name(mon.base_monster) + " "; - break; - - default: - break; - } - - if (mon.mons_species() == MONS_SLIME_CREATURE && desc != DESC_DBNAME) - { - ASSERT(mon.number <= 5); - const char* cardinals[] = {"", "large ", "very large ", - "enormous ", "titanic "}; - result += cardinals[mon.number - 1]; - } - - // Done here to cover cases of undead versions of hydras. - if (mons_species(nametype) == MONS_HYDRA - && mon.number > 0 && desc != DESC_DBNAME) - { - if (nametype == MONS_LERNAEAN_HYDRA) - result += "the "; - - if (mon.number < 11) - { - const char* cardinals[] = {"one", "two", "three", "four", "five", - "six", "seven", "eight", "nine", "ten"}; - result += cardinals[mon.number - 1]; - } - else - result += make_stringf("%d", mon.number); - - result += "-headed "; - } - - if (!mon.mname.empty()) - result += mon.mname; - else if (nametype == MONS_LERNAEAN_HYDRA) - result += "Lernaean hydra"; - else - { - // Add the base name. - if (invalid_monster_type(nametype) && nametype != MONS_PROGRAM_BUG) - result += make_stringf("INVALID MONSTER (#%d)", nametype); - else - 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]) - // XXX: Hack - && !starts_with(&result[2], "one-")) - { - result.insert(1, "n"); - } - - if (mons_is_unique(mon.type) && starts_with(result, "the ")) - { - switch (desc) - { - case DESC_CAP_THE: - case DESC_CAP_A: - result = upcase_first(result); - break; - - default: - break; - } - } - - if ((mon.flags & MF_KNOWN_MIMIC) && mons_is_shapeshifter(&mon)) - { - // If momentarily in original form, don't display "shaped - // shifter". - if (mons_genus(mon.type) != MONS_SHAPESHIFTER) - result += " shaped shifter"; - } - - // All done. - return (result); -} - std::string mons_type_name(int type, description_level_type desc) { std::string result; @@ -2168,6 +1868,27 @@ int mons_class_base_speed(int mc) 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)); @@ -2364,13 +2085,6 @@ bool mons_is_shapeshifter(const monsters *m) return (m->has_ench(ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER)); } -// 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 (mons_is_ghost_demon(m->type)) @@ -2379,16 +2093,6 @@ int mons_base_damage_brand(const monsters *m) return (SPWPN_NORMAL); } -mon_attitude_type monsters::temp_attitude() const -{ - if (has_ench(ENCH_CHARM)) - return ATT_FRIENDLY; - else if (has_ench(ENCH_NEUTRAL)) - return ATT_NEUTRAL; - else - return attitude; -} - bool mons_friendly(const monsters *m) { return (m->attitude == ATT_FRIENDLY || m->has_ench(ENCH_CHARM)); @@ -3309,198 +3013,6 @@ bool monster_senior(const monsters *m1, const monsters *m2, bool fleeing) } -/////////////////////////////////////////////////////////////////////////////// -// monsters methods - -monsters::monsters() - : type(MONS_NO_MONSTER), hit_points(0), max_hit_points(0), hit_dice(0), - ac(0), ev(0), speed(0), speed_increment(0), - target(), patrol_point(), 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 = MONS_NO_MONSTER; - base_monster = MONS_NO_MONSTER; - 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(pos())) - mgrd(pos()) = NON_MONSTER; - - position.reset(); - patrol_point.reset(); - travel_target = MTRAV_NONE; - travel_path.clear(); - ghost.reset(NULL); - seen_context = ""; -} - -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; - position = mon.position; - target = mon.target; - 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); -} - -bool monsters::swimming() const -{ - const dungeon_feature_type grid = grd(pos()); - return (feat_is_watery(grid) && mons_primary_habitat(this) == HT_WATER); -} - -static bool _player_near_water() -{ - for (adjacent_iterator ai(you.pos()); ai; ++ai) - if (feat_is_water(grd(*ai))) - return (true); - return (false); -} - -bool monsters::wants_submerge() const -{ - // Krakens never retreat when food (the player) is in range. - if (type == MONS_KRAKEN) - if (_player_near_water()) - return (false); - - // If we're in distress, we usually want to submerge. - if (env.cgrid(pos()) != EMPTY_CLOUD - || (hit_points < max_hit_points / 2 - && random2(max_hit_points + 1) >= hit_points)) - { - return (true); - } - - // Trapdoor spiders only hide themselves under the floor when they - // can't see their prey. - if (type == MONS_TRAPDOOR_SPIDER) - { - const actor* _foe = get_foe(); - return (_foe == NULL || !can_see(_foe)); - } - - const bool has_ranged_attack = (type == MONS_ELECTRIC_EEL - || type == MONS_LAVA_SNAKE - || mons_genus(type) == MONS_MERMAID - && you.species != SP_MERFOLK); - - int roll = 8; - // Shallow water takes a little more effort to submerge in, so we're - // less likely to bother. - if (grd(pos()) == DNGN_SHALLOW_WATER) - roll = roll * 7 / 5; - - const actor *tfoe = get_foe(); - if (tfoe && grid_distance(tfoe->pos(), pos()) > 1 && !has_ranged_attack) - roll /= 2; - - // Don't submerge if we just unsubmerged to shout - return (one_chance_in(roll) && seen_context != "bursts forth shouting"); -} - -bool monsters::submerged() const -{ - // FIXME, switch to 4.1's MF_SUBMERGED system which is much cleaner. - // Can't find any reference to MF_SUBMERGED anywhere. Don't know what - // this means. - abrahamwl - if (has_ench(ENCH_SUBMERGED)) - return (true); - - if (grd(pos()) == DNGN_DEEP_WATER - && !monster_habitable_grid(this, DNGN_DEEP_WATER)) - { - return (true); - } - - return (false); -} - -bool monsters::extra_balanced() const -{ - return (mons_genus(type) == MONS_NAGA); -} - -bool monsters::floundering() const -{ - const dungeon_feature_type grid = grd(pos()); - return (feat_is_water(grid) - && !cannot_fight() - // Can't use monster_habitable_grid() because that'll return - // true for non-water monsters in shallow water. - && mons_primary_habitat(this) != HT_WATER - && !mons_amphibious(this) - && !mons_flies(this) - && !extra_balanced()); -} - bool mons_class_can_pass(int mc, const dungeon_feature_type grid) { if (mons_class_wall_shielded(mc)) @@ -3521,4741 +3033,40 @@ bool mons_can_pass(const monsters *mon, dungeon_feature_type grid) return (mons_class_can_pass(montype, grid)); } -bool monsters::can_pass_through_feat(dungeon_feature_type grid) const -{ - return mons_can_pass(this, grid); -} - -bool monsters::is_habitable_feat(dungeon_feature_type actual_grid) const -{ - return monster_habitable_grid(this, actual_grid); -} - -bool monsters::can_drown() const +mon_inv_type equip_slot_to_mslot(equipment_type eq) { - // Presumably a shark in lava or a lavafish in deep water could - // drown, but that should never happen, so this simple check should - // be enough. - switch (mons_primary_habitat(this)) + switch (eq) { - case HT_WATER: - case HT_LAVA: - return (false); - default: - break; + case EQ_WEAPON: return MSLOT_WEAPON; + case EQ_BODY_ARMOUR: return MSLOT_ARMOUR; + case EQ_SHIELD: return MSLOT_SHIELD; + default: return (NUM_MONSTER_SLOTS); } - - // Mummies can fall apart in water or be incinerated in lava. - // Ghouls, vampires, and demons can drown in water or lava. Others - // just "sink like a rock", to never be seen again. - return (!res_asphyx() - || mons_genus(type) == MONS_MUMMY - || mons_genus(type) == MONS_GHOUL - || mons_genus(type) == MONS_VAMPIRE - || holiness() == MH_DEMONIC); -} - -size_type monsters::body_size(size_part_type /* psize */, bool /* base */) const -{ - const monsterentry *e = get_monster_data(type); - return (e ? e->size : SIZE_MEDIUM); } -int monsters::body_weight() const +mon_inv_type item_to_mslot(const item_def &item) { - int mclass = type; - - switch (mclass) + switch (item.base_type) { - 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_brand(int which_attack) -{ - const item_def *mweap = weapon(which_attack); - - if (!mweap) - { - if (mons_is_ghost_demon(type)) - return (ghost->brand); - - return (SPWPN_NORMAL); - } - - return (!is_range_weapon(*mweap) ? get_weapon_brand(*mweap) : SPWPN_NORMAL); -} - -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 : - (atk.type == AT_TENTACLE_SLAP) ? DVORP_TENTACLE - : DVORP_CRUSHING); - } - - return (get_vorpal_type(*mweap)); -} - -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_wield(const item_def& item, bool ignore_curse, - bool ignore_brand, bool ignore_shield, - bool ignore_transform) const -{ - // Monsters can only wield weapons or go unarmed (OBJ_UNASSIGNED - // means unarmed). - if (item.base_type != OBJ_WEAPONS && item.base_type != OBJ_UNASSIGNED) - return (false); - - // These *are* weapons, so they can't wield another weapon or - // unwield themselves. - if (type == MONS_DANCING_WEAPON) - return (false); - - // MF_HARD_RESET means that all items the monster is carrying will - // disappear when it does, so it can't accept new items or give up - // the ones it has. - if (flags & MF_HARD_RESET) - return (false); - - // Summoned items can only be held by summoned monsters. - if ((item.flags & ISFLAG_SUMMONED) && !is_summoned()) - return (false); - - item_def* weap1 = NULL; - if (inv[MSLOT_WEAPON] != NON_ITEM) - weap1 = &mitm[inv[MSLOT_WEAPON]]; - - int avail_slots = 1; - item_def* weap2 = NULL; - if (mons_wields_two_weapons(this)) - { - if (!weap1 || hands_reqd(*weap1, body_size()) != HANDS_TWO) - avail_slots = 2; - - const int offhand = mons_offhand_weapon_index(this); - if (offhand != NON_ITEM) - weap2 = &mitm[offhand]; - } - - // If we're already wielding it, then of course we can wield it. - if (&item == weap1 || &item == weap2) - return (true); - - // Barehanded needs two hands. - const bool two_handed = item.base_type == OBJ_UNASSIGNED - || hands_reqd(item, body_size()) == HANDS_TWO; - - item_def* _shield = NULL; - if (inv[MSLOT_SHIELD] != NON_ITEM) - { - ASSERT(!(weap1 && weap2)); - - if (two_handed && !ignore_shield) - return (false); - - _shield = &mitm[inv[MSLOT_SHIELD]]; - } - - if (!ignore_curse) - { - int num_cursed = 0; - if (weap1 && item_cursed(*weap1)) - num_cursed++; - if (weap2 && item_cursed(*weap2)) - num_cursed++; - if (_shield && item_cursed(*_shield)) - num_cursed++; - - if (two_handed && num_cursed > 0 || num_cursed >= avail_slots) - return (false); - } - - return could_wield(item, ignore_brand, ignore_transform); -} - -bool monsters::could_wield(const item_def &item, bool ignore_brand, - bool /* ignore_transform */) const -{ - ASSERT(is_valid_item(item)); - - // These *are* weapons, so they can't wield another weapon. - if (type == MONS_DANCING_WEAPON) - return (false); - - // Monsters can't use unrandarts with special effects. - if (is_special_unrandom_artefact(item) && !crawl_state.arena) - return (false); - - // Wimpy monsters (e.g. kobold, goblin) can't use halberds, etc. - if (!check_weapon_wieldable_size(item, body_size())) - return (false); - - if (!ignore_brand) - { - const int brand = get_weapon_brand(item); - - // Draconians won't use dragon slaying weapons. - if (brand == SPWPN_DRAGON_SLAYING && is_dragonkind(this)) - return (false); - - // Orcs won't use orc slaying weapons. - if (brand == SPWPN_ORC_SLAYING && is_orckind(this)) - return (false); - - // Demonic/undead monsters won't use holy weapons. - if (is_unholy() && is_holy_item(item)) - return (false); - - // Holy monsters and monsters that are gifts of good gods won't - // use evil weapons. - if ((mons_is_holy(this) || is_good_god(god)) - && is_evil_item(item)) - { - return (false); - } - - // Holy monsters that aren't gifts of chaotic gods and monsters - // that are gifts of good gods won't use chaotic weapons. - if (((mons_is_holy(this) && !is_chaotic_god(this->god)) - || is_good_god(god)) - && is_chaotic_item(item)) - { - return (false); - } - } - - return (true); -} - -bool monsters::can_throw_large_rocks() const -{ - return (type == MONS_STONE_GIANT - || ::mons_species(this->type) == MONS_CYCLOPS - || ::mons_species(this->type) == MONS_OGRE); -} - -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); -} - -static bool _needs_ranged_attack(const monsters *mon) -{ - // Prevent monsters that have conjurations from grabbing missiles. - if (mon->has_spell_of_type(SPTYP_CONJURATION)) - return (false); - - // Same for summonings, but make an exception for friendlies. - if (!mons_friendly(mon) && mon->has_spell_of_type(SPTYP_SUMMONING)) - return (false); - - // Blademasters don't want to throw stuff. - if (mon->type == MONS_DEEP_ELF_BLADEMASTER) - return (false); - - return (true); -} - -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. - - if (!_needs_ranged_attack(this)) - return (false); - - if (item.base_type == OBJ_WEAPONS - || item.base_type == OBJ_MISSILES && !has_launcher(item)) - { - return (is_throwable(this, item)); - } - - // Darts and stones are allowed even without launcher. - if (item.sub_type == MI_DART || item.sub_type == MI_STONE) - 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; - case SPWPN_CHAOS: - mpr("It is briefly surrounded by a scintillating aura of " - "random colours."); - break; - case SPWPN_PENETRATION: - mprf("%s %s briefly pass through it before %s manages to get a " - "firm grip on it.", - pronoun(PRONOUN_CAP_POSSESSIVE).c_str(), - hand_name(true).c_str(), - pronoun(PRONOUN_NOCAP).c_str()); - break; - case SPWPN_REAPING: - mpr("It is briefly surrounded by shifting shadows."); - 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) - { - if (is_artefact(item) && !is_special_unrandom_artefact(item)) - artefact_wpn_learn_prop(item, ARTP_BRAND); - else - 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) - { - if (is_artefact(item) && !is_special_unrandom_artefact(item)) - artefact_wpn_learn_prop(item, ARTP_BRAND); - else - 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 && you.can_see(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) -{ - ASSERT(is_valid_item(item)); - - const monsters *other_mon = item.holding_monster(); - - if (other_mon != NULL) - { - if (other_mon == this) - { - if (inv[slot] == item.index()) - { - mprf(MSGCH_DIAGNOSTICS, "Monster %s already holding item %s.", - name(DESC_PLAIN, true).c_str(), - item.name(DESC_PLAIN, false, true).c_str()); - return (false); - } - else - { - mprf(MSGCH_DIAGNOSTICS, "Item %s thinks it's already held by " - "monster %s.", - item.name(DESC_PLAIN, false, true).c_str(), - name(DESC_PLAIN, true).c_str()); - } - } - else if (other_mon->type == MONS_NO_MONSTER) - { - mprf(MSGCH_DIAGNOSTICS, "Item %s, held by dead monster, being " - "picked up by monster %s.", - item.name(DESC_PLAIN, false, true).c_str(), - name(DESC_PLAIN, true).c_str()); - } - else - { - mprf(MSGCH_DIAGNOSTICS, "Item %s, held by monster %s, being " - "picked up by monster %s.", - item.name(DESC_PLAIN, false, true).c_str(), - other_mon->name(DESC_PLAIN, true).c_str(), - name(DESC_PLAIN, true).c_str()); - } - } - - // 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 || slot == MSLOT_ALT_WEAPON) - && inv[MSLOT_SHIELD] != NON_ITEM - && hands_reqd(item, body_size()) == HANDS_TWO) - { - if (!drop_item(MSLOT_SHIELD, near)) - return (false); - } - - // Similarly, monsters won't pick up shields if they're - // wielding (or alt-wielding) a two-handed weapon. - if (slot == MSLOT_SHIELD) - { - const item_def* wpn = mslot_item(MSLOT_WEAPON); - const item_def* alt = mslot_item(MSLOT_ALT_WEAPON); - if (wpn && hands_reqd(*wpn, body_size()) == HANDS_TWO) - return (false); - if (alt && hands_reqd(*alt, body_size()) == HANDS_TWO) - return (false); - } - - if (inv[slot] != NON_ITEM) - { - item_def &dest(mitm[inv[slot]]); - if (items_stack(item, dest, 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 ); - merge_item_stacks(item, dest); - 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 item_index = item.index(); - unlink_item(item_index); - - inv[slot] = item_index; - - item.set_holding_monster(mindex()); - - 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 item_index = inv[eslot]; - if (item_index == NON_ITEM) - return (true); - - item_def* pitem = &mitm[item_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(*pitem, eslot, near)) - return (false); - was_unequipped = true; - } - - bool on_floor = true; - - if (pitem->flags & ISFLAG_SUMMONED) - { - on_floor = false; - - if (need_message(near)) - mprf("%s %s as %s drops %s!", - pitem->name(DESC_CAP_THE).c_str(), - summoned_poof_msg(this, *pitem).c_str(), - name(DESC_NOCAP_THE).c_str(), - pitem->quantity > 1 ? "them" : "it"); - - item_was_destroyed(*pitem, mindex()); - destroy_item(item_index); - } - else if (!move_item_to_grid(&item_index, pos())) - { - // Re-equip item if we somehow failed to drop it. - if (was_unequipped) - equip(*pitem, eslot, near); - - return (false); - } - - // move_item_to_grid could change item_index, so - // update pitem. - pitem = &mitm[item_index]; - - if (on_floor) - { - if (mons_friendly(this)) - pitem->flags |= ISFLAG_DROPPED_BY_ALLY; - - if (need_message(near)) - { - mprf("%s drops %s.", name(DESC_CAP_THE).c_str(), - pitem->name(DESC_NOCAP_A).c_str()); - } - - dungeon_feature_type feat = grd(pos()); - if (feat_destroys_items(feat)) - { - if ( player_can_hear(pos()) ) - mprf(MSGCH_SOUND, feat_item_destruction_message(feat)); - - item_was_destroyed(*pitem, mindex()); - unlink_item(item_index); - } - } - - inv[eslot] = NON_ITEM; - return (true); -} - -// We don't want monsters to pick up ammunition that cancels out with -// the launcher brand or that is identical to the launcher brand, -// the latter in hope of another monster wandering by who may want to -// use the ammo in question. -static bool _compatible_launcher_ammo_brands(item_def *launcher, - const item_def *ammo) -{ - // If the monster has no ammo then there's no compatibility problems - // to check. - if (ammo == NULL) - return (true); - - const int bow_brand = get_weapon_brand(*launcher); - const int ammo_brand = get_ammo_brand(*ammo); - - switch (ammo_brand) - { - case SPMSL_FLAME: - case SPMSL_FROST: - return (bow_brand != SPWPN_FLAME && bow_brand != SPWPN_FROST); - case SPMSL_CHAOS: - return (bow_brand != SPWPN_CHAOS); - default: - return (true); - } -} - -bool monsters::pickup_launcher(item_def &launch, int near) -{ - // Don't allow monsters to pick up launchers that would also - // refuse to pick up the matching ammo. - if (!_needs_ranged_attack(this)) - return (false); - - // Don't allow monsters to switch to another type of launcher - // as that would require them to also drop their ammunition - // and then try to find ammunition for their new launcher. - // However, they may switch to another launcher if they're - // out of ammo. (jpeg) - 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 || !missiles()) - && (mons_weapon_damage_rating(*elaunch) < mdam_rating - || mons_weapon_damage_rating(*elaunch) == mdam_rating - && get_weapon_brand(*elaunch) == SPWPN_NORMAL - && get_weapon_brand(launch) != SPWPN_NORMAL - && _compatible_launcher_ammo_brands(&launch, - missiles())) - && 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_unrandom_artefact(weapon)) - { - switch (weapon.special) - { - case UNRAND_ASMODEUS: - return (monster->type == MONS_ASMODEUS); - case UNRAND_DISPATER: - return (monster->type == MONS_DISPATER); - case UNRAND_CEREBOV: - return (monster->type == MONS_CEREBOV); - } - } - return (false); -} - -static int _ego_damage_bonus(item_def &item) -{ - switch (get_weapon_brand(item)) - { - case SPWPN_NORMAL: return 0; - case SPWPN_PROTECTION: return 1; - default: return 2; - case SPWPN_VORPAL: return 3; - } -} - -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::pickup_melee_weapon(item_def &item, int near) -{ - // Throwable weapons may be picked up as though dual-wielding. - const bool dual_wielding = (mons_wields_two_weapons(this) - || is_throwable(this, item)); - 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 new_wpn_dam = mons_weapon_damage_rating(item) - + _ego_damage_bonus(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. - const int old_wpn_dam = mons_weapon_damage_rating(*weap) - + _ego_damage_bonus(*weap); - - bool new_wpn_better = (new_wpn_dam > old_wpn_dam); - if (new_wpn_dam == old_wpn_dam) - { - // 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). - // For simplicity, don't apply this check to secondary weapons - // for dual wielding monsters. - int oldval = item_value(*weap, true); - int newval = item_value(item, true); - - // Vastly prefer matching racial type. - if (_item_race_matches_monster(*weap, this)) - oldval *= 2; - if (_item_race_matches_monster(item, this)) - newval *= 2; - - if (newval > oldval) - new_wpn_better = true; - } - - if (new_wpn_better && !weap->cursed()) - { - if (!dual_wielding - || i == MSLOT_WEAPON - || old_wpn_dam - < mons_weapon_damage_rating(*mslot_item(MSLOT_WEAPON)) - + _ego_damage_bonus(*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) -{ - const mon_inv_type slot = item_to_mslot(item); - - // If it's a melee weapon then pickup_melee_weapon() already rejected - // it, even though it can also be thrown. - if (slot == MSLOT_WEAPON) - return (false); - - ASSERT(slot == MSLOT_MISSILE); - - // If occupied, don't pick up a throwable weapons if it would just - // stack with an existing one. (Upgrading is possible.) - if (mslot_item(slot) - && (mons_is_wandering(this) || mons_friendly(this) && foe == MHITYOU) - && pickup(item, slot, 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(this, launch, - &mitm[exist_missile]), - mitm[exist_missile].quantity) - < _q_adj_damage(mons_thrown_weapon_damage(&item), item.quantity))) - { - if (inv[slot] != NON_ITEM && !drop_item(slot, near)) - return (false); - return pickup(item, slot, near); - } - return (false); -} - -bool monsters::wants_weapon(const item_def &weap) const -{ - if (!could_wield(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); - } - - // 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()) == 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); - } - - return (true); -} - -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()) - == HANDS_TWO)) - { - return (false); - } - - // Returns whether this armour is the monster's size. - return (check_armour_size(item, body_size())); -} - -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; - } -} - -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; - // And another hack or two... - case ARM_WIZARD_HAT: - if (this->type == MONS_GASTRONOK) - eq = EQ_BODY_ARMOUR; - break; - case ARM_CLOAK: - if (this->type == MONS_MAURICE) - 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, true); - int newval = item_value(item, true); - - // 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. - // Neither summons nor hostile enchantments are counted for - // this purpose. - if (mons_has_ranged_spell(this, true, false)) - 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 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 - &&_compatible_launcher_ammo_brands(launch, miss))) - { - 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); - - // Holy monsters and worshippers of good gods won't pick up evil - // wands. - if ((mons_is_holy(this) || is_good_god(god)) && is_evil_item(item)) - return (false); - - // If a monster already has 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); - } - - // Holy monsters and worshippers of good gods won't pick up evil - // scrolls. - if ((mons_is_holy(this) || is_good_god(god)) && is_evil_item(item)) - 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. - const potion_type ptype = static_cast(item.sub_type); - - if (!this->can_drink_potion(ptype)) - 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::pickup_misc(item_def &item, int near) -{ - // Never pick up runes. - if (item.sub_type == MISC_RUNE_OF_ZOT) - return (false); - - // Holy monsters and worshippers of good gods won't pick up evil - // miscellaneous items. - if ((mons_is_holy(this) || is_good_god(god)) && is_evil_item(item)) - return (false); - - return (pickup(item, MSLOT_MISCELLANY, near)); -} - -// Eaten items 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) - || you.friendly_pickup == FRIENDLY_PICKUP_PLAYER - && !(item.flags & (ISFLAG_DROPPED | ISFLAG_THROWN - | 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_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); - case OBJ_BOOKS: - if (force) - return pickup_misc(item, near); - // else fall through - default: - return (false); - } -} - -bool monsters::need_message(int &near) const -{ - return (near != -1 ? near - : (near = observable())); -} - -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 mi = (mslot == NUM_MONSTER_SLOTS) ? NON_ITEM : inv[mslot]; - return (mi == NON_ITEM ? NULL : &mitm[mi]); -} - -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, bool force_vis) const -{ - if (desc == DESC_NONE) - return (""); - - const bool possessive = - (desc == DESC_NOCAP_YOUR || desc == DESC_NOCAP_ITS); - - if (possessive) - desc = DESC_NOCAP_THE; - - std::string monnam; - if ((flags & MF_NAME_MASK) && (force_vis || you.can_see(this)) - || crawl_state.arena && mons_class_is_zombified(type)) - { - monnam = full_name(desc); - } - else - 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 (desc == DESC_NONE) - return (""); - - 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::full_name(description_level_type desc, - bool use_comma) const -{ - if (desc == DESC_NONE) - return (""); - - std::string title = _str_monam(*this, desc, true); - - const unsigned long flag = flags & MF_NAME_MASK; - - const int _type = mons_is_zombified(this) ? base_monster : type; - if (mons_genus(_type) == MONS_HYDRA && flag == 0) - return (title); - - if (has_base_name()) - { - if (flag == MF_NAME_SUFFIX) - { - title = base_name(desc, true); - title += " "; - title += mname; - } - else if (flag == MF_NAME_NO_THE) - { - title += " "; - title += base_name(DESC_PLAIN, true); - } - else if (flag == MF_NAME_REPLACE) - ; - else - { - if (use_comma) - title += ","; - title += " "; - title += base_name(DESC_NOCAP_THE, true); - } - } - return (title); -} - -std::string monsters::pronoun(pronoun_type pro, bool force_visible) const -{ - return (mons_pronoun(static_cast(type), pro, - force_visible || you.can_see(this))); -} - -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)); -} - -std::string monsters::hand_name(bool plural, bool *can_plural) const -{ - bool _can_plural; - if (can_plural == NULL) - can_plural = &_can_plural; - *can_plural = true; - - std::string str; - char ch = mons_char(type); - - const bool rand = (type == MONS_CHAOS_SPAWN); - - switch (get_mon_shape(this)) - { - case MON_SHAPE_CENTAUR: - case MON_SHAPE_NAGA: - // Defaults to "hand" - break; - case MON_SHAPE_HUMANOID: - case MON_SHAPE_HUMANOID_WINGED: - case MON_SHAPE_HUMANOID_TAILED: - case MON_SHAPE_HUMANOID_WINGED_TAILED: - if (ch == 'T' || ch == 'd' || ch == 'n' || mons_is_demon(type)) - str = "claw"; - break; - - case MON_SHAPE_QUADRUPED: - case MON_SHAPE_QUADRUPED_TAILLESS: - case MON_SHAPE_QUADRUPED_WINGED: - case MON_SHAPE_ARACHNID: - if (type == MONS_SCORPION || rand && one_chance_in(4)) - str = "pincer"; - else - { - str = "front "; - return (str + foot_name(plural, can_plural)); - } - break; - - case MON_SHAPE_BLOB: - case MON_SHAPE_SNAKE: - case MON_SHAPE_FISH: - return foot_name(plural, can_plural); - - case MON_SHAPE_BAT: - str = "wing"; - break; - - case MON_SHAPE_INSECT: - case MON_SHAPE_INSECT_WINGED: - case MON_SHAPE_CENTIPEDE: - str = "antenna"; - break; - - case MON_SHAPE_SNAIL: - str = "eye-stalk"; - break; - - case MON_SHAPE_PLANT: - str = "leaf"; - break; - - case MON_SHAPE_MISC: - if (ch == 'x' || ch == 'X' || rand) - { - str = "tentacle"; - break; - } - // Deliberate fallthrough. - case MON_SHAPE_FUNGUS: - str = "body"; - *can_plural = false; - break; - - case MON_SHAPE_ORB: - switch (type) - { - case MONS_GIANT_SPORE: - str = "rhizome"; - break; - - case MONS_GIANT_EYEBALL: - case MONS_EYE_OF_DRAINING: - case MONS_SHINING_EYE: - case MONS_EYE_OF_DEVASTATION: - *can_plural = false; - // Deliberate fallthrough. - case MONS_GREAT_ORB_OF_EYES: - str = "pupil"; - break; - - case MONS_GIANT_ORANGE_BRAIN: - default: - if (rand) - str = "rhizome"; - else - { - str = "body"; - *can_plural = false; - } - break; - } - } - - if (str.empty()) - { - // Reduce the chance of a random-shaped monster having hands. - if (rand && coinflip()) - return (hand_name(plural, can_plural)); - - str = "hand"; - } - - if (plural && *can_plural) - str = pluralise(str); - - return (str); -} - -std::string monsters::foot_name(bool plural, bool *can_plural) const -{ - bool _can_plural; - if (can_plural == NULL) - can_plural = &_can_plural; - *can_plural = true; - - std::string str; - char ch = mons_char(type); - - const bool rand = (type == MONS_CHAOS_SPAWN); - - switch (get_mon_shape(this)) - { - case MON_SHAPE_INSECT: - case MON_SHAPE_INSECT_WINGED: - case MON_SHAPE_ARACHNID: - case MON_SHAPE_CENTIPEDE: - str = "leg"; - break; - - case MON_SHAPE_HUMANOID: - case MON_SHAPE_HUMANOID_WINGED: - case MON_SHAPE_HUMANOID_TAILED: - case MON_SHAPE_HUMANOID_WINGED_TAILED: - if (type == MONS_MINOTAUR) - str = "hoof"; - else if (swimming() - && (type == MONS_MERFOLK || mons_genus(type) == MONS_MERMAID)) - { - str = "tail"; - *can_plural = false; - } - break; - - case MON_SHAPE_CENTAUR: - str = "hoof"; - break; - - case MON_SHAPE_QUADRUPED: - case MON_SHAPE_QUADRUPED_TAILLESS: - case MON_SHAPE_QUADRUPED_WINGED: - if (rand) - { - const char* feet[] = {"paw", "talon", "hoof"}; - str = RANDOM_ELEMENT(feet); - } - else if (ch == 'h') - str = "paw"; - else if (ch == 'l' || ch == 'D') - str = "talon"; - else if (type == MONS_YAK || type == MONS_DEATH_YAK) - str = "hoof"; - else if (ch == 'H') - { - if (type == MONS_MANTICORE || type == MONS_SPHINX) - str = "paw"; - else - str = "talon"; - } - break; - - case MON_SHAPE_BAT: - str = "claw"; - break; - - case MON_SHAPE_SNAKE: - case MON_SHAPE_FISH: - str = "tail"; - *can_plural = false; - break; - - case MON_SHAPE_PLANT: - str = "root"; - break; - - case MON_SHAPE_FUNGUS: - str = "stem"; - *can_plural = false; - break; - - case MON_SHAPE_BLOB: - str = "pseudopod"; - break; - - case MON_SHAPE_MISC: - if (ch == 'x' || ch == 'X' || rand) - { - str = "tentacle"; - break; - } - // Deliberate fallthrough. - case MON_SHAPE_SNAIL: - case MON_SHAPE_NAGA: - case MON_SHAPE_ORB: - str = "underside"; - *can_plural = false; - break; - } - - if (str.empty()) - { - // Reduce the chance of a random-shaped monster having feet. - if (rand && coinflip()) - return (foot_name(plural, can_plural)); - - return (plural ? "feet" : "foot"); - } - - if (plural && *can_plural) - str = pluralise(str); - - return (str); -} - -std::string monsters::arm_name(bool plural, bool *can_plural) const -{ - mon_body_shape shape = get_mon_shape(this); - - if (shape > MON_SHAPE_NAGA) - return hand_name(plural, can_plural); - - if (can_plural != NULL) - *can_plural = true; - - std::string str; - switch (mons_genus(type)) - { - case MONS_NAGA: - case MONS_DRACONIAN: str = "scaled arm"; break; - - case MONS_MUMMY: str = "bandaged wrapped arm"; break; - - case MONS_SKELETAL_WARRIOR: - case MONS_LICH: str = "bony arm"; break; - - default: str = "arm"; break; - } - - if (plural) - str = pluralise(str); - - return (str); -} - -monster_type monsters::id() const -{ - return (type); -} - -int monsters::mindex() const -{ - return (monster_index(this)); -} - -int monsters::get_experience_level() const -{ - return (hit_dice); -} - -void monsters::moveto(const coord_def& c) -{ - if (c != pos() && in_bounds(pos())) - mons_clear_trapping_net(this); - - position = c; -} - -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 (player_can_hear(pos())) - mpr("You hear a splashing noise.", MSGCH_SOUND); - } - - return (true); - } - - if (submerged()) - 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, true); - 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)); - add_ench(mon_enchant(ENCH_MIGHT, 0, KC_OTHER, duration * 10)); - if (simple_monster_message( this, " goes berserk!" )) - // Xom likes monsters going berserk. - xom_is_stimulated(mons_friendly(this) ? 32 : 128); -} - -void monsters::expose_to_element(beam_type flavour, int strength) -{ - switch (flavour) - { - case BEAM_COLD: - if (mons_class_flag(this->type, M_COLD_BLOOD) && this->res_cold() <= 0 && coinflip()) - slow_down(this, strength); - break; - default: - break; - } -} - -void monsters::banish(const std::string &) -{ - monster_die(this, KILL_RESET, NON_MONSTER); -} - -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::has_attack_flavour(int flavour) const -{ - for (int i = 0; i < 4; ++i) - { - const int attk_flavour = mons_attack_spec(this, i).flavour; - if (attk_flavour == flavour) - return (true); - } - - return (false); -} - -bool monsters::has_damage_type(int dam_type) -{ - for (int i = 0; i < 4; ++i) - { - const int dmg_type = damage_type(i); - if (dmg_type == dam_type) - return (true); - } - - return (false); -} - -bool monsters::confused() const -{ - return (mons_is_confused(this)); -} - -bool monsters::confused_by_you() const -{ - if (mons_class_flag(type, M_CONFUSED)) - return false; - - const mon_enchant me = get_ench(ENCH_CONFUSION); - return (me.ench == ENCH_CONFUSION && me.who == KC_YOU); -} - -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 (behaviour == BEH_SLEEP); -} - -bool monsters::backlit(bool check_haloed) const -{ - return (has_ench(ENCH_BACKLIGHT) - || ((check_haloed) ? haloed() : false)); -} - -bool monsters::haloed() const -{ - return (inside_halo(pos())); -} - -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) + shld->plus; - 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()) - 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 -{ - if (testbits(flags, MF_HONORARY_UNDEAD)) - return (MH_UNDEAD); - - return (mons_class_holiness(type)); -} - -bool monsters::is_unholy() const -{ - const mon_holy_type holi = holiness(); - - return (holi == MH_UNDEAD || holi == MH_DEMONIC); -} - -int monsters::res_fire() const -{ - const mon_resist_def res = get_mons_resists(this); - - int u = std::min(res.fire + res.hellfire * 3, 3); - - if (mons_itemuse(this) >= MONUSE_STARTING_EQUIPMENT) - { - u += _scan_mon_inv_randarts(this, ARTP_FIRE); - - const int armour = inv[MSLOT_ARMOUR]; - const int shld = 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 (shld != NON_ITEM && mitm[shld].base_type == OBJ_ARMOUR) - { - // check ego resistance - const int ego = get_armour_ego_type(mitm[shld]); - if (ego == SPARM_FIRE_RESISTANCE || ego == SPARM_RESISTANCE) - u += 1; - } - } - - if (u < -3) - u = -3; - else if (u > 3) - u = 3; - - return (u); -} - -int monsters::res_steam() const -{ - int res = get_mons_resists(this).steam; - if (has_equipped(EQ_BODY_ARMOUR, ARM_STEAM_DRAGON_ARMOUR)) - res += 3; - return (res + res_fire() / 2); -} - -int monsters::res_cold() const -{ - int u = get_mons_resists(this).cold; - - if (mons_itemuse(this) >= MONUSE_STARTING_EQUIPMENT) - { - u += _scan_mon_inv_randarts(this, ARTP_COLD); - - const int armour = inv[MSLOT_ARMOUR]; - const int shld = 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 (shld != NON_ITEM && mitm[shld].base_type == OBJ_ARMOUR) - { - // check ego resistance - const int ego = get_armour_ego_type(mitm[shld]); - if (ego == SPARM_COLD_RESISTANCE || ego == SPARM_RESISTANCE) - u += 1; - } - } - - if (u < -3) - u = -3; - else if (u > 3) - u = 3; - - return (u); -} - -int monsters::res_elec() const -{ - // This is a variable, not a player_xx() function, so can be above 1. - int u = 0; - - u += get_mons_resists(this).elec; - - // Don't bother checking equipment if the monster can't use it. - if (mons_itemuse(this) >= MONUSE_STARTING_EQUIPMENT) - { - u += _scan_mon_inv_randarts(this, ARTP_ELECTRICITY); - - // No ego armour, but storm dragon. - const int armour = 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); -} - -int monsters::res_asphyx() const -{ - int res = get_mons_resists(this).asphyx; - const mon_holy_type holi = holiness(); - if (is_unholy() - || holi == MH_NONLIVING - || holi == MH_PLANT) - { - res += 1; - } - return (res); -} - -int monsters::res_poison() const -{ - int u = get_mons_resists(this).poison; - - if (mons_itemuse(this) >= MONUSE_STARTING_EQUIPMENT) - { - u += _scan_mon_inv_randarts( this, ARTP_POISON ); - - const int armour = this->inv[MSLOT_ARMOUR]; - const int shld = this->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 (shld != NON_ITEM && mitm[shld].base_type == OBJ_ARMOUR) - { - // ego armour resistance - if (get_armour_ego_type(mitm[shld]) == SPARM_POISON_RESISTANCE) - u += 1; - } - } - - // Monsters can legitimately get multiple levels of poison resistance. - - return (u); -} - -int monsters::res_sticky_flame() const -{ - int res = get_mons_resists(this).sticky_flame; - if (has_equipped(EQ_BODY_ARMOUR, ARM_MOTTLED_DRAGON_ARMOUR)) - res += 1; - return (res); -} - -int monsters::res_rotting() const -{ - int res = get_mons_resists(this).rotting; - if (holiness() != MH_NATURAL) - res += 1; - return (res); -} - -int monsters::res_holy_energy(const actor *attacker) const -{ - if (mons_is_evil(this)) - return (-1); - - if (is_unholy()) - return (-2); - - if (is_good_god(god) - || mons_is_holy(this) - || mons_neutral(this) - || is_unchivalric_attack(attacker, this) - || is_good_god(you.religion) && is_follower(this)) - { - return (1); - } - - return (0); -} - -int monsters::res_negative_energy() const -{ - if (holiness() != MH_NATURAL - || type == MONS_SHADOW_DRAGON) - { - return (3); - } - - int u = 0; - - if (mons_itemuse(this) >= MONUSE_STARTING_EQUIPMENT) - { - u += _scan_mon_inv_randarts(this, ARTP_NEGATIVE_ENERGY); - - const int armour = this->inv[MSLOT_ARMOUR]; - const int shld = this->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 (shld != NON_ITEM && mitm[shld].base_type == OBJ_ARMOUR) - { - // check for ego resistance - if (get_armour_ego_type(mitm[shld]) == SPARM_POSITIVE_ENERGY) - u += 1; - } - } - - if (u > 3) - u = 3; - - return (u); -} - -int monsters::res_torment() const -{ - const mon_holy_type holy = holiness(); - if (holy == MH_UNDEAD - || holy == MH_DEMONIC - || holy == MH_NONLIVING) - { - return (1); - } - - return (0); -} - -int monsters::res_acid() const -{ - return (get_mons_resists(this).acid); -} - -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 ? agent->kill_alignment() : KC_OTHER, 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(bool) -{ - monster_blink(this); -} - -void monsters::teleport(bool now, bool) -{ - monster_teleport(this, now, false); -} - -bool monsters::alive() const -{ - return (hit_points > 0 && type != MONS_NO_MONSTER); -} - -god_type monsters::deity() const -{ - return (god); -} - -bool monsters::drain_exp(actor *agent, bool quiet, int pow) -{ - if (x_chance_in_y(res_negative_energy(), 3)) - return (false); - - if (!quiet && you.can_see(this)) - mprf("%s is drained!", name(DESC_CAP_THE).c_str()); - - // If quiet, don't clean up the monster in order to credit properly. - hurt(agent, 2 + random2(pow), BEAM_NEG, !quiet); - - if (alive()) - { - if (x_chance_in_y(pow, 15)) - { - hit_dice--; - experience = 0; - } - - max_hit_points -= 2 + random2(pow); - hit_points = std::min(max_hit_points, hit_points); - } - - return (true); -} - -bool monsters::rot(actor *agent, int amount, int immediate, bool quiet) -{ - if (res_rotting() > 0 || amount <= 0) - return (false); - - if (!quiet && you.can_see(this)) - { - mprf("%s %s!", name(DESC_CAP_THE).c_str(), - amount > 0 ? "rots" : "looks less resilient"); - } - - // Apply immediate damage because we can't handle rotting for - // monsters yet. - if (immediate > 0) - { - // If quiet, don't clean up the monster in order to credit - // properly. - hurt(agent, immediate, BEAM_MISSILE, !quiet); - - if (alive()) - { - max_hit_points -= immediate * 2; - hit_points = std::min(max_hit_points, hit_points); - } - } - - add_ench(mon_enchant(ENCH_ROT, std::min(amount, 4), - agent->kill_alignment())); - - return (true); -} - -int monsters::hurt(const actor *agent, int amount, beam_type flavour, - bool cleanup_dead) -{ - if (hit_points > 0 && type != -1) - { - if (amount == INSTANT_DEATH) - amount = hit_points; - else if (hit_dice <= 0) - amount = hit_points; - else if (amount <= 0 && hit_points <= max_hit_points) - return (0); - - amount = std::min(amount, hit_points); - hit_points -= amount; - - if (hit_points > max_hit_points) - { - amount += hit_points - max_hit_points; - hit_points = max_hit_points; - } - - // Allow the victim to exhibit passive damage behaviour (royal - // jelly). - kill_category whose = (agent == NULL) ? KC_OTHER : - (agent->atype() == ACT_PLAYER) ? KC_YOU : - mons_friendly_real((monsters*)agent) ? KC_FRIENDLY : - KC_OTHER; - react_to_damage(amount, flavour, whose); - } - - if (cleanup_dead && (hit_points <= 0 || hit_dice <= 0) && type != -1) - { - if (agent == NULL) - monster_die(this, KILL_MISC, NON_MONSTER); - else if (agent->atype() == ACT_PLAYER) - monster_die(this, KILL_YOU, NON_MONSTER); - else - monster_die(this, KILL_MON, agent->mindex()); - } - - return (amount); -} - -void monsters::confuse(actor *atk, int strength) -{ - enchant_monster_with_flavour(this, atk, BEAM_CONFUSION, strength); -} - -void monsters::paralyse(actor *atk, int strength) -{ - enchant_monster_with_flavour(this, atk, BEAM_PARALYSIS, strength); -} - -void monsters::petrify(actor *atk, int strength) -{ - if (mons_is_insubstantial(type)) - return; - - enchant_monster_with_flavour(this, atk, BEAM_PETRIFY, strength); -} - -void monsters::slow_down(actor *atk, int strength) -{ - enchant_monster_with_flavour(this, atk, BEAM_SLOW, strength); -} - -void monsters::set_ghost(const ghost_demon &g, bool has_name) -{ - ghost.reset(new ghost_demon(g)); - - if (has_name) - mname = ghost->name; -} - -void monsters::pandemon_init() -{ - hit_dice = ghost->xl; - max_hit_points = ghost->max_hp; - hit_points = max_hit_points; - ac = ghost->ac; - ev = ghost->ev; - flags = MF_INTERESTING; - // 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 = ghost->colour; - - load_spells(MST_GHOST); -} - -void monsters::ghost_init() -{ - type = MONS_PLAYER_GHOST; - god = ghost->religion; - hit_dice = ghost->xl; - max_hit_points = ghost->max_hp; - hit_points = max_hit_points; - ac = ghost->ac; - ev = ghost->ev; - speed = ghost->speed; - speed_increment = 70; - attitude = ATT_HOSTILE; - behaviour = BEH_WANDER; - flags = MF_INTERESTING; - foe = MHITNOT; - foe_memory = 0; - colour = ghost->colour; - number = MONS_NO_MONSTER; - load_spells(MST_GHOST); - - inv.init(NON_ITEM); - enchantments.clear(); - ench_countdown = 0; - - find_place_to_live(); -} - -void monsters::uglything_init(bool only_mutate) -{ - // If we're mutating an ugly thing, leave its experience level, hit - // dice and maximum hit points as they are. - if (!only_mutate) - { - hit_dice = ghost->xl; - max_hit_points = ghost->max_hp; - } - - hit_points = max_hit_points; - ac = ghost->ac; - ev = ghost->ev; - speed = ghost->speed; - speed_increment = 70; - colour = ghost->colour; -} - -void monsters::uglything_mutate(unsigned char force_colour) -{ - ghost->init_ugly_thing(type == MONS_VERY_UGLY_THING, true, force_colour); - uglything_init(true); -} - -void monsters::uglything_upgrade() -{ - ghost->ugly_thing_to_very_ugly_thing(); - uglything_init(); -} - -bool monsters::check_set_valid_home(const coord_def &place, - coord_def &chosen, - int &nvalid) const -{ - if (!in_bounds(place)) - return (false); - - if (actor_at(place)) - return (false); - - if (!monster_habitable_grid(this, grd(place))) - 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) - { - moveto(place); - return (true); - } - - return (false); -} - -bool monsters::find_home_near_place(const coord_def &c) -{ - for (int radius = 1; radius < 7; ++radius) - if (find_home_around(c, radius)) - return (true); - - return (false); -} - -bool monsters::find_home_near_player() -{ - return (find_home_near_place(you.pos())); -} - -bool monsters::find_home_anywhere() -{ - coord_def place(-1, -1); - int nvalid = 0; - for (int tries = 0; tries < 600; ++tries) - { - if (check_set_valid_home(random_in_bounds(), place, nvalid)) - { - moveto(place); - return (true); - } - } - - return (false); -} - -bool monsters::find_place_to_live(bool near_player) -{ - if (near_player && find_home_near_player() - || find_home_anywhere()) - { - mgrd(pos()) = mindex(); - 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.origin()); -} - -bool monsters::needs_transit() const -{ - return ((mons_is_unique(type) - || (flags & MF_BANISHED) - || you.level_type == LEVEL_DUNGEON - && hit_dice > 8 + random2(25) - && mons_can_use_stairs(this)) - && !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 (mons_species() == MONS_HYDRA - || mons_is_zombified(this) && base_monster == MONS_HYDRA); -} - -bool monsters::has_multitargeting() const -{ - if (mons_class_wields_two_weapons(type)) - return (true); - - // Hacky little list for now. evk - return (type == MONS_HYDRA - || type == MONS_TENTACLED_MONSTROSITY - || type == MONS_ELECTRIC_GOLEM); -} - -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); - - if (ench.ench == ENCH_FEAR - && (holiness() == MH_NONLIVING || has_ench(ENCH_BERSERK))) - { - 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::forget_random_spell() -{ - int which_spell = -1; - int count = 0; - for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) - if (spells[i] != SPELL_NO_SPELL && one_chance_in(++count)) - which_spell = i; - if (which_spell != -1) - spells[which_spell] = SPELL_NO_SPELL; -} - -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: - mons_clear_trapping_net(this); - - // Don't worry about invisibility. You should be able to see if - // something has submerged. - if (!quiet && mons_near(this)) - { - 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()); - } - else if (seen_context == "surfaces" - || seen_context == "bursts forth" - || seen_context == "emerges") - { - // The monster surfaced and submerged in the same turn - // without doing anything else. - interrupt_activity(AI_SEE_MONSTER, - activity_interrupt_data(this, - "surfaced")); - } - else if (crawl_state.arena) - mprf("%s submerges.", name(DESC_CAP_A, true).c_str()); - } - - // Pacified monsters leave the level when they submerge. - if (mons_is_pacified(this)) - make_mons_leave_level(this); - 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 = you.pos(); - foe = MHITYOU; - - if (is_patrolling()) - { - // Enslaved monsters stop patrolling and forget their patrol - // point; they're supposed to follow you now. - patrol_point.reset(); - } - if (you.can_see(this)) - learned_something_new(TUT_MONSTER_FRIENDLY, pos()); - break; - - default: - break; - } -} - -static bool _prepare_del_ench(monsters* mon, const mon_enchant &me) -{ - if (me.ench != ENCH_SUBMERGED) - return (true); - - // Lurking monsters only unsubmerge when their foe is in sight if the foe - // is right next to them. - if (mons_is_lurking(mon)) - { - const actor* foe = mon->get_foe(); - if (foe != NULL && mon->can_see(foe) - && !adjacent(mon->pos(), foe->pos())) - { - return (false); - } - } - - int midx = mon->mindex(); - - if (mgrd(mon->pos()) == NON_MONSTER) - mgrd(mon->pos()) = midx; - - if (mon->pos() != you.pos() && midx == mgrd(mon->pos())) - return (true); - - if (midx != mgrd(mon->pos())) - { - monsters* other_mon = &menv[mgrd(mon->pos())]; - - if (other_mon->type == MONS_NO_MONSTER - || other_mon->type == MONS_PROGRAM_BUG) - { - mgrd(mon->pos()) = midx; - - mprf(MSGCH_ERROR, "mgrd(%d,%d) points to %s monster, even " - "though it contains submerged monster %s (see bug 2293518)", - mon->pos().x, mon->pos().y, - other_mon->type == MONS_NO_MONSTER ? "dead" : "buggy", - mon->name(DESC_PLAIN, true).c_str()); - - if (mon->pos() != you.pos()) - return (true); - } - else - mprf(MSGCH_ERROR, "%s tried to unsubmerge while on same square as " - "%s (see bug 2293518)", mon->name(DESC_CAP_THE, true).c_str(), - mon->name(DESC_NOCAP_A, true).c_str()); - } - - // Monster un-submerging while under player or another monster. Try to - // move to an adjacent square in which the monster could have been - // submerged and have it unsubmerge from there. - coord_def target_square; - int okay_squares = 0; - - for (adjacent_iterator ai; ai; ++ai) - if (mgrd(*ai) == NON_MONSTER && *ai != you.pos() - && monster_can_submerge(mon, grd(*ai)) - && one_chance_in(++okay_squares)) - { - target_square = *ai; - } - - if (okay_squares > 0) - { - mon->move_to_pos(target_square); - 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 (adjacent_iterator ai; ai; ++ai) - { - if (mgrd(*ai) == NON_MONSTER - && monster_habitable_grid(mon, grd(*ai)) - && !find_trap(*ai)) - { - if (one_chance_in(++okay_squares)) - target_square = *ai; - } - } - - if (okay_squares > 0) - mon->move_to_pos(target_square); - - 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; - if (!quiet) - simple_monster_message(this, " is no longer moving quickly."); - break; - - case ENCH_MIGHT: - if (!quiet) - simple_monster_message(this, " no longer looks unusually strong."); - 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.", - this->pronoun(PRONOUN_NOCAP_POSSESSIVE, true).c_str()); - } - - 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) && !you.can_see_invisible() - && !has_ench(ENCH_SUBMERGED)) - { - if (!quiet) - { - mprf("%s appears from thin air!", - name(DESC_CAP_A, true).c_str()); - autotoggle_autopickup(false); - } - - handle_seen_interrupt(this); - } - break; - - case ENCH_CHARM: - if (!quiet) - simple_monster_message(this, " is no longer charmed."); - - if (you.can_see(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.reset(); - } - - // Reevaluate behaviour. - behaviour_event(this, ME_EVAL); - break; - - case ENCH_BACKLIGHT: - if (!quiet) - { - if (visible_to(&you)) - 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(this->pos()); - 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: - // Set duration to -1 so that monster_die() and any of its - // callees can tell that the monster ran out of time or was - // abjured. - add_ench(mon_enchant(ENCH_ABJ, 0, KC_OTHER, -1)); - - if (this->has_ench(ENCH_BERSERK)) - simple_monster_message(this, " is no longer berserk."); - - monster_die(this, quiet ? KILL_DISMISSED : KILL_RESET, NON_MONSTER); - 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(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 (crawl_state.arena) - mprf("%s surfaces.", name(DESC_CAP_A, true).c_str() ); - } - } - else if (mons_near(this) - && feat_compatible(grd(pos()), DNGN_DEEP_WATER)) - { - mpr("Something invisible bursts forth from the water."); - interrupt_activity(AI_FORCE_INTERRUPT); - } - break; - - case ENCH_SOUL_RIPE: - if (!quiet) - { - simple_monster_message(this, - "'s soul is no longer ripe for the taking."); - } - 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_MIGHT: 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: - case ENCH_LOWERED_MR: case ENCH_SOUL_RIPE: - lose_ench_levels(i->second, levels); - break; - - case ENCH_BERSERK: - del_ench(i->first); - del_ench(ENCH_HASTE, true); - del_ench(ENCH_MIGHT, true); - 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; - - case ENCH_SLOWLY_DYING: - { - const int actdur = speed_to_duration(speed) * levels; - if (lose_ench_duration(i->first, actdur)) - monster_die(this, KILL_MISC, NON_MONSTER, true); - 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, true); - del_ench(ENCH_MIGHT, true); - 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_MIGHT: - 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: - case ENCH_LOWERED_MR: - case ENCH_SOUL_RIPE: - decay_enchantment(me); - break; - - case ENCH_BATTLE_FRENZY: - decay_enchantment(me, false); - break; - - case ENCH_AQUATIC_LAND: - // Aquatic monsters lose hit points every turn they spend on dry land. - ASSERT(mons_habitat(this) == HT_WATER - && !feat_is_watery( grd(pos()) )); - - // Zombies don't take damage from flopping about on land. - if (mons_is_zombified(this)) - break; - - // We don't have a reasonable agent to give. - // Don't clean up the monster in order to credit properly. - hurt(NULL, 1 + random2(5), BEAM_NONE, false); - - // Credit the kill. - if (hit_points < 1) - { - monster_die(this, me.killer(), me.kill_agent()); - break; - } - break; - - case ENCH_HELD: - { - if (mons_is_stationary(this) || mons_cannot_act(this) - || asleep()) - { - break; - } - - int net = get_trapping_net(this->pos(), true); - - if (net == NON_ITEM) // Really shouldn't happen! - { - del_ench(ENCH_HELD); - break; - } - - // Handled in handle_pickup(). - if (mons_eats_items(this)) - 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) && !visible_to(&you)) - 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) && !visible_to(&you)) - 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 (visible_to(&you)) - { - 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(pos()) != 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(pos()); - - // Badly injured monsters prefer to stay submerged... - // electric 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 (hit_points <= max_hit_points / 2) - break; - else if (type == MONS_TRAPDOOR_SPIDER) - { - // This should probably never happen. - if (!mons_is_lurking(this)) - del_ench(ENCH_SUBMERGED); - break; - } - else if (((type == MONS_ELECTRIC_EEL || type == MONS_LAVA_SNAKE || type == MONS_KRAKEN) - && (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 (res_poison() < 0) - dam += roll_dice(2, poisonval) - 1; - - if (dam > 0) - { - // We don't have a reasonable agent to give. - // Don't clean up the monster in order to credit properly. - hurt(NULL, dam, BEAM_POISON, false); - -#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 - - // Credit the kill. - 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(NULL, 1); // nonlethal so we don't care about agent - if (hit_points < max_hit_points && coinflip()) - --max_hit_points; - } - - decay_enchantment(me, true); - break; - } - - // Assumption: monsters::res_fire has already been checked. - case ENCH_STICKY_FLAME: - { - if (feat_is_watery(grd(pos()))) - { - if (mons_near(this) && visible_to(&you)) - mprf("The flames covering %s go out.", - this->name(DESC_NOCAP_THE, false).c_str()); - del_ench(ENCH_STICKY_FLAME); - break; - } - int dam = resist_adjust_damage(this, BEAM_FIRE, res_fire(), - roll_dice(2, 4) - 1); - - if (dam > 0) - { - simple_monster_message(this, " burns!"); - // We don't have a reasonable agent to give. - // Don't clean up the monster in order to credit properly. - hurt(NULL, dam, BEAM_NAPALM, false); - -#if DEBUG_DIAGNOSTICS - mprf( MSGCH_DIAGNOSTICS, "sticky flame damage: %d", dam ); -#endif - - // Credit the kill. - 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_SLOWLY_DYING: - // If you are no longer dying, you must be dead. - if (decay_enchantment(me)) - { - if (::see_cell(this->position)) - { - mprf("A nearby %s withers and dies.", - this->name(DESC_PLAIN, false).c_str()); - } - - monster_die(this, KILL_MISC, NON_MONSTER, true); - } - break; - - case ENCH_SPORE_PRODUCTION: - // Very low chance of actually making a spore on each turn. - if(one_chance_in(5000)) - { - int idx[] = {0, 1, 2, 3, 4, 5, 6, 7}; - std::random_shuffle(idx, idx + 8); - - for (unsigned i = 0; i < 8; ++i) - { - coord_def adjacent = this->pos() + Compass[idx[i]]; - if (mons_class_can_pass(MONS_GIANT_SPORE, env.grid(adjacent)) - && !actor_at(adjacent)) - { - beh_type created_behavior = BEH_HOSTILE; - - if (this->attitude == ATT_FRIENDLY) - created_behavior = BEH_FRIENDLY; - - int rc = create_monster(mgen_data(MONS_GIANT_SPORE, - created_behavior, - 0, - 0, - adjacent, - MHITNOT, - MG_FORCE_PLACE)); - - if (rc != -1) - { - env.mons[rc].behaviour = BEH_WANDER; - - if (::see_cell(adjacent) && ::see_cell(pos())) - mpr("A nearby fungus spawns a giant spore."); - } - break; - } - } - } - break; - - case ENCH_GLOWING_SHAPESHIFTER: // This ench never runs out! - // Number of actions is fine for shapeshifters. Don't change - // shape while taking the stairs because monster_polymorph() has - // an assert about it. -cao - if (!(this->flags & MF_TAKING_STAIRS) && !asleep() - && (type == MONS_GLOWING_SHAPESHIFTER - || one_chance_in(4))) - { - monster_polymorph(this, RANDOM_MONSTER); - } - break; - - case ENCH_SHAPESHIFTER: // This ench never runs out! - if (!(this->flags & MF_TAKING_STAIRS) && !asleep() - && (type == MONS_SHAPESHIFTER - || x_chance_in_y(1000 / (15 * hit_dice / 5), 1000))) - { - monster_polymorph(this, RANDOM_MONSTER); - } - break; - - case ENCH_TP: - if (decay_enchantment(me, true)) - monster_teleport(this, true); - break; - - case ENCH_SLEEPY: - del_ench(ENCH_SLEEPY); - break; - - case ENCH_EAT_ITEMS: - break; - - default: - break; - } -} - -void monsters::mark_summoned(int longevity, bool mark_items, int summon_type) -{ - add_ench( mon_enchant(ENCH_ABJ, longevity) ); - if (summon_type != 0) - add_ench( mon_enchant(ENCH_SUMMON, summon_type, KC_OTHER, INT_MAX) ); - - 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; - } - } -} - -bool monsters::is_summoned(int* duration, int* summon_type) const -{ - return mons_is_summoned(this, duration, summon_type); -} - -void monsters::apply_enchantments() -{ - if (enchantments.empty()) - return; - - // The ordering in enchant_type makes sure that "super-enchantments" - // like berserk time out before their parts. - 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_real(this) ? KC_FRIENDLY : KC_OTHER); -} - -bool monsters::sicken(int amount) -{ - if (res_rotting() || (amount /= 2) < 1) - return (false); - - if (!has_ench(ENCH_SICK) && you.can_see(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); -} - -static 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); -} - -// Recalculate movement speed. -void monsters::fix_speed() -{ - speed = _mons_real_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 (mons_friendly(this) ? NULL : &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 || type == MONS_KRAKEN_TENTACLE) - 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 -{ - if (mons_is_ghost_demon(this->type)) - return (this->ghost->see_invis); - else if (mons_class_flag(this->type, M_SEE_INVIS)) - return (true); - else if (_scan_mon_inv_randarts(this, ARTP_EYESIGHT) > 0) - return (true); - return (false); -} - -bool monsters::invisible() const -{ - return (has_ench(ENCH_INVIS) && !backlit()); -} - -bool monsters::visible_to(const actor *looker) const -{ - bool vis = !invisible() || looker->can_see_invisible(); - return (vis && (this == looker || !has_ench(ENCH_SUBMERGED))); -} - -bool monsters::mon_see_cell(const coord_def& p, bool reach) const -{ - if (distance(pos(), p) > LOS_RADIUS * LOS_RADIUS + 1) - 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(pos(), p, DNGN_UNSEEN, max_disallowed, - true, true)); -} - -bool monsters::see_cell(const coord_def &c) const -{ - // XXX: using env.show since that's been filled anywa. - if (c == you.pos()) - return (you.see_cell(pos())); - - // TODO: Proper monster LOS. - return (mon_see_cell(c)); -} - -bool monsters::can_see(const actor *targ) const -{ - return (targ->visible_to(this) && see_cell(targ->pos())); -} - -bool monsters::near_foe() const -{ - const actor *afoe = get_foe(); - return (afoe && see_cell(afoe->pos())); -} - -bool monsters::can_mutate() const -{ - return (holiness() == MH_NATURAL || holiness() == MH_PLANT); -} - -bool monsters::can_safely_mutate() const -{ - return (can_mutate()); -} - -bool monsters::can_bleed() const -{ - return (mons_has_blood(type)); -} - -bool monsters::mutate() -{ - if (!can_mutate()) - return (false); - - // Polymorphing a (very) ugly thing will mutate it into a different - // (very) ugly thing. - if (type == MONS_UGLY_THING || type == MONS_VERY_UGLY_THING) - { - ugly_thing_mutate(this); - return (true); - } - - // Polymorphing a shapeshifter will make it revert to its original - // form. - if (this->has_ench(ENCH_GLOWING_SHAPESHIFTER)) - return (monster_polymorph(this, MONS_GLOWING_SHAPESHIFTER)); - if (this->has_ench(ENCH_SHAPESHIFTER)) - return (monster_polymorph(this, MONS_SHAPESHIFTER)); - - return (monster_polymorph(this, RANDOM_MONSTER)); -} - -bool monsters::is_icy() const -{ - return (mons_is_icy(type)); -} - -static bool _mons_is_fiery(int mc) -{ - return (mc == MONS_FIRE_VORTEX - || mc == MONS_FIRE_ELEMENTAL - || mc == MONS_FLAMING_CORPSE - || mc == MONS_EFREET - || mc == MONS_AZRAEL - || mc == MONS_LAVA_WORM - || mc == MONS_LAVA_FISH - || mc == MONS_LAVA_SNAKE - || mc == MONS_SALAMANDER - || mc == MONS_MOLTEN_GARGOYLE - || mc == MONS_ORB_OF_FIRE); -} - -bool monsters::is_fiery() const -{ - return (_mons_is_fiery(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_cell(pos()); - const bool see_old = ::see_cell(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(const coord_def &oldpos) -{ - if (oldpos != pos()) - dungeon_events.fire_position_event(DET_MONSTER_MOVED, pos()); - - if (alive() && has_ench(ENCH_AQUATIC_LAND)) - { - if (!feat_is_watery( grd(pos()) )) - simple_monster_message(this, " flops around on dry land!"); - else if (!feat_is_watery( grd(oldpos) )) - { - simple_monster_message(this, " dives back into the water!"); - del_ench(ENCH_AQUATIC_LAND); - } - } - - // Monsters stepping on traps: - trap_def* ptrap = find_trap(pos()); - if (ptrap) - ptrap->trigger(*this); - - if (alive()) - mons_check_pool(this, pos()); - - if (alive() && has_ench(ENCH_SUBMERGED) - && (!monster_can_submerge(this, grd(pos())) - || type == MONS_TRAPDOOR_SPIDER)) - { - del_ench(ENCH_SUBMERGED); - - if (type == MONS_TRAPDOOR_SPIDER) - behaviour_event(this, ME_EVAL); - } - - unsigned long &prop = env.map(pos()).property; - - if (prop & FPROP_BLOODY) - { - monster_type genus = mons_genus(type); - - if (genus == MONS_JELLY || genus == MONS_GIANT_SLUG) - { - prop &= ~FPROP_BLOODY; - if (see_cell(pos()) && !visible_to(&you)) - { - std::string desc = - feature_description(pos(), false, DESC_NOCAP_THE, false); - mprf("The bloodstain on %s disappears!", desc.c_str()); - } - } - } -} - -bool monsters::move_to_pos(const coord_def &newpos) -{ - if (actor_at(newpos)) - return (false); - - // Clear old cell pointer. - mgrd(pos()) = NON_MONSTER; - - // Set monster x,y to new value. - moveto(newpos); - - // Set new monster grid pointer to this monster. - mgrd(newpos) = mindex(); - - 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 (get_trap_type(this->pos()) != TRAP_SHAFT) - { - switch (grd(pos())) - { - 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 (visible_to(&you)) - { - 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->pos(), 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->pos(), 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 == MONS_NO_MONSTER || 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: - // [ds] Amphibious monsters get a significant speed boost - // when swimming, as discussed with dpeg. We do not - // distinguish between amphibians that favour land - // (HT_AMPHIBIOUS_LAND, such as hydras) and those that - // favour water (HT_AMPHIBIOUS_WATER, such as merfolk), but - // that's something we can think about. - if (mons_amphibious(this)) - return div_rand_round(mu.swim * 7, 10); - else - 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; + 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; } - - speed_increment -= energy_loss; } monster_type royal_jelly_ejectable_monster() @@ -8267,424 +3078,6 @@ monster_type royal_jelly_ejectable_monster() -1)); } -bool monsters::can_drink_potion(potion_type ptype) const -{ - if (mons_class_is_stationary(this->type)) - return (false); - - if (mons_itemuse(this) < MONUSE_STARTING_EQUIPMENT) - return (false); - - // These monsters cannot drink. - if (mons_is_skeletal(type) || mons_is_insubstantial(type) - || mons_species() == MONS_LICH || mons_genus(type) == MONS_MUMMY) - { - return (false); - } - - switch (ptype) - { - case POT_HEALING: - case POT_HEAL_WOUNDS: - return (holiness() != MH_NONLIVING - && holiness() != MH_PLANT); - case POT_BLOOD: - case POT_BLOOD_COAGULATED: - return (mons_species() == MONS_VAMPIRE); - case POT_SPEED: - case POT_MIGHT: - case POT_BERSERK_RAGE: - case POT_INVISIBILITY: - // If there are any item using monsters that are permanently - // invisible, this might have to be restricted. - return (true); - default: - break; - } - - return (false); -} - -bool monsters::should_drink_potion(potion_type ptype) const -{ - switch (ptype) - { - case POT_HEALING: - return (hit_points <= max_hit_points / 2) - || has_ench(ENCH_POISON) - || has_ench(ENCH_SICK) - || has_ench(ENCH_CONFUSION) - || has_ench(ENCH_ROT); - case POT_HEAL_WOUNDS: - return (hit_points <= max_hit_points / 2); - case POT_BLOOD: - case POT_BLOOD_COAGULATED: - return (hit_points <= max_hit_points / 2); - case POT_SPEED: - return (!has_ench(ENCH_HASTE)); - case POT_MIGHT: - return (!has_ench(ENCH_MIGHT) && foe_distance() <= 2); - case POT_BERSERK_RAGE: - // this implies !has_ench(ENCH_BERSERK_RAGE) - return (!has_ench(ENCH_MIGHT) && !has_ench(ENCH_HASTE) - && needs_berserk()); - case POT_INVISIBILITY: - // We're being nice: friendlies won't go invisible if the player - // won't be able to see them. - return (!has_ench(ENCH_INVIS) - && (you.can_see_invisible(false) || !mons_friendly(this))); - default: - break; - } - - return (false); -} - -// Return the ID status gained. -item_type_id_state_type monsters::drink_potion_effect(potion_type ptype) -{ - simple_monster_message(this, " drinks a potion."); - - item_type_id_state_type ident = ID_MON_TRIED_TYPE; - - switch (ptype) - { - case POT_HEALING: - { - heal(5 + random2(7)); - simple_monster_message(this, " is healed!"); - - const enchant_type cured_enchants[] = { - ENCH_POISON, ENCH_SICK, ENCH_CONFUSION, ENCH_ROT - }; - - // We can differentiate healing and heal wounds (and blood, - // for vampires) by seeing if any status ailments are cured. - for (unsigned int i = 0; i < ARRAYSZ(cured_enchants); ++i) - if (del_ench(cured_enchants[i])) - ident = ID_KNOWN_TYPE; - } - break; - - case POT_HEAL_WOUNDS: - heal(10 + random2avg(28, 3)); - simple_monster_message(this, " is healed!"); - break; - - case POT_BLOOD: - case POT_BLOOD_COAGULATED: - if (mons_species() == MONS_VAMPIRE) - { - heal(10 + random2avg(28, 3)); - simple_monster_message(this, " is healed!"); - } - break; - - case POT_SPEED: - if (enchant_monster_with_flavour(this, this, BEAM_HASTE)) - ident = ID_KNOWN_TYPE; - break; - - case POT_INVISIBILITY: - if (enchant_monster_with_flavour(this, this, BEAM_INVISIBILITY)) - ident = ID_KNOWN_TYPE; - break; - - case POT_MIGHT: - if (enchant_monster_with_flavour(this, this, BEAM_MIGHT)) - ident = ID_KNOWN_TYPE; - break; - - case POT_BERSERK_RAGE: - if (enchant_monster_with_flavour(this, this, BEAM_BERSERK)) - ident = ID_KNOWN_TYPE; - break; - - default: - break; - } - - return (ident); -} - -void monsters::react_to_damage(int damage, beam_type flavour, kill_category whose) -{ - if (!alive()) - return; - - // The royal jelly objects to taking damage and will SULK. :-) - if (type == MONS_ROYAL_JELLY && flavour != BEAM_TORMENT_DAMAGE - && damage > 8 && x_chance_in_y(damage, 50)) - { - mon_acting mact(this); - - 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 beha = 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, beha, 0, 0, - jpos, foe, 0, god)); - - 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) - && visible_to(&you); - - 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()); - } - } - } - else if (type == MONS_KRAKEN_TENTACLE && flavour != BEAM_TORMENT_DAMAGE) - { - if (!invalid_monster_index(number) - && menv[number].type == MONS_KRAKEN) - { - menv[number].hurt(&you, damage, flavour); - } - } - else if (type == MONS_BUSH && flavour == BEAM_FIRE - && damage>8 && x_chance_in_y(damage, 20)) - { - place_cloud(CLOUD_FIRE, pos(), 20+random2(15), whose, 5); - } -} - -///////////////////////////////////////////////////////////////////////// -// mon_enchant - -static const char *enchant_names[] = -{ - "none", "slow", "haste", "might", "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", "magic-vulnerable", - "soul-ripe", "decay", "hungry", "flopping", "spore-producing", - "downtrodden", "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: - case ENCH_MIGHT: - 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_SLOWLY_DYING: - // This may be a little too direct but the randomization at the end - // of this function is excessive for toadstools. -cao - return (2 * FRESHEST_CORPSE + random2(10)) - * speed_to_duration(mons->speed) * mons->speed / 10; - 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; - } - - cturn = std::max(2, cturn); - - int raw_duration = (cturn * speed_to_duration(mons->speed)); - raw_duration = std::max(15, fuzz_value(raw_duration, 60, 40)); - - 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 @foe_god@ and @god_is@ with foe's god name. // // Atheists get "You"/"you", and worshippers of nameless gods get "Your -- cgit v1.2.3-54-g00ecf