summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crawl-ref/source/makefile.obj1
-rw-r--r--crawl-ref/source/mon-util.cc5663
-rw-r--r--crawl-ref/source/mon-util.h6
-rw-r--r--crawl-ref/source/monster.cc5611
4 files changed, 5645 insertions, 5636 deletions
diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj
index 4b1bada8b0..d9ffb8bd5a 100644
--- a/crawl-ref/source/makefile.obj
+++ b/crawl-ref/source/makefile.obj
@@ -89,6 +89,7 @@ mon-pick.o \
mon-util.o \
monplace.o \
monspeak.o \
+monster.o \
monstuff.o \
mstuff2.o \
mt19937ar.o \
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 <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <cctype>
-#include <sstream>
-#include <algorithm>
-
-#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<monster_type> 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,1208 +3033,6 @@ 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
-{
- // 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))
- {
- case HT_WATER:
- case HT_LAVA:
- return (false);
- default:
- break;
- }
-
- // 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
-{
- int mclass = type;
-
- switch (mclass)
- {
- case MONS_SPECTRAL_THING:
- case MONS_SPECTRAL_WARRIOR:
- case MONS_ELECTRIC_GOLEM:
- case MONS_RAKSHASA_FAKE:
- return (0);
-
- case MONS_ZOMBIE_SMALL:
- case MONS_ZOMBIE_LARGE:
- case MONS_SKELETON_SMALL:
- case MONS_SKELETON_LARGE:
- case MONS_SIMULACRUM_SMALL:
- case MONS_SIMULACRUM_LARGE:
- mclass = number;
- break;
-
- default:
- break;
- }
-
- int weight = mons_weight(mclass);
-
- // weight == 0 in the monster entry indicates "no corpse". Can't
- // use CE_NOCORPSE, because the corpse-effect field is used for
- // corpseless monsters to indicate what happens if their blood
- // is sucked. Grrrr.
- if (weight == 0 && !mons_is_insubstantial(type))
- {
- const monsterentry *entry = get_monster_data(mclass);
- switch (entry->size)
- {
- case SIZE_TINY:
- weight = 150;
- break;
- case SIZE_LITTLE:
- weight = 300;
- break;
- case SIZE_SMALL:
- weight = 425;
- break;
- case SIZE_MEDIUM:
- weight = 550;
- break;
- case SIZE_LARGE:
- weight = 1300;
- break;
- case SIZE_BIG:
- weight = 1500;
- break;
- case SIZE_GIANT:
- weight = 1800;
- break;
- case SIZE_HUGE:
- weight = 2200;
- break;
- default:
- mpr("ERROR: invalid monster body weight");
- perror("monsters::body_weight(): invalid monster body weight");
- end(0);
- }
-
- switch (mclass)
- {
- case MONS_IRON_DEVIL:
- weight += 550;
- break;
-
- case MONS_STONE_GOLEM:
- case MONS_EARTH_ELEMENTAL:
- case MONS_CRYSTAL_GOLEM:
- weight *= 2;
- break;
-
- case MONS_IRON_DRAGON:
- case MONS_IRON_GOLEM:
- weight *= 3;
- break;
-
- case MONS_QUICKSILVER_DRAGON:
- case MONS_SILVER_STATUE:
- weight *= 4;
- break;
-
- case MONS_WOOD_GOLEM:
- weight *= 2;
- weight /= 3;
- break;
-
- case MONS_FLYING_SKULL:
- case MONS_CURSE_SKULL:
- case MONS_SKELETAL_DRAGON:
- case MONS_SKELETAL_WARRIOR:
- weight /= 2;
- break;
-
- case MONS_SHADOW_FIEND:
- case MONS_SHADOW_IMP:
- case MONS_SHADOW_DEMON:
- weight /= 3;
- break;
- }
-
- switch (monster_symbols[mclass].glyph)
- {
- case 'L':
- weight /= 2;
- break;
-
- case 'p':
- weight = 0;
- break;
- }
- }
-
- if (type == MONS_SKELETON_SMALL || type == MONS_SKELETON_LARGE)
- weight /= 2;
-
- return (weight);
-}
-
-int monsters::total_weight() const
-{
- int burden = 0;
-
- for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
- if (inv[i] != NON_ITEM)
- burden += item_mass(mitm[inv[i]]) * mitm[inv[i]].quantity;
-
- return (body_weight() + burden);
-}
-
-int monsters::damage_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<mon_inv_type>(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<mon_inv_type>(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<mon_inv_type>(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)
@@ -4759,3505 +3069,6 @@ mon_inv_type item_to_mslot(const item_def &item)
}
}
-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<mon_inv_type>(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<potion_type>(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<std::string> tmname(
- const_cast<monsters*>(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<monster_type>(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<monsters*>(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<int>(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<enchant_type>(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<enchant_type>(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;
- }
-
- speed_increment -= energy_loss;
-}
-
monster_type royal_jelly_ejectable_monster()
{
return static_cast<monster_type>(
@@ -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
diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h
index cf373ded5a..c6470c9169 100644
--- a/crawl-ref/source/mon-util.h
+++ b/crawl-ref/source/mon-util.h
@@ -632,7 +632,7 @@ int mons_weight(int mc);
int mons_class_base_speed(int mc);
int mons_class_zombie_base_speed(int zombie_base_mc);
int mons_base_speed(const monsters *mon);
-
+int mons_real_base_speed(int mc);
// last updated 12may2000 {dlb}
/* ***********************************************************************
@@ -841,6 +841,7 @@ bool monster_senior(const monsters *first, const monsters *second,
monster_type draco_subspecies(const monsters *mon);
unsigned char ugly_thing_random_colour();
int ugly_thing_colour_offset(const monsters *mon);
+std::string draconian_colour_name(monster_type mtype);
monster_type draconian_colour_by_name(const std::string &colour);
monster_type random_monster_at_grid(const coord_def& p);
@@ -890,4 +891,7 @@ bool mons_can_pass(const monsters *mon, dungeon_feature_type grid);
mon_inv_type equip_slot_to_mslot(equipment_type eq);
mon_inv_type item_to_mslot(const item_def &item);
+
+int scan_mon_inv_randarts(const monsters *mon,
+ artefact_prop_type ra_prop);
#endif
diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc
new file mode 100644
index 0000000000..a12666dc2e
--- /dev/null
+++ b/crawl-ref/source/monster.cc
@@ -0,0 +1,5611 @@
+/*
+ * File: monster.cc
+ * Summary: Monsters class methods
+ * Written by: Linley Henzell
+ */
+
+#include "AppHdr.h"
+
+#include "beam.h"
+#include "cloud.h"
+#include "delay.h"
+#include "dgnevent.h"
+#include "directn.h"
+#include "fight.h"
+#include "ghost.h"
+#include "goditem.h"
+#include "items.h"
+#include "kills.h"
+#include "misc.h"
+#include "monplace.h"
+#include "monstuff.h"
+#include "mstuff2.h"
+#include "mtransit.h"
+#include "random.h"
+#include "religion.h"
+#include "shopping.h" // for item values
+#include "spells3.h"
+#include "state.h"
+#include "traps.h"
+#include "tutorial.h"
+#include "view.h"
+#include "xom.h"
+
+struct mon_spellbook
+{
+ mon_spellbook_type type;
+ spell_type spells[NUM_MONSTER_SPELL_SLOTS];
+};
+
+static mon_spellbook mspell_list[] = {
+#include "mon-spll.h"
+};
+
+// Macro that saves some typing, nothing more.
+#define smc get_monster_data(mc)
+
+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);
+}
+
+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 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 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
+{
+ // 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))
+ {
+ case HT_WATER:
+ case HT_LAVA:
+ return (false);
+ default:
+ break;
+ }
+
+ // 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
+{
+ int mclass = type;
+
+ switch (mclass)
+ {
+ case MONS_SPECTRAL_THING:
+ case MONS_SPECTRAL_WARRIOR:
+ case MONS_ELECTRIC_GOLEM:
+ case MONS_RAKSHASA_FAKE:
+ return (0);
+
+ case MONS_ZOMBIE_SMALL:
+ case MONS_ZOMBIE_LARGE:
+ case MONS_SKELETON_SMALL:
+ case MONS_SKELETON_LARGE:
+ case MONS_SIMULACRUM_SMALL:
+ case MONS_SIMULACRUM_LARGE:
+ mclass = number;
+ break;
+
+ default:
+ break;
+ }
+
+ int weight = mons_weight(mclass);
+
+ // weight == 0 in the monster entry indicates "no corpse". Can't
+ // use CE_NOCORPSE, because the corpse-effect field is used for
+ // corpseless monsters to indicate what happens if their blood
+ // is sucked. Grrrr.
+ if (weight == 0 && !mons_is_insubstantial(type))
+ {
+ const monsterentry *entry = get_monster_data(mclass);
+ switch (entry->size)
+ {
+ case SIZE_TINY:
+ weight = 150;
+ break;
+ case SIZE_LITTLE:
+ weight = 300;
+ break;
+ case SIZE_SMALL:
+ weight = 425;
+ break;
+ case SIZE_MEDIUM:
+ weight = 550;
+ break;
+ case SIZE_LARGE:
+ weight = 1300;
+ break;
+ case SIZE_BIG:
+ weight = 1500;
+ break;
+ case SIZE_GIANT:
+ weight = 1800;
+ break;
+ case SIZE_HUGE:
+ weight = 2200;
+ break;
+ default:
+ mpr("ERROR: invalid monster body weight");
+ perror("monsters::body_weight(): invalid monster body weight");
+ end(0);
+ }
+
+ switch (mclass)
+ {
+ case MONS_IRON_DEVIL:
+ weight += 550;
+ break;
+
+ case MONS_STONE_GOLEM:
+ case MONS_EARTH_ELEMENTAL:
+ case MONS_CRYSTAL_GOLEM:
+ weight *= 2;
+ break;
+
+ case MONS_IRON_DRAGON:
+ case MONS_IRON_GOLEM:
+ weight *= 3;
+ break;
+
+ case MONS_QUICKSILVER_DRAGON:
+ case MONS_SILVER_STATUE:
+ weight *= 4;
+ break;
+
+ case MONS_WOOD_GOLEM:
+ weight *= 2;
+ weight /= 3;
+ break;
+
+ case MONS_FLYING_SKULL:
+ case MONS_CURSE_SKULL:
+ case MONS_SKELETAL_DRAGON:
+ case MONS_SKELETAL_WARRIOR:
+ weight /= 2;
+ break;
+
+ case MONS_SHADOW_FIEND:
+ case MONS_SHADOW_IMP:
+ case MONS_SHADOW_DEMON:
+ weight /= 3;
+ break;
+ }
+
+ switch (mons_char(mclass))
+ {
+ 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);
+}
+
+// Does not check whether the monster can dual-wield - that is the
+// caller's responsibility.
+static int _mons_offhand_weapon_index(const monsters *m)
+{
+ return (m->inv[MSLOT_ALT_WEAPON]);
+}
+
+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<mon_inv_type>(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<mon_inv_type>(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<mon_inv_type>(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()));
+}
+
+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<mon_inv_type>(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<potion_type>(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());
+}
+
+static const char *ugly_colour_names[] = {
+ "red", "brown", "green", "cyan", "purple", "white"
+};
+
+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 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 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<std::string> tmname(
+ const_cast<monsters*>(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<monster_type>(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<monsters*>(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<int>(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<enchant_type>(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<enchant_type>(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);
+}
+
+// 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;
+ }
+
+ speed_increment -= energy_loss;
+}
+
+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;
+}
+
+