/*
* File: monster.cc
* Summary: Monsters class methods
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "areas.h"
#include "beam.h"
#include "cloud.h"
#include "coordit.h"
#include "delay.h"
#include "dgnevent.h"
#include "dgn-shoals.h"
#include "directn.h"
#include "env.h"
#include "fight.h"
#include "fprop.h"
#include "ghost.h"
#include "goditem.h"
#include "itemname.h"
#include "items.h"
#include "kills.h"
#include "misc.h"
#include "mon-abil.h"
#include "mon-behv.h"
#include "mon-place.h"
#include "terrain.h"
#include "mgen_data.h"
#include "coord.h"
#include "mon-stuff.h"
#include "mon-transit.h"
#include "random.h"
#include "religion.h"
#include "shopping.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "shout.h"
#include "xom.h"
#include <algorithm>
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), base_monster(MONS_NO_MONSTER),
number(0), colour(BLACK), foe_memory(0), shield_blocks(0),
god(GOD_NO_GOD), ghost(), seen_context(""), props()
{
travel_path.clear();
if (crawl_state.arena)
foe = MHITNOT;
}
// 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 = "";
props.clear();
}
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;
props = mon.props;
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_TEMP_PACIF))
return ATT_GOOD_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 (mons_base_type(this) == MONS_KRAKEN && _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);
size_type ret = (e ? e->size : SIZE_MEDIUM);
// Slime creature size is increased by the number merged.
if (type == MONS_SLIME_CREATURE)
{
if (number == 2)
ret = SIZE_MEDIUM;
else if (number == 3)
ret = SIZE_LARGE;
else if (number == 4)
ret = SIZE_BIG;
else if (number == 5)
ret = SIZE_GIANT;
}
return (ret);
}
int monsters::body_weight(bool /*base*/) const
{
int mclass = type;
switch (mclass)
{
case MONS_SPECTRAL_THING:
case MONS_SPECTRAL_WARRIOR:
case MONS_ELECTRIC_GOLEM:
case MONS_RAKSHASA_FAKE:
case MONS_MARA_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))
{
weight = actor::body_weight();
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:
case MONS_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;
// Slime creature weight is multiplied by the number merged.
if (type == MONS_SLIME_CREATURE && number > 1)
weight *= number;
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 && attk.type != AT_WEAP_ONLY)
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 && weap1->cursed())
num_cursed++;
if (weap2 && weap2->cursed())
num_cursed++;
if (_shield && _shield->cursed())
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(item.is_valid());
// 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. kobolds, goblins) 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);
// Undead and demonic monsters won't use holy weapons.
if (undead_or_demonic() && is_holy_item(item))
return (false);
// Holy monsters and monsters that are gifts of good gods won't
// use unholy weapons.
if ((is_holy() || is_good_god(god)) && is_unholy_item(item))
return (false);
// Holy monsters that aren't gifts of chaotic gods and monsters
// that are gifts of good gods or Fedhas won't use potentially
// evil weapons.
if (((is_holy() && !is_chaotic_god(god))
|| (is_good_god(god) || god == GOD_FEDHAS))
&& is_potentially_evil_item(item))
{
return (false);
}
// Holy monsters and monsters that are gifts of good gods or
// Fedhas won't use evil weapons.
if (((is_holy() || is_good_god(god)) || god == GOD_FEDHAS)
&& 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 (((is_holy() && !is_chaotic_god(god)) || is_good_god(god))
&& is_chaotic_item(item))
{
return (false);
}
// Monsters that are gifts of Zin won't use unclean weapons.
if (god == GOD_ZIN && is_unclean_item(item))
return (false);
}
return (true);
}
bool monsters::can_throw_large_rocks() const
{
return (type == MONS_STONE_GIANT
|| ::mons_species(type) == MONS_CYCLOPS
|| ::mons_species(type) == MONS_OGRE);
}
bool monsters::can_speak()
{
// Priest and wizard monsters can always speak.
if (is_priest() || is_actual_spellcaster())
return (true);
// Silent or non-sentient monsters can't use the original speech.
if (mons_intel(this) < I_NORMAL
|| mons_shouts(type) == S_SILENT)
{
return (false);
}
// Does it have the proper vocal equipment?
const mon_body_shape shape = get_mon_shape(this);
return (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA);
}
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);
}
void monsters::bind_spell_flags()
{
// Bind spellcaster / priest flags from the base type. These may be
// overridden by vault defs for individual monsters.
// Alas, we don't know if the mon is zombified at the moment, if it is,
// the flags will be removed later.
if (mons_class_flag(type, M_SPELLCASTER))
flags |= MF_SPELLCASTER;
if (mons_class_flag(type, M_ACTUAL_SPELLS))
flags |= MF_ACTUAL_SPELLS;
if (mons_class_flag(type, M_PRIEST))
flags |= MF_PRIEST;
}
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 (!mon->friendly() && 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));
}
// Stones are allowed even without launcher.
if (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 (brand == SPWPN_EVASION)
ev += 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 (brand == SPWPN_EVASION)
ev -= 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(item.is_valid());
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(),
mindex()),
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(),
mindex()),
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;
}
if (pitem->flags & ISFLAG_SUMMONED)
{
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 (need_message(near))
{
mprf("%s drops %s.", name(DESC_CAP_THE).c_str(),
pitem->name(DESC_NOCAP_A).c_str());
}
if (!move_item_to_grid(&item_index, pos(), swimming()))
{
// Re-equip item if we somehow failed to drop it.
if (was_unequipped)
equip(*pitem, eslot, near);
return (false);
}
if (friendly() && item_index != NON_ITEM)
{
// move_item_to_grid could change item_index, so
// update pitem.
pitem = &mitm[item_index];
pitem->flags |= ISFLAG_DROPPED_BY_ALLY;
}
}
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);
case SPMSL_REAPING:
return (bow_brand != SPWPN_HOLY_WRATH);
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_ANGEL)
return (weapon.sub_type == WPN_HOLY_SCOURGE);
if (monster->type == MONS_DAEVA)
return (weapon.sub_type == WPN_HOLY_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_VORPAL: // deliberate
case SPWPN_PROTECTION: // fall through
case SPWPN_EVASION: return 1;
default: return 2;
}
}
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 && item.quantity == 1)
{
// 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 the weapon is a stack of throwing weaons, the monster
// will not use the stack as their primary melee weapon.
if (item.quantity != 1 && i == MSLOT_WEAPON)
continue;
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);
// Spellcasters shouldn't bother with missiles.
if (mons_has_ranged_spell(this, true, false))
return (false);
// 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) || friendly() && 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_genus(type) == MONS_NAGA)
eq = EQ_BODY_ARMOUR;
break;
case ARM_CENTAUR_BARDING:
if (::mons_species(type) == MONS_CENTAUR
|| ::mons_species(type) == MONS_YAKTAUR)
{
eq = EQ_BODY_ARMOUR;
}
break;
// And another hack or two...
case ARM_WIZARD_HAT:
if (type == MONS_GASTRONOK)
eq = EQ_BODY_ARMOUR;
break;
case ARM_CLOAK:
if (type == MONS_MAURICE
|| type == MONS_NIKOLA
|| type == MONS_CRAZY_YIUF)
{
eq = EQ_BODY_ARMOUR;
}
break;
case ARM_GLOVES:
if (type == MONS_NIKOLA)
eq = EQ_SHIELD;
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 (this->caught() && 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)
&& (!friendly() || 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 ((is_holy() || 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 ((is_holy() || 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 (!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.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT)
return (false);
// Holy monsters and worshippers of good gods won't pick up evil
// miscellaneous items.
if ((is_holy() || 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 mon-stuff.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)
|| friendly() && 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 (friendly())
{
// 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 (!friendly()
&& (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.
//
// Don't switch to alt weapon if it's a stack of throwing weapons.
if (alt && !is_range_weapon(*alt) && alt->quantity == 1
|| weap && !alt && type != MONS_STATUE)
{
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)
{
int colour_offset = -1;
if (mon->type == MONS_UGLY_THING || mon->type == MONS_VERY_UGLY_THING)
colour_offset = ugly_thing_colour_offset(mon->colour);
if (colour_offset == -1)
return ("buggy");
return (ugly_colour_names[colour_offset]);
}
static std::string _invalid_monster_str(monster_type type)
{
std::string str = "INVALID MONSTER ";
switch (type)
{
case NUM_MONSTERS:
return (str + "NUM_MONSTERS");
case MONS_NO_MONSTER:
return (str + "MONS_NO_MONSTER");
case MONS_PLAYER:
return (str + "MONS_PLAYER");
case RANDOM_DRACONIAN:
return (str + "RANDOM_DRACONIAN");
case RANDOM_BASE_DRACONIAN:
return (str + "RANDOM_BASE_DRACONIAN");
case RANDOM_NONBASE_DRACONIAN:
return (str + "RANDOM_NONBASE_DRACONIAN");
case WANDERING_MONSTER:
return (str + "WANDERING_MONSTER");
default:
break;
}
str += make_stringf("#%d", (int) type);
if (type < 0)
return (str);
if (type > NUM_MONSTERS)
{
str += make_stringf(" (NUM_MONSTERS + %d)",
int (NUM_MONSTERS - type));
return (str);
}
int i;
monster_type new_type;
for (i = 0; true; i++)
{
new_type = (monster_type) ( ((int) type) - i);
if (invalid_monster_type(new_type))
continue;
break;
}
str += make_stringf(" (%s + %d)",
mons_type_name(new_type, DESC_PLAIN).c_str(),
i);
return (str);
}
static std::string _str_monam(const monsters& mon, description_level_type desc,
bool force_seen)
{
monster_type type = mon.type;
if (!crawl_state.arena && you.misled())
type = mon.get_mislead_type();
if (type == MONS_NO_MONSTER)
return ("DEAD MONSTER");
else if (invalid_monster_type(type) && type != MONS_PROGRAM_BUG)
return _invalid_monster_str(type);
const bool arena_submerged = crawl_state.arena && !force_seen
&& mon.submerged();
// Handle non-visible case first.
if (!force_seen && !mon.observable() && !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(type))
return (get_mimic_item(&mon).name(desc));
if (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(type)->name);
if (type == MONS_PLAYER_GHOST)
{
if (mon.is_summoned())
return (apostrophise(mon.mname) + " illusion");
else
return (apostrophise(mon.mname) + " ghost");
}
// Some monsters might want the name of a different creature.
monster_type nametype = type;
// Tack on other prefixes.
switch (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
&& !testbits(mon.flags, MF_NAME_DESCRIPTOR))
{
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() || testbits(mon.flags, MF_NAME_DESCRIPTOR))
|| mons_genus(nametype) == MONS_HYDRA))
{
const bool use_your = mon.friendly();
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() || (testbits(mon.flags, MF_NAME_DESCRIPTOR)
&& !testbits(mon.flags, MF_NAME_DEFINITE)))
result = "A ";
else
result = "The ";
break;
case DESC_NOCAP_A:
if (mon.mname.empty() || (testbits(mon.flags, MF_NAME_DESCRIPTOR)
&& !testbits(mon.flags, MF_NAME_DEFINITE)))
result = "a ";
else
result = "the ";
break;
case DESC_PLAIN:
default:
break;
}
}
if (arena_submerged)
result += "submerged ";
// Tack on other prefixes.
switch (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 (type == MONS_SLIME_CREATURE && desc != DESC_DBNAME)
{
ASSERT(mon.number <= 5);
const char* cardinals[] = {"buggy ", "", "large ", "very large ",
"enormous ", "titanic "};
result += cardinals[mon.number];
}
if (type == MONS_BALLISTOMYCETE && desc != DESC_DBNAME)
result += mon.number ? "active " : "";
// 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 += _invalid_monster_str(nametype);
else
result += get_monster_data(nametype)->name;
}
// Add suffixes.
switch (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(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) && mon.is_shapeshifter())
{
// If momentarily in original form, don't display "shaped
// shifter".
if (mons_genus(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 || observable())
|| 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;
if (flag == MF_NAME_REPLACE && !testbits(flags, MF_NAME_DESCRIPTOR))
{
switch(desc)
{
case DESC_CAP_THE:
case DESC_CAP_A:
case DESC_CAP_YOUR:
title = uppercase_first(title);
break;
default:
break;
}
}
int _type = mons_is_zombified(this) ? base_monster : type;
if (!crawl_state.arena && you.misled())
_type = get_mislead_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_ADJECTIVE)
{
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);
}
}
if (flag == MF_NAME_ADJECTIVE)
title = apply_description(desc, title);
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:
case MONS_GOLDEN_EYE:
*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 (this - menv.buffer());
}
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);
set_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 */)
{
}
// Sends a monster into a frenzy.
void monsters::go_frenzy()
{
if (!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);
// store the attitude for later retrieval
props["old_attitude"] = short(attitude);
attitude = ATT_NEUTRAL;
add_ench(mon_enchant(ENCH_INSANE, 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, " flies into a frenzy!"))
// Xom likes monsters going insane.
xom_is_stimulated(friendly() ? 32 : 128);
}
void monsters::go_berserk(bool /* intentional */, bool /* potion */)
{
if (!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(friendly() ? 32 : 128);
}
void monsters::expose_to_element(beam_type flavour, int strength)
{
switch (flavour)
{
case BEAM_COLD:
if (mons_class_flag(type, M_COLD_BLOOD) && res_cold() <= 0
&& coinflip())
{
slow_down(this, strength);
}
break;
default:
break;
}
}
void monsters::banish(const std::string &)
{
coord_def old_pos = pos();
if (!silenced(pos()) && can_speak())
simple_monster_message(this, (" screams as " + pronoun(PRONOUN_NOCAP)
+ " is devoured by a tear in reality.").c_str(),
MSGCH_BANISHMENT);
else
simple_monster_message(this, " is devoured by a tear in reality.",
MSGCH_BANISHMENT);
monster_die(this, KILL_RESET, NON_MONSTER);
place_cloud(CLOUD_TLOC_ENERGY, old_pos, 5 + random2(8), KC_OTHER);
for (adjacent_iterator ai(old_pos); ai; ++ai)
if (!feat_is_solid(grd(*ai)) && env.cgrid(*ai) == EMPTY_CLOUD
&& coinflip())
{
place_cloud(CLOUD_TLOC_ENERGY, *ai, 1 + random2(8), KC_OTHER);
}
}
bool monsters::has_spells() const
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (spells[i] != SPELL_NO_SPELL)
return (true);
return (false);
}
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_holy_spell() const
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (is_holy_spell(spells[i]))
return (true);
return (false);
}
bool monsters::has_unholy_spell() const
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (is_unholy_spell(spells[i]))
return (true);
return (false);
}
bool monsters::has_evil_spell() const
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (is_evil_spell(spells[i]))
return (true);
return (false);
}
bool monsters::has_unclean_spell() const
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (is_unclean_spell(spells[i]))
return (true);
return (false);
}
bool monsters::has_chaotic_spell() const
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (is_chaotic_spell(spells[i]))
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 this->has_ench(ENCH_PARALYSIS);
}
bool monsters::cannot_act() const
{
return (paralysed()
|| petrified() && !petrifying());
}
bool monsters::cannot_move() const
{
return (cannot_act() || petrifying());
}
bool monsters::asleep() const
{
return (behaviour == BEH_SLEEP);
}
bool monsters::backlit(bool check_haloed) const
{
return (has_ench(ENCH_CORONA) || has_ench(ENCH_STICKY_FLAME)
|| ((check_haloed) ? haloed() : false));
}
bool monsters::caught() const
{
return this->has_ench(ENCH_HELD);
}
bool monsters::petrified() const
{
return has_ench(ENCH_PETRIFIED);
}
bool monsters::petrifying() const
{
return has_ench(ENCH_PETRIFYING);
}
bool monsters::friendly() const
{
return (attitude == ATT_FRIENDLY || has_ench(ENCH_CHARM));
}
bool monsters::neutral() const
{
return (attitude == ATT_NEUTRAL || has_ench(ENCH_TEMP_PACIF)
|| attitude == ATT_GOOD_NEUTRAL
|| attitude == ATT_STRICT_NEUTRAL);
}
bool monsters::good_neutral() const
{
return (attitude == ATT_GOOD_NEUTRAL || has_ench(ENCH_TEMP_PACIF));
}
bool monsters::strict_neutral() const
{
return (attitude == ATT_STRICT_NEUTRAL);
}
bool monsters::wont_attack() const
{
return (friendly() || good_neutral() || strict_neutral());
}
bool monsters::pacified() const
{
return (attitude == ATT_NEUTRAL && testbits(flags, MF_GOT_HALF_XP));
}
int monsters::shield_bonus() const
{
const item_def *shld = const_cast<monsters*>(this)->shield();
if (shld && get_armour_slot(*shld) == EQ_SHIELD)
{
// 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(actor *attacker)
{
actor::shield_block_succeeded(attacker);
++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, ev_ignore_type evit) const
{
int evasion = ev;
if (evit & EV_IGNORE_HELPLESS)
return (evasion);
if (paralysed() || asleep())
evasion = 0;
else if (caught())
evasion /= (body_size(PSIZE_BODY) + 2);
else if (confused())
evasion /= 2;
return (evasion);
}
bool monsters::heal(int amount, bool max_too)
{
if (mons_is_statue(type))
return (false);
if (amount < 1)
return (false);
else if (!max_too && hit_points == max_hit_points)
return (false);
hit_points += amount;
bool success = true;
if (hit_points > max_hit_points)
{
if (max_too)
{
const monsterentry* m = get_monster_data(type);
const int maxhp =
m->hpdice[0] * (m->hpdice[1] + m->hpdice[2]) + m->hpdice[3];
// Limit HP growth.
if (random2(3 * maxhp) > 2 * max_hit_points)
max_hit_points++;
else
success = false;
}
hit_points = max_hit_points;
}
return (success);
}
mon_holy_type monsters::holiness() const
{
if (testbits(flags, MF_HONORARY_UNDEAD))
return (MH_UNDEAD);
return (mons_class_holiness(type));
}
bool monsters::undead_or_demonic() const
{
const mon_holy_type holi = holiness();
return (holi == MH_UNDEAD || holi == MH_DEMONIC);
}
bool monsters::is_holy() const
{
if (holiness() == MH_HOLY)
return (true);
// Assume that all unknown gods (GOD_NAMELESS) are not holy.
if (is_priest() && is_good_god(god))
return (true);
if (has_holy_spell())
return (true);
return (false);
}
bool monsters::is_unholy() const
{
if (type == MONS_SILVER_STATUE)
return (true);
if (holiness() == MH_DEMONIC)
return (true);
if (has_unholy_spell())
return (true);
return (false);
}
bool monsters::is_evil() const
{
if (holiness() == MH_UNDEAD)
return (true);
// Assume that all unknown gods (GOD_NAMELESS) are evil.
if (is_priest() && (is_evil_god(god) || god == GOD_NAMELESS))
return (true);
if (has_evil_spell())
return (true);
if (has_attack_flavour(AF_DRAIN_XP)
|| has_attack_flavour(AF_VAMPIRIC))
{
return (true);
}
return (false);
}
bool monsters::is_unclean() const
{
if (has_unclean_spell())
return (true);
if (has_attack_flavour(AF_DISEASE)
|| has_attack_flavour(AF_HUNGER)
|| has_attack_flavour(AF_ROT)
|| has_attack_flavour(AF_STEAL)
|| has_attack_flavour(AF_STEAL_FOOD))
{
return (true);
}
return (false);
}
bool monsters::is_chaotic() const
{
if (type == MONS_UGLY_THING || type == MONS_VERY_UGLY_THING
|| type == MONS_CRAZY_YIUF)
{
return (true);
}
if (is_shapeshifter())
return (true);
// Assume that all unknown gods (GOD_NAMELESS) are not chaotic.
if (is_priest() && is_chaotic_god(god))
return (true);
if (has_chaotic_spell())
return (true);
if (has_attack_flavour(AF_MUTATE)
|| has_attack_flavour(AF_KLOWN)
|| has_attack_flavour(AF_CHAOS))
{
return (true);
}
return (false);
}
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 (undead_or_demonic()
|| holi == MH_NONLIVING
|| holi == MH_PLANT)
{
res += 1;
}
return (res);
}
int monsters::res_water_drowning() const
{
const int res = res_asphyx();
if (res)
return res;
switch (mons_habitat(this))
{
case HT_WATER:
case HT_AMPHIBIOUS_WATER:
return 1;
default:
return 0;
}
}
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 (mons_is_insubstantial(type))
res += 1;
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 (undead_or_demonic())
return (-2);
if (is_evil())
return (-1);
if (is_holy()
|| is_good_god(god)
|| neutral()
|| 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);
}
int monsters::res_magic() const
{
if (mons_immune_magic(this))
return MAG_IMMUNE;
int u = (get_monster_data(this->type))->resist_magic;
// Negative values get multiplied with monster hit dice.
if (u < 0)
u = this->hit_dice * -u * 4 / 3;
// Randarts have a multiplicative effect.
u *= (scan_mon_inv_randarts(this, ARTP_MAGIC) + 100);
u /= 100;
// ego armour resistance
const int armour = this->inv[MSLOT_ARMOUR];
const int _shield = this->inv[MSLOT_SHIELD];
if (armour != NON_ITEM
&& get_armour_ego_type( mitm[armour] ) == SPARM_MAGIC_RESISTANCE )
{
u += 30;
}
if (_shield != NON_ITEM
&& get_armour_ego_type( mitm[_shield] ) == SPARM_MAGIC_RESISTANCE )
{
u += 30;
}
if (this->has_ench(ENCH_LOWERED_MR))
u /= 2;
return (u);
}
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, 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() || 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 (alive())
{
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;
}
if (flavour == BEAM_NUKE || flavour == BEAM_DISINTEGRATION)
{
if (can_bleed())
blood_spray(pos(), id(), amount / 5);
if (!alive())
flags |= MF_EXPLODE_KILL;
}
// Allow the victim to exhibit passive damage behaviour (royal
// jelly).
kill_category whose = (agent == NULL) ? KC_OTHER :
(agent->atype() == ACT_PLAYER) ? KC_YOU :
((monsters*)agent)->friendly() ? 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;
// Summoned player ghosts are already given a position; calling this
// in those instances will cause a segfault. Instead, check to see
// if we have a home first. {due}
if (!in_bounds(pos()))
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 and current 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::dancing_weapon_init()
{
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))
&& !is_summoned());
}
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::can_use_spells() const
{
return (flags & MF_SPELLCASTER);
}
bool monsters::is_priest() const
{
return (flags & MF_PRIEST);
}
bool monsters::is_actual_spellcaster() const
{
return (flags & MF_ACTUAL_SPELLS);
}
bool monsters::is_shapeshifter() const
{
return (has_ench(ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER));
}
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 || 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_INSANE:
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 (pacified())
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 (!monster_at(mon->pos()))
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(you.pos()); ai; ++ai)
if (!actor_at(*ai)
&& 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(you.pos()); ai; ++ai)
{
if (!monster_at(*ai)
&& 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_TIDE:
shoals_release_tide(this);
break;
case ENCH_INSANE:
attitude = static_cast<mon_attitude_type>(props["old_attitude"].get_short());
// deliberate fall through
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_SWIFT:
if (!quiet)
simple_monster_message(this, " is no longer moving somewhat 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_TEMP_PACIF:
if (!quiet)
simple_monster_message(this, (" seems to come to "
+ pronoun(PRONOUN_NOCAP_POSSESSIVE) + " senses.").c_str());
// Yeah, this _is_ offensive to Zin, but hey, he deserves it (1KB).
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 (!petrified())
break;
if (!quiet)
simple_monster_message(this, " stops moving altogether!");
behaviour_event(this, ME_EVAL);
break;
case ENCH_FEAR:
if (holiness() == MH_NONLIVING || 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_CORONA:
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 (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_CORONA:
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_SWIFT:
case ENCH_BATTLE_FRENZY: case ENCH_TEMP_PACIF:
case ENCH_LOWERED_MR: case ENCH_SOUL_RIPE:
lose_ench_levels(i->second, levels);
break;
case ENCH_INSANE:
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);
monster_blink(this, true);
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_INSANE:
if (decay_enchantment(me))
{
simple_monster_message(this, " is no longer in an insane frenzy.");
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_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_SWIFT:
case ENCH_MIGHT:
case ENCH_FEAR:
case ENCH_PARALYSIS:
case ENCH_TEMP_PACIF:
case ENCH_PETRIFYING:
case ENCH_PETRIFIED:
case ENCH_SICK:
case ENCH_CORONA:
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) || cannot_act() || 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
&& !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 (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(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);
dprf("sticky flame damage: %d", dam);
// 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 (you.see_cell(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:
// Reduce the timer, if that means we lose the enchantment then
// spawn a spore and re-add the enchantment
if(decay_enchantment(me))
{
// Search for an open adjacent square to place a spore on
int idx[] = {0, 1, 2, 3, 4, 5, 6, 7};
std::random_shuffle(idx, idx + 8);
bool re_add = true;
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 = SAME_ATTITUDE(this);
int rc = create_monster(mgen_data(MONS_GIANT_SPORE,
created_behavior,
this,
0,
0,
adjacent,
MHITNOT,
MG_FORCE_PLACE));
if (rc != -1)
{
env.mons[rc].behaviour = BEH_WANDER;
env.mons[rc].number = 20;
if (you.see_cell(adjacent) && you.see_cell(pos()))
mpr("A ballistomycete spawns a giant spore.");
// Decrease the count and maybe become inactive
// again
if (this->number)
{
this->number--;
if (this->number == 0)
{
this->colour = MAGENTA;
this->del_ench(ENCH_SPORE_PRODUCTION);
re_add = false;
}
}
}
break;
}
}
// Re-add the enchantment (this resets the spore production
// timer).
if (re_add)
this->add_ench(ENCH_SPORE_PRODUCTION);
}
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
{
const mon_enchant abj = get_ench(ENCH_ABJ);
if (abj.ench == ENCH_NONE)
{
if (duration != NULL)
*duration = -1;
if (summon_type != NULL)
*summon_type = 0;
return (false);
}
if (duration != NULL)
*duration = abj.duration;
const mon_enchant summ = get_ench(ENCH_SUMMON);
if (summ.ench == ENCH_NONE)
{
if (summon_type != NULL)
*summon_type = 0;
return (true);
}
if (summon_type != NULL)
*summon_type = summ.degree;
switch (summ.degree)
{
// Temporarily dancing weapons are really there.
case SPELL_TUKIMAS_DANCE:
// A corpse/skeleton which was temporarily animated.
case SPELL_ANIMATE_DEAD:
case SPELL_ANIMATE_SKELETON:
// Fire vortices are made from real fire.
case SPELL_FIRE_STORM:
// Clones aren't really summoned (though their equipment might be).
case MON_SUMM_CLONE:
// Nor are body parts.
case SPELL_KRAKEN_TENTACLES:
// Some object which was animated, and thus not really summoned.
case MON_SUMM_ANIMATE:
return (false);
}
return (true);
}
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 (friendly() ? 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 (friendly() ? 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 (mons_intel(this) == I_PLANT)
return (false);
if (berserk() || has_ench(ENCH_FATIGUE))
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::frenzied() const
{
return (has_ench(ENCH_INSANE));
}
bool monsters::berserk() const
{
return (has_ench(ENCH_BERSERK) || has_ench(ENCH_INSANE));
}
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 (p == pos())
return (true);
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::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));
}
static bool _mons_is_icy(int mc)
{
return (mc == MONS_ICE_BEAST
|| mc == MONS_SIMULACRUM_SMALL
|| mc == MONS_SIMULACRUM_LARGE
|| mc == MONS_ICE_STATUE);
}
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 = you.see_cell(pos());
const bool see_old = you.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,
killer_type killer,
int killernum)
{
if (oldpos != pos())
dungeon_events.fire_position_event(DET_MONSTER_MOVED, pos());
if (alive() && mons_habitat(this) == HT_WATER
&& !feat_is_watery( grd(pos()) ) && !has_ench(ENCH_AQUATIC_LAND))
{
add_ench(ENCH_AQUATIC_LAND);
}
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);
}
// This may have been called via dungeon_terrain_changed instead
// of by the monster moving move, in that case grd(oldpos) will
// be the current position that became watery.
else
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(), killer, killernum);
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.pgrid(pos());
if (prop & FPROP_BLOODY)
{
monster_type genus = mons_genus(type);
if (genus == MONS_JELLY || genus == MONS_GIANT_SLUG)
{
prop &= ~FPROP_BLOODY;
if (you.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(false);
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 (!pacified())
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::hibernate(int)
{
if (!can_hibernate())
return;
behaviour = BEH_SLEEP;
add_ench(ENCH_SLEEPY);
add_ench(ENCH_SLEEP_WARY);
}
void monsters::put_to_sleep(actor *attacker, int strength)
{
if (has_ench(ENCH_SLEEPY))
return;
behaviour = BEH_SLEEP;
add_ench(ENCH_SLEEPY);
}
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);
}
monster_type monsters::get_mislead_type() const
{
if (props.exists("mislead_as"))
return static_cast<monster_type>(props["mislead_as"].get_short());
else
return type;
}
int monsters::action_energy(energy_use_type et) const
{
bool swift = has_ench(ENCH_SWIFT);
if (const monsterentry *me = find_monsterentry())
{
const mon_energy_usage &mu = me->energy_usage;
switch (et)
{
case EUT_MOVE: return mu.move - (swift ? 2 : 0);
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) - (swift ? 2 : 0);
else
return mu.swim - (swift ? 2 : 0);
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(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
|| type == MONS_GASTRONOK)
{
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 !berserk()
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) || !friendly()));
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 (type == MONS_SIXFIRHY && flavour == BEAM_ELECTRICITY)
{
if (!alive()) // overcharging is deadly
simple_monster_message(this, " explodes in an explosion of sparks!");
else if (heal(damage*2, false))
simple_monster_message(this, " seems to be charged up!");
return;
}
if (!alive())
return;
// The royal jelly objects to taking damage and will SULK. :-)
if (type == MONS_ROYAL_JELLY)
{
int lobes = hit_points / 12;
int oldlobes = (hit_points + damage) / 12;
if (lobes == oldlobes)
return;
mon_acting mact(this);
const int tospawn = oldlobes - lobes;
#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, this, 0, 0,
jpos, foe, 0, god));
if (nmons != -1 && nmons != NON_MONSTER)
{
// Don't allow milking the royal jelly.
menv[nmons].flags |= MF_NO_REWARD;
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)
&& mons_base_type(&menv[number]) == MONS_KRAKEN)
{
menv[number].hurt(&you, damage, flavour);
// We could be removed, undo this or certain post-hit effects will cry.
if (invalid_monster(this))
{
type = MONS_KRAKEN_TENTACLE;
hit_points = -1;
}
}
}
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", "berserk", "haste", "might", "fatigue", "slow", "fear",
"confusion", "invis", "poison", "rot", "summon", "abj", "corona",
"charm", "sticky_flame", "glowing_shapeshifter", "shapeshifter", "tp",
"sleep_wary", "submerged", "short_lived", "paralysis", "sick",
"sleepy", "held", "battle_frenzy", "temp_pacif", "petrifying",
"petrified", "lowered_mr", "soul_ripe", "slowly_dying", "eat_items",
"aquatic_land", "spore_production", "slouch", "swift", "tide",
"insane", "buggy"
};
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_SWIFT:
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_CORONA:
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_SPORE_PRODUCTION:
// This is used as a simple timer, when the enchantment runs out
// the monster will create a giant spore.
return (random_range(475, 525) * 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;
}