#include "AppHdr.h"
#include "monstuff.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#ifdef TARGET_OS_DOS
#include <conio.h>
#endif
#include "externs.h"
#include "arena.h"
#include "artefact.h"
#include "beam.h"
#include "cloud.h"
#include "colour.h"
#include "database.h"
#include "debug.h"
#include "delay.h"
#include "describe.h"
#include "dgnevent.h"
#include "directn.h"
#include "exclude.h"
#include "fight.h"
#include "files.h"
#include "ghost.h"
#include "godabil.h"
#include "hiscores.h"
#include "it_use2.h"
#include "itemname.h"
#include "items.h"
#include "itemprop.h"
#include "kills.h"
#include "los.h"
#include "makeitem.h"
#include "mapmark.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monspeak.h"
#include "mon-util.h"
#include "mutation.h"
#include "mstuff2.h"
#include "notes.h"
#include "player.h"
#include "religion.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "spells3.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "transfor.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "stash.h"
#include "xom.h"
static bool _wounded_damaged(monster_type mon_type);
static bool _handle_special_ability(monsters *monster, bolt & beem);
static bool _handle_pickup(monsters *monster);
static void _handle_behaviour(monsters *monster);
static void _set_nearest_monster_foe(monsters *monster);
static void _mons_in_cloud(monsters *monster);
static bool _mon_can_move_to_pos(const monsters *monster,
const coord_def& delta,
bool just_check = false);
static bool _is_trap_safe(const monsters *monster, const coord_def& where,
bool just_check = false);
static bool _monster_move(monsters *monster);
static spell_type _map_wand_to_mspell(int wand_type);
static bool _is_item_jelly_edible(const item_def &item);
static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move,
bool potentially_blocking);
static coord_def mmov;
static const coord_def mon_compass[8] = {
coord_def(-1,-1), coord_def(0,-1), coord_def(1,-1), coord_def(1,0),
coord_def( 1, 1), coord_def(0, 1), coord_def(-1,1), coord_def(-1,0)
};
static bool immobile_monster[MAX_MONSTERS];
static const std::string _just_seen("just seen");
#define ENERGY_SUBMERGE(entry) (std::max(entry->energy_usage.swim / 2, 1))
void get_mimic_item( const monsters *mimic, item_def &item )
{
ASSERT( mimic != NULL && mons_is_mimic( mimic->type ) );
item.base_type = OBJ_UNASSIGNED;
item.sub_type = 0;
item.special = 0;
item.colour = 0;
item.flags = 0;
item.quantity = 1;
item.plus = 0;
item.plus2 = 0;
item.pos = mimic->pos();
item.link = NON_ITEM;
int prop = 127 * mimic->pos().x + 269 * mimic->pos().y;
rng_save_excursion exc;
seed_rng( prop );
switch (mimic->type)
{
case MONS_WEAPON_MIMIC:
item.base_type = OBJ_WEAPONS;
item.sub_type = (59 * mimic->pos().x + 79 * mimic->pos().y)
% (WPN_MAX_NONBLESSED + 1);
prop %= 100;
if (prop < 20)
make_item_randart(item);
else if (prop < 50)
set_equip_desc(item, ISFLAG_GLOWING);
else if (prop < 80)
set_equip_desc(item, ISFLAG_RUNED);
else if (prop < 85)
set_equip_race(item, ISFLAG_ORCISH);
else if (prop < 90)
set_equip_race(item, ISFLAG_DWARVEN);
else if (prop < 95)
set_equip_race(item, ISFLAG_ELVEN);
break;
case MONS_ARMOUR_MIMIC:
item.base_type = OBJ_ARMOUR;
item.sub_type = (59 * mimic->pos().x + 79 * mimic->pos().y)
% NUM_ARMOURS;
prop %= 100;
if (prop < 20)
make_item_randart(item);
else if (prop < 40)
set_equip_desc(item, ISFLAG_GLOWING);
else if (prop < 60)
set_equip_desc(item, ISFLAG_RUNED);
else if (prop < 80)
set_equip_desc(item, ISFLAG_EMBROIDERED_SHINY);
else if (prop < 85)
set_equip_race(item, ISFLAG_ORCISH);
else if (prop < 90)
set_equip_race(item, ISFLAG_DWARVEN);
else if (prop < 95)
set_equip_race(item, ISFLAG_ELVEN);
break;
case MONS_SCROLL_MIMIC:
item.base_type = OBJ_SCROLLS;
item.sub_type = prop % NUM_SCROLLS;
break;
case MONS_POTION_MIMIC:
item.base_type = OBJ_POTIONS;
item.sub_type = prop % NUM_POTIONS;
break;
case MONS_GOLD_MIMIC:
default:
item.base_type = OBJ_GOLD;
item.quantity = 5 + prop % 30;
break;
}
item_colour(item); }
int get_mimic_colour( const monsters *mimic )
{
ASSERT( mimic != NULL && mons_is_mimic( mimic->type ) );
if (mimic->type == MONS_SCROLL_MIMIC)
return (LIGHTGREY);
else if (mimic->type == MONS_GOLD_MIMIC)
return (YELLOW);
item_def item;
get_mimic_item( mimic, item );
return (item.colour);
}
bool curse_an_item( bool decay_potions, bool quiet )
{
int count = 0;
int item = ENDOFPACK;
for (int i = 0; i < ENDOFPACK; i++)
{
if (!is_valid_item( you.inv[i] ))
continue;
if (you.inv[i].base_type == OBJ_WEAPONS
|| you.inv[i].base_type == OBJ_ARMOUR
|| you.inv[i].base_type == OBJ_JEWELLERY
|| you.inv[i].base_type == OBJ_POTIONS)
{
if (item_cursed( you.inv[i] ))
continue;
if (you.inv[i].base_type != OBJ_POTIONS
&& !you_tran_can_wear(you.inv[i])
&& item_is_equipped(you.inv[i]))
{
continue;
}
if (you.inv[i].base_type == OBJ_POTIONS
&& (!decay_potions || you.inv[i].sub_type == POT_DECAY))
{
continue;
}
count++;
if (one_chance_in( count ))
item = i;
}
}
if (item == ENDOFPACK)
return (false);
if (decay_potions && !quiet) mpr("You feel nervous for a moment...", MSGCH_MONSTER_SPELL);
if (you.inv[item].base_type == OBJ_POTIONS)
{
int amount;
if (you.inv[item].quantity <= 2)
amount = you.inv[item].quantity;
else
amount = 2 + random2(you.inv[item].quantity - 1);
split_potions_into_decay(item, amount);
if (item_value(you.inv[item], true) / amount > 2)
xom_is_stimulated(32 * amount);
}
else
{
do_curse_item( you.inv[item], false );
}
return (true);
}
void monster_drop_ething(monsters *monster, bool mark_item_origins,
int owner_id)
{
const bool hostile_grid = feat_destroys_items(grd(monster->pos()));
bool destroyed = false;
for (int i = NUM_MONSTER_SLOTS - 1; i >= 0; i--)
{
int item = monster->inv[i];
if (item != NON_ITEM)
{
const bool summoned_item =
testbits(mitm[item].flags, ISFLAG_SUMMONED);
if (hostile_grid || summoned_item)
{
item_was_destroyed(mitm[item], monster->mindex());
destroy_item( item );
if (!summoned_item)
destroyed = true;
}
else
{
if (mons_friendly(monster) && is_valid_item(mitm[item]))
mitm[item].flags |= ISFLAG_DROPPED_BY_ALLY;
move_item_to_grid(&item, monster->pos());
if (mark_item_origins && is_valid_item(mitm[item]))
origin_set_monster(mitm[item], monster);
}
monster->inv[i] = NON_ITEM;
}
}
if (destroyed)
mprf(MSGCH_SOUND, feat_item_destruction_message(grd(monster->pos())));
}
monster_type fill_out_corpse(const monsters* monster, item_def& corpse,
bool allow_weightless)
{
ASSERT(!invalid_monster_type(monster->type));
corpse.clear();
int summon_type;
if (mons_is_summoned(monster, NULL, &summon_type)
|| (monster->flags & (MF_BANISHED | MF_HARD_RESET)))
{
return (MONS_NO_MONSTER);
}
monster_type corpse_class = mons_species(monster->type);
if (mons_class_is_zombified(monster->type)
&& (summon_type == SPELL_ANIMATE_DEAD
|| summon_type == SPELL_ANIMATE_SKELETON
|| summon_type == MON_SUMM_ANIMATE))
{
corpse_class = mons_zombie_base(monster);
}
if (corpse_class == MONS_DRACONIAN)
{
if (monster->type == MONS_TIAMAT)
corpse_class = MONS_DRACONIAN;
else
corpse_class = draco_subspecies(monster);
}
if (monster->has_ench(ENCH_SHAPESHIFTER))
corpse_class = MONS_SHAPESHIFTER;
else if (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER))
corpse_class = MONS_GLOWING_SHAPESHIFTER;
if (mons_weight(corpse_class) == 0 && !allow_weightless)
return (MONS_NO_MONSTER);
corpse.flags = 0;
corpse.base_type = OBJ_CORPSES;
corpse.plus = corpse_class;
corpse.plus2 = 0; corpse.sub_type = CORPSE_BODY;
corpse.special = FRESHEST_CORPSE; corpse.quantity = 1;
corpse.orig_monnum = monster->type + 1;
corpse.props[MONSTER_NUMBER] = short(monster->number);
corpse.colour = mons_class_colour(corpse_class);
if (corpse.colour == BLACK)
corpse.colour = monster->colour;
if (!monster->mname.empty())
corpse.props[CORPSE_NAME_KEY] = monster->mname;
else if (mons_is_unique(monster->type))
{
corpse.props[CORPSE_NAME_KEY] = mons_type_name(monster->type,
DESC_PLAIN);
}
return (corpse_class);
}
int place_monster_corpse(const monsters *monster, bool silent,
bool force)
{
if (!in_bounds(monster->pos()))
return (-1);
if (feat_is_wall(grd(monster->pos())))
return (-1);
item_def corpse;
const monster_type corpse_class = fill_out_corpse(monster, corpse);
if (mons_class_is_zombified(monster->type))
force = true;
if (corpse_class == MONS_NO_MONSTER || (!force && coinflip()))
return (-1);
if (feat_destroys_items(grd(monster->pos())))
{
item_was_destroyed(corpse);
return (-1);
}
int o = get_item_slot();
if (o == NON_ITEM)
return (-1);
mitm[o] = corpse;
origin_set_monster(mitm[o], monster);
move_item_to_grid(&o, monster->pos());
if (see_cell(monster->pos()))
{
if (force && !silent)
{
if (you.can_see(monster))
simple_monster_message(monster, " turns back into a corpse!");
else
{
mprf("%s appears out of nowhere!",
mitm[o].name(DESC_CAP_A).c_str());
}
}
const bool poison = (mons_corpse_effect(corpse_class) == CE_POISONOUS
&& player_res_poison() <= 0);
tutorial_dissection_reminder(!poison);
}
return (o);
}
static void _tutorial_inspect_kill()
{
if (Options.tutorial_events[TUT_KILLED_MONSTER])
learned_something_new(TUT_KILLED_MONSTER);
}
#ifdef DGL_MILESTONES
static std::string _milestone_kill_verb(killer_type killer)
{
return (killer == KILL_RESET ? "banished " : "killed ");
}
static void _check_kill_milestone(const monsters *mons,
killer_type killer, int i)
{
if (mons->type == MONS_PLAYER_GHOST)
{
std::string milestone = _milestone_kill_verb(killer) + "the ghost of ";
milestone += get_ghost_description(*mons, true);
milestone += ".";
mark_milestone("ghost", milestone);
}
else if (mons_is_unique(mons->type))
{
mark_milestone("unique",
_milestone_kill_verb(killer)
+ mons->name(DESC_NOCAP_THE, true)
+ ".");
}
}
#endif
static void _give_monster_experience(monsters *victim,
int killer_index, int experience,
bool victim_was_born_friendly)
{
if (invalid_monster_index(killer_index))
return;
monsters *mon = &menv[killer_index];
if (!mon->alive())
return;
if ((!victim_was_born_friendly || !mons_friendly(mon))
&& !mons_aligned(killer_index, monster_index(victim)))
{
if (mon->gain_exp(experience))
{
if (you.religion != GOD_SHINING_ONE && you.religion != GOD_BEOGH
|| player_under_penance()
|| !one_chance_in(3))
{
return;
}
if (you.religion == GOD_SHINING_ONE
&& random2(you.piety) >= piety_breakpoint(0)
|| you.religion == GOD_BEOGH
&& random2(you.piety) >= piety_breakpoint(2))
{
bless_follower(mon);
}
}
}
}
static void _give_adjusted_experience(monsters *monster, killer_type killer,
bool pet_kill, int killer_index,
unsigned int *exp_gain,
unsigned int *avail_gain)
{
const int experience = exper_value(monster);
const bool created_friendly =
testbits(monster->flags, MF_CREATED_FRIENDLY);
const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL);
const bool no_xp = monster->has_ench(ENCH_ABJ) || !experience;
const bool already_got_half_xp = testbits(monster->flags, MF_GOT_HALF_XP);
bool need_xp_msg = false;
if (created_friendly || was_neutral || no_xp)
; else if (YOU_KILL(killer))
{
int old_lev = you.experience_level;
if (already_got_half_xp)
gain_exp( experience / 2, exp_gain, avail_gain );
else
gain_exp( experience, exp_gain, avail_gain );
if (old_lev == you.experience_level)
need_xp_msg = true;
}
else if (pet_kill && !already_got_half_xp)
{
int old_lev = you.experience_level;
gain_exp( experience / 2 + 1, exp_gain, avail_gain );
if (old_lev == you.experience_level)
need_xp_msg = true;
}
if (monster->type == MONS_GIANT_SPORE
|| monster->type == MONS_BALL_LIGHTNING)
{
need_xp_msg = false;
}
if (need_xp_msg
&& exp_gain > 0
&& !you.can_see(monster)
&& !crawl_state.arena)
{
mpr("You feel a bit more experienced.");
}
if (MON_KILL(killer) && !no_xp)
{
_give_monster_experience( monster, killer_index, experience,
created_friendly );
}
}
static bool _is_pet_kill(killer_type killer, int i)
{
if (!MON_KILL(killer))
return (false);
if (i == ANON_FRIENDLY_MONSTER)
return (true);
if (invalid_monster_index(i))
return (false);
const monsters *m = &menv[i];
if (mons_friendly(m)) return (true);
const mon_enchant me = m->get_ench(ENCH_CONFUSION);
return (me.ench == ENCH_CONFUSION
&& (me.who == KC_YOU || me.who == KC_FRIENDLY));
}
static bool _ely_protect_ally(monsters *monster)
{
if (you.religion != GOD_ELYVILON)
return (false);
if (monster->holiness() != MH_NATURAL
&& monster->holiness() != MH_HOLY
|| !mons_friendly(monster)
|| !you.can_see(monster) || !one_chance_in(20))
{
return (false);
}
monster->hit_points = 1;
snprintf(info, INFO_SIZE, " protects %s from harm!%s",
monster->name(DESC_NOCAP_THE).c_str(),
coinflip() ? "" : " You feel responsible.");
simple_god_message(info);
lose_piety(1);
return (true);
}
static bool _ely_heal_monster(monsters *monster, killer_type killer, int i)
{
if (you.religion == GOD_ELYVILON)
return (false);
god_type god = GOD_ELYVILON;
if (!you.penance[god] || !god_hates_your_god(god))
return (false);
const int ely_penance = you.penance[god];
if (mons_friendly(monster) || !one_chance_in(10))
return (false);
if (MON_KILL(killer) && !invalid_monster_index(i))
{
monsters *mon = &menv[i];
if (!mons_friendly(mon) || !one_chance_in(3))
return (false);
if (!mons_near(monster))
return (false);
}
else if (!YOU_KILL(killer))
return (false);
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "monster hp: %d, max hp: %d",
monster->hit_points, monster->max_hit_points);
#endif
monster->hit_points = std::min(1 + random2(ely_penance/3),
monster->max_hit_points);
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "new hp: %d, ely penance: %d",
monster->hit_points, ely_penance);
#endif
snprintf(info, INFO_SIZE, "%s heals %s%s",
god_name(god, false).c_str(),
monster->name(DESC_NOCAP_THE).c_str(),
monster->hit_points * 2 <= monster->max_hit_points ? "." : "!");
god_speaks(god, info);
dec_penance(god, 1 + random2(monster->hit_points/2));
return (true);
}
static bool _yred_enslave_soul(monsters *monster, killer_type killer)
{
if (you.religion == GOD_YREDELEMNUL && mons_enslaved_body_and_soul(monster)
&& mons_near(monster) && killer != KILL_RESET
&& killer != KILL_DISMISSED)
{
yred_make_enslaved_soul(monster, player_under_penance());
return (true);
}
return (false);
}
static bool _beogh_forcibly_convert_orc(monsters *monster, killer_type killer,
int i)
{
if (you.religion == GOD_BEOGH
&& mons_species(monster->type) == MONS_ORC
&& !mons_is_summoned(monster) && !mons_is_shapeshifter(monster)
&& !player_under_penance() && you.piety >= piety_breakpoint(2)
&& mons_near(monster))
{
bool convert = false;
if (YOU_KILL(killer))
convert = true;
else if (MON_KILL(killer) && !invalid_monster_index(i))
{
monsters *mon = &menv[i];
if (is_follower(mon) && !one_chance_in(3))
convert = true;
}
if (convert)
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Death convert attempt on %s, HD: %d, "
"your xl: %d",
monster->name(DESC_PLAIN).c_str(),
monster->hit_dice,
you.experience_level);
#endif
if (random2(you.piety) >= piety_breakpoint(0)
&& random2(you.experience_level) >= random2(monster->hit_dice)
&& random2(monster->hit_dice) > 2)
{
beogh_convert_orc(monster, true, MON_KILL(killer));
return (true);
}
}
}
return (false);
}
static bool _monster_avoided_death(monsters *monster, killer_type killer, int i)
{
if (monster->hit_points < -25
|| monster->hit_points < -monster->max_hit_points
|| monster->max_hit_points <= 0
|| monster->hit_dice < 1)
{
return (false);
}
if (_ely_protect_ally(monster))
return (true);
if (_ely_heal_monster(monster, killer, i))
return (true);
if (_yred_enslave_soul(monster, killer))
return (true);
if (_beogh_forcibly_convert_orc(monster, killer, i))
return (true);
return (false);
}
static bool _slime_vault_in_los()
{
bool in_los = false;
for (int x = 0; x < GXM && !in_los; ++x)
{
for (int y = 0; y < GYM; ++y)
{
if ((grd[x][y] == DNGN_STONE_WALL
|| grd[x][y] == DNGN_CLEAR_STONE_WALL)
&& see_cell(x, y))
{
in_los = true;
break;
}
}
}
return (in_los);
}
static bool _slime_vault_to_glass(bool silent)
{
unset_level_flags(LFLAG_NO_TELE_CONTROL, silent);
bool in_los = false;
if (!silent)
in_los = _slime_vault_in_los();
replace_area_wrapper(DNGN_STONE_WALL, DNGN_CLEAR_ROCK_WALL);
replace_area_wrapper(DNGN_CLEAR_STONE_WALL, DNGN_CLEAR_ROCK_WALL);
if (!silent)
{
if (in_los)
{
mpr("Suddenly, all colour oozes out of the stone walls.",
MSGCH_MONSTER_ENCHANT);
}
else
{
mpr("You feel a strange vibration for a moment.",
MSGCH_MONSTER_ENCHANT);
}
}
remove_all_jiyva_altars();
if (silenced(you.pos()))
{
god_speaks(GOD_JIYVA, "With an infernal shudder, the power ruling "
"this place vanishes!");
}
else
{
god_speaks(GOD_JIYVA, "With infernal noise, the power ruling this "
"place vanishes!");
}
return (true);
}
static bool _slime_vault_to_glass_offlevel()
{
return _slime_vault_to_glass(true);
}
static bool _slime_vault_to_glass_onlevel()
{
return _slime_vault_to_glass(false);
}
static bool _slime_vault_to_floor(bool silent)
{
unset_level_flags(LFLAG_NO_TELE_CONTROL, silent);
bool in_los = false;
if (!silent)
in_los = _slime_vault_in_los();
replace_area_wrapper(DNGN_STONE_WALL, DNGN_FLOOR);
replace_area_wrapper(DNGN_CLEAR_STONE_WALL, DNGN_FLOOR);
if (silenced(you.pos()))
mpr("An unexplained breeze blows through the dungeon.", MSGCH_GOD);
else
mpr("You hear the sound of toppling stones.", MSGCH_GOD);
return (true);
}
static bool _slime_vault_to_floor_offlevel()
{
return _slime_vault_to_floor(true);
}
static bool _slime_vault_to_floor_onlevel()
{
return _slime_vault_to_floor(false);
}
void slime_vault_change(bool glass)
{
const level_id target(BRANCH_SLIME_PITS, 6);
if (is_existing_level(target))
{
if (glass)
{
apply_to_level(target, true,
target == level_id::current() ?
_slime_vault_to_glass_onlevel :
_slime_vault_to_glass_offlevel);
}
else
{
apply_to_level(target, true,
target == level_id::current() ?
_slime_vault_to_floor_onlevel :
_slime_vault_to_floor_offlevel);
}
}
}
static void _fire_monster_death_event(monsters *monster,
killer_type killer,
int i, bool polymorph)
{
int type = monster->type;
if (monster->mname == "shaped Royal Jelly"
&& monster->type != MONS_PLAYER_GHOST)
{
type = MONS_ROYAL_JELLY;
}
if (killer == KILL_RESET)
{
if (type == MONS_ROYAL_JELLY)
{
if (you.can_see(monster))
mpr("You feel a great sense of loss.");
else
mpr("You feel a great sense of loss, and the brush of the "
"Abyss.");
}
return;
}
dungeon_events.fire_event(
dgn_event(DET_MONSTER_DIED, monster->pos(), 0,
monster_index(monster), killer));
if (type == MONS_ROYAL_JELLY && !polymorph)
{
you.royal_jelly_dead = true;
if (jiyva_is_dead())
slime_vault_change(true);
}
}
static void _mummy_curse(monsters* monster, killer_type killer, int index)
{
int pow;
switch (killer)
{
case KILL_MISC:
case KILL_RESET:
case KILL_DISMISSED:
return;
default:
break;
}
switch (monster->type)
{
case MONS_MENKAURE:
case MONS_MUMMY: pow = 1; break;
case MONS_GUARDIAN_MUMMY: pow = 3; break;
case MONS_MUMMY_PRIEST: pow = 8; break;
case MONS_GREATER_MUMMY: pow = 11; break;
case MONS_KHUFU: pow = 15; break;
default:
mpr("Unknown mummy type.", MSGCH_DIAGNOSTICS);
return;
}
if (YOU_KILL(killer))
index = NON_MONSTER;
if (index != NON_MONSTER && invalid_monster_index(index))
return;
actor* target;
if (index == NON_MONSTER)
target = &you;
else
{
if (index == monster->mindex())
return;
target = &menv[index];
}
if (!target->alive())
return;
if ((monster->type == MONS_MUMMY || monster->type == MONS_MENKAURE) && YOU_KILL(killer))
curse_an_item(true);
else
{
if (index == NON_MONSTER)
{
mpr("You feel extremely nervous for a moment...",
MSGCH_MONSTER_SPELL);
}
else if (you.can_see(target))
{
mprf(MSGCH_MONSTER_SPELL, "A malignant aura surrounds %s.",
target->name(DESC_NOCAP_THE).c_str());
}
MiscastEffect(target, monster_index(monster), SPTYP_NECROMANCY,
pow, random2avg(88, 3), "a mummy death curse");
}
}
static bool _spore_goes_pop(monsters *monster, killer_type killer,
int killer_index, bool pet_kill, bool wizard)
{
if (monster->hit_points > 0 || monster->hit_points <= -15 || wizard
|| killer == KILL_RESET || killer == KILL_DISMISSED)
{
return (false);
}
bolt beam;
const int type = monster->type;
beam.is_tracer = false;
beam.is_explosion = true;
beam.beam_source = monster_index(monster);
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.source = monster->pos();
beam.target = monster->pos();
beam.thrower = crawl_state.arena ? KILL_MON
: monster->attitude == ATT_FRIENDLY ? KILL_YOU : KILL_MON;
beam.aux_source.clear();
if (YOU_KILL(killer))
beam.aux_source = "set off by themselves";
else if (pet_kill)
beam.aux_source = "set off by their pet";
const char* msg = NULL;
const char* sanct_msg = NULL;
if (type == MONS_GIANT_SPORE)
{
beam.flavour = BEAM_SPORE;
beam.damage = dice_def(3, 15);
beam.name = "explosion of spores";
beam.colour = LIGHTGREY;
beam.ex_size = 2;
msg = "The giant spore explodes!";
sanct_msg = "By Zin's power, the giant spore's explosion is "
"contained.";
}
else if (type == MONS_BALL_LIGHTNING)
{
beam.flavour = BEAM_ELECTRICITY;
beam.damage = dice_def(3, 20);
beam.name = "blast of lightning";
beam.colour = LIGHTCYAN;
beam.ex_size = coinflip() ? 3 : 2;
msg = "The ball lightning explodes!";
sanct_msg = "By Zin's power, the ball lightning's explosion "
"is contained.";
}
else
{
msg::streams(MSGCH_DIAGNOSTICS) << "Unknown spore type: "
<< static_cast<int>(type)
<< std::endl;
return (false);
}
bool saw = false;
if (you.can_see(monster))
{
saw = true;
viewwindow(true, false);
if (is_sanctuary(monster->pos()))
mpr(sanct_msg, MSGCH_GOD);
else
mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, msg);
}
if (is_sanctuary(monster->pos()))
return (false);
mgrd(monster->pos()) = NON_MONSTER;
monster->pos().reset();
monster->hit_points = -16;
if (saw)
viewwindow(true, false);
beam.explode();
return (true);
}
void _monster_die_cloud(const monsters* monster, bool corpse, bool silent,
bool summoned)
{
if (monster->type == MONS_CHAOS_SPAWN)
{
summoned = true;
corpse = false;
}
if (!summoned)
return;
std::string prefix = " ";
if (corpse)
{
if (mons_weight(mons_species(monster->type)) == 0)
return;
prefix = "'s corpse ";
}
std::string msg = summoned_poof_msg(monster);
msg += "!";
cloud_type cloud = CLOUD_NONE;
if (msg.find("smoke") != std::string::npos)
cloud = random_smoke_type();
else if (msg.find("chaos") != std::string::npos)
cloud = CLOUD_CHAOS;
if (!silent)
simple_monster_message(monster, (prefix + msg).c_str());
if (cloud != CLOUD_NONE)
{
place_cloud(cloud, monster->pos(), 1 + random2(3),
monster->kill_alignment());
}
}
static void _hogs_to_humans()
{
int any = 0;
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *monster = &menv[i];
if (monster->type == MONS_HOG)
{
if (!crawl_state.arena)
{
monster->attitude = ATT_GOOD_NEUTRAL;
monster->flags |= MF_WAS_NEUTRAL;
}
monster->type = MONS_HUMAN;
behaviour_event(monster, ME_EVAL);
any++;
}
}
if (any == 1)
mpr("No longer under Kirke's spell, the hog turns into a human!");
else if (any > 1)
{
mpr("No longer under Kirke's spell, all hogs revert to their human "
"forms!");
}
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_PIG)
untransform();
}
static int _tentacle_too_far(monsters *head, monsters *tentacle)
{
return grid_distance(head->pos(), tentacle->pos()) > LOS_RADIUS;
}
void mons_relocated(monsters *monster)
{
if (monster->type == MONS_KRAKEN)
{
int headnum = monster_index(monster);
if (invalid_monster_index(headnum))
return;
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *tentacle = &menv[i];
if (tentacle->type == MONS_KRAKEN_TENTACLE
&& (int)tentacle->number == headnum
&& _tentacle_too_far(monster, tentacle))
{
monster_die(tentacle, KILL_RESET, -1, true, false);
}
}
}
else if (monster->type == MONS_KRAKEN_TENTACLE)
{
if (invalid_monster_index(monster->number)
|| menv[monster->number].type != MONS_KRAKEN
|| _tentacle_too_far(&menv[monster->number], monster))
{
monster_die(monster, KILL_RESET, -1, true, false);
}
}
}
static int _destroy_tentacles(monsters *head)
{
int tent = 0;
int headnum = monster_index(head);
if (invalid_monster_index(headnum))
return 0;
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *monster = &menv[i];
if (monster->type == MONS_KRAKEN_TENTACLE
&& (int)monster->number == headnum)
{
if (mons_near(monster))
tent++;
monster->hurt(monster, INSTANT_DEATH);
}
}
return tent;
}
int monster_die(monsters *monster, killer_type killer,
int killer_index, bool silent, bool wizard)
{
if (invalid_monster(monster))
return (-1);
if (you.level_type == LEVEL_ABYSS)
monster->flags &= ~MF_BANISHED;
if (!silent && _monster_avoided_death(monster, killer, killer_index))
return (-1);
crawl_state.inc_mon_acting(monster);
ASSERT(!( YOU_KILL(killer) && crawl_state.arena ));
mons_clear_trapping_net(monster);
update_beholders(monster, true);
if (mons_near(monster) || wizard)
remove_auto_exclude(monster);
int summon_type = 0;
int duration = 0;
const bool summoned = mons_is_summoned(monster, &duration,
&summon_type);
const int monster_killed = monster_index(monster);
const bool hard_reset = testbits(monster->flags, MF_HARD_RESET);
const bool gives_xp = (!summoned && !mons_class_flag(monster->type,
M_NO_EXP_GAIN));
const bool drop_items = !hard_reset;
const bool mons_reset(killer == KILL_RESET || killer == KILL_DISMISSED);
const bool submerged = monster->submerged();
bool in_transit = false;
#ifdef DGL_MILESTONES
if (!crawl_state.arena)
_check_kill_milestone(monster, killer, killer_index);
#endif
if (MON_KILL(killer) && monster_killed == killer_index)
{
if (monster->confused_by_you())
{
ASSERT(!crawl_state.arena);
killer = KILL_YOU_CONF;
}
}
else if (MON_KILL(killer) && monster->has_ench(ENCH_CHARM))
{
ASSERT(!crawl_state.arena);
killer = KILL_YOU_CONF; }
if (!mons_reset && !crawl_state.arena && MONST_INTERESTING(monster))
{
take_note(Note(NOTE_KILL_MONSTER,
monster->type, mons_friendly(monster),
monster->full_name(DESC_NOCAP_A).c_str()));
}
if (killer == KILL_YOU && you.duration[DUR_BERSERKER])
{
if (you.religion == GOD_TROG
&& !player_under_penance() && you.piety > random2(1000))
{
const int bonus = 3 + random2avg( 10, 2 );
you.duration[DUR_BERSERKER] += bonus;
you.duration[DUR_MIGHT] += bonus;
haste_player(bonus);
mpr("You feel the power of Trog in you as your rage grows.",
MSGCH_GOD, GOD_TROG);
}
else if (wearing_amulet(AMU_RAGE) && one_chance_in(30))
{
const int bonus = 2 + random2(4);
you.duration[DUR_BERSERKER] += bonus;
you.duration[DUR_MIGHT] += bonus;
haste_player(bonus);
mpr("Your amulet glows a violent red.");
}
}
if (you.prev_targ == monster_killed)
{
you.prev_targ = MHITNOT;
crawl_state.cancel_cmd_repeat();
}
if (killer == KILL_YOU)
crawl_state.cancel_cmd_repeat();
const bool pet_kill = _is_pet_kill(killer, killer_index);
bool did_death_message = false;
if (monster->type == MONS_GIANT_SPORE
|| monster->type == MONS_BALL_LIGHTNING)
{
did_death_message =
_spore_goes_pop(monster, killer, killer_index, pet_kill, wizard);
}
else if (monster->type == MONS_FIRE_VORTEX
|| monster->type == MONS_SPATIAL_VORTEX)
{
if (!silent && !mons_reset)
{
simple_monster_message(monster, " dissipates!",
MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
silent = true;
}
if (monster->type == MONS_FIRE_VORTEX && !wizard && !mons_reset
&& !submerged)
{
place_cloud(CLOUD_FIRE, monster->pos(), 2 + random2(4),
monster->kill_alignment());
}
if (killer == KILL_RESET)
killer = KILL_DISMISSED;
}
else if (monster->type == MONS_SIMULACRUM_SMALL
|| monster->type == MONS_SIMULACRUM_LARGE)
{
if (!silent && !mons_reset)
{
simple_monster_message(monster, " vapourises!",
MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
silent = true;
}
if (!wizard && !mons_reset && !submerged)
{
place_cloud(CLOUD_COLD, monster->pos(), 2 + random2(4),
monster->kill_alignment());
}
if (killer == KILL_RESET)
killer = KILL_DISMISSED;
}
else if (monster->type == MONS_DANCING_WEAPON)
{
if (!hard_reset)
{
if (killer == KILL_RESET)
killer = KILL_DISMISSED;
}
if (!silent && !hard_reset)
{
int w_idx = monster->inv[MSLOT_WEAPON];
if (w_idx != NON_ITEM && !(mitm[w_idx].flags & ISFLAG_SUMMONED))
{
simple_monster_message(monster, " falls from the air.",
MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
silent = true;
}
else
killer = KILL_RESET;
}
}
const bool death_message = !silent && !did_death_message
&& mons_near(monster)
&& (monster->visible_to(&you)
|| crawl_state.arena);
const bool created_friendly = testbits(monster->flags, MF_CREATED_FRIENDLY);
bool anon = (killer_index == ANON_FRIENDLY_MONSTER);
const mon_holy_type targ_holy = monster->holiness();
switch (killer)
{
case KILL_YOU: case KILL_YOU_MISSILE: case KILL_YOU_CONF: {
const bool bad_kill = god_hates_killing(you.religion, monster);
const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL);
if (death_message)
{
if (killer == KILL_YOU_CONF
&& (anon || !invalid_monster_index(killer_index)))
{
mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "%s is %s!",
monster->name(DESC_CAP_THE).c_str(),
_wounded_damaged(monster->type) ?
"destroyed" : "killed");
}
else
{
mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "You %s %s!",
_wounded_damaged(monster->type) ? "destroy" : "kill",
monster->name(DESC_NOCAP_THE).c_str());
}
if ((created_friendly || was_neutral) && gives_xp)
mpr("That felt strangely unrewarding.");
}
if (gives_xp)
_tutorial_inspect_kill();
if (bad_kill || !created_friendly && gives_xp)
{
if (targ_holy == MH_NATURAL)
{
did_god_conduct(DID_KILL_LIVING,
monster->hit_dice, true, monster);
if (mons_is_evil(monster))
{
did_god_conduct(DID_KILL_NATURAL_EVIL,
monster->hit_dice, true, monster);
}
}
else if (targ_holy == MH_UNDEAD)
{
did_god_conduct(DID_KILL_UNDEAD,
monster->hit_dice, true, monster);
}
else if (targ_holy == MH_DEMONIC)
{
did_god_conduct(DID_KILL_DEMON,
monster->hit_dice, true, monster);
}
if (mons_is_chaotic(monster))
{
did_god_conduct(DID_KILL_CHAOTIC,
monster->hit_dice, true, monster);
}
if (mons_is_magic_user(monster))
{
did_god_conduct(DID_KILL_WIZARD,
monster->hit_dice, true, monster);
}
if (mons_class_flag(monster->type, M_PRIEST))
{
did_god_conduct(DID_KILL_PRIEST,
monster->hit_dice, true, monster);
}
if (targ_holy == MH_HOLY)
{
did_god_conduct(DID_KILL_HOLY, monster->hit_dice,
true, monster);
}
if (feawn_protects(monster))
{
did_god_conduct(DID_KILL_PLANT, monster->hit_dice,
true, monster);
}
if (mons_is_slime(monster))
{
did_god_conduct(DID_KILL_SLIME, monster->hit_dice,
true, monster);
}
if (mons_is_fast(monster))
{
did_god_conduct(DID_KILL_FAST, monster->hit_dice,
true, monster);
}
}
if (player_mutation_level(MUT_DEATH_STRENGTH)
|| (!created_friendly && gives_xp
&& (you.religion == GOD_MAKHLEB
|| you.religion == GOD_SHINING_ONE
&& mons_is_evil_or_unholy(monster))
&& !player_under_penance()
&& random2(you.piety) >= piety_breakpoint(0)))
{
if (you.hp < you.hp_max)
{
mpr("You feel a little better.");
inc_hp(monster->hit_dice + random2(monster->hit_dice),
false);
}
}
if (!created_friendly && gives_xp
&& (you.religion == GOD_MAKHLEB
|| you.religion == GOD_VEHUMET
|| you.religion == GOD_SHINING_ONE
&& mons_is_evil_or_unholy(monster))
&& !player_under_penance()
&& random2(you.piety) >= piety_breakpoint(0))
{
if (you.magic_points < you.max_magic_points)
{
mpr("You feel your power returning.");
inc_mp(1 + random2(monster->hit_dice / 2), false);
}
}
if (!created_friendly
&& gives_xp
&& (you.religion == GOD_BEOGH
&& random2(you.piety) >= piety_breakpoint(2))
&& !player_under_penance())
{
bless_follower();
}
if (you.duration[DUR_DEATH_CHANNEL]
&& monster->holiness() == MH_NATURAL
&& mons_can_be_zombified(monster)
&& gives_xp)
{
const monster_type spectre_type = mons_species(monster->type);
if (spectre_type != MONS_HYDRA || monster->number != 0)
{
const int spectre =
create_monster(
mgen_data(MONS_SPECTRAL_THING, BEH_FRIENDLY,
0, 0, monster->pos(), MHITYOU,
0, static_cast<god_type>(you.attribute[ATTR_DIVINE_DEATH_CHANNEL]),
spectre_type, monster->number));
if (spectre != -1)
{
if (death_message)
mpr("A glowing mist starts to gather...");
name_zombie(&menv[spectre], monster);
}
}
}
break;
}
case KILL_MON: case KILL_MON_MISSILE: if (!silent)
{
simple_monster_message(monster,
_wounded_damaged(monster->type) ?
" is destroyed!" : " dies!",
MSGCH_MONSTER_DAMAGE,
MDAM_DEAD);
}
if (crawl_state.arena)
break;
if (mons_friendly(monster)
&& monster->type != MONS_DANCING_WEAPON)
{
did_god_conduct(DID_FRIEND_DIED, 1 + (monster->hit_dice / 2),
true, monster);
}
if (pet_kill && feawn_protects(monster))
{
did_god_conduct(DID_ALLY_KILLED_PLANT, 1 + (monster->hit_dice / 2),
true, monster);
}
if (!created_friendly && gives_xp && pet_kill
&& (anon || !invalid_monster_index(killer_index)))
{
bool notice = false;
monsters *killer_mon = NULL;
if (!anon)
{
killer_mon = &menv[killer_index];
if (killer_mon->type == MONS_NO_MONSTER)
anon = true;
}
const mon_holy_type killer_holy =
anon ? MH_NATURAL : killer_mon->holiness();
if (you.religion == GOD_SHINING_ONE
|| you.religion == GOD_YREDELEMNUL
|| you.religion == GOD_KIKUBAAQUDGHA
|| you.religion == GOD_VEHUMET
|| you.religion == GOD_MAKHLEB
|| you.religion == GOD_LUGONU
|| !anon && mons_is_god_gift(killer_mon))
{
if (killer_holy == MH_UNDEAD)
{
const bool confused =
anon ? false : !mons_friendly(killer_mon);
if (targ_holy == MH_NATURAL)
{
notice |= did_god_conduct(
!confused ? DID_LIVING_KILLED_BY_UNDEAD_SLAVE :
DID_LIVING_KILLED_BY_SERVANT,
monster->hit_dice);
}
else if (targ_holy == MH_UNDEAD)
{
notice |= did_god_conduct(
!confused ? DID_UNDEAD_KILLED_BY_UNDEAD_SLAVE :
DID_UNDEAD_KILLED_BY_SERVANT,
monster->hit_dice);
}
else if (targ_holy == MH_DEMONIC)
{
notice |= did_god_conduct(
!confused ? DID_DEMON_KILLED_BY_UNDEAD_SLAVE :
DID_DEMON_KILLED_BY_SERVANT,
monster->hit_dice);
}
}
else if (targ_holy == MH_NATURAL)
{
notice |= did_god_conduct(DID_LIVING_KILLED_BY_SERVANT,
monster->hit_dice);
if (mons_is_evil(monster))
{
notice |= did_god_conduct(
DID_NATURAL_EVIL_KILLED_BY_SERVANT,
monster->hit_dice);
}
}
else if (targ_holy == MH_UNDEAD)
{
notice |= did_god_conduct(DID_UNDEAD_KILLED_BY_SERVANT,
monster->hit_dice);
}
else if (targ_holy == MH_DEMONIC)
{
notice |= did_god_conduct(DID_DEMON_KILLED_BY_SERVANT,
monster->hit_dice);
}
}
if (targ_holy == MH_HOLY)
{
if (killer_holy == MH_UNDEAD)
{
const bool confused =
anon ? false : !mons_friendly(killer_mon);
notice |= did_god_conduct(
!confused ? DID_HOLY_KILLED_BY_UNDEAD_SLAVE :
DID_HOLY_KILLED_BY_SERVANT,
monster->hit_dice);
}
else
notice |= did_god_conduct(DID_HOLY_KILLED_BY_SERVANT,
monster->hit_dice);
}
if (you.religion == GOD_VEHUMET
&& notice
&& !player_under_penance()
&& random2(you.piety) >= piety_breakpoint(0))
{
if (you.magic_points < you.max_magic_points)
{
mpr("You feel your power returning.");
inc_mp(1 + random2(monster->hit_dice / 2), false);
}
}
if (you.religion == GOD_SHINING_ONE
&& mons_is_evil_or_unholy(monster)
&& !player_under_penance()
&& random2(you.piety) >= piety_breakpoint(0)
&& !invalid_monster_index(killer_index))
{
if (!one_chance_in(3) && killer_mon->alive()
&& bless_follower(killer_mon))
{
break;
}
if (killer_mon->alive()
&& killer_mon->hit_points < killer_mon->max_hit_points)
{
simple_monster_message(killer_mon,
" looks invigorated.");
heal_monster(killer_mon,
1 + random2(monster->hit_dice / 4), false);
}
}
if (you.religion == GOD_BEOGH
&& random2(you.piety) >= piety_breakpoint(2)
&& !player_under_penance()
&& !one_chance_in(3)
&& !invalid_monster_index(killer_index))
{
bless_follower(killer_mon);
}
}
break;
case KILL_MISC:
if (!silent)
{
simple_monster_message(monster,
_wounded_damaged(monster->type) ?
" is destroyed!" : " dies!",
MSGCH_MONSTER_DAMAGE,
MDAM_DEAD);
}
break;
case KILL_RESET:
if (mons_is_pacified(monster) || !monster->needs_transit())
{
if (!mons_is_summoned(monster))
monster->destroy_inventory();
break;
}
monster->flags |= MF_BANISHED;
monster->set_transit(level_id(LEVEL_ABYSS));
in_transit = true;
monster->destroy_inventory();
monster->patrol_point.reset();
monster->travel_path.clear();
monster->travel_target = MTRAV_NONE;
break;
case KILL_DISMISSED:
break;
default:
monster->destroy_inventory();
break;
}
if (monster->foe == MHITNOT)
{
if (!mons_wont_attack(monster) && !crawl_state.arena)
monster->foe = MHITYOU;
else if (!invalid_monster_index(killer_index))
monster->foe = killer_index;
}
if (!silent && !wizard && see_cell(monster->pos()))
{
if (monster->alive() && !in_transit && (!summoned || duration > 0))
monster->hit_points = -1;
mons_speaks(monster);
}
if (monster->type == MONS_BORIS && !in_transit)
{
you.unique_creatures[monster->type] = false;
}
else if (monster->type == MONS_KIRKE && !in_transit)
{
_hogs_to_humans();
}
else if (monster->type == MONS_KRAKEN)
{
if (_destroy_tentacles(monster) && !in_transit)
{
mpr("The kraken is slain, and its tentacles slide "
"back into the water like the carrion they now are.");
}
}
else if (!mons_is_summoned(monster))
{
if (mons_genus(monster->type) == MONS_MUMMY)
_mummy_curse(monster, killer, killer_index);
}
if (!wizard && !submerged)
_monster_die_cloud(monster, !mons_reset, silent, summoned);
int corpse = -1;
if (!mons_reset)
{
if (!summoned)
corpse = place_monster_corpse(monster, silent);
}
if (!mons_reset && !crawl_state.arena)
{
you.kills->record_kill(monster, killer, pet_kill);
kill_category kc =
(killer == KILL_YOU || killer == KILL_YOU_MISSILE) ? KC_YOU :
(pet_kill)? KC_FRIENDLY :
KC_OTHER;
unsigned int exp_gain = 0, avail_gain = 0;
_give_adjusted_experience(monster, killer, pet_kill, killer_index,
&exp_gain, &avail_gain);
PlaceInfo& curr_PlaceInfo = you.get_place_info();
PlaceInfo delta;
delta.mon_kill_num[kc]++;
delta.mon_kill_exp += exp_gain;
delta.mon_kill_exp_avail += avail_gain;
you.global_info += delta;
you.global_info.assert_validity();
curr_PlaceInfo += delta;
curr_PlaceInfo.assert_validity();
}
_fire_monster_death_event(monster, killer, killer_index, false);
if (crawl_state.arena)
arena_monster_died(monster, killer, killer_index, silent, corpse);
const coord_def mwhere = monster->pos();
if (drop_items)
monster_drop_ething(monster, YOU_KILL(killer) || pet_kill);
else
{
monster->destroy_inventory();
}
if (!silent && !wizard && !mons_reset && corpse != -1
&& !(monster->flags & MF_KNOWN_MIMIC)
&& mons_is_shapeshifter(monster))
{
simple_monster_message(monster, "'s shape twists and changes "
"as it dies.");
}
if (mons_near(monster) && !monster->visible_to(&you))
autotoggle_autopickup(false);
crawl_state.dec_mon_acting(monster);
monster_cleanup(monster);
if (see_cell(mwhere))
{
view_update_at(mwhere);
update_screen();
}
return (corpse);
}
void monster_cleanup(monsters *monster)
{
crawl_state.mon_gone(monster);
unsigned int monster_killed = monster_index(monster);
monster->reset();
for (int dmi = 0; dmi < MAX_MONSTERS; dmi++)
if (menv[dmi].foe == monster_killed)
menv[dmi].foe = MHITNOT;
if (you.pet_target == monster_killed)
you.pet_target = MHITNOT;
}
static bool _jelly_divide(monsters *parent)
{
if (!mons_class_flag(parent->type, M_SPLITS))
return (false);
const int reqd = std::max(parent->hit_dice * 8, 50);
if (parent->hit_points < reqd)
return (false);
monsters *child = NULL;
coord_def child_spot;
int num_spots = 0;
for (adjacent_iterator ai(parent->pos()); ai; ++ai)
if (actor_at(*ai) == NULL && parent->can_pass_through(*ai))
if ( one_chance_in(++num_spots) )
child_spot = *ai;
if ( num_spots == 0 )
return (false);
int k = 0;
for (k = 0; k < MAX_MONSTERS; k++)
{
child = &menv[k];
if (child->type == -1)
break;
else if (k == MAX_MONSTERS - 1)
return (false);
}
parent->max_hit_points /= 2;
if (parent->hit_points > parent->max_hit_points)
parent->hit_points = parent->max_hit_points;
parent->init_experience();
parent->experience = parent->experience * 3 / 5 + 1;
*child = *parent;
child->max_hit_points = child->hit_points;
child->speed_increment = 70 + random2(5);
child->moveto(child_spot);
mgrd(child->pos()) = k;
if (!simple_monster_message(parent, " splits in two!"))
if (player_can_hear(parent->pos()) || player_can_hear(child->pos()))
mpr("You hear a squelching noise.", MSGCH_SOUND);
if (crawl_state.arena)
arena_placed_monster(child);
return (true);
}
void alert_nearby_monsters(void)
{
monsters *monster = 0;
for (int it = 0; it < MAX_MONSTERS; it++)
{
monster = &menv[it];
if (monster->alive()
&& mons_near(monster)
&& !monster->asleep())
{
behaviour_event(monster, ME_ALERT, MHITYOU);
}
}
}
static bool _valid_morph(monsters *monster, monster_type new_mclass)
{
const dungeon_feature_type current_tile = grd(monster->pos());
new_mclass = mons_species(new_mclass);
if (mons_genus(new_mclass) == MONS_DRACONIAN
&& new_mclass != MONS_DRACONIAN
&& !player_in_branch(BRANCH_HALL_OF_ZOT)
&& !one_chance_in(10))
{
return (false);
}
if (mons_class_holiness(new_mclass) != monster->holiness()
|| mons_class_flag(new_mclass, M_UNIQUE) || mons_class_flag(new_mclass, M_NO_EXP_GAIN) || new_mclass == mons_species(monster->type) || new_mclass == MONS_PROGRAM_BUG
|| mons_class_is_zombified(new_mclass)
|| mons_is_ghost_demon(new_mclass)
|| new_mclass == MONS_TEST_SPAWNER
|| new_mclass == MONS_ORB_GUARDIAN
|| mons_is_statue(new_mclass))
{
return (false);
}
return (monster_habitable_grid(new_mclass, current_tile));
}
static bool _is_poly_power_unsuitable( poly_power_type power,
int src_pow, int tgt_pow, int relax )
{
switch (power)
{
case PPT_LESS:
return (tgt_pow > src_pow - 3 + (relax * 3) / 2)
|| (power == PPT_LESS && (tgt_pow < src_pow - (relax / 2)));
case PPT_MORE:
return (tgt_pow < src_pow + 2 - relax)
|| (power == PPT_MORE && (tgt_pow > src_pow + relax));
default:
case PPT_SAME:
return (tgt_pow < src_pow - relax)
|| (tgt_pow > src_pow + (relax * 3) / 2);
}
}
bool monster_polymorph(monsters *monster, monster_type targetc,
poly_power_type power,
bool force_beh)
{
ASSERT(!(monster->flags & MF_TAKING_STAIRS));
ASSERT(!(monster->flags & MF_BANISHED) || you.level_type == LEVEL_ABYSS);
std::string str_polymon;
int source_power, target_power, relax;
int tries = 1000;
source_power = monster->hit_dice;
relax = 1;
if (targetc == RANDOM_MONSTER)
{
do
{
targetc = random_monster_at_grid(monster->pos());
targetc = mons_species(targetc);
target_power = mons_power(targetc);
if (one_chance_in(200))
relax++;
if (relax > 50)
return (simple_monster_message(monster, " shudders."));
}
while (tries-- && (!_valid_morph(monster, targetc)
|| _is_poly_power_unsuitable(power, source_power,
target_power, relax)));
}
if (!_valid_morph(monster, targetc))
{
return (simple_monster_message(monster,
" looks momentarily different."));
}
bool can_see = you.can_see(monster);
bool can_see_new = !mons_class_flag(targetc, M_INVIS) || you.can_see_invisible();
bool need_note = false;
std::string old_name = monster->full_name(DESC_CAP_A);
if (can_see && MONST_INTERESTING(monster))
need_note = true;
std::string new_name = "";
if (monster->type == MONS_OGRE && targetc == MONS_TWO_HEADED_OGRE)
str_polymon = " grows a second head";
else
{
if (mons_is_shapeshifter(monster))
str_polymon = " changes into ";
else if (targetc == MONS_PULSATING_LUMP)
str_polymon = " degenerates into ";
else if (you.religion == GOD_JIYVA
&& (targetc == MONS_DEATH_OOZE
|| targetc == MONS_OOZE
|| targetc == MONS_JELLY
|| targetc == MONS_BROWN_OOZE
|| targetc == MONS_SLIME_CREATURE
|| targetc == MONS_GIANT_AMOEBA
|| targetc == MONS_ACID_BLOB
|| targetc == MONS_AZURE_JELLY))
{
str_polymon = " quivers uncontrollably and liquefies into ";
}
else
str_polymon = " evaporates and reforms as ";
if (!can_see_new)
{
new_name = "something unseen";
str_polymon += "something you cannot see";
}
else
{
str_polymon += mons_type_name(targetc, DESC_NOCAP_A);
if (targetc == MONS_PULSATING_LUMP)
str_polymon += " of flesh";
}
}
str_polymon += "!";
bool player_messaged = can_see
&& simple_monster_message(monster, str_polymon.c_str());
update_beholders(monster, true);
_fire_monster_death_event(monster, KILL_MISC, NON_MONSTER, true);
unsigned long flags =
monster->flags & ~(MF_INTERESTING | MF_SEEN | MF_ATT_CHANGE_ATTEMPT
| MF_WAS_IN_VIEW | MF_BAND_MEMBER
| MF_HONORARY_UNDEAD | MF_KNOWN_MIMIC);
std::string name;
if (!monster->mname.empty())
name = monster->mname;
else if (mons_is_unique(monster->type))
{
flags |= MF_INTERESTING;
name = monster->name(DESC_PLAIN, true);
if (monster->type == MONS_ROYAL_JELLY)
{
name = "shaped Royal Jelly";
flags |= MF_NAME_SUFFIX;
}
else if (monster->type == MONS_LERNAEAN_HYDRA)
{
name = "shaped Lernaean hydra";
flags |= MF_NAME_SUFFIX;
}
const size_t the_pos = name.find(" the ");
if (the_pos != std::string::npos)
name = name.substr(0, the_pos);
}
const monster_type real_targetc =
(monster->has_ench(ENCH_GLOWING_SHAPESHIFTER)) ? MONS_GLOWING_SHAPESHIFTER :
(monster->has_ench(ENCH_SHAPESHIFTER)) ? MONS_SHAPESHIFTER
: targetc;
const god_type god =
(player_will_anger_monster(real_targetc)
|| (you.religion == GOD_BEOGH
&& mons_species(real_targetc) != MONS_ORC)) ? GOD_NO_GOD
: monster->god;
if (god == GOD_NO_GOD)
flags &= ~MF_GOD_GIFT;
const int old_hp = monster->hit_points;
const int old_hp_max = monster->max_hit_points;
const bool old_mon_caught = mons_is_caught(monster);
const char old_ench_countdown = monster->ench_countdown;
mon_enchant abj = monster->get_ench(ENCH_ABJ);
mon_enchant charm = monster->get_ench(ENCH_CHARM);
mon_enchant neutral = monster->get_ench(ENCH_NEUTRAL);
mon_enchant shifter = monster->get_ench(ENCH_GLOWING_SHAPESHIFTER,
ENCH_SHAPESHIFTER);
mon_enchant sub = monster->get_ench(ENCH_SUBMERGED);
mon_enchant summon = monster->get_ench(ENCH_SUMMON);
mon_enchant tp = monster->get_ench(ENCH_TP);
monster_spells spl = monster->spells;
const bool need_save_spells
= (!name.empty()
&& (!mons_class_flag(monster->type, M_SPELLCASTER)
|| mons_class_flag(monster->type, M_ACTUAL_SPELLS)));
monster->type = targetc;
monster->base_monster = MONS_NO_MONSTER;
monster->number = 0;
define_monster(monster_index(monster));
monster->mname = name;
monster->flags = flags;
monster->god = god;
if (need_save_spells
&& (!mons_class_flag(monster->type, M_SPELLCASTER)
|| mons_class_flag(monster->type, M_ACTUAL_SPELLS)))
{
monster->spells = spl;
}
monster->add_ench(abj);
monster->add_ench(charm);
monster->add_ench(neutral);
monster->add_ench(shifter);
monster->add_ench(sub);
monster->add_ench(summon);
monster->add_ench(tp);
if (monster->has_ench(ENCH_SUBMERGED)
&& !monster_can_submerge(monster, grd(monster->pos())))
{
monster->del_ench(ENCH_SUBMERGED);
}
monster->ench_countdown = old_ench_countdown;
if (mons_class_flag(monster->type, M_INVIS))
monster->add_ench(ENCH_INVIS);
if (!player_messaged && you.can_see(monster))
{
mprf("%s appears out of thin air!", monster->name(DESC_CAP_A).c_str());
autotoggle_autopickup(false);
player_messaged = true;
}
monster->hit_points = monster->max_hit_points
* ((old_hp * 100) / old_hp_max) / 100
+ random2(monster->max_hit_points);
monster->hit_points = std::min(monster->max_hit_points,
monster->hit_points);
monster->hit_points = std::max(monster->hit_points, 1);
monster->speed_increment = 67 + random2(6);
monster_drop_ething(monster);
mark_interesting_monst(monster);
if (new_name.empty())
new_name = monster->full_name(DESC_NOCAP_A);
if (need_note
|| can_see && you.can_see(monster) && MONST_INTERESTING(monster))
{
take_note(Note(NOTE_POLY_MONSTER, 0, 0, old_name.c_str(),
new_name.c_str()));
if (you.can_see(monster))
monster->flags |= MF_SEEN;
}
if (you.can_see(monster))
{
seen_monster(monster);
if (can_see && shifter.ench != ENCH_NONE)
monster->flags |= MF_KNOWN_MIMIC;
}
if (old_mon_caught)
check_net_will_hold_monster(monster);
if (!force_beh)
player_angers_monster(monster);
xom_is_stimulated(mons_is_shapeshifter(monster) ? 16 :
power == PPT_LESS || mons_friendly(monster) ? 32 :
power == PPT_SAME ? 64 : 128);
return (player_messaged);
}
static coord_def _random_monster_nearby_habitable_space(const monsters& mon,
bool allow_adjacent,
bool respect_los)
{
const bool respect_sanctuary = mons_wont_attack(&mon);
coord_def target;
int tries;
for (tries = 0; tries < 150; ++tries)
{
const coord_def delta(random2(13) - 6, random2(13) - 6);
if (delta.origin())
continue;
if (delta.rdist() == 1 && !allow_adjacent)
continue;
target = delta + mon.pos();
if (!in_bounds(target))
continue;
if (!monster_habitable_grid(&mon, grd(target)))
continue;
if (respect_sanctuary && is_sanctuary(target))
continue;
if (target == you.pos())
continue;
if (num_feats_between(mon.pos(), target,
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL,
true, true) > 0)
{
continue;
}
if (respect_los && !mon.mon_see_cell(target))
continue;
break;
}
if (tries == 150)
target = mon.pos();
return (target);
}
bool monster_blink(monsters *monster, bool quiet)
{
coord_def near = _random_monster_nearby_habitable_space(*monster, false,
true);
if (near == monster->pos())
return (false);
if (!quiet)
simple_monster_message(monster, " blinks!");
if (!(monster->flags & MF_WAS_IN_VIEW))
monster->seen_context = "thin air";
const coord_def oldplace = monster->pos();
if (!monster->move_to_pos(near))
return (false);
place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3),
monster->kill_alignment());
monster->check_redraw(oldplace);
monster->apply_location_effects(oldplace);
mons_relocated(monster);
return (true);
}
bool mon_can_be_slimified(monsters *monster)
{
const mon_holy_type holi = monster->holiness();
return (holi == MH_UNDEAD
|| holi == MH_NATURAL
&& !mons_is_slime(monster));
}
void slimify_monster(monsters *mon, bool hostile)
{
if (mon->holiness() == MH_UNDEAD)
monster_polymorph(mon, MONS_DEATH_OOZE);
else
{
const int x = mon->hit_dice + (coinflip() ? 1 : -1) * random2(5);
if (x < 3)
monster_polymorph(mon, MONS_OOZE);
else if (x >= 3 && x < 5)
monster_polymorph(mon, MONS_JELLY);
else if (x >= 5 && x < 7)
monster_polymorph(mon, MONS_BROWN_OOZE);
else if (x >= 7 && x <= 11)
{
if (coinflip())
monster_polymorph(mon, MONS_SLIME_CREATURE);
else
monster_polymorph(mon, MONS_GIANT_AMOEBA);
}
else
{
if (coinflip())
monster_polymorph(mon, MONS_ACID_BLOB);
else
monster_polymorph(mon, MONS_AZURE_JELLY);
}
}
if (!mons_eats_items(mon))
mon->add_ench(ENCH_EAT_ITEMS);
if (!hostile)
mon->attitude = ATT_STRICT_NEUTRAL;
else
mon->attitude = ATT_HOSTILE;
mons_make_god_gift(mon, GOD_JIYVA);
}
static void _set_random_target(monsters* mon)
{
mon->target = random_in_bounds(); for (int tries = 0; tries < 150; ++tries)
{
coord_def delta = coord_def(random2(13), random2(13)) - coord_def(6, 6);
if (delta.origin())
continue;
const coord_def newtarget = delta + mon->pos();
if (!in_bounds(newtarget))
continue;
mon->target = newtarget;
break;
}
}
static void _set_random_slime_target(monsters* mon)
{
int item_idx;
coord_def orig_target = mon->target;
for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false); ri; ++ri)
{
item_idx = igrd(*ri);
if (item_idx != NON_ITEM)
{
for (stack_iterator si(*ri); si; ++si)
{
item_def& item(*si);
if (_is_item_jelly_edible(item))
{
mon->target = *ri;
break;
}
}
}
}
if (mon->target == mon->pos() || mon->target == you.pos())
_set_random_target(mon);
}
bool random_near_space(const coord_def& origin, coord_def& target,
bool allow_adjacent, bool restrict_los,
bool forbid_dangerous, bool forbid_sanctuary)
{
#define RNS_OFFSET 6
#define RNS_WIDTH (2*RNS_OFFSET + 1)
FixedArray<bool, RNS_WIDTH, RNS_WIDTH> tried;
const coord_def tried_o = coord_def(RNS_OFFSET, RNS_OFFSET);
tried.init(false);
const bool trans_wall_block = trans_wall_blocking(origin);
const bool origin_is_player = (you.pos() == origin);
int min_walls_between = 0;
if (trans_wall_block)
{
min_walls_between = num_feats_between(origin, you.pos(),
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL);
}
for (int tries = 0; tries < 150; tries++)
{
coord_def p = coord_def(random2(RNS_WIDTH), random2(RNS_WIDTH));
if (tried(p))
continue;
else
tried(p) = true;
target = origin + (p - tried_o);
if (target == origin)
continue;
if (!in_bounds(target)
|| restrict_los && !see_cell(target)
|| grd(target) < DNGN_SHALLOW_WATER
|| actor_at(target)
|| !allow_adjacent && distance(origin, target) <= 2
|| forbid_sanctuary && is_sanctuary(target))
{
continue;
}
if (forbid_dangerous)
{
const int cloud = env.cgrid(target);
if (cloud != EMPTY_CLOUD
&& is_damaging_cloud(env.cloud[cloud].type, true))
{
continue;
}
}
if (!trans_wall_block && !origin_is_player)
return (true);
if (!origin_is_player && !see_cell(target))
return (true);
if (origin_is_player)
{
if (see_cell_no_trans(target))
return (true);
continue;
}
int walls_passed = num_feats_between(target, origin,
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL,
true, true);
if (walls_passed == 0)
return (true);
if (origin_is_player)
continue;
int walls_between = num_feats_between(target, you.pos(),
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL);
if (walls_between >= min_walls_between)
return (true);
}
return (false);
}
static bool _habitat_okay( const monsters *monster, dungeon_feature_type targ )
{
return (monster_habitable_grid(monster, targ));
}
bool swap_places(monsters *monster)
{
coord_def loc;
if (swap_check(monster, loc))
{
swap_places(monster, loc);
return true;
}
return false;
}
bool swap_places(monsters *monster, const coord_def &loc)
{
ASSERT(map_bounds(loc));
ASSERT(_habitat_okay(monster, grd(loc)));
if (mgrd(loc) != NON_MONSTER)
{
mpr("Something prevents you from swapping places.");
return (false);
}
mpr("You swap places.");
mgrd(monster->pos()) = NON_MONSTER;
monster->moveto(loc);
mgrd(monster->pos()) = monster_index(monster);
return true;
}
bool swap_check(monsters *monster, coord_def &loc, bool quiet)
{
loc = you.pos();
if (is_feat_dangerous(grd(monster->pos())))
{
canned_msg(MSG_UNTHINKING_ACT);
return (false);
}
if (mons_is_caught(monster))
{
if (!quiet)
simple_monster_message(monster, " is held in a net!");
return (false);
}
bool swap = _habitat_okay( monster, grd(loc) );
if (!swap)
{
int num_found = 0;
for (adjacent_iterator ai; ai; ++ai)
if (mgrd(*ai) == NON_MONSTER && _habitat_okay( monster, grd(*ai))
&& one_chance_in(++num_found))
{
loc = *ai;
}
if (num_found)
swap = true;
}
if (!swap && !quiet)
{
simple_monster_message( monster, " resists." );
interrupt_activity( AI_HIT_MONSTER, monster );
}
return (swap);
}
bool monster_can_hit_monster(monsters *monster, const monsters *targ)
{
if (!targ->submerged() || monster->has_damage_type(DVORP_TENTACLE))
return (true);
if (grd(targ->pos()) != DNGN_SHALLOW_WATER)
return (false);
const item_def *weapon = monster->weapon();
return (weapon && weapon_skill(*weapon) == SK_POLEARMS);
}
void mons_get_damage_level(const monsters* monster, std::string& desc,
mon_dam_level_type& dam_level)
{
if (monster->hit_points <= monster->max_hit_points / 6)
{
desc += "almost ";
desc += _wounded_damaged(monster->type) ? "destroyed" : "dead";
dam_level = MDAM_ALMOST_DEAD;
return;
}
if (monster->hit_points <= monster->max_hit_points / 4)
{
desc += "severely ";
dam_level = MDAM_SEVERELY_DAMAGED;
}
else if (monster->hit_points <= monster->max_hit_points / 3)
{
desc += "heavily ";
dam_level = MDAM_HEAVILY_DAMAGED;
}
else if (monster->hit_points <= monster->max_hit_points * 3 / 4)
{
desc += "moderately ";
dam_level = MDAM_MODERATELY_DAMAGED;
}
else if (monster->hit_points < monster->max_hit_points)
{
desc += "lightly ";
dam_level = MDAM_LIGHTLY_DAMAGED;
}
else
{
desc += "not ";
dam_level = MDAM_OKAY;
}
desc += _wounded_damaged(monster->type) ? "damaged" : "wounded";
}
std::string get_wounds_description(const monsters *monster)
{
if (!monster->alive() || monster->hit_points == monster->max_hit_points)
return "";
if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS))
return "";
std::string desc;
mon_dam_level_type dam_level;
mons_get_damage_level(monster, desc, dam_level);
desc.insert(0, " is ");
desc += ".";
return desc;
}
void print_wounds(const monsters *monster)
{
if (!monster->alive() || monster->hit_points == monster->max_hit_points)
return;
if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS))
return;
std::string desc;
mon_dam_level_type dam_level;
mons_get_damage_level(monster, desc, dam_level);
desc.insert(0, " is ");
desc += ".";
simple_monster_message(monster, desc.c_str(), MSGCH_MONSTER_DAMAGE,
dam_level);
}
static bool _wounded_damaged(monster_type mon_type)
{
const mon_holy_type holi = mons_class_holiness(mon_type);
return (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT);
}
void behaviour_event(monsters *mon, mon_event_type event, int src,
coord_def src_pos, bool allow_shout)
{
ASSERT(src >= 0 && src <= MHITYOU);
ASSERT(!crawl_state.arena || src != MHITYOU);
ASSERT(in_bounds(src_pos) || src_pos.origin());
const beh_type old_behaviour = mon->behaviour;
bool isSmart = (mons_intel(mon) > I_ANIMAL);
bool wontAttack = mons_wont_attack_real(mon);
bool sourceWontAttack = false;
bool setTarget = false;
bool breakCharm = false;
bool was_sleeping = mon->asleep();
if (src == MHITYOU)
sourceWontAttack = true;
else if (src != MHITNOT)
sourceWontAttack = mons_wont_attack_real( &menv[src] );
if (is_sanctuary(mon->pos()) && mons_is_fleeing_sanctuary(mon))
{
mon->behaviour = BEH_FLEE;
mon->foe = MHITYOU;
mon->target = env.sanctuary_pos;
return;
}
switch (event)
{
case ME_DISTURB:
if (mon->asleep())
{
mon->behaviour = BEH_WANDER;
if (mons_near(mon))
remove_auto_exclude(mon, true);
}
if (!isSmart || mon->foe == MHITNOT || mons_is_wandering(mon))
{
if (mon->is_patrolling())
break;
ASSERT(!src_pos.origin());
mon->target = src_pos;
}
break;
case ME_WHACK:
case ME_ANNOY:
if (event == ME_WHACK
|| ((wontAttack != sourceWontAttack || isSmart)
&& !mons_is_fleeing(mon) && !mons_is_panicking(mon)))
{
if (mons_class_flag(mon->type, M_NO_EXP_GAIN)
&& mon->attitude != ATT_FRIENDLY
&& mon->attitude != ATT_GOOD_NEUTRAL)
{
return;
}
mon->foe = src;
if (mon->asleep() && mons_near(mon))
remove_auto_exclude(mon, true);
if (!mons_is_cornered(mon))
mon->behaviour = BEH_SEEK;
if (src == MHITYOU)
{
mon->attitude = ATT_HOSTILE;
breakCharm = true;
}
}
if (event == ME_WHACK)
setTarget = true;
break;
case ME_ALERT:
if (mons_friendly(mon) && mon->is_patrolling()
&& !mon->asleep())
{
break;
}
if (mon->asleep() && mons_near(mon))
remove_auto_exclude(mon, true);
if (!mons_is_fleeing(mon) && !mons_is_panicking(mon)
&& !mons_is_cornered(mon))
{
mon->behaviour = BEH_SEEK;
}
if (mon->foe == MHITNOT)
mon->foe = src;
if (!src_pos.origin()
&& (mon->foe == MHITNOT || mon->foe == src
|| mons_is_wandering(mon)))
{
if (mon->is_patrolling())
break;
mon->target = src_pos;
if (src == MHITYOU && src_pos == you.pos()
&& !see_cell(mon->pos()))
{
const dungeon_feature_type can_move =
(mons_amphibious(mon)) ? DNGN_DEEP_WATER
: DNGN_SHALLOW_WATER;
_try_pathfind(mon, can_move, true);
}
}
break;
case ME_SCARE:
if (mons_is_stationary(mon) || mon->has_ench(ENCH_BERSERK))
{
mon->del_ench(ENCH_FEAR, true, true);
break;
}
if (mon->holiness() == MH_PLANT
|| mon->holiness() == MH_NONLIVING)
{
mon->del_ench(ENCH_FEAR, true, true);
break;
}
mon->behaviour = BEH_FLEE;
mon->foe = src;
mon->target = src_pos;
if (src == MHITYOU)
{
if (mons_friendly(mon))
{
breakCharm = true;
mon->foe = MHITNOT;
_set_random_target(mon);
}
else
setTarget = true;
}
else if (mons_friendly(mon) && !crawl_state.arena)
mon->foe = MHITYOU;
if (see_cell(mon->pos()))
learned_something_new(TUT_FLEEING_MONSTER);
break;
case ME_CORNERED:
if (mon->behaviour != BEH_FLEE && !mon->has_ench(ENCH_FEAR))
break;
if (mons_is_pacified(mon))
break;
if (!mons_is_cornered(mon))
{
if (mons_friendly(mon) && !crawl_state.arena)
{
mon->foe = MHITYOU;
simple_monster_message(mon, " returns to your side!");
}
else
simple_monster_message(mon, " turns to fight!");
}
mon->behaviour = BEH_CORNERED;
break;
case ME_EVAL:
break;
}
if (setTarget)
{
if (src == MHITYOU)
{
mon->target = you.pos();
mon->attitude = ATT_HOSTILE;
}
else if (src != MHITNOT)
mon->target = menv[src].pos();
}
if (breakCharm)
mon->del_ench(ENCH_CHARM);
_handle_behaviour(mon);
ASSERT(in_bounds(mon->target) || mon->target.origin());
if (was_sleeping && !mon->asleep() && allow_shout
&& mon->foe == MHITYOU && !mons_wont_attack(mon))
{
handle_monster_shouts(mon);
}
const bool wasLurking =
(old_behaviour == BEH_LURK && !mons_is_lurking(mon));
const bool isPacified = mons_is_pacified(mon);
if ((wasLurking || isPacified)
&& (event == ME_DISTURB || event == ME_ALERT || event == ME_EVAL))
{
mon->behaviour = old_behaviour;
}
else if (wasLurking && mon->has_ench(ENCH_SUBMERGED)
&& !mon->del_ench(ENCH_SUBMERGED))
{
mon->behaviour = BEH_LURK;
}
ASSERT(!crawl_state.arena
|| mon->foe != MHITYOU && mon->target != you.pos());
}
static bool _choose_random_patrol_target_grid(monsters *mon)
{
const int intel = mons_intel(mon);
if (intel == I_PLANT && coinflip())
return (true);
if (grid_distance(mon->pos(), mon->patrol_point) > 2 * LOS_RADIUS)
return (false);
const bool patrol_seen = mon->mon_see_cell(mon->patrol_point,
habitat2grid(mons_primary_habitat(mon)));
if (intel == I_PLANT && !patrol_seen)
{
return (false);
}
const int rad = (intel >= I_ANIMAL || !patrol_seen) ? LOS_RADIUS : 5;
const bool is_smart = (intel >= I_NORMAL);
los_def patrol(mon->patrol_point, opacity_monmove(*mon), bounds_radius(rad));
patrol.update();
los_def lm(mon->pos(), opacity_monmove(*mon));
if (is_smart || !patrol_seen)
{
lm.update();
}
int count_grids = 0;
for (radius_iterator ri(mon->patrol_point, LOS_RADIUS, true, false);
ri; ++ri)
{
if (*ri == mon->pos())
continue;
if (!mon->can_pass_through_feat(grd(*ri)))
continue;
if (monster_at(*ri))
continue;
if (patrol_seen)
{
if (!patrol.see_cell(*ri) &&
(!is_smart || !lm.see_cell(*ri)))
{
continue;
}
}
else
{
if (!patrol.see_cell(*ri) ||
!lm.see_cell(*ri))
{
continue;
}
}
bool set_target = false;
if (intel == I_PLANT && *ri == mon->patrol_point)
{
count_grids += 3;
if (x_chance_in_y(3, count_grids))
set_target = true;
}
else if (one_chance_in(++count_grids))
set_target = true;
if (set_target)
mon->target = *ri;
}
return (count_grids);
}
static void _check_lava_water_in_sight()
{
you.lava_in_sight = you.water_in_sight = 0;
for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri)
{
const coord_def ep = *ri - you.pos() + coord_def(ENV_SHOW_OFFSET,
ENV_SHOW_OFFSET);
if (env.show(ep))
{
const dungeon_feature_type feat = grd(*ri);
if (feat == DNGN_LAVA)
{
you.lava_in_sight = 1;
if (you.water_in_sight > 0)
break;
}
else if (feat == DNGN_DEEP_WATER)
{
you.water_in_sight = 1;
if (you.lava_in_sight > 0)
break;
}
}
}
}
static void _mark_neighbours_target_unreachable(monsters *mon)
{
const mon_intel_type intel = mons_intel(mon);
if (intel > I_NORMAL)
return;
const bool flies = mons_flies(mon);
const bool amphibious = mons_amphibious(mon);
const habitat_type habit = mons_primary_habitat(mon);
for (radius_iterator ri(mon->pos(), 2, true, false); ri; ++ri)
{
if (*ri == mon->pos())
continue;
if (!mon->mon_see_cell(*ri))
continue;
monsters* const m = monster_at(*ri);
if (m == NULL)
continue;
if (mons_intel(m) > intel)
continue;
if (mons_primary_habitat(m) != habit)
continue;
if (!flies && mons_flies(m))
continue;
if (you.water_in_sight > 0 && !amphibious && mons_amphibious(m))
continue;
if (m->travel_target == MTRAV_NONE)
m->travel_target = MTRAV_UNREACHABLE;
}
}
static bool _is_level_exit(const coord_def& pos)
{
if (feat_is_stair(grd(pos)))
return (true);
const trap_type tt = get_trap_type(pos);
if (tt == TRAP_TELEPORT || tt == TRAP_SHAFT)
return (true);
return (false);
}
static void _find_all_level_exits(std::vector<level_exit> &e)
{
e.clear();
for (rectangle_iterator ri(1); ri; ++ri)
{
if (!in_bounds(*ri))
continue;
if (_is_level_exit(*ri))
e.push_back(level_exit(*ri, false));
}
}
static int _mons_find_nearest_level_exit(const monsters *mon,
std::vector<level_exit> &e,
bool reset = false)
{
if (e.empty() || reset)
_find_all_level_exits(e);
int retval = -1;
int old_dist = -1;
for (unsigned int i = 0; i < e.size(); ++i)
{
if (e[i].unreachable)
continue;
int dist = grid_distance(mon->pos(), e[i].target);
if (old_dist == -1 || old_dist >= dist)
{
if (!mons_is_native_in_branch(mon)
&& grd(e[i].target) == DNGN_UNDISCOVERED_TRAP)
{
continue;
}
retval = i;
old_dist = dist;
}
}
return (retval);
}
static void _mons_indicate_level_exit(const monsters *mon)
{
const dungeon_feature_type feat = grd(mon->pos());
const bool is_shaft = (get_trap_type(mon->pos()) == TRAP_SHAFT);
if (feat_is_gate(feat))
simple_monster_message(mon, " passes through the gate.");
else if (feat_is_travelable_stair(feat))
{
command_type dir = feat_stair_direction(feat);
simple_monster_message(mon,
make_stringf(" %s the %s.",
dir == CMD_GO_UPSTAIRS ? "goes up" :
dir == CMD_GO_DOWNSTAIRS ? "goes down"
: "takes",
feat_is_escape_hatch(feat) ? "escape hatch"
: "stairs").c_str());
}
else if (is_shaft)
{
simple_monster_message(mon,
make_stringf(" %s the shaft.",
mons_flies(mon) ? "goes down"
: "jumps into").c_str());
}
}
void make_mons_leave_level(monsters *mon)
{
if (mons_is_pacified(mon))
{
if (you.can_see(mon))
_mons_indicate_level_exit(mon);
mon->flags |= MF_HARD_RESET;
monster_die(mon, KILL_DISMISSED, NON_MONSTER);
}
}
static void _set_no_path_found(monsters *mon)
{
#ifdef DEBUG_PATHFIND
mpr("No path found!");
#endif
mon->travel_target = MTRAV_UNREACHABLE;
_mark_neighbours_target_unreachable(mon);
}
static bool _target_is_unreachable(monsters *mon)
{
return (mon->travel_target == MTRAV_UNREACHABLE
|| mon->travel_target == MTRAV_KNOWN_UNREACHABLE);
}
bool can_go_straight(const coord_def& p1, const coord_def& p2,
dungeon_feature_type allowed)
{
if (distance(p1, p2) > get_los_radius_sq())
return (false);
dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE;
if (allowed != DNGN_UNSEEN)
max_disallowed = static_cast<dungeon_feature_type>(allowed - 1);
return (!num_feats_between(p1, p2, DNGN_UNSEEN, max_disallowed,
true, true));
}
static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move,
bool potentially_blocking)
{
if (potentially_blocking && mons_intel(mon) >= I_NORMAL
&& !mons_friendly(mon) && mons_has_los_ability(mon->type)
|| grid_distance(mon->pos(), you.pos()) == 1)
{
potentially_blocking = false;
}
else
{
if (you.lava_in_sight == -1 || you.water_in_sight == -1)
_check_lava_water_in_sight();
if (!potentially_blocking && !mons_flies(mon)
&& (mons_intel(mon) < I_NORMAL
|| mons_friendly(mon)
|| (!mons_has_ranged_spell(mon, true)
&& !mons_has_ranged_attack(mon))))
{
const habitat_type habit = mons_primary_habitat(mon);
if (you.lava_in_sight > 0 && habit != HT_LAVA
|| you.water_in_sight > 0 && habit != HT_WATER
&& can_move != DNGN_DEEP_WATER)
{
potentially_blocking = true;
}
}
}
if (!potentially_blocking
|| can_go_straight(mon->pos(), you.pos(), can_move))
{
if (mon->travel_target != MTRAV_PATROL
&& mon->travel_target != MTRAV_NONE)
{
if (mon->is_travelling())
mon->travel_path.clear();
mon->travel_target = MTRAV_NONE;
}
return (false);
}
if (!_target_is_unreachable(mon) || one_chance_in(12))
{
#ifdef DEBUG_PATHFIND
mprf("%s: Player out of reach! What now?",
mon->name(DESC_PLAIN).c_str());
#endif
if (mon->is_travelling() && mon->travel_target == MTRAV_PLAYER)
{
const int len = mon->travel_path.size();
const coord_def targ = mon->travel_path[len - 1];
if (can_go_straight(targ, you.pos(), can_move))
{
if (mon->pos() == mon->travel_path[0])
{
mon->travel_path.erase( mon->travel_path.begin() );
if (!mon->travel_path.empty())
{
mon->target = mon->travel_path[0];
return (true);
}
}
else if (can_go_straight(mon->pos(), mon->travel_path[0],
can_move))
{
mon->target = mon->travel_path[0];
return (true);
}
}
}
const int dist = grid_distance(mon->pos(), you.pos());
#ifdef DEBUG_PATHFIND
mprf("Need to calculate a path... (dist = %d)", dist);
#endif
const int range = mons_tracking_range(mon);
if (range > 0 && dist > range)
{
mon->travel_target = MTRAV_UNREACHABLE;
#ifdef DEBUG_PATHFIND
mprf("Distance too great, don't attempt pathfinding! (%s)",
mon->name(DESC_PLAIN).c_str());
#endif
return (false);
}
#ifdef DEBUG_PATHFIND
mprf("Need a path for %s from (%d, %d) to (%d, %d), max. dist = %d",
mon->name(DESC_PLAIN).c_str(), mon->pos(), you.pos(), range);
#endif
monster_pathfind mp;
if (range > 0)
mp.set_range(range);
if (mp.init_pathfind(mon, you.pos()))
{
mon->travel_path = mp.calc_waypoints();
if (!mon->travel_path.empty())
{
mon->target = mon->travel_path[0];
mon->travel_target = MTRAV_PLAYER;
return (true);
}
else
_set_no_path_found(mon);
}
else
_set_no_path_found(mon);
}
return (false);
}
static bool _pacified_leave_level(monsters *mon, std::vector<level_exit> e,
int e_index)
{
if (_is_level_exit(mon->pos())
|| (e_index != -1 && mon->pos() == e[e_index].target)
|| grid_distance(mon->pos(), you.pos()) >= LOS_RADIUS * 4)
{
make_mons_leave_level(mon);
return (true);
}
return (false);
}
static int _count_water_neighbours(coord_def p)
{
int water_count = 0;
for (adjacent_iterator ai(p); ai; ++ai)
{
if (grd(*ai) == DNGN_SHALLOW_WATER)
water_count++;
else if (grd(*ai) == DNGN_DEEP_WATER)
water_count += 2;
}
return (water_count);
}
static bool _find_siren_water_target(monsters *mon)
{
ASSERT(mon->type == MONS_SIREN);
if ((mon->pos() - you.pos()).rdist() >= 6)
return (false);
if (_count_water_neighbours(mon->pos()) >= 16)
return (true);
if (mon->travel_target == MTRAV_SIREN)
{
coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]);
#ifdef DEBUG_PATHFIND
mprf("siren target is (%d, %d), dist = %d",
targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist());
#endif
if ((mon->pos() - targ_pos).rdist() > 2)
return (true);
}
int best_water_count = 0;
coord_def best_target;
bool first = true;
while (true)
{
int best_num = 0;
for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false);
ri; ++ri)
{
if (!feat_is_water(grd(*ri)))
continue;
if (first && (mon->pos() - *ri).rdist() > (you.pos() - *ri).rdist())
continue;
const int water_count = _count_water_neighbours(*ri);
if (water_count < best_water_count)
continue;
if (water_count > best_water_count)
{
best_water_count = water_count;
best_target = *ri;
best_num = 1;
}
else {
const int old_dist = (mon->pos() - best_target).rdist();
const int new_dist = (mon->pos() - *ri).rdist();
if (new_dist > old_dist)
continue;
if (new_dist < old_dist)
{
best_target = *ri;
best_num = 1;
}
else if (one_chance_in(++best_num))
best_target = *ri;
}
}
if (!first || best_water_count > 0)
break;
first = false;
}
if (!best_water_count)
return (false);
if (best_target == mon->pos())
return (true);
monster_pathfind mp;
#ifdef WIZARD
for (rectangle_iterator ri(1); ri; ++ri)
env.map(*ri).property &= ~(FPROP_HIGHLIGHT);
#endif
if (mp.init_pathfind(mon, best_target))
{
mon->travel_path = mp.calc_waypoints();
if (!mon->travel_path.empty())
{
#ifdef WIZARD
for (unsigned int i = 0; i < mon->travel_path.size(); i++)
env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT;
#endif
#ifdef DEBUG_PATHFIND
mprf("Found a path to (%d, %d) with %d surrounding water squares",
best_target.x, best_target.y, best_water_count);
#endif
mon->target = mon->travel_path[0];
mon->travel_target = MTRAV_SIREN;
return (true);
}
}
return (false);
}
static bool _find_wall_target(monsters *mon)
{
ASSERT(mons_wall_shielded(mon));
if (mon->travel_target == MTRAV_WALL)
{
coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]);
if (cell_is_solid(targ_pos)
&& monster_habitable_grid(mon, grd(targ_pos)))
{
#ifdef DEBUG_PATHFIND
mprf("%s target is (%d, %d), dist = %d",
mon->name(DESC_PLAIN, true).c_str(),
targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist());
#endif
return (true);
}
mon->travel_path.clear();
mon->travel_target = MTRAV_NONE;
}
int best_dist = INT_MAX;
bool best_closer_to_player = false;
coord_def best_target;
for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false);
ri; ++ri)
{
if (!cell_is_solid(*ri)
|| !monster_habitable_grid(mon, grd(*ri)))
{
continue;
}
int dist = (mon->pos() - *ri).rdist();
bool closer_to_player = false;
if (dist > (you.pos() - *ri).rdist())
closer_to_player = true;
if (dist < best_dist)
{
best_dist = dist;
best_closer_to_player = closer_to_player;
best_target = *ri;
}
else if (best_closer_to_player && !closer_to_player
&& dist == best_dist)
{
best_closer_to_player = false;
best_target = *ri;
}
}
if (best_dist == INT_MAX || !in_bounds(best_target))
return (false);
monster_pathfind mp;
#ifdef WIZARD
for (rectangle_iterator ri(1); ri; ++ri)
env.map(*ri).property &= ~(FPROP_HIGHLIGHT);
#endif
if (mp.init_pathfind(mon, best_target))
{
mon->travel_path = mp.calc_waypoints();
if (!mon->travel_path.empty())
{
#ifdef WIZARD
for (unsigned int i = 0; i < mon->travel_path.size(); i++)
env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT;
#endif
#ifdef DEBUG_PATHFIND
mprf("Found a path to (%d, %d)", best_target.x, best_target.y);
#endif
mon->target = mon->travel_path[0];
mon->travel_target = MTRAV_WALL;
return (true);
}
}
return (false);
}
static bool _handle_monster_travelling(monsters *mon,
const dungeon_feature_type can_move)
{
#ifdef DEBUG_PATHFIND
mprf("Monster %s reached target (%d, %d)",
mon->name(DESC_PLAIN).c_str(), mon->target.x, mon->target.y);
#endif
if (mon->pos() == mon->travel_path[0])
{
#ifdef DEBUG_PATHFIND
mpr("Arrived at first waypoint.");
#endif
mon->travel_path.erase( mon->travel_path.begin() );
if (mon->travel_path.empty())
{
#ifdef DEBUG_PATHFIND
mpr("We reached the end of our path: stop travelling.");
#endif
mon->travel_target = MTRAV_NONE;
return (true);
}
else
{
mon->target = mon->travel_path[0];
#ifdef DEBUG_PATHFIND
mprf("Next waypoint: (%d, %d)", mon->target.x, mon->target.y);
#endif
return (false);
}
}
if (!can_go_straight(mon->pos(), mon->travel_path[0], can_move))
{
#ifdef DEBUG_PATHFIND
mpr("Can't see waypoint grid.");
#endif
int erase = -1; const int size = mon->travel_path.size();
for (int i = size - 1; i >= 0; --i)
{
if (can_go_straight(mon->pos(), mon->travel_path[i], can_move))
{
mon->target = mon->travel_path[i];
erase = i;
break;
}
}
if (erase > 0)
{
#ifdef DEBUG_PATHFIND
mprf("Need to erase %d of %d waypoints.",
erase, size);
#endif
while (0 < erase--)
mon->travel_path.erase( mon->travel_path.begin() );
}
else
{
monster_pathfind mp;
const int len = mon->travel_path.size();
if (mp.init_pathfind(mon, mon->travel_path[len-1]))
{
mon->travel_path = mp.calc_waypoints();
if (!mon->travel_path.empty())
{
mon->target = mon->travel_path[0];
#ifdef DEBUG_PATHFIND
mprf("Next waypoint: (%d, %d)",
mon->target.x, mon->target.y);
#endif
}
else
{
mon->travel_target = MTRAV_NONE;
return (true);
}
}
else
{
mon->travel_path.clear();
mon->travel_target = MTRAV_NONE;
return (true);
}
}
}
return (false);
}
static bool _handle_monster_patrolling(monsters *mon)
{
if (!_choose_random_patrol_target_grid(mon))
{
if (mons_intel(mon) == I_PLANT)
{
if (mons_friendly(mon))
{
mon->patrol_point = mon->pos();
}
else
{
mon->patrol_point.reset();
mon->travel_target = MTRAV_NONE;
return (true);
}
}
else
{
monster_pathfind mp;
if (mp.init_pathfind(mon, mon->patrol_point))
{
mon->travel_path = mp.calc_waypoints();
if (!mon->travel_path.empty())
{
mon->target = mon->travel_path[0];
mon->travel_target = MTRAV_PATROL;
}
else
{
mon->target = mon->patrol_point;
}
}
else
{
mon->patrol_point.reset();
mon->travel_target = MTRAV_NONE;
return (true);
}
}
}
else
{
#ifdef DEBUG_PATHFIND
mprf("Monster %s (pp: %d, %d) is now patrolling to (%d, %d)",
mon->name(DESC_PLAIN).c_str(),
mon->patrol_point.x, mon->patrol_point.y,
mon->target.x, mon->target.y);
#endif
}
return (false);
}
static void _check_wander_target(monsters *mon, bool isPacified = false,
dungeon_feature_type can_move = DNGN_UNSEEN)
{
if (mon->pos() == mon->target
|| mons_is_batty(mon) || !isPacified && one_chance_in(20))
{
bool need_target = true;
if (!can_move)
{
can_move = (mons_amphibious(mon) ? DNGN_DEEP_WATER
: DNGN_SHALLOW_WATER);
}
if (mon->is_travelling())
need_target = _handle_monster_travelling(mon, can_move);
if (need_target && mon->is_patrolling())
need_target = _handle_monster_patrolling(mon);
if (need_target)
_set_random_target(mon);
}
}
static void _arena_set_foe(monsters *mons)
{
const int mind = monster_index(mons);
int nearest = -1;
int best_distance = -1;
int nearest_unseen = -1;
int best_unseen_distance = -1;
for (int i = 0; i < MAX_MONSTERS; ++i)
{
if (mind == i)
continue;
const monsters *other(&menv[i]);
if (!other->alive() || mons_aligned(mind, i))
continue;
if (other->type == MONS_TEST_SPAWNER
&& mons->type != MONS_TEST_SPAWNER)
{
continue;
}
const int distance = grid_distance(mons->pos(), other->pos());
const bool seen = mons->can_see(other);
if (seen)
{
if (best_distance == -1 || distance < best_distance)
{
best_distance = distance;
nearest = i;
}
}
else
{
if (best_unseen_distance == -1 || distance < best_unseen_distance)
{
best_unseen_distance = distance;
nearest_unseen = i;
}
}
if ((best_distance == -1 || distance < best_distance)
&& mons->can_see(other))
{
best_distance = distance;
nearest = i;
}
}
if (nearest != -1)
{
mons->foe = nearest;
mons->target = menv[nearest].pos();
mons->behaviour = BEH_SEEK;
}
else if (nearest_unseen != -1)
{
mons->target = menv[nearest_unseen].pos();
if (mons->type == MONS_TEST_SPAWNER)
{
mons->foe = nearest_unseen;
mons->behaviour = BEH_SEEK;
}
else
mons->behaviour = BEH_WANDER;
}
else
{
mons->foe = MHITNOT;
mons->behaviour = BEH_WANDER;
}
if (mons->behaviour == BEH_WANDER)
_check_wander_target(mons);
ASSERT(mons->foe == MHITNOT || !mons->target.origin());
}
static void _handle_behaviour(monsters *mon)
{
bool changed = true;
bool isFriendly = mons_friendly(mon);
bool isNeutral = mons_neutral(mon);
bool wontAttack = mons_wont_attack_real(mon);
bool proxPlayer = mons_near(mon) && !crawl_state.arena;
bool trans_wall_block = trans_wall_blocking(mon->pos());
#ifdef WIZARD
if (proxPlayer && you.skills[SK_STEALTH] > 27 && !mons_wont_attack(mon))
proxPlayer = false;
#endif
bool proxFoe;
bool isHurt = (mon->hit_points <= mon->max_hit_points / 4 - 1);
bool isHealthy = (mon->hit_points > mon->max_hit_points / 2);
bool isSmart = (mons_intel(mon) > I_ANIMAL);
bool isScared = mon->has_ench(ENCH_FEAR);
bool isMobile = !mons_is_stationary(mon);
bool isPacified = mons_is_pacified(mon);
bool patrolling = mon->is_patrolling();
static std::vector<level_exit> e;
static int e_index = -1;
if (mon->has_ench(ENCH_CONFUSION))
{
_set_random_target(mon);
return;
}
if (mons_is_fleeing_sanctuary(mon)
&& mons_is_fleeing(mon)
&& is_sanctuary(you.pos()))
{
return;
}
if (crawl_state.arena)
{
if (Options.arena_force_ai)
{
if (!mon->get_foe() || mon->target.origin() || one_chance_in(3))
mon->foe = MHITNOT;
if (mon->foe == MHITNOT || mon->foe == MHITYOU)
_arena_set_foe(mon);
return;
}
else if (mon->foe == MHITYOU)
mon->foe = MHITNOT;
}
if (mons_wall_shielded(mon) && cell_is_solid(mon->pos()))
{
if (mon->behaviour == BEH_CORNERED || mon->behaviour == BEH_PANIC
|| isScared)
{
mon->behaviour = BEH_FLEE;
}
}
const dungeon_feature_type can_move =
(mons_amphibious(mon)) ? DNGN_DEEP_WATER : DNGN_SHALLOW_WATER;
if (mon->foe != MHITNOT && mon->foe != MHITYOU)
{
const monsters& foe_monster = menv[mon->foe];
if (!foe_monster.alive())
mon->foe = MHITNOT;
if (mons_friendly(&foe_monster) == isFriendly)
mon->foe = MHITNOT;
}
if (proxPlayer && !you.visible_to(mon))
{
proxPlayer = false;
const int intel = mons_intel(mon);
if (grid_distance(you.pos(), mon->pos()) == 1
&& one_chance_in(3))
{
proxPlayer = true;
}
if (intel == I_NORMAL && one_chance_in(13)
|| intel == I_HIGH && one_chance_in(6))
{
proxPlayer = true;
}
}
if (isFriendly
&& you.pet_target != MHITNOT
&& (mon->foe == MHITNOT || mon->foe == MHITYOU)
&& !mon->has_ench(ENCH_BERSERK)
&& mon->mons_species() != MONS_GIANT_SPORE )
{
mon->foe = you.pet_target;
}
if ((mon->has_ench(ENCH_BERSERK) || mon->mons_species() == MONS_GIANT_SPORE)
&& (mon->foe == MHITNOT || isFriendly && mon->foe == MHITYOU))
{
if (!isFriendly && proxPlayer && mons_intel(mon) >= I_NORMAL)
mon->foe = MHITYOU;
else
_set_nearest_monster_foe(mon);
}
if (!isPacified && mon->foe == MHITNOT)
_set_nearest_monster_foe(mon);
if (mon->foe == monster_index(mon))
mon->foe = MHITNOT;
if (mon->foe != MHITNOT && mon->foe != MHITYOU
&& wontAttack && mons_wont_attack_real(&menv[mon->foe]))
{
mon->foe = MHITNOT;
}
if (isNeutral && mon->foe != MHITNOT
&& (mon->foe == MHITYOU || mons_neutral(&menv[mon->foe])))
{
mon->foe = MHITNOT;
}
if (!isFriendly && !isNeutral
&& mon->foe != MHITYOU && mon->foe != MHITNOT
&& proxPlayer && !(mon->has_ench(ENCH_BERSERK)) && isHealthy
&& !one_chance_in(3))
{
mon->foe = MHITYOU;
}
if (mon->foe != MHITNOT && mon->foe != MHITYOU)
{
const monsters& foe_monster = menv[mon->foe];
if (!foe_monster.alive())
mon->foe = MHITNOT;
if (mons_friendly(&foe_monster) == isFriendly)
mon->foe = MHITNOT;
}
while (changed)
{
actor* afoe = mon->get_foe();
proxFoe = afoe && mon->can_see(afoe);
coord_def foepos = coord_def(0,0);
if (afoe)
foepos = afoe->pos();
if (mon->foe == MHITYOU)
proxFoe = proxPlayer;
beh_type new_beh = mon->behaviour;
unsigned short new_foe = mon->foe;
switch (mon->behaviour)
{
case BEH_SLEEP:
mon->target = mon->pos();
new_foe = MHITNOT;
break;
case BEH_LURK:
case BEH_SEEK:
if (mon->foe == MHITNOT)
{
if (crawl_state.arena || !proxPlayer || isNeutral || patrolling)
new_beh = BEH_WANDER;
else
{
new_foe = MHITYOU;
mon->target = you.pos();
}
break;
}
if (!proxFoe)
{
if (mon->travel_target == MTRAV_SIREN)
mon->travel_target = MTRAV_NONE;
if (mon->foe == MHITYOU && mon->is_travelling()
&& mon->travel_target == MTRAV_PLAYER)
{
#ifdef DEBUG_PATHFIND
mpr("Player out of LoS... start wandering.");
#endif
new_beh = BEH_WANDER;
break;
}
if (isFriendly)
{
if (patrolling || crawl_state.arena)
{
new_foe = MHITNOT;
new_beh = BEH_WANDER;
}
else
{
new_foe = MHITYOU;
mon->target = foepos;
}
break;
}
ASSERT(mon->foe != MHITNOT);
if (mon->foe_memory > 0)
{
if (mon->pos() == mon->target)
{
if (mon->foe == MHITYOU)
{
if (one_chance_in(you.skills[SK_STEALTH]/3))
mon->target = you.pos();
else
mon->foe_memory = 0;
}
else
{
if (coinflip()) mon->target = menv[mon->foe].pos();
else
mon->foe_memory = 0;
}
}
if (mon->foe_memory < 2)
{
mon->foe_memory = 0;
new_beh = BEH_WANDER;
}
break;
}
ASSERT(mon->foe_memory == 0);
switch (mons_intel(mon))
{
case I_HIGH:
mon->foe_memory = 100 + random2(200);
break;
case I_NORMAL:
mon->foe_memory = 50 + random2(100);
break;
case I_ANIMAL:
case I_INSECT:
mon->foe_memory = 25 + random2(75);
break;
case I_PLANT:
mon->foe_memory = 10 + random2(50);
break;
}
break; }
ASSERT(proxFoe && mon->foe != MHITNOT);
if (mon->foe == MHITYOU)
{
if (mon->type == MONS_SIREN
&& player_mesmerised_by(mon)
&& _find_siren_water_target(mon))
{
break;
}
if (_try_pathfind(mon, can_move, trans_wall_block))
break;
if (isFriendly && one_chance_in(8))
{
_set_random_target(mon);
mon->foe = MHITNOT;
new_beh = BEH_WANDER;
}
else
{
mon->target = you.pos();
}
}
else
{
mon->target = menv[mon->foe].pos();
}
if (isHurt && !isSmart && isMobile
&& (!mons_is_zombified(mon) || mon->type == MONS_SPECTRAL_THING)
&& mon->holiness() != MH_PLANT
&& mon->holiness() != MH_NONLIVING)
{
new_beh = BEH_FLEE;
}
break;
case BEH_WANDER:
if (isPacified)
{
if (mon->travel_target != MTRAV_PATROL)
{
new_foe = MHITNOT;
mon->travel_path.clear();
e_index = _mons_find_nearest_level_exit(mon, e);
if (e_index == -1 || one_chance_in(20))
e_index = _mons_find_nearest_level_exit(mon, e, true);
if (e_index != -1)
{
mon->travel_target = MTRAV_PATROL;
patrolling = true;
mon->patrol_point = e[e_index].target;
mon->target = e[e_index].target;
}
else
{
mon->travel_target = MTRAV_NONE;
patrolling = false;
mon->patrol_point.reset();
_set_random_target(mon);
}
}
if (_pacified_leave_level(mon, e, e_index))
return;
}
if (mons_strict_neutral(mon) && mons_is_slime(mon)
&& you.religion == GOD_JIYVA)
{
_set_random_slime_target(mon);
}
if (proxFoe && !mons_is_batty(mon))
{
new_beh = BEH_SEEK;
break;
}
_check_wander_target(mon, isPacified, can_move);
if (!proxFoe && mon->foe != MHITNOT
&& one_chance_in(isSmart ? 60 : 20)
|| isPacified && one_chance_in(isSmart ? 40 : 120))
{
new_foe = MHITNOT;
if (mon->is_travelling() && mon->travel_target != MTRAV_PATROL
|| isPacified)
{
#ifdef DEBUG_PATHFIND
mpr("It's been too long! Stop travelling.");
#endif
mon->travel_path.clear();
mon->travel_target = MTRAV_NONE;
if (isPacified && e_index != -1)
e[e_index].unreachable = true;
}
}
break;
case BEH_FLEE:
if (isHealthy && !isScared)
new_beh = BEH_SEEK;
if (isFriendly)
{
if (mon->foe == MHITYOU)
mon->target = you.pos();
}
else if (mons_wall_shielded(mon) && _find_wall_target(mon))
; else if (proxFoe)
{
mon->target = foepos;
}
break;
case BEH_CORNERED:
if (mon->holiness() == MH_PLANT
|| mon->holiness() == MH_NONLIVING)
{
break;
}
if (isHealthy)
new_beh = BEH_SEEK;
if (!proxFoe)
{
if ((isFriendly || proxPlayer) && !isNeutral && !patrolling)
new_foe = MHITYOU;
else
new_beh = BEH_WANDER;
}
else
{
mon->target = foepos;
}
break;
default:
return; }
changed = (new_beh != mon->behaviour || new_foe != mon->foe);
mon->behaviour = new_beh;
if (mon->foe != new_foe)
mon->foe_memory = 0;
mon->foe = new_foe;
}
if (mon->travel_target == MTRAV_WALL && cell_is_solid(mon->pos()))
{
if (mon->behaviour == BEH_FLEE)
{
mon->target = mon->pos();
mon->foe = MHITNOT;
}
}
}
static bool _mons_check_foe(monsters *mon, const coord_def& p,
bool friendly, bool neutral)
{
if (!inside_level_bounds(p))
return (false);
if (!friendly && !neutral && p == you.pos()
&& you.visible_to(mon) && !is_sanctuary(p))
{
return (true);
}
if (monsters *foe = monster_at(p))
{
if (foe != mon
&& mon->can_see(foe)
&& (friendly || !is_sanctuary(p))
&& (mons_friendly(foe) != friendly
|| (neutral && !mons_neutral(foe))))
{
return (true);
}
}
return (false);
}
void _set_nearest_monster_foe(monsters *mon)
{
const bool friendly = mons_friendly_real(mon);
const bool neutral = mons_neutral(mon);
for (int k = 1; k <= LOS_RADIUS; ++k)
{
std::vector<coord_def> monster_pos;
for (int i = -k; i <= k; ++i)
for (int j = -k; j <= k; (abs(i) == k ? j++ : j += 2*k))
{
const coord_def p = mon->pos() + coord_def(i, j);
if (_mons_check_foe(mon, p, friendly, neutral))
monster_pos.push_back(p);
}
if (monster_pos.empty())
continue;
const coord_def mpos = monster_pos[random2(monster_pos.size())];
if (mpos == you.pos())
mon->foe = MHITYOU;
else
mon->foe = env.mgrid(mpos);
return;
}
}
bool choose_any_monster(const monsters* mon)
{
return (true);
}
monsters *choose_random_nearby_monster(int weight,
bool (*suitable)(const monsters* mon),
bool in_sight, bool prefer_named,
bool prefer_priest)
{
return choose_random_monster_on_level(weight, suitable, in_sight, true,
prefer_named, prefer_priest);
}
monsters *choose_random_monster_on_level(int weight,
bool (*suitable)(const monsters* mon),
bool in_sight, bool near_by,
bool prefer_named, bool prefer_priest)
{
monsters *chosen = NULL;
radius_iterator ri(you.pos(), near_by ? 9 : std::max(GXM, GYM),
true, in_sight);
for (; ri; ++ri)
{
if (monsters *mon = monster_at(*ri))
{
if (suitable(mon))
{
int mon_weight = 1;
if (prefer_named && mon->is_named())
mon_weight++;
if (prefer_priest && mons_class_flag(mon->type, M_PRIEST))
mon_weight++;
if (x_chance_in_y(mon_weight, (weight += mon_weight)))
chosen = mon;
}
}
}
return chosen;
}
bool simple_monster_message(const monsters *monster, const char *event,
msg_channel_type channel,
int param,
description_level_type descrip)
{
if ((mons_near(monster) || crawl_state.arena)
&& (channel == MSGCH_MONSTER_SPELL || channel == MSGCH_FRIEND_SPELL
|| monster->visible_to(&you) || crawl_state.arena))
{
std::string msg = monster->name(descrip);
msg += event;
msg = apostrophise_fixup(msg);
if (channel == MSGCH_PLAIN && mons_wont_attack_real(monster))
channel = MSGCH_FRIEND_ACTION;
mpr(msg.c_str(), channel, param);
return (true);
}
return (false);
}
static bool _mon_on_interesting_grid(monsters *mon)
{
if (one_chance_in(4))
return (false);
const dungeon_feature_type feat = grd(mon->pos());
switch (feat)
{
case DNGN_ALTAR_ELYVILON:
if (!one_chance_in(3))
return (false);
case DNGN_ALTAR_ZIN:
case DNGN_ALTAR_SHINING_ONE:
return (mons_is_holy(mon));
case DNGN_ALTAR_BEOGH:
case DNGN_ENTER_ORCISH_MINES:
case DNGN_RETURN_FROM_ORCISH_MINES:
return (mons_is_native_in_branch(mon, BRANCH_ORCISH_MINES));
case DNGN_ENTER_ELVEN_HALLS:
case DNGN_RETURN_FROM_ELVEN_HALLS:
return (mons_is_native_in_branch(mon, BRANCH_ELVEN_HALLS));
case DNGN_ENTER_HIVE:
return (mons_is_native_in_branch(mon, BRANCH_HIVE));
default:
return (false);
}
}
static void _maybe_set_patrol_route(monsters *monster)
{
if (mons_is_wandering(monster)
&& !mons_friendly(monster)
&& !monster->is_patrolling()
&& _mon_on_interesting_grid(monster))
{
monster->patrol_point = monster->pos();
}
}
static bool _allied_monster_at(monsters *mon, coord_def a, coord_def b,
coord_def c)
{
std::vector<coord_def> pos;
pos.push_back(mon->pos() + a);
pos.push_back(mon->pos() + b);
pos.push_back(mon->pos() + c);
for (unsigned int i = 0; i < pos.size(); i++)
{
if (!in_bounds(pos[i]))
continue;
const monsters *ally = monster_at(pos[i]);
if (ally == NULL)
continue;
if (mons_is_stationary(ally))
continue;
if (mons_intel(mon) <= I_NORMAL && !mons_wont_attack_real(mon)
&& mons_genus(mon->type) != mons_genus(ally->type))
{
continue;
}
if (mons_aligned(mon->mindex(), ally->mindex()))
return (true);
}
return (false);
}
static bool _ranged_allied_monster_in_dir(monsters *mon, coord_def p)
{
coord_def pos = mon->pos();
for (int i = 1; i <= LOS_RADIUS; i++)
{
pos += p;
if (!in_bounds(pos))
break;
const monsters* ally = monster_at(pos);
if (ally == NULL)
continue;
if (mons_aligned(mon->mindex(), ally->mindex()))
{
if (mons_intel(mon) <= I_NORMAL && !mons_wont_attack(mon)
&& mons_genus(mon->type) != mons_genus(ally->type))
{
return (false);
}
if (mons_has_ranged_attack(ally)
|| mons_has_ranged_spell(ally, true))
{
return (true);
}
}
break;
}
return (false);
}
static void _handle_movement(monsters *monster)
{
coord_def delta;
_maybe_set_patrol_route(monster);
if (is_sanctuary(monster->pos())
&& mons_is_influenced_by_sanctuary(monster)
&& !mons_is_fleeing_sanctuary(monster))
{
mons_start_fleeing_from_sanctuary(monster);
}
else if (mons_is_fleeing_sanctuary(monster)
&& !is_sanctuary(monster->pos()))
{
if (monster->holiness() == MH_NONLIVING
|| monster->has_ench(ENCH_BERSERK)
|| x_chance_in_y(2, 5))
{
mons_stop_fleeing_from_sanctuary(monster);
}
}
if (mons_class_flag(monster->type, M_BURROWS) && monster->foe == MHITYOU)
{
delta = you.pos() - monster->pos();
}
else
{
delta = monster->target - monster->pos();
if (crawl_state.arena && Options.arena_force_ai
&& !mons_is_stationary(monster))
{
const bool ranged = (mons_has_ranged_attack(monster)
|| mons_has_ranged_spell(monster));
const bool glass_ok = mons_has_smite_attack(monster);
if (delta.abs() > 2
&& (!ranged
|| !monster->mon_see_cell(monster->target, !glass_ok)))
{
monster_pathfind mp;
if (mp.init_pathfind(monster, monster->target))
delta = mp.next_pos(monster->pos()) - monster->pos();
}
}
}
mmov.x = (delta.x > 0) ? 1 : ((delta.x < 0) ? -1 : 0);
mmov.y = (delta.y > 0) ? 1 : ((delta.y < 0) ? -1 : 0);
if (mons_is_fleeing(monster) && monster->travel_target != MTRAV_WALL
&& (!mons_friendly(monster)
|| monster->target != you.pos()))
{
mmov *= -1;
}
if (is_sanctuary(monster->pos() + mmov)
&& (!is_sanctuary(monster->pos())
|| monster->pos() + mmov == you.pos()))
{
mmov.reset();
}
const coord_def s = monster->pos() + mmov;
if (!in_bounds_x(s.x))
mmov.x = 0;
if (!in_bounds_y(s.y))
mmov.y = 0;
if (mmov.origin())
return;
if (delta.rdist() > 3)
{
if (abs(delta.x) > abs(delta.y) && coinflip())
mmov.y = 0;
if (abs(delta.y) > abs(delta.x) && coinflip())
mmov.x = 0;
}
const coord_def newpos(monster->pos() + mmov);
FixedArray < bool, 3, 3 > good_move;
for (int count_x = 0; count_x < 3; count_x++)
for (int count_y = 0; count_y < 3; count_y++)
{
const int targ_x = monster->pos().x + count_x - 1;
const int targ_y = monster->pos().y + count_y - 1;
if (!in_bounds(targ_x, targ_y))
{
good_move[count_x][count_y] = false;
continue;
}
good_move[count_x][count_y] =
_mon_can_move_to_pos(monster, coord_def(count_x-1, count_y-1));
}
if (mons_wall_shielded(monster))
{
if (mmov.x != 0 && mmov.y != 0) {
bool updown = false;
bool leftright = false;
coord_def t = monster->pos() + coord_def(mmov.x, 0);
if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
updown = true;
t = monster->pos() + coord_def(0, mmov.y);
if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
leftright = true;
if (updown && (!leftright || coinflip()))
mmov.y = 0;
else if (leftright)
mmov.x = 0;
}
else if (mmov.x == 0 && monster->target.x == monster->pos().x)
{
bool left = false;
bool right = false;
coord_def t = monster->pos() + coord_def(-1, mmov.y);
if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
left = true;
t = monster->pos() + coord_def(1, mmov.y);
if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
right = true;
if (left && (!right || coinflip()))
mmov.x = -1;
else if (right)
mmov.x = 1;
}
else if (mmov.y == 0 && monster->target.y == monster->pos().y)
{
bool up = false;
bool down = false;
coord_def t = monster->pos() + coord_def(mmov.x, -1);
if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
up = true;
t = monster->pos() + coord_def(mmov.x, 1);
if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
down = true;
if (up && (!down || coinflip()))
mmov.y = -1;
else if (down)
mmov.y = 1;
}
}
if ((newpos == you.pos()
|| mgrd(newpos) != NON_MONSTER && monster->foe == mgrd(newpos))
&& mons_intel(monster) >= I_ANIMAL
&& coinflip()
&& !mons_is_confused(monster) && !mons_is_caught(monster)
&& !monster->has_ench(ENCH_BERSERK))
{
if (mmov.y == 0)
{
if (!good_move[1][0] && !good_move[1][2]
&& (good_move[mmov.x+1][0] || good_move[mmov.x+1][2])
&& (_allied_monster_at(monster, coord_def(-mmov.x, -1),
coord_def(-mmov.x, 0),
coord_def(-mmov.x, 1))
|| mons_intel(monster) >= I_NORMAL
&& !mons_wont_attack_real(monster)
&& _ranged_allied_monster_in_dir(monster,
coord_def(-mmov.x, 0))))
{
if (good_move[mmov.x+1][0])
mmov.y = -1;
if (good_move[mmov.x+1][2] && (mmov.y == 0 || coinflip()))
mmov.y = 1;
}
}
else if (mmov.x == 0)
{
if (!good_move[0][1] && !good_move[2][1]
&& (good_move[0][mmov.y+1] || good_move[2][mmov.y+1])
&& (_allied_monster_at(monster, coord_def(-1, -mmov.y),
coord_def(0, -mmov.y),
coord_def(1, -mmov.y))
|| mons_intel(monster) >= I_NORMAL
&& !mons_wont_attack_real(monster)
&& _ranged_allied_monster_in_dir(monster,
coord_def(0, -mmov.y))))
{
if (good_move[0][mmov.y+1])
mmov.x = -1;
if (good_move[2][mmov.y+1] && (mmov.x == 0 || coinflip()))
mmov.x = 1;
}
}
else {
if (good_move[mmov.x+1][1])
{
if (!good_move[1][mmov.y+1]
&& (_allied_monster_at(monster, coord_def(-mmov.x, -1),
coord_def(-mmov.x, 0),
coord_def(-mmov.x, 1))
|| mons_intel(monster) >= I_NORMAL
&& !mons_wont_attack_real(monster)
&& _ranged_allied_monster_in_dir(monster,
coord_def(-mmov.x, -mmov.y))))
{
mmov.y = 0;
}
}
else if (good_move[1][mmov.y+1]
&& (_allied_monster_at(monster, coord_def(-1, -mmov.y),
coord_def(0, -mmov.y),
coord_def(1, -mmov.y))
|| mons_intel(monster) >= I_NORMAL
&& !mons_wont_attack_real(monster)
&& _ranged_allied_monster_in_dir(monster,
coord_def(-mmov.x, -mmov.y))))
{
mmov.x = 0;
}
}
}
if (mmov.origin())
return;
if (crawl_state.arena)
return;
if (monster->seen_context != _just_seen)
return;
monster->seen_context.clear();
if (!(monster->flags & MF_WAS_IN_VIEW))
return;
const coord_def old_pos = monster->pos();
const int old_dist = grid_distance(you.pos(), old_pos);
if (grid_distance(you.pos(), old_pos + mmov) >= old_dist)
{
monster->seen_context = _just_seen;
return;
}
if (see_cell(old_pos + mmov))
return;
int matches = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (i == 0 && j == 0)
continue;
if (!good_move[i][j])
continue;
delta.set(i - 1, j - 1);
coord_def tmp = old_pos + delta;
if (grid_distance(you.pos(), tmp) < old_dist && see_cell(tmp))
{
if (one_chance_in(++matches))
mmov = delta;
break;
}
}
monster->seen_context = _just_seen;
}
static void _make_mons_stop_fleeing(monsters *mon)
{
if (mons_is_fleeing(mon))
behaviour_event(mon, ME_CORNERED);
}
static bool _is_player_or_mon_sanct(const monsters* monster)
{
return (is_sanctuary(you.pos())
|| is_sanctuary(monster->pos()));
}
bool mons_avoids_cloud(const monsters *monster, cloud_struct cloud,
bool placement)
{
bool extra_careful = placement;
cloud_type cl_type = cloud.type;
if (placement)
extra_careful = true;
switch (cl_type)
{
case CLOUD_MIASMA:
return (!monster->res_rotting());
case CLOUD_FIRE:
case CLOUD_FOREST_FIRE:
if (monster->res_fire() > 1)
return (false);
if (extra_careful)
return (true);
if (mons_intel(monster) >= I_ANIMAL && monster->res_fire() < 0)
return (true);
if (monster->hit_points >= 15 + random2avg(46, 5))
return (false);
break;
case CLOUD_STINK:
if (monster->res_poison() > 0)
return (false);
if (extra_careful)
return (true);
if (mons_intel(monster) >= I_ANIMAL && monster->res_poison() < 0)
return (true);
if (x_chance_in_y(monster->hit_dice - 1, 5))
return (false);
if (monster->hit_points >= random2avg(19, 2))
return (false);
break;
case CLOUD_COLD:
if (monster->res_cold() > 1)
return (false);
if (extra_careful)
return (true);
if (mons_intel(monster) >= I_ANIMAL && monster->res_cold() < 0)
return (true);
if (monster->hit_points >= 15 + random2avg(46, 5))
return (false);
break;
case CLOUD_POISON:
if (monster->res_poison() > 0)
return (false);
if (extra_careful)
return (true);
if (mons_intel(monster) >= I_ANIMAL && monster->res_poison() < 0)
return (true);
if (monster->hit_points >= random2avg(37, 4))
return (false);
break;
case CLOUD_GREY_SMOKE:
if (placement)
return (false);
if (mons_intel(monster) > I_ANIMAL || coinflip())
return (false);
if (monster->res_fire() > 0)
return (false);
if (monster->hit_points >= random2avg(19, 2))
return (false);
break;
case CLOUD_RAIN:
if (monster->is_fiery() && extra_careful)
return (true);
if (monster->flight_mode() != FL_NONE)
return (false);
if (monster_habitable_grid(monster, DNGN_DEEP_WATER))
return (false);
if (grd(cloud.pos) == DNGN_SHALLOW_WATER)
return (true);
return (false);
break;
default:
break;
}
if (is_harmless_cloud(cl_type)
|| mons_intel(monster) == I_PLANT && !extra_careful)
{
return (false);
}
return (true);
}
bool mons_avoids_cloud(const monsters *monster, int cloud_num,
cloud_type *cl_type, bool placement)
{
if (cloud_num == EMPTY_CLOUD)
{
if (cl_type != NULL)
*cl_type = CLOUD_NONE;
return (false);
}
const cloud_struct &cloud = env.cloud[cloud_num];
if (cl_type != NULL)
*cl_type = cloud.type;
if (!mons_avoids_cloud(monster, cloud, placement))
return (false);
if (!in_bounds(monster->pos()) || monster->pos() == cloud.pos)
return (true);
const int our_cloud_num = env.cgrid(monster->pos());
if (our_cloud_num == EMPTY_CLOUD)
return (true);
const cloud_struct &our_cloud = env.cloud[our_cloud_num];
return (!mons_avoids_cloud(monster, our_cloud, true));
}
static void _handle_nearby_ability(monsters *monster)
{
actor *foe = monster->get_foe();
if (!foe
|| !monster->can_see(foe)
|| monster->asleep()
|| monster->submerged())
{
return;
}
#define MON_SPEAK_CHANCE 21
if (monster->is_patrolling() || mons_is_wandering(monster)
|| monster->attitude == ATT_NEUTRAL)
{
;
}
else if ((mons_class_flag(monster->type, M_SPEAKS)
|| !monster->mname.empty())
&& one_chance_in(MON_SPEAK_CHANCE))
{
mons_speaks(monster);
}
else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED)
{
int chance = MON_SPEAK_CHANCE * 4;
if (testbits(monster->flags, MF_BAND_MEMBER))
chance *= 10;
if (mons_is_fleeing(monster))
chance /= 2;
if (monster->has_ench(ENCH_CONFUSION))
chance /= 2;
if (one_chance_in(chance))
mons_speaks(monster);
}
if (monster_can_submerge(monster, grd(monster->pos()))
&& !monster->caught() && !player_mesmerised_by(monster) && !mons_is_lurking(monster) && monster->wants_submerge())
{
monsterentry* entry = get_monster_data(monster->type);
monster->add_ench(ENCH_SUBMERGED);
monster->speed_increment -= ENERGY_SUBMERGE(entry);
update_beholders(monster);
return;
}
switch (monster->type)
{
case MONS_SPATIAL_VORTEX:
case MONS_KILLER_KLOWN:
monster->colour = random_colour();
break;
case MONS_GIANT_EYEBALL:
if (coinflip()
&& !mons_is_wandering(monster)
&& !mons_is_fleeing(monster)
&& !mons_is_pacified(monster)
&& !_is_player_or_mon_sanct(monster))
{
if (you.can_see(monster) && you.can_see(foe))
mprf("%s stares at %s.",
monster->name(DESC_CAP_THE).c_str(),
foe->name(DESC_NOCAP_THE).c_str());
foe->paralyse(monster, 2 + random2(3));
}
break;
case MONS_EYE_OF_DRAINING:
if (coinflip()
&& foe->atype() == ACT_PLAYER
&& !mons_is_wandering(monster)
&& !mons_is_fleeing(monster)
&& !mons_is_pacified(monster)
&& !_is_player_or_mon_sanct(monster))
{
simple_monster_message(monster, " stares at you.");
dec_mp(5 + random2avg(13, 3));
heal_monster(monster, 10, true); }
break;
case MONS_AIR_ELEMENTAL:
if (one_chance_in(5))
monster->add_ench(ENCH_SUBMERGED);
break;
case MONS_PANDEMONIUM_DEMON:
if (monster->ghost->cycle_colours)
monster->colour = random_colour();
break;
default:
break;
}
}
static bool _siren_movement_effect(const monsters *monster)
{
bool do_resist = (you.attribute[ATTR_HELD] || you_resist_magic(70));
if (!do_resist)
{
coord_def dir(coord_def(0,0));
if (monster->pos().x < you.pos().x)
dir.x = -1;
else if (monster->pos().x > you.pos().x)
dir.x = 1;
if (monster->pos().y < you.pos().y)
dir.y = -1;
else if (monster->pos().y > you.pos().y)
dir.y = 1;
const coord_def newpos = you.pos() + dir;
if (!in_bounds(newpos) || is_feat_dangerous(grd(newpos))
|| !you.can_pass_through_feat(grd(newpos)))
{
do_resist = true;
}
else
{
bool swapping = false;
monsters *mon = monster_at(newpos);
if (mon)
{
if (mons_wont_attack(mon)
&& !mons_is_stationary(mon)
&& !mons_cannot_act(mon)
&& !mon->asleep()
&& swap_check(mon, you.pos(), true))
{
swapping = true;
}
else if (!mon->submerged())
do_resist = true;
}
if (!do_resist)
{
const coord_def oldpos = you.pos();
mprf("The pull of her song draws you forwards.");
if (swapping)
{
if (mgrd(oldpos) != NON_MONSTER)
{
mprf("Something prevents you from swapping places "
"with %s.",
mon->name(DESC_NOCAP_THE).c_str());
return (do_resist);
}
int swap_mon = mgrd(newpos);
mgrd(newpos) = NON_MONSTER;
mon->moveto(oldpos);
mgrd(mon->pos()) = swap_mon;
mprf("You swap places with %s.",
mon->name(DESC_NOCAP_THE).c_str());
}
move_player_to_grid(newpos, true, true, true);
if (swapping)
mon->apply_location_effects(newpos);
}
}
}
return (do_resist);
}
static bool _handle_special_ability(monsters *monster, bolt & beem)
{
bool used = false;
const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN)
? draco_subspecies( monster )
: static_cast<monster_type>( monster->type );
if ((!mons_near(monster)
|| monster->asleep()
|| monster->submerged())
&& monster->mons_species() != MONS_SLIME_CREATURE)
{
return (false);
}
const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL
: MSGCH_MONSTER_SPELL);
spell_type spell = SPELL_NO_SPELL;
switch (mclass)
{
case MONS_UGLY_THING:
case MONS_VERY_UGLY_THING:
used = ugly_thing_mutate(monster, true);
break;
case MONS_SLIME_CREATURE:
used = slime_split_merge(monster);
if (!monster->alive())
return (true);
break;
case MONS_ORC_KNIGHT:
case MONS_ORC_WARLORD:
case MONS_SAINT_ROKA:
if (is_sanctuary(monster->pos()))
break;
used = orc_battle_cry(monster);
break;
case MONS_ORANGE_STATUE:
if (_is_player_or_mon_sanct(monster))
break;
used = orange_statue_effects(monster);
break;
case MONS_SILVER_STATUE:
if (_is_player_or_mon_sanct(monster))
break;
used = silver_statue_effects(monster);
break;
case MONS_BALL_LIGHTNING:
if (is_sanctuary(monster->pos()))
break;
if (monster->attitude == ATT_HOSTILE
&& distance(you.pos(), monster->pos()) <= 5)
{
monster->hit_points = -1;
used = true;
break;
}
for (int i = 0; i < MAX_MONSTERS; i++)
{
monsters *targ = &menv[i];
if (targ->type == MONS_NO_MONSTER)
continue;
if (distance(monster->pos(), targ->pos()) >= 5)
continue;
if (mons_atts_aligned(monster->attitude, targ->attitude))
continue;
coord_def diff = targ->pos() - monster->pos();
coord_def sg(sgn(diff.x), sgn(diff.y));
coord_def t = monster->pos() + sg;
if (!inside_level_bounds(t))
continue;
if (!feat_is_solid(grd(t)))
{
monster->hit_points = -1;
used = true;
break;
}
}
break;
case MONS_LAVA_SNAKE:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (coinflip())
break;
beem.name = "glob of lava";
beem.aux_source = "glob of lava";
beem.range = 6;
beem.damage = dice_def(3, 10);
beem.hit = 20;
beem.colour = RED;
beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
beem.flavour = BEAM_LAVA;
beem.beam_source = monster_index(monster);
beem.thrower = KILL_MON;
fire_tracer(monster, beem);
if (mons_should_fire(beem))
{
_make_mons_stop_fleeing(monster);
simple_monster_message(monster, " spits lava!");
beem.fire();
used = true;
}
break;
case MONS_ELECTRIC_EEL:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (coinflip())
break;
beem.name = "bolt of electricity";
beem.aux_source = "bolt of electricity";
beem.range = 8;
beem.damage = dice_def( 3, 6 );
beem.hit = 50;
beem.colour = LIGHTCYAN;
beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
beem.flavour = BEAM_ELECTRICITY;
beem.beam_source = monster_index(monster);
beem.thrower = KILL_MON;
beem.is_beam = true;
fire_tracer(monster, beem);
if (mons_should_fire(beem))
{
_make_mons_stop_fleeing(monster);
simple_monster_message(monster,
" shoots out a bolt of electricity!");
beem.fire();
used = true;
}
break;
case MONS_ACID_BLOB:
case MONS_OKLOB_PLANT:
case MONS_YELLOW_DRACONIAN:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (_is_player_or_mon_sanct(monster))
break;
if (one_chance_in(3))
{
spell = SPELL_ACID_SPLASH;
setup_mons_cast(monster, beem, spell);
fire_tracer(monster, beem);
if (mons_should_fire(beem))
{
_make_mons_stop_fleeing(monster);
mons_cast(monster, beem, spell);
mmov.reset();
used = true;
}
}
break;
case MONS_MOTH_OF_WRATH:
if (one_chance_in(3))
used = moth_incite_monsters(monster);
break;
case MONS_SNORG:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (monster->foe == MHITNOT
|| monster->foe == MHITYOU && mons_friendly(monster))
{
break;
}
if (monster->hit_points == monster->max_hit_points && !one_chance_in(4))
break;
if (one_chance_in(5))
monster->go_berserk(true);
break;
case MONS_PIT_FIEND:
if (one_chance_in(3))
break;
case MONS_FIEND:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (_is_player_or_mon_sanct(monster))
break;
if (one_chance_in(4))
{
spell_type spell_cast = SPELL_NO_SPELL;
switch (random2(4))
{
case 0:
if (!mons_friendly(monster))
{
_make_mons_stop_fleeing(monster);
spell_cast = SPELL_SYMBOL_OF_TORMENT;
mons_cast(monster, beem, spell_cast);
used = true;
break;
}
case 1:
case 2:
case 3:
spell_cast = SPELL_HELLFIRE;
setup_mons_cast(monster, beem, spell_cast);
fire_tracer(monster, beem);
if (mons_should_fire(beem))
{
_make_mons_stop_fleeing(monster);
mons_cast(monster, beem, spell_cast);
used = true;
}
break;
}
mmov.reset();
}
break;
case MONS_IMP:
case MONS_PHANTOM:
case MONS_INSUBSTANTIAL_WISP:
case MONS_BLINK_FROG:
case MONS_KILLER_KLOWN:
case MONS_PRINCE_RIBBIT:
if (one_chance_in(7) || mons_is_caught(monster) && one_chance_in(3))
used = monster_blink(monster);
break;
case MONS_MANTICORE:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (random2(16) >= static_cast<int>(monster->number))
break;
beem.name = "volley of spikes";
beem.aux_source = "volley of spikes";
beem.range = 6;
beem.hit = 14;
beem.damage = dice_def( 2, 10 );
beem.beam_source = monster_index(monster);
beem.type = dchar_glyph(DCHAR_FIRED_MISSILE);
beem.colour = LIGHTGREY;
beem.flavour = BEAM_MISSILE;
beem.thrower = KILL_MON;
beem.is_beam = false;
fire_tracer(monster, beem);
if (mons_should_fire(beem))
{
_make_mons_stop_fleeing(monster);
simple_monster_message(monster, " flicks its tail!");
beem.fire();
used = true;
monster->number--;
}
break;
case MONS_PLAYER_GHOST:
{
const ghost_demon &ghost = *(monster->ghost);
if (ghost.species < SP_RED_DRACONIAN
|| ghost.species == SP_GREY_DRACONIAN
|| ghost.species >= SP_BASE_DRACONIAN
|| ghost.xl < 7
|| one_chance_in(ghost.xl - 5))
{
break;
}
}
case MONS_WHITE_DRACONIAN:
case MONS_RED_DRACONIAN:
spell = SPELL_DRACONIAN_BREATH;
case MONS_ICE_DRAGON:
if (spell == SPELL_NO_SPELL)
spell = SPELL_COLD_BREATH;
case MONS_DRAGON:
case MONS_HELL_HOUND:
case MONS_LINDWURM:
case MONS_FIREDRAKE:
case MONS_XTAHUA:
if (spell == SPELL_NO_SPELL)
spell = SPELL_FIRE_BREATH;
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (monster->type != MONS_HELL_HOUND && x_chance_in_y(3, 13)
|| one_chance_in(10))
{
setup_mons_cast(monster, beem, spell);
fire_tracer(monster, beem);
if (mons_should_fire(beem))
{
_make_mons_stop_fleeing(monster);
mons_cast(monster, beem, spell);
mmov.reset();
used = true;
}
}
break;
case MONS_MERMAID:
case MONS_SIREN:
{
if (crawl_state.arena)
break;
if (!you.delay_queue.empty())
{
delay_queue_item delay = you.delay_queue.front();
if (delay.type == DELAY_ASCENDING_STAIRS
|| delay.type == DELAY_DESCENDING_STAIRS)
{
#ifdef DEBUG_DIAGNOSTICS
mpr("Taking stairs, don't mesmerise.", MSGCH_DIAGNOSTICS);
#endif
break;
}
}
if (monster->has_ench(ENCH_CONFUSION)
|| mons_is_fleeing(monster)
|| mons_is_pacified(monster)
|| mons_friendly(monster)
|| !player_can_hear(monster->pos()))
{
break;
}
if (you.duration[DUR_BERSERKER])
break;
if (you.species == SP_MERFOLK && !one_chance_in(4))
break;
if (monster->invisible()
&& monster->hit_points <= monster->max_hit_points / 2
&& !one_chance_in(3))
{
break;
}
bool already_mesmerised = player_mesmerised_by(monster);
if (one_chance_in(5)
|| monster->foe == MHITYOU && !already_mesmerised && coinflip())
{
noisy(12, monster->pos(), monster->mindex(), true);
bool did_resist = false;
if (you.can_see(monster))
{
simple_monster_message(monster,
make_stringf(" chants %s song.",
already_mesmerised ? "her luring" : "a haunting").c_str(),
spl);
if (monster->type == MONS_SIREN)
{
if (_siren_movement_effect(monster))
{
canned_msg(MSG_YOU_RESIST); did_resist = true;
}
}
}
else
{
if (already_mesmerised)
mpr("You hear a luring song.", MSGCH_SOUND);
else
{
if (one_chance_in(4)) {
if (coinflip())
mpr("You hear a haunting song.", MSGCH_SOUND);
else
mpr("You hear an eerie melody.", MSGCH_SOUND);
canned_msg(MSG_YOU_RESIST); }
break;
}
}
if (!already_mesmerised
&& (you.species == SP_MERFOLK || you_resist_magic(100)))
{
if (!did_resist)
canned_msg(MSG_YOU_RESIST);
break;
}
if (!you.duration[DUR_MESMERISED])
{
you.duration[DUR_MESMERISED] = 7;
you.mesmerised_by.push_back(monster_index(monster));
mprf(MSGCH_WARN, "You are mesmerised by %s!",
monster->name(DESC_NOCAP_THE).c_str());
}
else
{
you.duration[DUR_MESMERISED] += 5;
if (!already_mesmerised)
you.mesmerised_by.push_back(monster_index(monster));
}
used = true;
if (you.duration[DUR_MESMERISED] > 12)
you.duration[DUR_MESMERISED] = 12;
}
break;
}
default:
break;
}
if (used)
monster->lose_energy(EUT_SPECIAL);
return (used);
}
static bool _handle_potion(monsters *monster, bolt & beem)
{
if (monster->asleep()
|| monster->inv[MSLOT_POTION] == NON_ITEM
|| !one_chance_in(3))
{
return (false);
}
bool rc = false;
const int potion_idx = monster->inv[MSLOT_POTION];
item_def& potion = mitm[potion_idx];
const potion_type ptype = static_cast<potion_type>(potion.sub_type);
if (monster->can_drink_potion(ptype) && monster->should_drink_potion(ptype))
{
const bool was_visible = you.can_see(monster);
const item_type_id_state_type id = monster->drink_potion_effect(ptype);
if (was_visible && id != ID_UNKNOWN_TYPE)
set_ident_type(OBJ_POTIONS, ptype, id);
if (dec_mitm_item_quantity(potion_idx, 1))
monster->inv[MSLOT_POTION] = NON_ITEM;
else if (is_blood_potion(potion))
remove_oldest_blood_potion(potion);
monster->lose_energy(EUT_ITEM);
rc = true;
}
return (rc);
}
static bool _handle_reaching(monsters *monster)
{
bool ret = false;
const int wpn = monster->inv[MSLOT_WEAPON];
if (monster->submerged())
return (false);
if (mons_aligned(monster_index(monster), monster->foe))
return (false);
if (wpn != NON_ITEM && get_weapon_brand(mitm[wpn]) == SPWPN_REACHING)
{
if (monster->foe == MHITYOU)
{
const coord_def delta = monster->pos() - you.pos();
const int x_middle = std::max(monster->pos().x, you.pos().x)
- (abs(delta.x) / 2);
const int y_middle = std::max(monster->pos().y, you.pos().y)
- (abs(delta.y) / 2);
const coord_def middle(x_middle, y_middle);
if (monster->target == you.pos()
&& grid_distance(monster->pos(), you.pos()) == 2
&& (see_cell_no_trans(monster->pos())
|| grd(middle) > DNGN_MAX_NONREACH))
{
ret = true;
monster_attack(monster, false);
}
}
else if (monster->foe != MHITNOT)
{
monsters& mfoe = menv[monster->foe];
coord_def foepos = mfoe.pos();
if (monster->target == foepos
&& monster->mon_see_cell(foepos, true)
&& grid_distance(monster->pos(), foepos) == 2)
{
ret = true;
monsters_fight(monster, &mfoe, false);
}
}
}
if (ret && !is_artefact(mitm[wpn]) && you.can_see(monster))
set_ident_flags(mitm[wpn], ISFLAG_KNOW_TYPE);
return (ret);
}
static bool _handle_scroll(monsters *monster)
{
if (monster->asleep()
|| mons_is_confused(monster)
|| monster->submerged()
|| monster->inv[MSLOT_SCROLL] == NON_ITEM
|| !one_chance_in(3))
{
return (false);
}
if (mitm[monster->inv[MSLOT_SCROLL]].base_type != OBJ_SCROLLS)
return (false);
bool read = false;
item_type_id_state_type ident = ID_UNKNOWN_TYPE;
bool was_visible = you.can_see(monster);
const int scroll_type = mitm[monster->inv[MSLOT_SCROLL]].sub_type;
switch (scroll_type)
{
case SCR_TELEPORTATION:
if (!monster->has_ench(ENCH_TP))
{
if (mons_is_caught(monster) || mons_is_fleeing(monster)
|| mons_is_pacified(monster))
{
simple_monster_message(monster, " reads a scroll.");
monster_teleport(monster, false);
read = true;
ident = ID_KNOWN_TYPE;
}
}
break;
case SCR_BLINKING:
if (mons_is_caught(monster) || mons_is_fleeing(monster)
|| mons_is_pacified(monster))
{
if (mons_near(monster))
{
simple_monster_message(monster, " reads a scroll.");
monster_blink(monster);
read = true;
ident = ID_KNOWN_TYPE;
}
}
break;
case SCR_SUMMONING:
if (mons_near(monster))
{
simple_monster_message(monster, " reads a scroll.");
const int mon = create_monster(
mgen_data(MONS_ABOMINATION_SMALL, SAME_ATTITUDE(monster),
0, 0, monster->pos(), monster->foe, MG_FORCE_BEH));
read = true;
if (mon != -1)
{
if (you.can_see(&menv[mon]))
{
mprf("%s appears!", menv[mon].name(DESC_CAP_A).c_str());
ident = ID_KNOWN_TYPE;
}
player_angers_monster(&menv[mon]);
}
else if (you.can_see(monster))
canned_msg(MSG_NOTHING_HAPPENS);
}
break;
}
if (read)
{
if (dec_mitm_item_quantity(monster->inv[MSLOT_SCROLL], 1))
monster->inv[MSLOT_SCROLL] = NON_ITEM;
if (ident != ID_UNKNOWN_TYPE && was_visible)
set_ident_type(OBJ_SCROLLS, scroll_type, ident);
monster->lose_energy(EUT_ITEM);
}
return read;
}
static bool _handle_wand(monsters *monster, bolt &beem)
{
if (!mons_near(monster)
|| monster->asleep()
|| monster->has_ench(ENCH_SUBMERGED)
|| monster->inv[MSLOT_WAND] == NON_ITEM
|| mitm[monster->inv[MSLOT_WAND]].plus <= 0
|| coinflip())
{
return (false);
}
bool niceWand = false;
bool zap = false;
bool was_visible = you.can_see(monster);
item_def &wand(mitm[monster->inv[MSLOT_WAND]]);
const spell_type mzap = _map_wand_to_mspell(wand.sub_type);
if (mzap == SPELL_NO_SPELL)
return (false);
int power = 30 + monster->hit_dice;
bolt theBeam = mons_spells(monster, mzap, power);
beem.name = theBeam.name;
beem.beam_source = monster_index(monster);
beem.source = monster->pos();
beem.colour = theBeam.colour;
beem.range = theBeam.range;
beem.damage = theBeam.damage;
beem.ench_power = theBeam.ench_power;
beem.hit = theBeam.hit;
beem.type = theBeam.type;
beem.flavour = theBeam.flavour;
beem.thrower = theBeam.thrower;
beem.is_beam = theBeam.is_beam;
beem.is_explosion = theBeam.is_explosion;
#if HISCORE_WEAPON_DETAIL
beem.aux_source =
wand.name(DESC_QUALNAME, false, true, false, false);
#else
beem.aux_source =
wand.name(DESC_QUALNAME, false, true, false, false,
ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES);
#endif
const int wand_type = wand.sub_type;
switch (wand_type)
{
case WAND_DISINTEGRATION:
beem.damage.size = beem.damage.size * 2 / 3;
break;
case WAND_ENSLAVEMENT:
case WAND_DIGGING:
case WAND_RANDOM_EFFECTS:
return (false);
case WAND_POLYMORPH_OTHER:
if (!one_chance_in(5))
return (false);
break;
case WAND_HASTING:
if (!monster->has_ench(ENCH_HASTE))
{
beem.target = monster->pos();
niceWand = true;
break;
}
return (false);
case WAND_HEALING:
if (monster->hit_points <= monster->max_hit_points / 2)
{
beem.target = monster->pos();
niceWand = true;
break;
}
return (false);
case WAND_INVISIBILITY:
if (!monster->has_ench(ENCH_INVIS)
&& !monster->has_ench(ENCH_SUBMERGED)
&& (!mons_friendly(monster) || you.can_see_invisible(false)))
{
beem.target = monster->pos();
niceWand = true;
break;
}
return (false);
case WAND_TELEPORTATION:
if (monster->hit_points <= monster->max_hit_points / 2
|| mons_is_caught(monster))
{
if (!monster->has_ench(ENCH_TP)
&& !one_chance_in(20))
{
beem.target = monster->pos();
niceWand = true;
break;
}
break;
}
return (false);
}
if (!niceWand)
{
fire_tracer( monster, beem );
zap = mons_should_fire(beem);
}
if (niceWand || zap)
{
if (!niceWand)
_make_mons_stop_fleeing(monster);
if (!simple_monster_message(monster, " zaps a wand."))
{
if (!silenced(you.pos()))
mpr("You hear a zap.", MSGCH_SOUND);
}
wand.plus--;
beem.is_tracer = false;
beem.fire();
if (was_visible)
{
if (niceWand || !beem.is_enchantment() || beem.obvious_effect)
set_ident_type(OBJ_WANDS, wand_type, ID_KNOWN_TYPE);
else
set_ident_type(OBJ_WANDS, wand_type, ID_MON_TRIED_TYPE);
if (wand.plus2 >= 0)
wand.plus2++;
}
monster->lose_energy(EUT_ITEM);
return (true);
}
return (false);
}
static spell_type _get_draconian_breath_spell( monsters *monster )
{
spell_type draco_breath = SPELL_NO_SPELL;
if (mons_genus( monster->type ) == MONS_DRACONIAN)
{
switch (draco_subspecies( monster ))
{
case MONS_DRACONIAN:
case MONS_YELLOW_DRACONIAN: break;
default:
draco_breath = SPELL_DRACONIAN_BREATH;
break;
}
}
if (draco_breath != SPELL_NO_SPELL)
{
bolt beem;
setup_mons_cast(monster, beem, draco_breath);
fire_tracer(monster, beem);
if (!mons_should_fire(beem))
draco_breath = SPELL_NO_SPELL;
}
return (draco_breath);
}
static bool _is_emergency_spell(const monster_spells &msp, int spell)
{
for (int i = 0; i < 5; ++i)
if (msp[i] == spell)
return (false);
return (msp[5] == spell);
}
static bool _mon_has_spells(monsters *monster)
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
if (monster->spells[i] != SPELL_NO_SPELL)
return (true);
return (false);
}
static bool _handle_spell(monsters *monster, bolt &beem)
{
bool monsterNearby = mons_near(monster);
bool finalAnswer = false; const spell_type draco_breath = _get_draconian_breath_spell(monster);
const bool spellcasting_poly
= !mons_class_flag(monster->type, M_SPELLCASTER)
&& mons_class_flag(monster->type, M_SPEAKS)
&& _mon_has_spells(monster);
if (is_sanctuary(monster->pos()) && !mons_wont_attack(monster))
return (false);
if (monster->asleep()
|| monster->submerged()
|| !mons_class_flag(monster->type, M_SPELLCASTER)
&& !spellcasting_poly
&& draco_breath == SPELL_NO_SPELL)
{
return (false);
}
const bool priest = mons_class_flag(monster->type, M_PRIEST);
const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD;
if (silenced(monster->pos())
&& (priest || wizard || spellcasting_poly
|| mons_class_flag(monster->type, M_SPELL_NO_SILENT)))
{
return (false);
}
if (mons_is_shapeshifter(monster) && (priest || wizard))
return (false);
else if (monster->has_ench(ENCH_CONFUSION)
&& !mons_class_flag(monster->type, M_CONFUSED))
{
return (false);
}
else if (monster->type == MONS_PANDEMONIUM_DEMON
&& !monster->ghost->spellcaster)
{
return (false);
}
else if (random2(200) > monster->hit_dice + 50
|| monster->type == MONS_BALL_LIGHTNING && coinflip())
{
return (false);
}
else if (spellcasting_poly && coinflip()) return (false);
else
{
spell_type spell_cast = SPELL_NO_SPELL;
monster_spells hspell_pass(monster->spells);
if (!mon_enemies_around(monster))
{
if (monster->has_spell(SPELL_DIG)
&& mons_is_seeking(monster))
{
spell_cast = SPELL_DIG;
finalAnswer = true;
}
else if ((monster->has_spell(SPELL_MINOR_HEALING)
|| monster->has_spell(SPELL_MAJOR_HEALING))
&& monster->hit_points < monster->max_hit_points)
{
spell_cast = monster->has_spell(SPELL_MAJOR_HEALING) ?
SPELL_MAJOR_HEALING : SPELL_MINOR_HEALING;
finalAnswer = true;
}
else if (mons_is_fleeing(monster) || mons_is_pacified(monster))
{
int foundcount = 0;
for (int i = NUM_MONSTER_SPELL_SLOTS - 1; i >= 0; --i)
{
if (ms_useful_fleeing_out_of_sight(monster, hspell_pass[i])
&& one_chance_in(++foundcount))
{
spell_cast = hspell_pass[i];
finalAnswer = true;
}
}
}
else if (monster->foe == MHITYOU && !monsterNearby)
return (false);
}
if (!finalAnswer && mon_enemies_around(monster)
&& mons_is_caught(monster) && one_chance_in(4))
{
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
{
if (ms_quick_get_away(monster, hspell_pass[i]))
{
spell_cast = hspell_pass[i];
finalAnswer = true;
break;
}
}
}
if (!finalAnswer
&& monster->hit_points < monster->max_hit_points / 4
&& !one_chance_in(4))
{
if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
&& ms_low_hitpoint_cast(monster, hspell_pass[5]))
{
spell_cast = hspell_pass[5];
finalAnswer = true;
}
if (!finalAnswer)
{
int found_spell = 0;
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
{
if (ms_low_hitpoint_cast(monster, hspell_pass[i])
&& one_chance_in(++found_spell))
{
spell_cast = hspell_pass[i];
finalAnswer = true;
}
}
}
}
if (!finalAnswer)
{
if (mons_wont_attack(monster) && !mon_enemies_around(monster)
&& !one_chance_in(10))
{
return (false);
}
int num_no_spell = 0;
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
{
if (hspell_pass[i] == SPELL_NO_SPELL)
num_no_spell++;
else if (ms_waste_of_time(monster, hspell_pass[i])
|| hspell_pass[i] == SPELL_DIG)
{
hspell_pass[i] = SPELL_NO_SPELL;
num_no_spell++;
}
}
if (num_no_spell == NUM_MONSTER_SPELL_SLOTS
&& draco_breath == SPELL_NO_SPELL)
{
return (false);
}
const bolt orig_beem = beem;
for (int loopy = 0; loopy < 4; ++loopy)
{
beem = orig_beem;
bool spellOK = false;
if (mons_is_fleeing(monster) || mons_is_pacified(monster))
{
spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL
: hspell_pass[5]);
if (spell_cast != SPELL_NO_SPELL
&& mons_is_pacified(monster)
&& spell_harms_area(spell_cast))
{
spell_cast = SPELL_NO_SPELL;
}
}
else
{
spell_cast = hspell_pass[random2(5)];
}
if (spell_cast == SPELL_NO_SPELL)
continue;
setup_mons_cast(monster, beem, spell_cast);
if (spell_needs_tracer(spell_cast))
{
const bool explode =
spell_is_direct_explosion(spell_cast);
fire_tracer(monster, beem, explode);
if (mons_should_fire(beem))
spellOK = true;
}
else
{
spellOK = true;
if (ms_direct_nasty(spell_cast)
&& mons_aligned(monster_index(monster),
monster->foe))
{
spellOK = false;
}
else if (monster->foe == MHITYOU || monster->foe == MHITNOT)
{
if (!you.visible_to(monster)
&& (monster->target != you.pos() || coinflip()))
{
spellOK = false;
}
}
else if (!monster->can_see(&menv[monster->foe]))
{
spellOK = false;
}
else if (monster->type == MONS_DAEVA
&& monster->god == GOD_SHINING_ONE)
{
const monsters *mon = &menv[monster->foe];
if (is_unchivalric_attack(monster, mon)
&& !tso_unchivalric_attack_safe_monster(mon))
{
spellOK = false;
}
}
}
if (!spellOK)
{
spell_cast = (coinflip() ? hspell_pass[2]
: SPELL_NO_SPELL);
}
if (spell_cast != SPELL_NO_SPELL)
break;
}
}
if (draco_breath != SPELL_NO_SPELL
&& (spell_cast == SPELL_NO_SPELL
|| !_is_emergency_spell(hspell_pass, spell_cast)
&& one_chance_in(4))
&& !_is_player_or_mon_sanct(monster))
{
spell_cast = draco_breath;
finalAnswer = true;
}
if (spell_cast == SPELL_NO_SPELL)
return (false);
if (spell_cast == SPELL_POLYMORPH_OTHER && mons_friendly(monster))
return (false);
if (spell_cast == SPELL_ANIMATE_DEAD
&& !animate_dead(monster, 100, SAME_ATTITUDE(monster),
monster->foe, god, false))
{
return (false);
}
if (monster->type == MONS_BALL_LIGHTNING)
monster->hit_points = -1;
if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK)
{
if (monsterNearby)
{
mons_cast_noise(monster, beem, spell_cast);
monster_blink(monster);
monster->lose_energy(EUT_SPELL);
}
else
return (false);
}
else
{
if (spell_needs_foe(spell_cast))
_make_mons_stop_fleeing(monster);
mons_cast(monster, beem, spell_cast);
mmov.reset();
monster->lose_energy(EUT_SPELL);
}
}
return (true);
}
int mons_thrown_weapon_damage(const item_def *weap)
{
if (!weap || get_weapon_brand(*weap) != SPWPN_RETURNING)
return (0);
return std::max(0, (property(*weap, PWPN_DAMAGE) + weap->plus2 / 2));
}
int mons_weapon_damage_rating(const item_def &launcher)
{
return (property(launcher, PWPN_DAMAGE) + launcher.plus2);
}
int mons_missile_damage(monsters *mons, const item_def *launch,
const item_def *missile)
{
if (!missile || (!launch && !is_throwable(mons, *missile)))
return (0);
const int missile_damage = property(*missile, PWPN_DAMAGE) / 2 + 1;
const int launch_damage = launch? property(*launch, PWPN_DAMAGE) : 0;
return std::max(0, launch_damage + missile_damage);
}
int mons_pick_best_missile(monsters *mons, item_def **launcher,
bool ignore_melee)
{
*launcher = NULL;
item_def *melee = NULL, *launch = NULL;
for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i)
{
if (item_def *item = mons->mslot_item(static_cast<mon_inv_type>(i)))
{
if (is_range_weapon(*item))
launch = item;
else if (!ignore_melee)
melee = item;
}
}
const item_def *missiles = mons->missiles();
if (launch && missiles && !missiles->launched_by(*launch))
launch = NULL;
const int tdam = mons_thrown_weapon_damage(melee);
const int fdam = mons_missile_damage(mons, launch, missiles);
if (!tdam && !fdam)
return (NON_ITEM);
else if (tdam >= fdam)
return (melee->index());
else
{
*launcher = launch;
return (missiles->index());
}
}
static bool _handle_throw(monsters *monster, bolt & beem)
{
if (monster->incapacitated()
|| monster->asleep()
|| monster->submerged())
{
return (false);
}
if (mons_itemuse(monster) < MONUSE_STARTING_EQUIPMENT)
return (false);
const bool archer = mons_class_flag(monster->type, M_ARCHER);
if (one_chance_in(archer? 9 : 5))
return (false);
if (monster->foe == MHITYOU && !mons_near(monster))
return (false);
if (!archer && adjacent(beem.target, monster->pos()))
return (false);
if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
&& !one_chance_in(8))
{
return (false);
}
item_def *launcher = NULL;
const item_def *weapon = NULL;
const int mon_item = mons_pick_best_missile(monster, &launcher);
if (mon_item == NON_ITEM || !is_valid_item(mitm[mon_item]))
return (false);
if (_is_player_or_mon_sanct(monster))
return (false);
item_def *missile = &mitm[mon_item];
const actor *act = actor_at(beem.target);
if (missile->base_type == OBJ_MISSILES
&& missile->sub_type == MI_THROWING_NET
&& act && act->caught())
{
return (false);
}
if (launcher)
{
weapon = monster->mslot_item(MSLOT_WEAPON);
if (weapon && weapon != launcher && weapon->cursed())
return (false);
}
setup_generic_throw( monster, beem );
beem.damage = dice_def(10, 10);
beem.item = missile;
fire_tracer( monster, beem );
beem.damage = 0;
if (mons_should_fire( beem ))
{
_make_mons_stop_fleeing(monster);
if (launcher && launcher != weapon)
monster->swap_weapons();
beem.name.clear();
return (mons_throw( monster, beem, mon_item ));
}
return (false);
}
static bool _handle_monster_spell(monsters *monster, bolt &beem)
{
if (!mons_is_shapeshifter(monster)
|| !mons_class_flag(monster->type, M_ACTUAL_SPELLS))
{
if (_handle_spell(monster, beem))
return (true);
}
return (false);
}
static void _monster_add_energy(monsters *monster)
{
if (monster->speed > 0)
{
const int energy_gained =
std::max(1, div_rand_round(monster->speed * you.time_taken, 10));
monster->speed_increment += energy_gained;
}
}
int mons_natural_regen_rate(monsters *monster)
{
int divider =
std::max(div_rand_round(15 - monster->hit_dice, 4), 1);
switch (monster->holiness())
{
case MH_UNDEAD:
divider *= (mons_enslaved_soul(monster)) ? 2 : 4;
break;
case MH_NONLIVING:
divider *= 5;
break;
default:
break;
}
return (std::max(div_rand_round(monster->hit_dice, divider), 1));
}
static inline bool _mons_natural_regen_roll(monsters *monster)
{
const int regen_rate = mons_natural_regen_rate(monster);
return (x_chance_in_y(regen_rate, 25));
}
static void _monster_regenerate(monsters *monster)
{
if (monster->has_ench(ENCH_SICK) || !mons_can_regenerate(monster))
return;
if (mons_primary_habitat(monster) != HT_LAND
&& !monster_habitable_grid(monster, grd(monster->pos())))
{
return;
}
if (monster_descriptor(monster->type, MDSC_REGENERATES)
|| (monster->type == MONS_FIRE_ELEMENTAL
&& (grd(monster->pos()) == DNGN_LAVA
|| cloud_type_at(monster->pos()) == CLOUD_FIRE))
|| (monster->type == MONS_WATER_ELEMENTAL
&& feat_is_watery(grd(monster->pos())))
|| (monster->type == MONS_AIR_ELEMENTAL
&& env.cgrid(monster->pos()) == EMPTY_CLOUD
&& one_chance_in(3))
|| _mons_natural_regen_roll(monster))
{
heal_monster(monster, 1, false);
}
}
static bool _swap_monsters(monsters* mover, monsters* moved)
{
if (mons_is_stationary(moved))
return (false);
if (mover->confused())
return (false);
if (!is_sanctuary(mover->pos()) || !is_sanctuary(moved->pos()))
return (false);
if (mons_wont_attack_real(mover) == mons_wont_attack_real(moved)
|| mons_is_fleeing(mover) == mons_is_fleeing(moved))
{
return (false);
}
if ((mons_friendly(mover) || mons_friendly(moved))
&& you.pet_target != MHITYOU && you.pet_target != MHITNOT)
{
return (false);
}
if (!mover->can_pass_through(moved->pos())
|| !moved->can_pass_through(mover->pos()))
{
return (false);
}
if (!monster_habitable_grid(mover, grd(moved->pos()))
|| !monster_habitable_grid(moved, grd(mover->pos())))
{
return (false);
}
const coord_def mover_pos = mover->pos();
const coord_def moved_pos = moved->pos();
mover->pos() = moved_pos;
moved->pos() = mover_pos;
mgrd(mover->pos()) = mover->mindex();
mgrd(moved->pos()) = moved->mindex();
if (you.can_see(mover) && you.can_see(moved))
{
mprf("%s and %s swap places.", mover->name(DESC_CAP_THE).c_str(),
moved->name(DESC_NOCAP_THE).c_str());
}
return (true);
}
static void _swim_or_move_energy(monsters *mon)
{
const dungeon_feature_type feat = grd(mon->pos());
mon->lose_energy( (feat >= DNGN_LAVA && feat <= DNGN_SHALLOW_WATER
&& !mon->airborne()) ? EUT_SWIM
: EUT_MOVE );
}
static void _khufu_drop_tomb(monsters *monster)
{
int count = 0;
monster->behaviour = BEH_SEEK; for (adjacent_iterator ai(monster->pos()); ai; ++ai)
{
if (grd(*ai) == DNGN_ROCK_WALL)
{
grd(*ai) = DNGN_FLOOR;
count++;
}
}
if (count)
if (mons_near(monster))
mpr("The walls disappear!");
else
mpr("You hear a deep rumble.");
monster->number = 0;
monster->lose_energy(EUT_SPELL);
}
#ifdef DEBUG
# define DEBUG_ENERGY_USE(problem) \
if (monster->speed_increment == old_energy && monster->alive()) \
mprf(MSGCH_DIAGNOSTICS, \
problem " for monster '%s' consumed no energy", \
monster->name(DESC_PLAIN).c_str(), true);
#else
# define DEBUG_ENERGY_USE(problem) ((void) 0)
#endif
static void _handle_monster_move(monsters *monster)
{
monster->hit_points = std::min(monster->max_hit_points,
monster->hit_points);
if (testbits( monster->flags, MF_JUST_SUMMONED ))
{
monster->flags &= ~MF_JUST_SUMMONED;
return;
}
mon_acting mact(monster);
_monster_add_energy(monster);
if (monster->speed == 0
&& env.cgrid(monster->pos()) != EMPTY_CLOUD
&& !monster->submerged())
{
_mons_in_cloud( monster );
}
monster->ench_countdown -= you.time_taken;
while (monster->ench_countdown < 0)
{
monster->ench_countdown += 10;
monster->apply_enchantments();
if (monster->type == MONS_NO_MONSTER)
return;
else if (monster->hit_points < 1)
break;
}
if (monster->foe_memory > 0)
monster->foe_memory--;
if (monster->type == MONS_GLOWING_SHAPESHIFTER)
monster->add_ench(ENCH_GLOWING_SHAPESHIFTER);
if (monster->type == MONS_SHAPESHIFTER)
monster->add_ench(ENCH_SHAPESHIFTER);
if (monster->foe != MHITNOT && mons_is_wandering(monster)
&& mons_is_batty(monster))
{
monster->behaviour = BEH_SEEK;
}
monster->check_speed();
monsterentry* entry = get_monster_data(monster->type);
if (!entry)
return;
int old_energy = INT_MAX;
int non_move_energy = std::min(entry->energy_usage.move,
entry->energy_usage.swim);
#if DEBUG_MONS_SCAN
bool monster_was_floating = mgrd(monster->pos()) != monster->mindex();
#endif
while (monster->has_action_energy())
{
if (!monster->alive())
break;
const coord_def old_pos = monster->pos();
#if DEBUG_MONS_SCAN
if (!monster_was_floating
&& mgrd(monster->pos()) != monster->mindex())
{
mprf(MSGCH_ERROR, "Monster %s became detached from mgrd "
"in _handle_monster_move() loop",
monster->name(DESC_PLAIN, true).c_str());
mpr("[[[[[[[[[[[[[[[[[[", MSGCH_WARN);
debug_mons_scan();
mpr("]]]]]]]]]]]]]]]]]]", MSGCH_WARN);
monster_was_floating = true;
}
else if (monster_was_floating
&& mgrd(monster->pos()) == monster->mindex())
{
mprf(MSGCH_DIAGNOSTICS, "Monster %s re-attached itself to mgrd "
"in _handle_monster_move() loop",
monster->name(DESC_PLAIN, true).c_str());
monster_was_floating = false;
}
#endif
if (monster->speed_increment >= old_energy)
{
#ifdef DEBUG
if (monster->speed_increment == old_energy)
{
mprf(MSGCH_DIAGNOSTICS, "'%s' has same energy as last loop",
monster->name(DESC_PLAIN, true).c_str());
}
else
{
mprf(MSGCH_DIAGNOSTICS, "'%s' has MORE energy than last loop",
monster->name(DESC_PLAIN, true).c_str());
}
#endif
monster->speed_increment = old_energy - 10;
old_energy = monster->speed_increment;
continue;
}
old_energy = monster->speed_increment;
monster->shield_blocks = 0;
cloud_type cl_type;
const int cloud_num = env.cgrid(monster->pos());
const bool avoid_cloud = mons_avoids_cloud(monster, cloud_num,
&cl_type);
if (cl_type != CLOUD_NONE)
{
if (avoid_cloud)
{
if (monster->submerged())
{
monster->speed_increment -= entry->energy_usage.swim;
break;
}
if (monster->type == MONS_NO_MONSTER)
{
monster->speed_increment -= entry->energy_usage.move;
break; }
}
_mons_in_cloud(monster);
if (monster->type == MONS_NO_MONSTER)
{
monster->speed_increment = 1;
break;
}
}
if (monster->type == MONS_TIAMAT && one_chance_in(3))
{
const int cols[] = { RED, WHITE, DARKGREY, GREEN, MAGENTA };
monster->colour = RANDOM_ELEMENT(cols);
}
_monster_regenerate(monster);
if (mons_cannot_act(monster))
{
monster->speed_increment -= non_move_energy;
continue;
}
_handle_behaviour(monster);
if (!monster->alive())
break;
ASSERT(!crawl_state.arena || monster->foe != MHITYOU);
ASSERT(in_bounds(monster->target) || monster->target.origin());
if (avoid_cloud
&& monster_can_submerge(monster, grd(monster->pos()))
&& !monster->caught()
&& !monster->submerged())
{
monster->add_ench(ENCH_SUBMERGED);
monster->speed_increment -= ENERGY_SUBMERGE(entry);
continue;
}
if (monster->speed >= 100)
{
monster->speed_increment -= non_move_energy;
continue;
}
if (igrd(monster->pos()) != NON_ITEM
&& (mons_itemuse(monster) >= MONUSE_WEAPONS_ARMOUR
|| mons_itemeat(monster) != MONEAT_NOTHING))
{
if (!mons_neutral(monster) && !monster->has_ench(ENCH_CHARM)
|| (you.religion == GOD_JIYVA && mons_is_slime(monster))
&& (!mons_friendly(monster)
|| you.friendly_pickup != FRIENDLY_PICKUP_NONE))
{
if (_handle_pickup(monster))
{
DEBUG_ENERGY_USE("handle_pickup()");
continue;
}
}
}
if (mons_is_lurking(monster) || monster->has_ench(ENCH_SUBMERGED))
{
if (monster->foe != MHITNOT
&& grid_distance(monster->target, monster->pos()) <= 1)
{
if (monster->submerged())
{
if (monster->hit_points <= monster->max_hit_points / 2
|| monster->has_ench(ENCH_FEAR)
|| avoid_cloud)
{
monster->speed_increment -= non_move_energy;
continue;
}
if (!monster->del_ench(ENCH_SUBMERGED))
{
monster->speed_increment -= non_move_energy;
continue;
}
}
monster->behaviour = BEH_SEEK;
}
else
{
monster->speed_increment -= non_move_energy;
continue;
}
}
if (mons_is_caught(monster))
{
_swim_or_move_energy(monster);
}
else if (!mons_is_petrified(monster))
{
_handle_movement(monster);
if (mons_is_confused(monster)
|| monster->type == MONS_AIR_ELEMENTAL
&& monster->submerged())
{
mmov.reset();
int pfound = 0;
for (adjacent_iterator ai(monster->pos(), false); ai; ++ai)
if (monster->can_pass_through(*ai))
if (one_chance_in(++pfound))
mmov = *ai - monster->pos();
const coord_def newcell = mmov + monster->pos();
monsters* enemy = monster_at(newcell);
if (enemy
&& newcell != monster->pos()
&& !is_sanctuary(monster->pos()))
{
if (monsters_fight(monster, enemy))
{
mmov.reset();
DEBUG_ENERGY_USE("monsters_fight()");
continue;
}
else
{
if (monster->add_ench(mon_enchant(ENCH_FEAR)))
{
behaviour_event(monster, ME_SCARE,
MHITNOT, newcell);
}
break;
}
}
}
}
_handle_nearby_ability(monster);
if (monster->type == MONS_KHUFU && monster->number
&& monster->hit_points==monster->max_hit_points)
_khufu_drop_tomb(monster);
if (!monster->asleep() && !mons_is_wandering(monster)
&& !monster->has_ench(ENCH_BERSERK)
|| monster->type == MONS_SLIME_CREATURE)
{
bolt beem;
beem.source = monster->pos();
beem.target = monster->target;
beem.beam_source = monster->mindex();
const bool friendly_or_near =
mons_friendly(monster) || monster->near_foe();
if (friendly_or_near
|| monster->type == MONS_TEST_SPAWNER
|| monster->type == MONS_SLIME_CREATURE)
{
if (coinflip() ? _handle_special_ability(monster, beem)
|| _handle_monster_spell(monster, beem)
: _handle_monster_spell(monster, beem)
|| _handle_special_ability(monster, beem))
{
DEBUG_ENERGY_USE("spell or special");
continue;
}
}
if (friendly_or_near)
{
if (_handle_potion(monster, beem))
{
DEBUG_ENERGY_USE("_handle_potion()");
continue;
}
if (_handle_scroll(monster))
{
DEBUG_ENERGY_USE("_handle_scroll()");
continue;
}
if (_handle_wand(monster, beem))
{
DEBUG_ENERGY_USE("_handle_wand()");
continue;
}
if (_handle_reaching(monster))
{
DEBUG_ENERGY_USE("_handle_reaching()");
continue;
}
}
if (_handle_throw(monster, beem))
{
DEBUG_ENERGY_USE("_handle_throw()");
continue;
}
}
if (!mons_is_caught(monster))
{
if (monster->pos() + mmov == you.pos())
{
ASSERT(!crawl_state.arena);
if (!mons_friendly(monster))
{
monster->foe = MHITYOU;
monster->target = you.pos();
monster_attack(monster);
if (mons_is_batty(monster))
{
monster->behaviour = BEH_WANDER;
_set_random_target(monster);
}
DEBUG_ENERGY_USE("monster_attack()");
mmov.reset();
continue;
}
}
monsters* targ = monster_at(monster->pos() + mmov);
if (targ
&& targ != monster
&& !mons_aligned(monster->mindex(), targ->mindex())
&& monster_can_hit_monster(monster, targ))
{
if (_swap_monsters(monster, targ))
{
_swim_or_move_energy(monster);
continue;
}
else if (monsters_fight(monster, targ))
{
if (mons_is_batty(monster))
{
monster->behaviour = BEH_WANDER;
_set_random_target(monster);
}
mmov.reset();
DEBUG_ENERGY_USE("monsters_fight()");
continue;
}
}
if (invalid_monster(monster) || mons_is_stationary(monster))
{
if (monster->speed_increment == old_energy)
monster->speed_increment -= non_move_energy;
continue;
}
if (mons_cannot_move(monster) || !_monster_move(monster))
monster->speed_increment -= non_move_energy;
}
update_beholders(monster);
if (monster->alive())
{
_handle_behaviour(monster);
ASSERT(in_bounds(monster->target) || monster->target.origin());
}
}
if (monster->type != MONS_NO_MONSTER && monster->hit_points < 1)
monster_die(monster, KILL_MISC, NON_MONSTER);
}
void handle_monsters()
{
memset(immobile_monster, 0, sizeof immobile_monster);
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *monster = &menv[i];
if (!monster->alive() || immobile_monster[i])
continue;
const coord_def oldpos = monster->pos();
_handle_monster_move(monster);
if (!invalid_monster(monster) && monster->pos() != oldpos)
immobile_monster[i] = true;
if (you.banished)
{
if (you.duration[DUR_MESMERISED])
{
you.mesmerised_by.clear();
you.duration[DUR_MESMERISED] = 0;
}
break;
}
}
for (int i = 0; i < MAX_MONSTERS; i++)
menv[i].flags &= ~MF_JUST_SUMMONED;
}
static bool _is_item_jelly_edible(const item_def &item)
{
if (is_artefact(item))
return (false);
if (item.base_type == OBJ_MISSILES
&& (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
{
return (false);
}
if (item.base_type == OBJ_ORBS
|| (item.base_type == OBJ_MISCELLANY
&& (item.sub_type == MISC_RUNE_OF_ZOT
|| item.sub_type == MISC_HORN_OF_GERYON)))
{
return (false);
}
return (true);
}
static bool _monster_eat_item(monsters *monster, bool nearby)
{
if (!mons_eats_items(monster))
return (false);
if (mons_friendly(monster) && you.religion != GOD_JIYVA)
return (false);
int hps_gained = 0;
int max_eat = roll_dice(1, 10);
int eaten = 0;
bool eaten_net = false;
for (stack_iterator si(monster->pos());
si && eaten < max_eat && hps_gained < 50; ++si)
{
if (!_is_item_jelly_edible(*si))
continue;
#if DEBUG_DIAGNOSTICS || DEBUG_EATERS
mprf(MSGCH_DIAGNOSTICS,
"%s eating %s", monster->name(DESC_PLAIN, true).c_str(),
si->name(DESC_PLAIN).c_str());
#endif
int quant = si->quantity;
if (si->base_type != OBJ_GOLD)
{
quant = std::min(quant, max_eat - eaten);
hps_gained += (quant * item_mass(*si)) / 20 + quant;
eaten += quant;
if (mons_is_caught(monster)
&& si->base_type == OBJ_MISSILES
&& si->sub_type == MI_THROWING_NET
&& item_is_stationary(*si))
{
monster->del_ench(ENCH_HELD, true);
eaten_net = true;
}
}
else
{
if (quant > 500)
quant = 500 + roll_dice(2, (quant - 500) / 2);
hps_gained += quant / 10 + 1;
eaten++;
}
if (you.religion == GOD_JIYVA)
{
const int quantity = si->quantity;
const int value = item_value(*si) / quantity;
int pg = 0;
int timeout = 0;
for (int m = 0; m < quantity; ++m)
{
if (x_chance_in_y(value / 2 + 1, 30 + you.piety / 4))
{
if (timeout <= 0)
pg += random2(item_value(*si) / 6);
else
timeout -= value / 5;
}
}
if (pg > 0)
{
simple_god_message(" appreciates your sacrifice.");
gain_piety(pg);
}
if (you.piety > 80 && random2(you.piety) > 50 && one_chance_in(4))
{
if (you.can_safely_mutate())
{
simple_god_message(" alters your body.");
bool success = false;
const int rand = random2(100);
if (rand < 40)
success = mutate(RANDOM_MUTATION, true, false, true);
else if (rand < 60)
{
success = delete_mutation(RANDOM_MUTATION, true, false,
true);
}
else
{
success = mutate(RANDOM_GOOD_MUTATION, true, false,
true);
}
if (success)
{
timeout = (100 + roll_dice(2, 4));
you.num_gifts[you.religion]++;
take_note(Note(NOTE_GOD_GIFT, you.religion));
}
else
mpr("You feel as though nothing has changed.");
}
}
}
if (quant >= si->quantity)
item_was_destroyed(*si, monster->mindex());
dec_mitm_item_quantity(si.link(), quant);
}
if (eaten > 0)
{
hps_gained = std::max(hps_gained, 1);
hps_gained = std::min(hps_gained, 50);
monster->hit_points += hps_gained;
monster->max_hit_points = std::max(monster->hit_points,
monster->max_hit_points);
if (player_can_hear(monster->pos()))
{
mprf(MSGCH_SOUND, "You hear a%s slurping noise.",
nearby ? "" : " distant");
}
if (eaten_net)
simple_monster_message(monster, " devours the net!");
_jelly_divide(monster);
}
return (eaten > 0);
}
static bool _monster_eat_single_corpse(monsters *monster, item_def& item,
bool do_heal, bool nearby)
{
if (item.base_type != OBJ_CORPSES || item.sub_type != CORPSE_BODY)
return (false);
monster_type mt = static_cast<monster_type>(item.plus);
if (do_heal)
{
monster->hit_points += 1 + random2(mons_weight(mt)) / 100;
monster->hit_points = std::min(100, monster->hit_points);
monster->max_hit_points = std::max(monster->hit_points,
monster->max_hit_points);
}
if (nearby)
{
mprf("%s eats %s.", monster->name(DESC_CAP_THE).c_str(),
item.name(DESC_NOCAP_THE).c_str());
}
const int max_chunks = mons_weight(mt) / 150;
if (!food_is_rotten(item))
bleed_onto_floor(monster->pos(), mt, max_chunks, true);
if (mons_skeleton(mt) && one_chance_in(3))
turn_corpse_into_skeleton(item);
else
destroy_item(item.index());
return (true);
}
static bool _monster_eat_corpse(monsters *monster, bool do_heal, bool nearby)
{
if (!mons_eats_corpses(monster))
return (false);
int eaten = 0;
for (stack_iterator si(monster->pos()); si; ++si)
{
if (_monster_eat_single_corpse(monster, *si, do_heal, nearby))
{
eaten++;
break;
}
}
return (eaten > 0);
}
static bool _monster_eat_food(monsters *monster, bool nearby)
{
if (!mons_eats_food(monster))
return (false);
if (mons_is_fleeing(monster))
return (false);
int eaten = 0;
for (stack_iterator si(monster->pos()); si; ++si)
{
const bool is_food = (si->base_type == OBJ_FOOD);
const bool is_corpse = (si->base_type == OBJ_CORPSES
&& si->sub_type == CORPSE_BODY);
if (!is_food && !is_corpse)
continue;
if ((mons_wont_attack(monster)
|| grid_distance(monster->pos(), you.pos()) > 1)
&& coinflip())
{
if (is_food)
{
if (nearby)
{
mprf("%s eats %s.", monster->name(DESC_CAP_THE).c_str(),
quant_name(*si, 1, DESC_NOCAP_THE).c_str());
}
dec_mitm_item_quantity(si.link(), 1);
eaten++;
break;
}
else
{
if (_monster_eat_single_corpse(monster, *si,
monster->holiness() == MH_UNDEAD,
nearby))
{
eaten++;
break;
}
}
}
}
return (eaten > 0);
}
static bool _handle_pickup(monsters *monster)
{
if (monster->asleep() || monster->submerged())
return (false);
const bool nearby = mons_near(monster);
int count_pickup = 0;
if (mons_itemeat(monster) != MONEAT_NOTHING)
{
if (mons_eats_items(monster))
{
if (_monster_eat_item(monster, nearby))
return (false);
}
else if (mons_eats_corpses(monster))
{
if (_monster_eat_corpse(monster, monster->holiness() == MH_UNDEAD,
nearby))
{
return (false);
}
}
else if (mons_eats_food(monster))
{
if (_monster_eat_food(monster, nearby))
return (false);
}
}
if (mons_itemuse(monster) >= MONUSE_WEAPONS_ARMOUR)
{
for (stack_iterator si(monster->pos()); si; ++si)
{
if (monster->pickup_item(*si, nearby))
count_pickup++;
if (count_pickup > 1 || coinflip())
break;
}
}
return (count_pickup > 0);
}
static void _jelly_grows(monsters *monster)
{
if (player_can_hear(monster->pos()))
{
mprf(MSGCH_SOUND, "You hear a%s slurping noise.",
mons_near(monster) ? "" : " distant");
}
monster->hit_points += 5;
if (monster->hit_points > monster->max_hit_points)
monster->max_hit_points = monster->hit_points;
_jelly_divide(monster);
}
static bool _mons_can_displace(const monsters *mpusher, const monsters *mpushee)
{
if (invalid_monster(mpusher) || invalid_monster(mpushee))
return (false);
const int ipushee = monster_index(mpushee);
if (invalid_monster_index(ipushee))
return (false);
if (immobile_monster[ipushee])
return (false);
if (mons_is_confused(mpusher) || mons_is_confused(mpushee)
|| mons_cannot_move(mpusher) || mons_cannot_move(mpushee)
|| mons_is_stationary(mpusher) || mons_is_stationary(mpushee)
|| mpusher->asleep())
{
return (false);
}
if (mons_is_batty(mpusher) || mons_is_batty(mpushee))
return (false);
if (!monster_shover(mpusher))
return (false);
if (!monster_senior(mpusher, mpushee, mons_is_fleeing(mpusher)))
return (false);
return (true);
}
static bool _monster_swaps_places( monsters *mon, const coord_def& delta )
{
if (delta.origin())
return (false);
monsters* const m2 = monster_at(mon->pos() + delta);
if (!m2)
return (false);
if (!_mons_can_displace(mon, m2))
return (false);
if (m2->asleep())
{
if (coinflip())
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"Alerting monster %s at (%d,%d)",
m2->name(DESC_PLAIN).c_str(), m2->pos().x, m2->pos().y);
#endif
behaviour_event(m2, ME_ALERT, MHITNOT);
}
return (false);
}
const coord_def c = mon->pos();
const coord_def n = mon->pos() + delta;
if (!_habitat_okay(mon, grd(n)) || !_habitat_okay(m2, grd(c)))
return (false);
_swim_or_move_energy(mon);
mon->pos() = n;
mgrd(n) = monster_index(mon);
m2->pos() = c;
const int m2i = monster_index(m2);
ASSERT(m2i >= 0 && m2i < MAX_MONSTERS);
mgrd(c) = m2i;
immobile_monster[m2i] = true;
mon->check_redraw(c);
mon->apply_location_effects(c);
m2->check_redraw(c);
m2->apply_location_effects(n);
mon->seen_context.clear();
m2->seen_context.clear();
return (false);
}
static bool _do_move_monster(monsters *monster, const coord_def& delta)
{
const coord_def f = monster->pos() + delta;
if (!in_bounds(f))
return (false);
if (f == you.pos())
{
monster_attack(monster);
return (true);
}
if (monsters* def = monster_at(f))
{
monsters_fight(monster, def);
return (true);
}
if (monster->seen_context == _just_seen && !see_cell(f))
simple_monster_message(monster, " moves out of view.");
else if (Options.tutorial_left && (monster->flags & MF_WAS_IN_VIEW)
&& !see_cell(f))
{
learned_something_new(TUT_MONSTER_LEFT_LOS, monster->pos());
}
monster->seen_context.clear();
_swim_or_move_energy(monster);
if (grd(monster->pos()) == DNGN_DEEP_WATER && grd(f) != DNGN_DEEP_WATER
&& !monster_habitable_grid(monster, DNGN_DEEP_WATER))
{
monster->seen_context = "emerges from the water";
}
mgrd(monster->pos()) = NON_MONSTER;
monster->pos() = f;
mgrd(monster->pos()) = monster_index(monster);
monster->check_redraw(monster->pos() - delta);
monster->apply_location_effects(monster->pos() - delta);
return (true);
}
void mons_check_pool(monsters *monster, const coord_def &oldpos,
killer_type killer, int killnum)
{
if (monster->airborne())
return;
dungeon_feature_type grid = grd(monster->pos());
if ((grid == DNGN_LAVA || grid == DNGN_DEEP_WATER)
&& !monster_habitable_grid(monster, grid))
{
const bool message = mons_near(monster);
if (message && (oldpos == monster->pos() || grd(oldpos) != grid))
{
mprf("%s falls into the %s!",
monster->name(DESC_CAP_THE).c_str(),
grid == DNGN_LAVA ? "lava" : "water");
}
if (grid == DNGN_LAVA && monster->res_fire() >= 2)
grid = DNGN_DEEP_WATER;
if (grid == DNGN_LAVA || monster->can_drown())
{
if (message)
{
if (grid == DNGN_LAVA)
{
simple_monster_message(monster, " is incinerated.",
MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
}
else if (mons_genus(monster->type) == MONS_MUMMY)
{
simple_monster_message(monster, " falls apart.",
MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
}
else
{
simple_monster_message(monster, " drowns.",
MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
}
}
if (killer == KILL_NONE)
{
killer = KILL_MON;
killnum = monster_index(monster);
}
if (!_yred_enslave_soul(monster, killer))
monster_die(monster, killer, killnum, true);
}
}
}
static int _estimated_trap_damage(trap_type trap)
{
switch (trap)
{
case TRAP_BLADE: return (10 + random2(30));
case TRAP_DART: return (random2(4));
case TRAP_ARROW: return (random2(7));
case TRAP_SPEAR: return (random2(10));
case TRAP_BOLT: return (random2(13));
case TRAP_AXE: return (random2(15));
default: return (0);
}
}
static bool _is_trap_safe(const monsters *monster, const coord_def& where,
bool just_check)
{
const int intel = mons_intel(monster);
const trap_def *ptrap = find_trap(where);
if (!ptrap)
return (true);
const trap_def& trap = *ptrap;
const bool player_knows_trap = (trap.is_known(&you));
if (player_knows_trap && mons_friendly(monster) && trap.type == TRAP_ZOT)
return (false);
if (intel == I_PLANT)
return (true);
if (trap.type == TRAP_SHAFT && monster->will_trigger_shaft())
{
if (mons_is_fleeing(monster) && intel >= I_NORMAL
|| mons_is_pacified(monster))
{
return (true);
}
return (false);
}
const bool mechanical = (trap.category() == DNGN_TRAP_MECHANICAL);
if (trap.is_known(monster))
{
if (just_check)
return (false); else
{
const int x = where.x - monster->pos().x;
const int y = where.y - monster->pos().y;
if ((_mon_can_move_to_pos(monster, coord_def(x-1, y), true)
|| _mon_can_move_to_pos(monster, coord_def(x+1,y), true))
&& (_mon_can_move_to_pos(monster, coord_def(x,y-1), true)
|| _mon_can_move_to_pos(monster, coord_def(x,y+1), true)))
{
return (false);
}
}
}
if (intelligent_ally(monster) && trap.type == TRAP_TELEPORT
&& player_knows_trap && mons_near(monster))
{
return (false);
}
if (mechanical && monster->hit_points >= monster->max_hit_points / 2
&& (intel == I_ANIMAL
|| monster->hit_points > _estimated_trap_damage(trap.type)))
{
return (true);
}
if (mons_wont_attack(monster) || crawl_state.arena)
{
return (mechanical ? mons_flies(monster)
: !trap.is_known(monster) || trap.type != TRAP_ZOT);
}
else
return (!mechanical || mons_flies(monster));
}
static void _mons_open_door(monsters* monster, const coord_def &pos)
{
dungeon_feature_type grid = grd(pos);
const char *adj = "", *noun = "door";
bool was_secret = false;
bool was_seen = false;
std::set<coord_def> all_door;
find_connected_range(pos, DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR, all_door);
get_door_description(all_door.size(), &adj, &noun);
for (std::set<coord_def>::iterator i = all_door.begin();
i != all_door.end(); ++i)
{
const coord_def& dc = *i;
if (grd(dc) == DNGN_SECRET_DOOR && see_cell(dc))
{
grid = grid_secret_door_appearance(dc);
was_secret = true;
}
if (see_cell(dc))
was_seen = true;
else
set_terrain_changed(dc);
grd[dc.x][dc.y] = DNGN_OPEN_DOOR;
}
if (was_seen)
{
viewwindow(true, false);
if (was_secret)
{
mprf("%s was actually a secret door!",
feature_description(grid, NUM_TRAPS, false,
DESC_CAP_THE, false).c_str());
learned_something_new(TUT_SEEN_SECRET_DOOR, pos);
}
std::string open_str = "opens the ";
open_str += adj;
open_str += noun;
open_str += ".";
monster->seen_context = open_str;
if (!you.can_see(monster))
{
mprf("Something unseen %s", open_str.c_str());
interrupt_activity(AI_FORCE_INTERRUPT);
}
else if (!you_are_delayed())
{
mprf("%s %s", monster->name(DESC_CAP_A).c_str(),
open_str.c_str());
}
}
monster->lose_energy(EUT_MOVE);
}
static bool _no_habitable_adjacent_grids(const monsters *mon)
{
for (adjacent_iterator ai(mon->pos()); ai; ++ai)
if (_habitat_okay(mon, grd(*ai)))
return (false);
return (true);
}
static bool _mon_can_move_to_pos(const monsters *monster,
const coord_def& delta, bool just_check)
{
const coord_def targ = monster->pos() + delta;
if (!in_bounds(targ))
return (false);
if (grd(targ) == DNGN_OPEN_SEA)
return (false);
if (!mons_wont_attack(monster)
&& is_sanctuary(targ)
&& !is_sanctuary(monster->pos()))
{
return (false);
}
if (is_sanctuary(monster->pos()) && actor_at(targ))
return (false);
const dungeon_feature_type target_grid = grd(targ);
const habitat_type habitat = mons_primary_habitat(monster);
if (monster->type == MONS_KRAKEN && target_grid == DNGN_SHALLOW_WATER)
return (false);
bool no_water = false;
if (monster->type == MONS_FIRE_ELEMENTAL || one_chance_in(5))
no_water = true;
cloud_type targ_cloud_type;
const int targ_cloud_num = env.cgrid(targ);
if (mons_avoids_cloud(monster, targ_cloud_num, &targ_cloud_type))
return (false);
if (mons_class_flag(monster->type, M_BURROWS)
&& (target_grid == DNGN_ROCK_WALL
|| target_grid == DNGN_CLEAR_ROCK_WALL))
{
if (!in_bounds(targ))
return (false);
if (delta.x != 0 && delta.y != 0)
return (false);
}
else if (!monster->can_pass_through_feat(target_grid)
|| no_water && feat_is_water(target_grid))
{
return (false);
}
else if (!_habitat_okay(monster, target_grid))
{
if (grd(monster->pos()) == target_grid
&& _no_habitable_adjacent_grids(monster))
{
return (true);
}
return (false);
}
if (monster->type == MONS_WANDERING_MUSHROOM && see_cell(targ))
return (false);
if (monster->type == MONS_WATER_ELEMENTAL
&& (target_grid == DNGN_LAVA
|| targ_cloud_type == CLOUD_FIRE
|| targ_cloud_type == CLOUD_FOREST_FIRE
|| targ_cloud_type == CLOUD_STEAM))
{
return (false);
}
if (monster->type == MONS_FIRE_ELEMENTAL
&& (feat_is_watery(target_grid)
|| targ_cloud_type == CLOUD_COLD))
{
return (false);
}
if (habitat == HT_WATER
&& targ != you.pos()
&& target_grid != DNGN_DEEP_WATER
&& grd(monster->pos()) == DNGN_DEEP_WATER
&& monster->hit_points < (monster->max_hit_points * 3) / 4)
{
return (false);
}
if (monsters *targmonster = monster_at(targ))
{
if (just_check)
{
if (targ == monster->pos())
return (true);
return (false); }
if (mons_aligned(monster->mindex(), targmonster->mindex())
&& !_mons_can_displace(monster, targmonster))
{
return (false);
}
}
if (mons_wont_attack(monster)
&& monster->foe != MHITYOU
&& (monster->foe != MHITNOT || monster->is_patrolling())
&& targ == you.pos())
{
return (false);
}
if (!_is_trap_safe(monster, targ, just_check))
return (false);
return (true);
}
static void _find_good_alternate_move(monsters *monster,
const FixedArray<bool, 3, 3>& good_move)
{
const int current_distance = distance(monster->pos(), monster->target);
int dir = -1;
for (int i = 0; i < 8; i++)
{
if (mon_compass[i] == mmov)
{
dir = i;
break;
}
}
if (dir == -1)
return;
int dist[2];
for (int j = 1; j <= 2; j++)
{
const int FAR_AWAY = 1000000;
const int sdir = coinflip() ? j : -j;
const int inc = -2 * sdir;
for (int mod = sdir, i = 0; i < 2; mod += inc, i++)
{
const int newdir = (dir + 8 + mod) % 8;
if (good_move[mon_compass[newdir].x+1][mon_compass[newdir].y+1])
{
dist[i] = distance(monster->pos()+mon_compass[newdir],
monster->target);
}
else
{
dist[i] = (mons_is_fleeing(monster)) ? (-FAR_AWAY) : FAR_AWAY;
}
}
const int dir0 = ((dir + 8 + sdir) % 8);
const int dir1 = ((dir + 8 - sdir) % 8);
if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY)
continue;
if (mons_is_fleeing(monster))
{
if (dist[0] >= dist[1] && dist[0] >= current_distance)
{
mmov = mon_compass[dir0];
break;
}
if (dist[1] >= dist[0] && dist[1] >= current_distance)
{
mmov = mon_compass[dir1];
break;
}
}
else
{
if (dist[0] <= dist[1] && dist[0] <= current_distance)
{
mmov = mon_compass[dir0];
break;
}
if (dist[1] <= dist[0] && dist[1] <= current_distance)
{
mmov = mon_compass[dir1];
break;
}
}
}
}
static bool _monster_move(monsters *monster)
{
FixedArray<bool, 3, 3> good_move;
const habitat_type habitat = mons_primary_habitat(monster);
bool deep_water_available = false;
if (monster->type == MONS_TRAPDOOR_SPIDER)
{
if (monster->submerged())
return (false);
const actor *foe = monster->get_foe();
const bool can_see = foe && monster->can_see(foe);
if (monster_can_submerge(monster, grd(monster->pos()))
&& !can_see && !mons_is_confused(monster)
&& !monster->caught()
&& !monster->has_ench(ENCH_BERSERK))
{
monster->add_ench(ENCH_SUBMERGED);
monster->behaviour = BEH_LURK;
return (false);
}
}
if (monster->has_ench(ENCH_BERSERK))
{
int noise_level = get_shout_noise_level(mons_shouts(monster->type));
if (noise_level > 0)
{
if (you.can_see(monster))
{
if (one_chance_in(10))
{
mprf(MSGCH_TALK_VISUAL, "%s rages.",
monster->name(DESC_CAP_THE).c_str());
}
noisy(noise_level, monster->pos(), monster->mindex());
}
else if (one_chance_in(5))
handle_monster_shouts(monster, true);
else
{
noisy(noise_level, monster->pos(), monster->mindex());
}
}
}
if (monster->confused())
{
if (!mmov.origin() || one_chance_in(15))
{
const coord_def newpos = monster->pos() + mmov;
if (in_bounds(newpos)
&& (habitat == HT_LAND
|| monster_habitable_grid(monster, grd(newpos))))
{
return _do_move_monster(monster, mmov);
}
}
return (false);
}
if (monster->has_ench(ENCH_AQUATIC_LAND))
{
std::vector<coord_def> adj_water;
std::vector<coord_def> adj_move;
for (adjacent_iterator ai(monster->pos()); ai; ++ai)
{
if (!cell_is_solid(*ai))
{
adj_move.push_back(*ai);
if (feat_is_watery(grd(*ai)))
adj_water.push_back(*ai);
}
}
if (adj_move.empty())
{
simple_monster_message(monster, " flops around on dry land!");
return (false);
}
std::vector<coord_def> moves = adj_water;
if (adj_water.empty() || coinflip())
moves = adj_move;
coord_def newpos = monster->pos();
int count = 0;
for (unsigned int i = 0; i < moves.size(); ++i)
if (one_chance_in(++count))
newpos = moves[i];
const monsters *mon2 = monster_at(newpos);
if (newpos == you.pos() && mons_wont_attack(monster)
|| (mon2 && mons_wont_attack(monster) == mons_wont_attack(mon2)))
{
simple_monster_message(monster, " flops around on dry land!");
return (false);
}
return _do_move_monster(monster, newpos - monster->pos());
}
if (mmov.origin())
return (false);
for (int count_x = 0; count_x < 3; count_x++)
for (int count_y = 0; count_y < 3; count_y++)
{
const int targ_x = monster->pos().x + count_x - 1;
const int targ_y = monster->pos().y + count_y - 1;
if (!in_bounds(targ_x, targ_y))
{
good_move[count_x][count_y] = false;
continue;
}
dungeon_feature_type target_grid = grd[targ_x][targ_y];
if (target_grid == DNGN_DEEP_WATER)
deep_water_available = true;
good_move[count_x][count_y] =
_mon_can_move_to_pos(monster, coord_def(count_x-1, count_y-1));
}
const coord_def newpos = monster->pos() + mmov;
if (grd(newpos) == DNGN_CLOSED_DOOR
|| feat_is_secret_door(grd(newpos)) && mons_intel(monster) >= I_NORMAL)
{
if (mons_is_zombified(monster))
{
if (mons_class_itemuse(monster->base_monster) >= MONUSE_OPEN_DOORS)
{
_mons_open_door(monster, newpos);
return (true);
}
}
else if (mons_itemuse(monster) >= MONUSE_OPEN_DOORS)
{
_mons_open_door(monster, newpos);
return (true);
}
}
if ((grd(newpos) == DNGN_CLOSED_DOOR || grd(newpos) == DNGN_OPEN_DOOR)
&& mons_itemeat(monster) == MONEAT_ITEMS
&& !feature_marker_at(newpos, DNGN_PERMAROCK_WALL))
{
grd(newpos) = DNGN_FLOOR;
_jelly_grows(monster);
if (see_cell(newpos))
{
viewwindow(true, false);
if (!you.can_see(monster))
{
mpr("The door mysteriously vanishes.");
interrupt_activity( AI_FORCE_INTERRUPT );
}
}
}
if (habitat == HT_WATER
&& deep_water_available
&& grd(monster->pos()) != DNGN_DEEP_WATER
&& grd(newpos) != DNGN_DEEP_WATER
&& newpos != you.pos()
&& (one_chance_in(3)
|| monster->hit_points <= (monster->max_hit_points * 3) / 4))
{
int count = 0;
for (int cx = 0; cx < 3; cx++)
for (int cy = 0; cy < 3; cy++)
{
if (good_move[cx][cy]
&& grd[monster->pos().x + cx - 1][monster->pos().y + cy - 1]
== DNGN_DEEP_WATER)
{
if (one_chance_in(++count))
{
mmov.x = cx - 1;
mmov.y = cy - 1;
}
}
}
}
if (good_move[mmov.x + 1][mmov.y + 1] == false)
_find_good_alternate_move(monster, good_move);
if (mons_class_flag(monster->type, M_BURROWS))
{
const dungeon_feature_type feat = grd(monster->pos() + mmov);
if ((feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL)
&& good_move[mmov.x + 1][mmov.y + 1] == true)
{
grd(monster->pos() + mmov) = DNGN_FLOOR;
set_terrain_changed(monster->pos() + mmov);
if (player_can_hear(monster->pos() + mmov))
{
mpr((monster->type == MONS_BORING_BEETLE) ?
"You hear a grinding noise." :
"You hear a sizzling sound.", MSGCH_SOUND);
}
}
}
bool ret = false;
if (good_move[mmov.x + 1][mmov.y + 1] && !mmov.origin())
{
if (monster->pos() + mmov == you.pos())
{
ret = monster_attack(monster);
mmov.reset();
}
if (testbits(monster->flags, MF_TAKING_STAIRS))
{
const delay_type delay = current_delay_action();
if (delay != DELAY_ASCENDING_STAIRS
&& delay != DELAY_DESCENDING_STAIRS)
{
monster->flags &= ~MF_TAKING_STAIRS;
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"BUG: %s was marked as follower when not following!",
monster->name(DESC_PLAIN).c_str(), true);
#endif
}
else
{
ret = true;
mmov.reset();
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"%s is skipping movement in order to follow.",
monster->name(DESC_CAP_THE).c_str(), true );
#endif
}
}
if (monsters* targ = monster_at(monster->pos() + mmov))
{
if (mons_aligned(monster->mindex(), targ->mindex()))
ret = _monster_swaps_places(monster, mmov);
else
{
monsters_fight(monster, targ);
ret = true;
}
mmov.reset();
}
if (monster->type == MONS_EFREET
|| monster->type == MONS_FIRE_ELEMENTAL)
{
place_cloud( CLOUD_FIRE, monster->pos(),
2 + random2(4), monster->kill_alignment() );
}
if (monster->type == MONS_ROTTING_DEVIL
|| monster->type == MONS_CURSE_TOE)
{
place_cloud( CLOUD_MIASMA, monster->pos(),
2 + random2(3), monster->kill_alignment() );
}
}
else
{
mmov.reset();
_make_mons_stop_fleeing(monster);
}
if (mmov.x || mmov.y || (monster->confused() && one_chance_in(6)))
return (_do_move_monster(monster, mmov));
return (ret);
}
static void _mons_in_cloud(monsters *monster)
{
int wc = env.cgrid(monster->pos());
int hurted = 0;
bolt beam;
const int speed = ((monster->speed > 0) ? monster->speed : 10);
bool wake = false;
if (mons_is_mimic( monster->type ))
{
mimic_alert(monster);
return;
}
const cloud_struct &cloud(env.cloud[wc]);
switch (cloud.type)
{
case CLOUD_DEBUGGING:
mprf(MSGCH_ERROR,
"Monster %s stepped on a nonexistent cloud at (%d,%d)",
monster->name(DESC_PLAIN, true).c_str(),
monster->pos().x, monster->pos().y);
return;
case CLOUD_FIRE:
case CLOUD_FOREST_FIRE:
if (monster->type == MONS_FIRE_VORTEX
|| monster->type == MONS_EFREET
|| monster->type == MONS_FIRE_ELEMENTAL)
{
return;
}
simple_monster_message(monster, " is engulfed in flames!");
hurted +=
resist_adjust_damage( monster,
BEAM_FIRE,
monster->res_fire(),
((random2avg(16, 3) + 6) * 10) / speed );
hurted -= random2(1 + monster->ac);
break;
case CLOUD_STINK:
simple_monster_message(monster, " is engulfed in noxious gasses!");
if (monster->res_poison() > 0)
return;
beam.flavour = BEAM_CONFUSION;
beam.thrower = cloud.killer;
if (cloud.whose == KC_FRIENDLY)
beam.beam_source = ANON_FRIENDLY_MONSTER;
if (mons_class_is_confusable(monster->type)
&& 1 + random2(27) >= monster->hit_dice)
{
beam.apply_enchantment_to_monster(monster);
}
hurted += (random2(3) * 10) / speed;
break;
case CLOUD_COLD:
simple_monster_message(monster, " is engulfed in freezing vapours!");
hurted +=
resist_adjust_damage( monster,
BEAM_COLD,
monster->res_cold(),
((6 + random2avg(16, 3)) * 10) / speed );
hurted -= random2(1 + monster->ac);
break;
case CLOUD_POISON:
simple_monster_message(monster, " is engulfed in a cloud of poison!");
if (monster->res_poison() > 0)
return;
poison_monster(monster, cloud.whose);
wake = true;
hurted += (random2(8) * 10) / speed;
if (monster->res_poison() < 0)
hurted += (random2(4) * 10) / speed;
break;
case CLOUD_STEAM:
{
simple_monster_message(monster, " is engulfed in steam!");
const int steam_base_damage = steam_cloud_damage(cloud);
hurted +=
resist_adjust_damage(
monster,
BEAM_STEAM,
monster->res_steam(),
(random2avg(steam_base_damage, 2) * 10) / speed);
hurted -= random2(1 + monster->ac);
break;
}
case CLOUD_MIASMA:
simple_monster_message(monster, " is engulfed in a dark miasma!");
if (monster->res_rotting())
return;
miasma_monster(monster, cloud.whose);
hurted += (10 * random2avg(12, 3)) / speed; break;
case CLOUD_RAIN:
if (monster->is_fiery())
{
if (!silenced(monster->pos()))
simple_monster_message(monster, " sizzles in the rain!");
else
simple_monster_message(monster, " steams in the rain!");
hurted += ((4 * random2(3)) - random2(monster->ac));
wake = true;
}
break;
case CLOUD_MUTAGENIC:
simple_monster_message(monster, " is engulfed in a mutagenic fog!");
if (monster->can_mutate() && !mons_immune_magic(monster)
&& 1 + random2(27) >= monster->hit_dice
&& !monster->res_asphyx())
{
if (monster->mutate())
wake = true;
}
break;
default: return;
}
if ((wake || hurted > 0) && monster->asleep())
{
behaviour_event(monster, ME_DISTURB, MHITNOT, monster->pos());
}
if (hurted > 0)
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "%s takes %d damage from cloud.",
monster->name(DESC_CAP_THE).c_str(), hurted);
#endif
monster->hurt(NULL, hurted, BEAM_MISSILE, false);
if (monster->hit_points < 1)
{
mon_enchant death_ench(ENCH_NONE, 0, cloud.whose);
monster_die(monster, cloud.killer, death_ench.kill_agent());
}
}
}
bool monster_descriptor(int which_class, mon_desc_type which_descriptor)
{
if (which_descriptor == MDSC_LEAVES_HIDE)
{
switch (which_class)
{
case MONS_DRAGON:
case MONS_TROLL:
case MONS_ICE_DRAGON:
case MONS_STEAM_DRAGON:
case MONS_MOTTLED_DRAGON:
case MONS_STORM_DRAGON:
case MONS_GOLDEN_DRAGON:
case MONS_SWAMP_DRAGON:
case MONS_YAK:
case MONS_SHEEP:
return (true);
default:
return (false);
}
}
if (which_descriptor == MDSC_REGENERATES)
{
switch (which_class)
{
case MONS_CACODEMON:
case MONS_DEEP_TROLL:
case MONS_HELLWING:
case MONS_IMP:
case MONS_IRON_TROLL:
case MONS_LEMURE:
case MONS_ROCK_TROLL:
case MONS_SLIME_CREATURE:
case MONS_SNORG:
case MONS_PURGY:
case MONS_TROLL:
case MONS_HYDRA:
case MONS_KILLER_KLOWN:
case MONS_LERNAEAN_HYDRA:
case MONS_DISSOLUTION:
return (true);
default:
return (false);
}
}
if (which_descriptor == MDSC_NOMSG_WOUNDS)
{
if (mons_class_is_zombified(which_class)
&& which_class != MONS_SPECTRAL_THING)
{
return (true);
}
switch (which_class)
{
case MONS_RAKSHASA:
case MONS_RAKSHASA_FAKE:
return (true);
default:
return (false);
}
}
return (false);
}
bool message_current_target()
{
if (crawl_state.is_replaying_keys())
{
if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU)
return (false);
return (you.can_see(&menv[you.prev_targ]));
}
if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU)
{
const monsters *montarget = &menv[you.prev_targ];
if (you.can_see(montarget))
{
mprf(MSGCH_PROMPT, "Current target: %s "
"(use p or f to fire at it again.)",
montarget->name(DESC_PLAIN).c_str());
return (true);
}
mpr("You have no current target.");
}
return (false);
}
unsigned int monster_index(const monsters *monster)
{
return (monster - menv.buffer());
}
bool heal_monster(monsters * patient, int health_boost,
bool permit_growth)
{
if (mons_is_statue(patient->type))
return (false);
if (health_boost < 1)
return (false);
else if (!permit_growth && patient->hit_points == patient->max_hit_points)
return (false);
patient->hit_points += health_boost;
bool success = true;
if (patient->hit_points > patient->max_hit_points)
{
if (permit_growth)
{
const monsterentry* m = get_monster_data(patient->type);
const int maxhp =
m->hpdice[0] * (m->hpdice[1] + m->hpdice[2]) + m->hpdice[3];
if (random2(3 * maxhp) > 2 * patient->max_hit_points)
patient->max_hit_points++;
else
success = false;
}
patient->hit_points = patient->max_hit_points;
}
return (success);
}
static spell_type _map_wand_to_mspell(int wand_type)
{
switch (wand_type)
{
case WAND_FLAME: return SPELL_THROW_FLAME;
case WAND_FROST: return SPELL_THROW_FROST;
case WAND_SLOWING: return SPELL_SLOW;
case WAND_HASTING: return SPELL_HASTE;
case WAND_MAGIC_DARTS: return SPELL_MAGIC_DART;
case WAND_HEALING: return SPELL_MINOR_HEALING;
case WAND_PARALYSIS: return SPELL_PARALYSE;
case WAND_FIRE: return SPELL_BOLT_OF_FIRE;
case WAND_COLD: return SPELL_BOLT_OF_COLD;
case WAND_CONFUSION: return SPELL_CONFUSE;
case WAND_INVISIBILITY: return SPELL_INVISIBILITY;
case WAND_TELEPORTATION: return SPELL_TELEPORT_OTHER;
case WAND_LIGHTNING: return SPELL_LIGHTNING_BOLT;
case WAND_DRAINING: return SPELL_BOLT_OF_DRAINING;
case WAND_DISINTEGRATION: return SPELL_DISINTEGRATE;
case WAND_POLYMORPH_OTHER: return SPELL_POLYMORPH_OTHER;
default: return SPELL_NO_SPELL;
}
}
void seen_monster(monsters *monster)
{
set_auto_exclude(monster);
monster->flags |= MF_WAS_IN_VIEW;
if (monster->flags & MF_SEEN)
return;
monster->flags |= MF_SEEN;
if (!mons_is_mimic(monster->type))
{
if (Options.tutorial_left)
tutorial_first_monster(*monster);
if (MONST_INTERESTING(monster))
{
take_note(
Note(NOTE_SEEN_MONSTER, monster->type, 0,
monster->name(DESC_NOCAP_A, true).c_str()));
}
}
}
bool shift_monster(monsters *mon, coord_def p)
{
coord_def result;
int count = 0;
if (p.origin())
p = mon->pos();
for (adjacent_iterator ai(p); ai; ++ai)
{
if (grd(*ai) != DNGN_FLOOR)
continue;
if (actor_at(*ai))
continue;
if (one_chance_in(++count))
result = *ai;
}
if (count > 0)
{
mgrd(mon->pos()) = NON_MONSTER;
mon->moveto(result);
mgrd(result) = mon->mindex();
}
return (count > 0);
}
static void _vanish_orig_eq(monsters* mons)
{
for (int i = 0; i < NUM_MONSTER_SLOTS; ++i)
{
if (mons->inv[i] == NON_ITEM)
continue;
item_def &item(mitm[mons->inv[i]]);
if (!is_valid_item(item))
continue;
if (item.orig_place != 0 || item.orig_monnum != 0
|| !item.inscription.empty()
|| is_unrandom_artefact(item)
|| (item.flags & (ISFLAG_DROPPED | ISFLAG_THROWN | ISFLAG_NOTED_GET
| ISFLAG_BEEN_IN_INV) ) )
{
continue;
}
item.flags |= ISFLAG_SUMMONED;
}
}
int dismiss_monsters(std::string pattern) {
const bool keep_item = strip_tag(pattern, "keepitem");
text_pattern tpat(pattern);
int ndismissed = 0;
for (int mon = 0; mon < MAX_MONSTERS; mon++)
{
monsters *monster = &menv[mon];
if (monster->alive() &&
(tpat.empty() || tpat.matches(monster->name(DESC_PLAIN, true))))
{
if (!keep_item)
_vanish_orig_eq(monster);
monster_die(monster, KILL_DISMISSED, NON_MONSTER, false, true);
++ndismissed;
}
}
return (ndismissed);
}