summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/monstuff.cc
diff options
context:
space:
mode:
authorMatthew Cline <zelgadis@sourceforge.net>2009-11-01 01:55:04 -0700
committerMatthew Cline <zelgadis@sourceforge.net>2009-11-01 01:55:04 -0700
commit204607ad3e8aecb32e01f303b1e86545d3d57f62 (patch)
tree02cbad03b0e787e266cf650414d56022d34d5f6a /crawl-ref/source/monstuff.cc
parent82ab217c80b03d6e85cf0547c84e919535eb6827 (diff)
downloadcrawl-ref-204607ad3e8aecb32e01f303b1e86545d3d57f62.tar.gz
crawl-ref-204607ad3e8aecb32e01f303b1e86545d3d57f62.zip
Split up monstuff.cc
A lot of monstuff.cc was moved into mon-abil.cc (monster abilities), mon-act.cc (the main monster loop), mon-behv.cc (monster behaviour) and mon-cast.cc (monster spells). mstuff2.cc was completely merged into other files.
Diffstat (limited to 'crawl-ref/source/monstuff.cc')
-rw-r--r--crawl-ref/source/monstuff.cc6685
1 files changed, 372 insertions, 6313 deletions
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index 3f4e706d27..0f01faf0e4 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -7,55 +7,36 @@
#include "AppHdr.h"
#include "monstuff.h"
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <algorithm>
+//#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 "mon-behv.h"
#include "monplace.h"
#include "monspeak.h"
-#include "mon-util.h"
-#include "mutation.h"
-#include "mstuff2.h"
#include "notes.h"
#include "player.h"
+#include "random.h"
#include "religion.h"
#include "spl-mis.h"
#include "spl-util.h"
-#include "spells3.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
@@ -67,38 +48,6 @@
#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);
-
-// [dshaligram] Doesn't need to be extern.
-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];
-
-// A probably needless optimization: convert the C string "just seen" to
-// a C++ string just once, instead of twice every time a monster moves.
-static const std::string _just_seen("just seen");
-
-#define ENERGY_SUBMERGE(entry) (std::max(entry->energy_usage.swim / 2, 1))
// This function creates an artificial item to represent a mimic's appearance.
// Eventually, mimics could be redone to be more like dancing weapons...
@@ -2012,70 +1961,6 @@ void monster_cleanup(monsters *monster)
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;
-
- // First, find a suitable spot for the child {dlb}:
- 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;
-
- // Now that we have a spot, find a monster slot {dlb}:
- for (k = 0; k < MAX_MONSTERS; k++)
- {
- child = &menv[k];
-
- if (child->type == -1)
- break;
- else if (k == MAX_MONSTERS - 1)
- return (false);
- }
-
- // Handle impact of split on parent {dlb}:
- 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;
-
- // Create child {dlb}:
- // This is terribly partial and really requires
- // more thought as to generation ... {dlb}
- *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);
-}
-
// If you're invis and throw/zap whatever, alerts menv to your position.
void alert_nearby_monsters(void)
{
@@ -2593,52 +2478,6 @@ void slimify_monster(monsters *mon, bool hostile)
mons_make_god_gift(mon, GOD_JIYVA);
}
-static void _set_random_target(monsters* mon)
-{
- mon->target = random_in_bounds(); // If we don't find anything better.
- 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)
-{
- // Strictly neutral slimes will go for the nearest item.
- 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);
-}
-
// allow_adjacent: allow target to be adjacent to origin.
// restrict_LOS: restrict target to be within PLAYER line of sight.
bool random_near_space(const coord_def& origin, coord_def& target,
@@ -2958,552 +2797,6 @@ static bool _wounded_damaged(monster_type mon_type)
return (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT);
}
-//---------------------------------------------------------------
-//
-// behaviour_event
-//
-// 1. Change any of: monster state, foe, and attitude
-// 2. Call handle_behaviour to re-evaluate AI state and target x,y
-//
-//---------------------------------------------------------------
-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:
- // Assumes disturbed by noise...
- if (mon->asleep())
- {
- mon->behaviour = BEH_WANDER;
-
- if (mons_near(mon))
- remove_auto_exclude(mon, true);
- }
-
- // A bit of code to make Projected Noise actually do
- // something again. Basically, dumb monsters and
- // monsters who aren't otherwise occupied will at
- // least consider the (apparent) source of the noise
- // interesting for a moment. -- bwr
- 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:
- // Will turn monster against <src>, unless they
- // are BOTH friendly or good neutral AND stupid,
- // or else fleeing anyway. Hitting someone over
- // the head, of course, always triggers this code.
- if (event == ME_WHACK
- || ((wontAttack != sourceWontAttack || isSmart)
- && !mons_is_fleeing(mon) && !mons_is_panicking(mon)))
- {
- // Monster types that you can't gain experience from cannot
- // fight back, so don't bother having them do so. If you
- // worship Feawn, create a ring of friendly plants, and try
- // to break out of the ring by killing a plant, you'll get
- // a warning prompt and penance only once. Without the
- // hostility check, the plant will remain friendly until it
- // dies, and you'll get a warning prompt and penance once
- // *per hit*. This may not be the best way to address the
- // issue, though. -cao
- 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;
- }
- }
-
- // Now set target so that monster can whack back (once) at an
- // invisible foe.
- if (event == ME_WHACK)
- setTarget = true;
- break;
-
- case ME_ALERT:
- // Allow monsters falling asleep while patrolling (can happen if
- // they're left alone for a long time) to be woken by this event.
- if (mons_friendly(mon) && mon->is_patrolling()
- && !mon->asleep())
- {
- break;
- }
-
- if (mon->asleep() && mons_near(mon))
- remove_auto_exclude(mon, true);
-
- // Will alert monster to <src> and turn them
- // against them, unless they have a current foe.
- // It won't turn friends hostile either.
- 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;
-
- // XXX: Should this be done in _handle_behaviour()?
- 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:
- // Stationary monsters can't flee, and berserking monsters
- // are too enraged.
- if (mons_is_stationary(mon) || mon->has_ench(ENCH_BERSERK))
- {
- mon->del_ench(ENCH_FEAR, true, true);
- break;
- }
-
- // Neither do plants or nonliving beings.
- if (mon->holiness() == MH_PLANT
- || mon->holiness() == MH_NONLIVING)
- {
- mon->del_ench(ENCH_FEAR, true, true);
- break;
- }
-
- // Assume monsters know where to run from, even if player is
- // invisible.
- mon->behaviour = BEH_FLEE;
- mon->foe = src;
- mon->target = src_pos;
- if (src == MHITYOU)
- {
- // Friendly monsters don't become hostile if you read a
- // scroll of fear, but enslaved ones will.
- // Send friendlies off to a random target so they don't cling
- // to you in fear.
- 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:
- // Some monsters can't flee.
- if (mon->behaviour != BEH_FLEE && !mon->has_ench(ENCH_FEAR))
- break;
-
- // Pacified monsters shouldn't change their behaviour.
- if (mons_is_pacified(mon))
- break;
-
- // Just set behaviour... foe doesn't change.
- 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();
- }
-
- // Now, break charms if appropriate.
- if (breakCharm)
- mon->del_ench(ENCH_CHARM);
-
- // Do any resultant foe or state changes.
- _handle_behaviour(mon);
- ASSERT(in_bounds(mon->target) || mon->target.origin());
-
- // If it woke up and you're its new foe, it might shout.
- 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))
- {
- // Lurking monsters or pacified monsters leaving the level won't
- // stop doing so just because they noticed something.
- mon->behaviour = old_behaviour;
- }
- else if (wasLurking && mon->has_ench(ENCH_SUBMERGED)
- && !mon->del_ench(ENCH_SUBMERGED))
- {
- // The same goes for lurking submerged monsters, if they can't
- // unsubmerge.
- 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);
-
- // Zombies will occasionally just stand around.
- // This does not mean that they don't move every second turn. Rather,
- // once they reach their chosen target, there's a 50% chance they'll
- // just remain there until next turn when this function is called
- // again.
- if (intel == I_PLANT && coinflip())
- return (true);
-
- // If there's no chance we'll find the patrol point, quit right away.
- if (grid_distance(mon->pos(), mon->patrol_point) > 2 * LOS_RADIUS)
- return (false);
-
- // Can the monster see the patrol point from its current position?
- const bool patrol_seen = mon->mon_see_cell(mon->patrol_point,
- habitat2grid(mons_primary_habitat(mon)));
-
- if (intel == I_PLANT && !patrol_seen)
- {
- // Really stupid monsters won't even try to get back into the
- // patrol zone.
- return (false);
- }
-
- // While the patrol point is in easy reach, monsters of insect/plant
- // intelligence will only use a range of 5 (distance from the patrol point).
- // Otherwise, try to get back using the full LOS.
- 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)
- {
- // For stupid monsters, don't bother if the patrol point is in sight.
- lm.update();
- }
-
- int count_grids = 0;
- for (radius_iterator ri(mon->patrol_point, LOS_RADIUS, true, false);
- ri; ++ri)
- {
- // Don't bother for the current position. If everything fails,
- // we'll stay here anyway.
- if (*ri == mon->pos())
- continue;
-
- if (!mon->can_pass_through_feat(grd(*ri)))
- continue;
-
- // Don't bother moving to squares (currently) occupied by a
- // monster. We'll usually be able to find other target squares
- // (and if we're not, we couldn't move anyway), and this avoids
- // monsters trying to move onto a grid occupied by a plant or
- // sleeping monster.
- if (monster_at(*ri))
- continue;
-
- if (patrol_seen)
- {
- // If the patrol point can be easily (within LOS) reached
- // from the current position, it suffices if the target is
- // within reach of the patrol point OR the current position:
- // we can easily get there.
- // Only smart monsters will even attempt to move out of the
- // patrol area.
- // NOTE: Either of these can take us into a position where the
- // target cannot be easily reached (e.g. blocked by a wall)
- // and the patrol point is out of sight, too. Such a case
- // will be handled below, though it might take a while until
- // a monster gets out of a deadlock. (5% chance per turn.)
- if (!patrol.see_cell(*ri) &&
- (!is_smart || !lm.see_cell(*ri)))
- {
- continue;
- }
- }
- else
- {
- // If, however, the patrol point is out of reach, we have to
- // make sure the new target brings us into reach of it.
- // This means that the target must be reachable BOTH from
- // the patrol point AND the current position.
- if (!patrol.see_cell(*ri) ||
- !lm.see_cell(*ri))
- {
- continue;
- }
-
- // If this fails for all surrounding squares (probably because
- // we're too far away), we fall back to heading directly for
- // the patrol point.
- }
-
- bool set_target = false;
- if (intel == I_PLANT && *ri == mon->patrol_point)
- {
- // Slightly greater chance to simply head for the centre.
- 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);
-}
-
-//#define DEBUG_PATHFIND
-
-// Check all grids in LoS and mark lava and/or water as seen if the
-// appropriate grids are encountered, so we later only need to do the
-// visibility check for monsters that can't pass a feature potentially in
-// the way. We don't care about shallow water as most monsters can safely
-// cross that, and fire elementals alone aren't really worth the extra
-// hassle. :)
-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)
- {
- // XXX: remove explicit coordinate translation.
- 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;
- }
- }
- }
-}
-
-// If a monster can see but not directly reach the target, and then fails to
-// find a path to get there, mark all surrounding (in a radius of 2) monsters
-// of the same (or greater) movement restrictions as also being unable to
-// find a path, so we won't need to calculate again.
-// Should there be a direct path to the target for a monster thus marked, it
-// will still be able to come nearer (and the mark will then be cleared).
-static void _mark_neighbours_target_unreachable(monsters *mon)
-{
- // Highly intelligent monsters are perfectly capable of pathfinding
- // and don't need their neighbour's advice.
- 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;
-
- // Don't alert monsters out of sight (e.g. on the other side of
- // a wall).
- if (!mon->mon_see_cell(*ri))
- continue;
-
- monsters* const m = monster_at(*ri);
- if (m == NULL)
- continue;
-
- // Don't restrict smarter monsters as they might find a path
- // a dumber monster wouldn't.
- if (mons_intel(m) > intel)
- continue;
-
- // Monsters of differing habitats might prefer different routes.
- if (mons_primary_habitat(m) != habit)
- continue;
-
- // A flying monster has an advantage over a non-flying one.
- if (!flies && mons_flies(m))
- continue;
-
- // Same for a swimming one, around water.
- 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)
-{
- // All types of stairs.
- if (feat_is_stair(grd(pos)))
- return (true);
-
- // Teleportation and shaft traps.
- 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)
- {
- // Ignore teleportation and shaft traps that the monster
- // shouldn't know about.
- if (!mons_is_native_in_branch(mon)
- && grd(e[i].target) == DNGN_UNDISCOVERED_TRAP)
- {
- continue;
- }
-
- retval = i;
- old_dist = dist;
- }
- }
-
- return (retval);
-}
-
// If _mons_find_level_exits() is ever expanded to handle more grid
// types, this should be expanded along with it.
static void _mons_indicate_level_exit(const monsters *mon)
@@ -3547,23 +2840,6 @@ void make_mons_leave_level(monsters *mon)
}
}
-static void _set_no_path_found(monsters *mon)
-{
-#ifdef DEBUG_PATHFIND
- mpr("No path found!");
-#endif
-
- mon->travel_target = MTRAV_UNREACHABLE;
- // Pass information on to nearby monsters.
- _mark_neighbours_target_unreachable(mon);
-}
-
-static bool _target_is_unreachable(monsters *mon)
-{
- return (mon->travel_target == MTRAV_UNREACHABLE
- || mon->travel_target == MTRAV_KNOWN_UNREACHABLE);
-}
-
// Checks whether there is a straight path from p1 to p2 that passes
// through features >= allowed.
// If it exists, such a path may be missed; on the other hand, it
@@ -3583,1295 +2859,6 @@ bool can_go_straight(const coord_def& p1, const coord_def& p2,
true, true));
}
-// The monster is trying to get to the player (MHITYOU).
-// Check whether there's an unobstructed path to the player (in sight!),
-// either by using an existing travel_path or calculating a new one.
-// Returns true if no further handling necessary, else false.
-static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move,
- bool potentially_blocking)
-{
- // Just because we can *see* the player, that doesn't mean
- // we can actually get there. To find about that, we first
- // check for transparent walls. If there are transparent
- // walls in the way we'll need pathfinding, no matter what.
- // (Though monsters with a los attack don't need to get any
- // closer to hurt the player.)
- // If no walls are detected, there could still be a river
- // or a pool of lava in the way. So we check whether there
- // is water or lava in LoS (boolean) and if so, try to find
- // a way around it. It's possible that the player can see
- // lava but it actually has no influence on the monster's
- // movement (because it's lying in the opposite direction)
- // but if so, we'll find that out during path finding.
- // In another attempt of optimization, don't bother with
- // path finding if the monster in question has no trouble
- // travelling through water or flying across lava.
- // Also, if no path is found (too far away, perhaps) set a
- // flag, so we don't directly calculate the whole thing again
- // next turn, and even extend that flag to neighbouring
- // monsters of similar movement restrictions.
-
- // Smart monsters that can fire through walls won't use
- // pathfinding, and it's also not necessary if the monster
- // is already adjacent to you.
- 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 we don't already know whether there's water or lava
- // in LoS of the player, find out now.
- if (you.lava_in_sight == -1 || you.water_in_sight == -1)
- _check_lava_water_in_sight();
-
- // Flying monsters don't see water/lava as obstacle.
- // Also don't use pathfinding if the monster can shoot
- // across the blocking terrain, and is smart enough to
- // realise that.
- 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))
- {
- // The player is easily reachable.
- // Clear travel path and target, if necessary.
- 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);
- }
-
- // Even if the target has been to "unreachable" (the monster already tried,
- // and failed, to find a path) there's a chance of trying again.
- 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 we're already on our way, do nothing.
- 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];
-
- // Current target still valid?
- if (can_go_straight(targ, you.pos(), can_move))
- {
- // Did we reach the target?
- if (mon->pos() == mon->travel_path[0])
- {
- // Get next waypoint.
- 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);
- }
- }
- }
-
- // Use pathfinding to find a (new) path to the player.
- 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())
- {
- // Okay then, we found a path. Let's use it!
- 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);
- }
-
- // We didn't find a path.
- return (false);
-}
-
-// Returns true if a monster left the level.
-static bool _pacified_leave_level(monsters *mon, std::vector<level_exit> e,
- int e_index)
-{
- // If a pacified monster is leaving the level, and has reached an
- // exit (whether that exit was its target or not), handle it here.
- // Likewise, if a pacified monster is far enough away from the
- // player, make it leave the level.
- 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);
-}
-
-// Counts deep water twice.
-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);
-}
-
-// Pick the nearest water grid that is surrounded by the most
-// water squares within LoS.
-static bool _find_siren_water_target(monsters *mon)
-{
- ASSERT(mon->type == MONS_SIREN);
-
- // Moving away could break the entrancement, so don't do this.
- if ((mon->pos() - you.pos()).rdist() >= 6)
- return (false);
-
- // Already completely surrounded by deep water.
- 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;
-
- // In the first iteration only count water grids that are
- // not closer to the player than to the siren.
- if (first && (mon->pos() - *ri).rdist() > (you.pos() - *ri).rdist())
- continue;
-
- // Counts deep water twice.
- 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 // water_count == best_water_count
- {
- 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;
-
- // Else start the second iteration.
- first = false;
- }
-
- if (!best_water_count)
- return (false);
-
- // We're already optimally placed.
- if (best_target == mon->pos())
- return (true);
-
- monster_pathfind mp;
-#ifdef WIZARD
- // Remove old highlighted areas to make place for the new ones.
- 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
- // Okay then, we found a path. Let's use it!
- 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]);
-
- // Target grid might have changed since we started, like if the
- // player destroys the wall the monster wants to hide in.
- if (cell_is_solid(targ_pos)
- && monster_habitable_grid(mon, grd(targ_pos)))
- {
- // Wall is still good.
-#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
- // Remove old highlighted areas to make place for the new ones.
- 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
- // Okay then, we found a path. Let's use it!
- mon->target = mon->travel_path[0];
- mon->travel_target = MTRAV_WALL;
- return (true);
- }
- }
- return (false);
-}
-
-// Returns true if further handling neeeded.
-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
-
- // Hey, we reached our first waypoint!
- 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);
- }
- }
-
- // Can we still see our next waypoint?
- if (!can_go_straight(mon->pos(), mon->travel_path[0], can_move))
- {
-#ifdef DEBUG_PATHFIND
- mpr("Can't see waypoint grid.");
-#endif
- // Apparently we got sidetracked a bit.
- // Check the waypoints vector backwards and pick the first waypoint
- // we can see.
-
- // XXX: Note that this might still not be the best thing to do
- // since another path might be even *closer* to our actual target now.
- // Not by much, though, since the original path was optimal (A*) and
- // the distance between the waypoints is rather small.
-
- int erase = -1; // Erase how many waypoints?
- 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
- // Erase all waypoints that came earlier:
- // we don't need them anymore.
- while (0 < erase--)
- mon->travel_path.erase( mon->travel_path.begin() );
- }
- else
- {
- // We can't reach our old path from our current
- // position, so calculate a new path instead.
- monster_pathfind mp;
-
- // The last coordinate in the path vector is our destination.
- 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
- {
- // Or just forget about the whole thing.
- mon->travel_path.clear();
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- }
- }
-
- // Else, we can see the next waypoint and are making good progress.
- // Carry on, then!
- return (false);
-}
-
-// Returns true if further handling neeeded.
-static bool _handle_monster_patrolling(monsters *mon)
-{
- if (!_choose_random_patrol_target_grid(mon))
- {
- // If we couldn't find a target that is within easy reach
- // of the monster and close to the patrol point, depending
- // on monster intelligence, do one of the following:
- // * set current position as new patrol point
- // * forget about patrolling
- // * head back to patrol point
-
- if (mons_intel(mon) == I_PLANT)
- {
- // Really stupid monsters forget where they're supposed to be.
- if (mons_friendly(mon))
- {
- // Your ally was told to wait, and wait it will!
- // (Though possibly not where you told it to.)
- mon->patrol_point = mon->pos();
- }
- else
- {
- // Stop patrolling.
- mon->patrol_point.reset();
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- }
- else
- {
- // It's time to head back!
- // Other than for tracking the player, there's currently
- // no distinction between smart and stupid monsters when
- // it comes to travelling back to the patrol point. This
- // is in part due to the flavour of e.g. bees finding
- // their way back to the Hive (and patrolling should
- // really be restricted to cases like this), and for the
- // other part it's not all that important because we
- // calculate the path once and then follow it home, and
- // the player won't ever see the orderly fashion the
- // bees will trudge along.
- // What he will see is them swarming back to the Hive
- // entrance after some time, and that is what matters.
- 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
- {
- // We're so close we don't even need a path.
- mon->target = mon->patrol_point;
- }
- }
- else
- {
- // Stop patrolling.
- 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)
-{
- // default wander behaviour
- 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 we still need a target because we're not travelling
- // (any more), check for patrol routes instead.
- if (need_target && mon->is_patrolling())
- need_target = _handle_monster_patrolling(mon);
-
- // XXX: This is really dumb wander behaviour... instead of
- // changing the goal square every turn, better would be to
- // have the monster store a direction and have the monster
- // head in that direction for a while, then shift the
- // direction to the left or right. We're changing this so
- // wandering monsters at least appear to have some sort of
- // attention span. -- bwr
- 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;
-
- // Don't fight test spawners, since they're only pseudo-monsters
- // placed to spawn real monsters, plus they're impossible to
- // kill. But test spawners can fight each other, to give them a
- // target to spawn against.
- 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());
-}
-
-//---------------------------------------------------------------
-//
-// handle_behaviour
-//
-// 1. Evaluates current AI state
-// 2. Sets monster target x,y based on current foe
-//
-// XXX: Monsters of I_NORMAL or above should select a new target
-// if their current target is another monster which is sitting in
-// a wall and is immune to most attacks while in a wall, unless
-// the monster has a spell or special/nearby ability which isn't
-// affected by the wall.
-//---------------------------------------------------------------
-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);
-
- // Whether the player is in LOS of the monster and can see
- // or has guessed the player's location.
- bool proxPlayer = mons_near(mon) && !crawl_state.arena;
-
- bool trans_wall_block = trans_wall_blocking(mon->pos());
-
-#ifdef WIZARD
- // If stealth is greater than actually possible (wizmode level)
- // pretend the player isn't there, but only for hostile monsters.
- 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;
-
- // Check for confusion -- early out.
- 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;
- }
- // If we're not forcing monsters to attack, just make sure they're
- // not targetting the player in arena mode.
- else if (mon->foe == MHITYOU)
- mon->foe = MHITNOT;
- }
-
- if (mons_wall_shielded(mon) && cell_is_solid(mon->pos()))
- {
- // Monster is safe, so its behaviour can be simplified to fleeing.
- 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;
-
- // Validate current target exists.
- 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;
- }
-
- // Change proxPlayer depending on invisibility and standing
- // in shallow water.
- if (proxPlayer && !you.visible_to(mon))
- {
- proxPlayer = false;
-
- const int intel = mons_intel(mon);
- // Sometimes, if a player is right next to a monster, they will 'see'.
- if (grid_distance(you.pos(), mon->pos()) == 1
- && one_chance_in(3))
- {
- proxPlayer = true;
- }
-
- // [dshaligram] Very smart monsters have a chance of clueing in to
- // invisible players in various ways.
- if (intel == I_NORMAL && one_chance_in(13)
- || intel == I_HIGH && one_chance_in(6))
- {
- proxPlayer = true;
- }
- }
-
- // Set friendly target, if they don't already have one.
- // Berserking allies ignore your commands!
- 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;
- }
-
- // Instead, berserkers attack nearest monsters.
- if ((mon->has_ench(ENCH_BERSERK) || mon->mons_species() == MONS_GIANT_SPORE)
- && (mon->foe == MHITNOT || isFriendly && mon->foe == MHITYOU))
- {
- // Intelligent monsters prefer to attack the player,
- // even when berserking.
- if (!isFriendly && proxPlayer && mons_intel(mon) >= I_NORMAL)
- mon->foe = MHITYOU;
- else
- _set_nearest_monster_foe(mon);
- }
-
- // Pacified monsters leaving the level prefer not to attack.
- // Others choose the nearest foe.
- if (!isPacified && mon->foe == MHITNOT)
- _set_nearest_monster_foe(mon);
-
- // Monsters do not attack themselves. {dlb}
- if (mon->foe == monster_index(mon))
- mon->foe = MHITNOT;
-
- // Friendly and good neutral monsters do not attack other friendly
- // and good neutral monsters.
- if (mon->foe != MHITNOT && mon->foe != MHITYOU
- && wontAttack && mons_wont_attack_real(&menv[mon->foe]))
- {
- mon->foe = MHITNOT;
- }
-
- // Neutral monsters prefer not to attack players, or other neutrals.
- if (isNeutral && mon->foe != MHITNOT
- && (mon->foe == MHITYOU || mons_neutral(&menv[mon->foe])))
- {
- mon->foe = MHITNOT;
- }
-
- // Unfriendly monsters fighting other monsters will usually
- // target the player, if they're healthy.
- if (!isFriendly && !isNeutral
- && mon->foe != MHITYOU && mon->foe != MHITNOT
- && proxPlayer && !(mon->has_ench(ENCH_BERSERK)) && isHealthy
- && !one_chance_in(3))
- {
- mon->foe = MHITYOU;
- }
-
- // Validate current target again.
- 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; // Take invis into account.
-
- // Track changes to state; attitude never changes here.
- beh_type new_beh = mon->behaviour;
- unsigned short new_foe = mon->foe;
-
- // Take care of monster state changes.
- switch (mon->behaviour)
- {
- case BEH_SLEEP:
- // default sleep state
- mon->target = mon->pos();
- new_foe = MHITNOT;
- break;
-
- case BEH_LURK:
- case BEH_SEEK:
- // No foe? Then wander or seek the player.
- if (mon->foe == MHITNOT)
- {
- if (crawl_state.arena || !proxPlayer || isNeutral || patrolling)
- new_beh = BEH_WANDER;
- else
- {
- new_foe = MHITYOU;
- mon->target = you.pos();
- }
- break;
- }
-
- // Foe gone out of LOS?
- 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)
- {
- // We've got a target, so we'll continue on our way.
-#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 we've arrived at our target x,y
- // do a stealth check. If the foe
- // fails, monster will then start
- // tracking foe's CURRENT position,
- // but only for a few moves (smell and
- // intuition only go so far).
-
- 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()) // XXX: cheesy!
- mon->target = menv[mon->foe].pos();
- else
- mon->foe_memory = 0;
- }
- }
-
- // Either keep chasing, or start wandering.
- if (mon->foe_memory < 2)
- {
- mon->foe_memory = 0;
- new_beh = BEH_WANDER;
- }
- break;
- }
-
- ASSERT(mon->foe_memory == 0);
- // Hack: smarter monsters will tend to pursue the player longer.
- 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; // switch/case BEH_SEEK
- }
-
- ASSERT(proxFoe && mon->foe != MHITNOT);
- // Monster can see foe: continue 'tracking'
- // by updating target x,y.
- if (mon->foe == MHITYOU)
- {
- // The foe is the player.
- 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;
-
- // Whew. If we arrived here, path finding didn't yield anything
- // (or wasn't even attempted) and we need to set our target
- // the traditional way.
-
- // Sometimes, your friends will wander a bit.
- if (isFriendly && one_chance_in(8))
- {
- _set_random_target(mon);
- mon->foe = MHITNOT;
- new_beh = BEH_WANDER;
- }
- else
- {
- mon->target = you.pos();
- }
- }
- else
- {
- // We have a foe but it's not the player.
- mon->target = menv[mon->foe].pos();
- }
-
- // Smart monsters, zombified monsters other than spectral
- // things, plants, and nonliving monsters cannot flee.
- 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 a pacified monster isn't travelling toward
- // someplace from which it can leave the level, make it
- // start doing so. If there's no such place, either
- // search the level for such a place again, or travel
- // randomly.
- 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);
- }
-
- // Is our foe in LOS?
- // Batty monsters don't automatically reseek so that
- // they'll flitter away, we'll reset them just before
- // they get movement in handle_monsters() instead. -- bwr
- if (proxFoe && !mons_is_batty(mon))
- {
- new_beh = BEH_SEEK;
- break;
- }
-
- _check_wander_target(mon, isPacified, can_move);
-
- // During their wanderings, monsters will eventually relax
- // their guard (stupid ones will do so faster, smart
- // monsters have longer memories). Pacified monsters will
- // also eventually switch the place from which they want to
- // leave the level, in case their current choice is blocked.
- 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:
- // Check for healed.
- if (isHealthy && !isScared)
- new_beh = BEH_SEEK;
-
- // Smart monsters flee until they can flee no more...
- // possible to get a 'CORNERED' event, at which point
- // we can jump back to WANDER if the foe isn't present.
-
- if (isFriendly)
- {
- // Special-cased below so that it will flee *towards* you.
- if (mon->foe == MHITYOU)
- mon->target = you.pos();
- }
- else if (mons_wall_shielded(mon) && _find_wall_target(mon))
- ; // Wall target found.
- else if (proxFoe)
- {
- // Special-cased below so that it will flee *from* the
- // correct position.
- mon->target = foepos;
- }
- break;
-
- case BEH_CORNERED:
- // Plants and nonliving monsters cannot fight back.
- if (mon->holiness() == MH_PLANT
- || mon->holiness() == MH_NONLIVING)
- {
- break;
- }
-
- if (isHealthy)
- new_beh = BEH_SEEK;
-
- // Foe gone out of LOS?
- if (!proxFoe)
- {
- if ((isFriendly || proxPlayer) && !isNeutral && !patrolling)
- new_foe = MHITYOU;
- else
- new_beh = BEH_WANDER;
- }
- else
- {
- mon->target = foepos;
- }
- break;
-
- default:
- return; // uh oh
- }
-
- 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)
- {
- // Monster is safe, so stay put.
- 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);
-}
-
-// Choose random nearest monster as a foe.
-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;
- }
-}
-
// The default suitable() function for choose_random_nearby_monster().
bool choose_any_monster(const monsters* mon)
{
@@ -4969,512 +2956,6 @@ bool simple_monster_message(const monsters *monster, const char *event,
return (false);
}
-// Altars as well as branch entrances are considered interesting for
-// some monster types.
-static bool _mon_on_interesting_grid(monsters *mon)
-{
- // Patrolling shouldn't happen all the time.
- if (one_chance_in(4))
- return (false);
-
- const dungeon_feature_type feat = grd(mon->pos());
-
- switch (feat)
- {
- // Holy beings will tend to patrol around altars to the good gods.
- case DNGN_ALTAR_ELYVILON:
- if (!one_chance_in(3))
- return (false);
- // else fall through
- case DNGN_ALTAR_ZIN:
- case DNGN_ALTAR_SHINING_ONE:
- return (mons_is_holy(mon));
-
- // Orcs will tend to patrol around altars to Beogh, and guard the
- // stairway from and to the Orcish Mines.
- 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));
-
- // Same for elves and the Elven Halls.
- case DNGN_ENTER_ELVEN_HALLS:
- case DNGN_RETURN_FROM_ELVEN_HALLS:
- return (mons_is_native_in_branch(mon, BRANCH_ELVEN_HALLS));
-
- // Killer bees always return to their hive.
- case DNGN_ENTER_HIVE:
- return (mons_is_native_in_branch(mon, BRANCH_HIVE));
-
- default:
- return (false);
- }
-}
-
-// If a hostile monster finds itself on a grid of an "interesting" feature,
-// while unoccupied, it will remain in that area, and try to return to it
-// if it left it for fighting, seeking etc.
-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();
- }
-}
-
-// Check whether there's a monster of the same type and alignment adjacent
-// to the given monster in at least one of three given directions (relative to
-// the monster position).
-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;
-
- // Hostile monsters of normal intelligence only move aside for
- // monsters of the same genus.
- 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);
-}
-
-// Check up to eight grids in the given direction for whether there's a
-// monster of the same alignment as the given monster that happens to
-// have a ranged attack. If this is true for the first monster encountered,
-// returns true. Otherwise returns 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()))
- {
- // Hostile monsters of normal intelligence only move aside for
- // monsters of the same type.
- 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);
-}
-
-//---------------------------------------------------------------
-//
-// handle_movement
-//
-// Move the monster closer to its target square.
-//
-//---------------------------------------------------------------
-static void _handle_movement(monsters *monster)
-{
- coord_def delta;
-
- _maybe_set_patrol_route(monster);
-
- // Monsters will try to flee out of a sanctuary.
- 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()))
- {
- // Once outside there's a chance they'll regain their courage.
- // Nonliving and berserking monsters always stop immediately,
- // since they're only being forced out rather than actually
- // scared.
- if (monster->holiness() == MH_NONLIVING
- || monster->has_ench(ENCH_BERSERK)
- || x_chance_in_y(2, 5))
- {
- mons_stop_fleeing_from_sanctuary(monster);
- }
- }
-
- // Some calculations.
- if (mons_class_flag(monster->type, M_BURROWS) && monster->foe == MHITYOU)
- {
- // Boring beetles always move in a straight line in your
- // direction.
- 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));
-
- // Smiters are happy if they have clear visibility through
- // glass, but other monsters must go around.
- const bool glass_ok = mons_has_smite_attack(monster);
-
- // Monsters in the arena are smarter than the norm and
- // always pathfind to their targets.
- 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();
- }
- }
- }
-
- // Move the monster.
- 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;
- }
-
- // Don't allow monsters to enter a sanctuary or attack you inside a
- // sanctuary, even if you're right next to them.
- if (is_sanctuary(monster->pos() + mmov)
- && (!is_sanctuary(monster->pos())
- || monster->pos() + mmov == you.pos()))
- {
- mmov.reset();
- }
-
- // Bounds check: don't let fleeing monsters try to run off the grid.
- 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;
-
- // Now quit if we can't move.
- if (mmov.origin())
- return;
-
- if (delta.rdist() > 3)
- {
- // Reproduced here is some semi-legacy code that makes monsters
- // move somewhat randomly along oblique paths. It is an
- // exceedingly good idea, given crawl's unique line of sight
- // properties.
- //
- // Added a check so that oblique movement paths aren't used when
- // close to the target square. -- bwr
-
- // Sometimes we'll just move parallel the x axis.
- if (abs(delta.x) > abs(delta.y) && coinflip())
- mmov.y = 0;
-
- // Sometimes we'll just move parallel the y axis.
- 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;
-
- // Bounds check: don't consider moving out of grid!
- 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))
- {
- // The rock worm will try to move along through rock for as long as
- // possible. If the player is walking through a corridor, for example,
- // moving along in the wall beside him is much preferable to actually
- // leaving the wall.
- // This might cause the rock worm to take detours but it still
- // comes off as smarter than otherwise.
- if (mmov.x != 0 && mmov.y != 0) // diagonal movement
- {
- 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 the monster is moving in your direction, whether to attack or
- // protect you, or towards a monster it intends to attack, check
- // whether we first need to take a step to the side to make sure the
- // reinforcement can follow through. Only do this with 50% chance,
- // though, so it's not completely predictable.
-
- // First, check whether the monster is smart enough to even consider
- // this.
- 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 the monster is moving parallel to the x or y axis, check
- // whether
- //
- // a) the neighbouring grids are blocked
- // b) there are other unblocked grids adjacent to the target
- // c) there's at least one allied monster waiting behind us.
- //
- // (For really smart monsters, also check whether there's a
- // monster farther back in the corridor that has some kind of
- // ranged attack.)
- 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 // We're moving diagonally.
- {
- 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;
- }
- }
- }
-
- // Now quit if we can't move.
- if (mmov.origin())
- return;
-
- // Try to stay in sight of the player if we're moving towards
- // him/her, in order to avoid the monster coming into view,
- // shouting, and then taking a step in a path to the player which
- // temporarily takes it out of view, which can lead to the player
- // getting "comes into view" and shout messages with no monster in
- // view.
-
- // Doesn't matter for arena mode.
- if (crawl_state.arena)
- return;
-
- // Did we just come into view?
- if (monster->seen_context != _just_seen)
- return;
-
- monster->seen_context.clear();
-
- // If the player can't see us, it doesn't matter.
- 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);
-
- // We're not moving towards the player.
- if (grid_distance(you.pos(), old_pos + mmov) >= old_dist)
- {
- // Give a message if we move back out of view.
- monster->seen_context = _just_seen;
- return;
- }
-
- // We're already staying in the player's LOS.
- if (see_cell(old_pos + mmov))
- return;
-
- // Try to find a move that brings us closer to the player while
- // keeping us in view.
- 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;
- }
- }
-
- // The only way to get closer to the player is to step out of view;
- // give a message so they player isn't confused about its being
- // announced as coming into view but not being seen.
- 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)
{
@@ -5639,1544 +3120,6 @@ bool mons_avoids_cloud(const monsters *monster, int cloud_num,
return (!mons_avoids_cloud(monster, our_cloud, true));
}
-//---------------------------------------------------------------
-//
-// handle_nearby_ability
-//
-// Gives monsters a chance to use a special ability when they're
-// next to the player.
-//
-//---------------------------------------------------------------
-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)
- {
- // Very fast wandering/patrolling monsters might, in one monster turn,
- // move into the player's LOS and then back out (or the player
- // might move into their LOS and the monster move back out before
- // the player's view has a chance to update) so prevent them
- // from speaking.
- ;
- }
- 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)
- {
- // Non-humanoid-ish monsters have a low chance of speaking
- // without the M_SPEAKS flag, to give the dungeon some
- // atmosphere/flavour.
- int chance = MON_SPEAK_CHANCE * 4;
-
- // Band members are a lot less likely to speak, since there's
- // a lot of them.
- if (testbits(monster->flags, MF_BAND_MEMBER))
- chance *= 10;
-
- // However, confused and fleeing monsters are more interesting.
- if (mons_is_fleeing(monster))
- chance /= 2;
- if (monster->has_ench(ENCH_CONFUSION))
- chance /= 2;
-
- if (one_chance_in(chance))
- mons_speaks(monster);
- }
- // Okay then, don't speak.
-
- if (monster_can_submerge(monster, grd(monster->pos()))
- && !monster->caught() // No submerging while caught.
- && !player_mesmerised_by(monster) // No submerging if player entranced.
- && !mons_is_lurking(monster) // Handled elsewhere.
- && 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:
- // Choose random colour.
- 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());
-
- // Subtly different from old paralysis behaviour, but
- // it'll do.
- 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); // heh heh {dlb}
- }
- 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;
- }
-}
-
-// Returns true if you resist the siren's call.
-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);
- // Pick the monster up.
- mgrd(newpos) = NON_MONSTER;
- mon->moveto(oldpos);
-
- // Plunk it down.
- 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);
-}
-
-//---------------------------------------------------------------
-//
-// handle_special_ability
-//
-//---------------------------------------------------------------
-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 );
-
- // Slime creatures can split while out of sight.
- 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:
- // A (very) ugly thing's proximity to you if you're glowing, or
- // to others of its kind, can mutate it into a different (very)
- // ugly thing.
- used = ugly_thing_mutate(monster, true);
- break;
-
- case MONS_SLIME_CREATURE:
- // Slime creatures may split or merge depending on the
- // situation.
- 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;
-
- // Faking LOS by checking the neighbouring square.
- 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;
-
- // Setup tracer.
- 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.
- fire_tracer(monster, beem);
-
- // Good idea?
- 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;
-
- // Setup tracer.
- 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.
- fire_tracer(monster, beem);
-
- // Good idea?
- 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.
- fire_tracer(monster, beem);
-
- // Good idea?
- 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;
- }
-
- // There's a 5% chance of Snorg spontaneously going berserk that
- // increases to 20% once he is wounded.
- 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;
- // deliberate fall through
- case MONS_FIEND:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (_is_player_or_mon_sanct(monster))
- break;
-
- // Friendly fiends won't use torment, preferring hellfire
- // (right now there is no way a monster can predict how
- // badly they'll damage the player with torment) -- GDL
-
- // Well, I guess you could allow it if the player is torment
- // resistant, but there's a very good reason torment resistant
- // players can't cast Torment themselves, and allowing your
- // allies to cast it would just introduce harmless Torment
- // through the backdoor. Thus, shouldn't happen. (jpeg)
- 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;
- }
- // deliberate fallthrough -- see above
- case 1:
- case 2:
- case 3:
- spell_cast = SPELL_HELLFIRE;
- setup_mons_cast(monster, beem, spell_cast);
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- 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;
-
- // The fewer spikes the manticore has left, the less
- // likely it will use them.
- if (random2(16) >= static_cast<int>(monster->number))
- break;
-
- // Do the throwing right here, since the beam is so
- // easy to set up and doesn't involve inventory.
-
- // Set up the beam.
- 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.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
- simple_monster_message(monster, " flicks its tail!");
- beem.fire();
- used = true;
- // Decrement # of volleys left.
- 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;
- }
- }
- // Intentional fallthrough
-
- case MONS_WHITE_DRACONIAN:
- case MONS_RED_DRACONIAN:
- spell = SPELL_DRACONIAN_BREATH;
- // Intentional fallthrough
-
- case MONS_ICE_DRAGON:
- if (spell == SPELL_NO_SPELL)
- spell = SPELL_COLD_BREATH;
- // Intentional fallthrough
-
- // Dragon breath weapons:
- 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.
- fire_tracer(monster, beem);
-
- // Good idea?
- 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:
- {
- // Don't behold observer in the arena.
- if (crawl_state.arena)
- break;
-
- // Don't behold player already half down or up the stairs.
- 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;
- }
- }
-
- // Won't sing if either of you silenced, or it's friendly,
- // confused, fleeing, or leaving the level.
- if (monster->has_ench(ENCH_CONFUSION)
- || mons_is_fleeing(monster)
- || mons_is_pacified(monster)
- || mons_friendly(monster)
- || !player_can_hear(monster->pos()))
- {
- break;
- }
-
- // Don't even try on berserkers. Mermaids know their limits.
- if (you.duration[DUR_BERSERKER])
- break;
-
- // Reduce probability because of spamminess.
- if (you.species == SP_MERFOLK && !one_chance_in(4))
- break;
-
- // A wounded invisible mermaid is less likely to give away her position.
- 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); // flavour only
- did_resist = true;
- }
- }
- }
- else
- {
- // If you're already mesmerised by an invisible mermaid she
- // can still prolong the enchantment; otherwise you "resist".
- if (already_mesmerised)
- mpr("You hear a luring song.", MSGCH_SOUND);
- else
- {
- if (one_chance_in(4)) // reduce spamminess
- {
- if (coinflip())
- mpr("You hear a haunting song.", MSGCH_SOUND);
- else
- mpr("You hear an eerie melody.", MSGCH_SOUND);
-
- canned_msg(MSG_YOU_RESIST); // flavour only
- }
- break;
- }
- }
-
- // Once mesmerised by a particular monster, you cannot resist
- // anymore.
- 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);
-}
-
-//---------------------------------------------------------------
-//
-// _handle_potion
-//
-// Give the monster a chance to quaff a potion. Returns true if
-// the monster imbibed.
-//
-//---------------------------------------------------------------
-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);
-
- // Drink the potion.
- const item_type_id_state_type id = monster->drink_potion_effect(ptype);
-
- // Give ID if necessary.
- if (was_visible && id != ID_UNKNOWN_TYPE)
- set_ident_type(OBJ_POTIONS, ptype, id);
-
- // Remove it from inventory.
- 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);
-
- // This check isn't redundant -- player may be invisible.
- 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();
- // Same comments as to invisibility as above.
- if (monster->target == foepos
- && monster->mon_see_cell(foepos, true)
- && grid_distance(monster->pos(), foepos) == 2)
- {
- ret = true;
- monsters_fight(monster, &mfoe, false);
- }
- }
- }
-
- // Player saw the item reach.
- if (ret && !is_artefact(mitm[wpn]) && you.can_see(monster))
- set_ident_flags(mitm[wpn], ISFLAG_KNOW_TYPE);
-
- return (ret);
-}
-
-//---------------------------------------------------------------
-//
-// handle_scroll
-//
-// Give the monster a chance to read a scroll. Returns true if
-// the monster read something.
-//
-//---------------------------------------------------------------
-static bool _handle_scroll(monsters *monster)
-{
- // Yes, there is a logic to this ordering {dlb}:
- if (monster->asleep()
- || mons_is_confused(monster)
- || monster->submerged()
- || monster->inv[MSLOT_SCROLL] == NON_ITEM
- || !one_chance_in(3))
- {
- return (false);
- }
-
- // Make sure the item actually is a scroll.
- 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);
-
- // Notice how few cases are actually accounted for here {dlb}:
- 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;
-}
-
-//---------------------------------------------------------------
-//
-// handle_wand
-//
-// Give the monster a chance to zap a wand. Returns true if the
-// monster zapped.
-//
-//---------------------------------------------------------------
-static bool _handle_wand(monsters *monster, bolt &beem)
-{
- // Yes, there is a logic to this ordering {dlb}:
- 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]]);
-
- // map wand type to monster spell type
- const spell_type mzap = _map_wand_to_mspell(wand.sub_type);
- if (mzap == SPELL_NO_SPELL)
- return (false);
-
- // set up the beam
- 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:
- // Dial down damage from wands of disintegration, since
- // disintegration beams can do large amounts of damage.
- beem.damage.size = beem.damage.size * 2 / 3;
- break;
-
- case WAND_ENSLAVEMENT:
- case WAND_DIGGING:
- case WAND_RANDOM_EFFECTS:
- // These have been deemed "too tricky" at this time {dlb}:
- return (false);
-
- case WAND_POLYMORPH_OTHER:
- // Monsters can be very trigger happy with wands, reduce this
- // for polymorph.
- if (!one_chance_in(5))
- return (false);
- break;
-
- // These are wands that monsters will aim at themselves {dlb}:
- 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;
- }
- // This break causes the wand to be tried on the player.
- break;
- }
- return (false);
- }
-
- // Fire tracer, if necessary.
- if (!niceWand)
- {
- fire_tracer( monster, beem );
-
- // Good idea?
- 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);
- }
-
- // charge expenditure {dlb}
- 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);
-
- // Increment zap count.
- if (wand.plus2 >= 0)
- wand.plus2++;
- }
-
- monster->lose_energy(EUT_ITEM);
-
- return (true);
- }
-
- return (false);
-}
-
-// Returns a suitable breath weapon for the draconian; does not handle all
-// draconians, does fire a tracer.
-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: // already handled as ability
- break;
- default:
- draco_breath = SPELL_DRACONIAN_BREATH;
- break;
- }
- }
-
-
- if (draco_breath != SPELL_NO_SPELL)
- {
- // [ds] Check line-of-fire here. It won't happen elsewhere.
- 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)
-{
- // If the emergency spell appears early, it's probably not a dedicated
- // escape 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);
-}
-
-//---------------------------------------------------------------
-//
-// handle_spell
-//
-// Give the monster a chance to cast a spell. Returns true if
-// a spell was cast.
-//
-//---------------------------------------------------------------
-static bool _handle_spell(monsters *monster, bolt &beem)
-{
- bool monsterNearby = mons_near(monster);
- bool finalAnswer = false; // as in: "Is that your...?" {dlb}
- const spell_type draco_breath = _get_draconian_breath_spell(monster);
-
- // A polymorphed unique will retain his or her spells even in another
- // form. If the new form has the SPELLCASTER flag, casting happens as
- // normally, otherwise we need to enforce it, but it only happens with
- // a 50% chance.
- 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);
-
- // Yes, there is a logic to this ordering {dlb}:
- if (monster->asleep()
- || monster->submerged()
- || !mons_class_flag(monster->type, M_SPELLCASTER)
- && !spellcasting_poly
- && draco_breath == SPELL_NO_SPELL)
- {
- return (false);
- }
-
- // If the monster's a priest, assume summons come from priestly
- // abilities, in which case they'll have the same god. If the
- // monster is neither a priest nor a wizard, assume summons come
- // from intrinsic abilities, in which case they'll also have the
- // same god.
- 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);
- }
-
- // Shapeshifters don't get spells.
- 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()) // 50% chance of not casting
- return (false);
- else
- {
- spell_type spell_cast = SPELL_NO_SPELL;
- monster_spells hspell_pass(monster->spells);
-
- // 1KB: the following code is never used for unfriendlies!
- if (!mon_enemies_around(monster))
- {
- // Force the casting of dig when the player is not visible -
- // this is EVIL!
- 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)
- {
- // The player's out of sight!
- // Quick, let's take a turn to heal ourselves. -- bwr
- 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))
- {
- // Since the player isn't around, we'll extend the monster's
- // normal choices to include the self-enchant slot.
- 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);
- }
-
- // Monsters caught in a net try to get away.
- // This is only urgent if enemies are around.
- 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;
- }
- }
- }
-
- // Promote the casting of useful spells for low-HP monsters.
- if (!finalAnswer
- && monster->hit_points < monster->max_hit_points / 4
- && !one_chance_in(4))
- {
- // Note: There should always be at least some chance we don't
- // get here... even if the monster is on its last HP. That
- // way we don't have to worry about monsters infinitely casting
- // Healing on themselves (e.g. orc high priests).
- 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 nothing found by now, safe friendlies and good
- // neutrals will rarely cast.
- if (mons_wont_attack(monster) && !mon_enemies_around(monster)
- && !one_chance_in(10))
- {
- return (false);
- }
-
- // Remove healing/invis/haste if we don't need them.
- 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)
- {
- // Should monster not have selected dig by now,
- // it never will.
- hspell_pass[i] = SPELL_NO_SPELL;
- num_no_spell++;
- }
- }
-
- // If no useful spells... cast no spell.
- if (num_no_spell == NUM_MONSTER_SPELL_SLOTS
- && draco_breath == SPELL_NO_SPELL)
- {
- return (false);
- }
-
- const bolt orig_beem = beem;
- // Up to four tries to pick a spell.
- for (int loopy = 0; loopy < 4; ++loopy)
- {
- beem = orig_beem;
-
- bool spellOK = false;
-
- // Setup spell - monsters that are fleeing or pacified
- // and leaving the level will always try to choose their
- // emergency spell.
- if (mons_is_fleeing(monster) || mons_is_pacified(monster))
- {
- spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL
- : hspell_pass[5]);
-
- // Pacified monsters leaving the level won't choose
- // emergency spells harmful to the area.
- if (spell_cast != SPELL_NO_SPELL
- && mons_is_pacified(monster)
- && spell_harms_area(spell_cast))
- {
- spell_cast = SPELL_NO_SPELL;
- }
- }
- else
- {
- // Randomly picking one of the non-emergency spells:
- spell_cast = hspell_pass[random2(5)];
- }
-
- if (spell_cast == SPELL_NO_SPELL)
- continue;
-
- // Setup the spell.
- setup_mons_cast(monster, beem, spell_cast);
-
- // beam-type spells requiring tracers
- if (spell_needs_tracer(spell_cast))
- {
- const bool explode =
- spell_is_direct_explosion(spell_cast);
- fire_tracer(monster, beem, explode);
- // Good idea?
- if (mons_should_fire(beem))
- spellOK = true;
- }
- else
- {
- // All direct-effect/summoning/self-enchantments/etc.
- 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)
- {
- // XXX: Note the crude hack so that monsters can
- // use ME_ALERT to target (we should really have
- // a measure of time instead of peeking to see
- // if the player is still there). -- bwr
- 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];
-
- // Don't allow TSO-worshipping daevas to make
- // unchivalric magic attacks, except against
- // appropriate monsters.
- if (is_unchivalric_attack(monster, mon)
- && !tso_unchivalric_attack_safe_monster(mon))
- {
- spellOK = false;
- }
- }
- }
-
- // If not okay, then maybe we'll cast a defensive spell.
- if (!spellOK)
- {
- spell_cast = (coinflip() ? hspell_pass[2]
- : SPELL_NO_SPELL);
- }
-
- if (spell_cast != SPELL_NO_SPELL)
- break;
- }
- }
-
- // If there's otherwise no ranged attack use the breath weapon.
- // The breath weapon is also occasionally used.
- 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;
- }
-
- // Should the monster *still* not have a spell, well, too bad {dlb}:
- if (spell_cast == SPELL_NO_SPELL)
- return (false);
-
- // Friendly monsters don't use polymorph other, for fear of harming
- // the player.
- if (spell_cast == SPELL_POLYMORPH_OTHER && mons_friendly(monster))
- return (false);
-
- // Try to animate dead: if nothing rises, pretend we didn't cast it.
- 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;
-
- // FINALLY! determine primary spell effects {dlb}:
- if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK)
- {
- // Why only cast blink if nearby? {dlb}
- 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);
- }
- } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ...
-
- return (true);
-}
-
// Returns a rough estimate of damage from throwing the wielded weapon.
int mons_thrown_weapon_damage(const item_def *weap)
{
@@ -7244,135 +3187,6 @@ int mons_pick_best_missile(monsters *mons, item_def **launcher,
}
}
-//---------------------------------------------------------------
-//
-// handle_throw
-//
-// Give the monster a chance to throw something. Returns true if
-// the monster hurled.
-//
-//---------------------------------------------------------------
-static bool _handle_throw(monsters *monster, bolt & beem)
-{
- // Yes, there is a logic to this ordering {dlb}:
- 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);
- // Highly-specialised archers are more likely to shoot than talk.
- if (one_chance_in(archer? 9 : 5))
- return (false);
-
- // Don't allow offscreen throwing for now.
- if (monster->foe == MHITYOU && !mons_near(monster))
- return (false);
-
- // Monsters won't shoot in melee range, largely for balance reasons.
- // Specialist archers are an exception to this rule.
- if (!archer && adjacent(beem.target, monster->pos()))
- return (false);
-
- // Greatly lowered chances if the monster is fleeing or pacified and
- // leaving the level.
- 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];
-
- // Throwing a net at a target that is already caught would be
- // completely useless, so bail out.
- 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 the attack needs a launcher that we can't wield, bail out.
- if (launcher)
- {
- weapon = monster->mslot_item(MSLOT_WEAPON);
- if (weapon && weapon != launcher && weapon->cursed())
- return (false);
- }
-
- // Ok, we'll try it.
- setup_generic_throw( monster, beem );
-
- // Set fake damage for the tracer.
- beem.damage = dice_def(10, 10);
-
- // Set item for tracer, even though it probably won't be used
- beem.item = missile;
-
- // Fire tracer.
- fire_tracer( monster, beem );
-
- // Clear fake damage (will be set correctly in mons_throw).
- beem.damage = 0;
-
- // Good idea?
- if (mons_should_fire( beem ))
- {
- // Monsters shouldn't shoot if fleeing, so let them "turn to attack".
- _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)
-{
- // Shapeshifters don't get spells.
- if (!mons_is_shapeshifter(monster)
- || !mons_class_flag(monster->type, M_ACTUAL_SPELLS))
- {
- if (_handle_spell(monster, beem))
- return (true);
- }
-
- return (false);
-}
-
-// Give the monster its action energy (aka speed_increment).
-static void _monster_add_energy(monsters *monster)
-{
- if (monster->speed > 0)
- {
- // Randomise to make counting off monster moves harder:
- 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)
{
// A HD divider ranging from 3 (at 1 HD) to 1 (at 8 HD).
@@ -7398,1165 +3212,6 @@ int mons_natural_regen_rate(monsters *monster)
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));
-}
-
-// Do natural regeneration for monster.
-static void _monster_regenerate(monsters *monster)
-{
- if (monster->has_ench(ENCH_SICK) || !mons_can_regenerate(monster))
- return;
-
- // Non-land creatures out of their element cannot regenerate.
- 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)
-{
- // Can't swap with a stationary monster.
- if (mons_is_stationary(moved))
- return (false);
-
- // Swapping is a purposeful action.
- if (mover->confused())
- return (false);
-
- // Right now just happens in sanctuary.
- if (!is_sanctuary(mover->pos()) || !is_sanctuary(moved->pos()))
- return (false);
-
- // A friendly or good-neutral monster moving past a fleeing hostile
- // or neutral monster, or vice versa.
- if (mons_wont_attack_real(mover) == mons_wont_attack_real(moved)
- || mons_is_fleeing(mover) == mons_is_fleeing(moved))
- {
- return (false);
- }
-
- // Don't swap places if the player explicitly ordered their pet to
- // attack monsters.
- 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);
- }
-
- // Okay, we can do the swap.
- 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());
-
- // FIXME: Replace check with mons_is_swimming()?
- 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; // don't wander on duty!
- 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);
-
- // Monster just summoned (or just took stairs), skip this action.
- if (testbits( monster->flags, MF_JUST_SUMMONED ))
- {
- monster->flags &= ~MF_JUST_SUMMONED;
- return;
- }
-
- mon_acting mact(monster);
-
- _monster_add_energy(monster);
-
- // Handle clouds on nonmoving monsters.
- if (monster->speed == 0
- && env.cgrid(monster->pos()) != EMPTY_CLOUD
- && !monster->submerged())
- {
- _mons_in_cloud( monster );
- }
-
- // Apply monster enchantments once for every normal-speed
- // player turn.
- monster->ench_countdown -= you.time_taken;
- while (monster->ench_countdown < 0)
- {
- monster->ench_countdown += 10;
- monster->apply_enchantments();
-
- // If the monster *merely* died just break from the loop
- // rather than quit altogether, since we have to deal with
- // giant spores and ball lightning exploding at the end of the
- // function, but do return if the monster's data has been
- // reset, since then the monster type is invalid.
- if (monster->type == MONS_NO_MONSTER)
- return;
- else if (monster->hit_points < 1)
- break;
- }
-
- // Memory is decremented here for a reason -- we only want it
- // decrementing once per monster "move".
- if (monster->foe_memory > 0)
- monster->foe_memory--;
-
- // Otherwise there are potential problems with summonings.
- if (monster->type == MONS_GLOWING_SHAPESHIFTER)
- monster->add_ench(ENCH_GLOWING_SHAPESHIFTER);
-
- if (monster->type == MONS_SHAPESHIFTER)
- monster->add_ench(ENCH_SHAPESHIFTER);
-
- // We reset batty monsters from wander to seek here, instead
- // of in handle_behaviour() since that will be called with
- // every single movement, and we want these monsters to
- // hit and run. -- bwr
- 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())
- {
- // The continues & breaks are WRT this.
- 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; // problem with vortices
- }
- }
-
- _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);
-
- // _handle_behaviour() could make the monster leave the level.
- if (!monster->alive())
- break;
-
- ASSERT(!crawl_state.arena || monster->foe != MHITYOU);
- ASSERT(in_bounds(monster->target) || monster->target.origin());
-
- // Submerging monsters will hide from clouds.
- 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))
- {
- // Keep neutral and charmed monsters from picking up stuff.
- // Same for friendlies if friendly_pickup is set to "none".
- 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;
- }
- }
- }
-
- // Lurking monsters only stop lurking if their target is right
- // next to them, otherwise they just sit there.
- // However, if the monster is involuntarily submerged but
- // still alive (e.g., nonbreathing which had water poured
- // on top of it), this doesn't apply.
- if (mons_is_lurking(monster) || monster->has_ench(ENCH_SUBMERGED))
- {
- if (monster->foe != MHITNOT
- && grid_distance(monster->target, monster->pos()) <= 1)
- {
- if (monster->submerged())
- {
- // Don't unsubmerge if the monster is too damaged or
- // if the monster is afraid, or if it's avoiding the
- // cloud on top of the water.
- 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))
- {
- // Couldn't unsubmerge.
- monster->speed_increment -= non_move_energy;
- continue;
- }
- }
- monster->behaviour = BEH_SEEK;
- }
- else
- {
- monster->speed_increment -= non_move_energy;
- continue;
- }
- }
-
- if (mons_is_caught(monster))
- {
- // Struggling against the net takes time.
- _swim_or_move_energy(monster);
- }
- else if (!mons_is_petrified(monster))
- {
- // Calculates mmov based on monster target.
- _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();
-
- // OK, mmov determined.
- 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
- {
- // FIXME: None of these work!
- // Instead run away!
- 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)
- // Berserking monsters are limited to running up and
- // hitting their foes.
- && !monster->has_ench(ENCH_BERSERK)
- // Slime creatures can split while wandering or resting.
- || monster->type == MONS_SLIME_CREATURE)
- {
- bolt beem;
-
- beem.source = monster->pos();
- beem.target = monster->target;
- beem.beam_source = monster->mindex();
-
- // Prevents unfriendlies from nuking you from offscreen.
- // How nice!
- const bool friendly_or_near =
- mons_friendly(monster) || monster->near_foe();
- if (friendly_or_near
- || monster->type == MONS_TEST_SPAWNER
- // Slime creatures can split when offscreen.
- || monster->type == MONS_SLIME_CREATURE)
- {
- // [ds] Special abilities shouldn't overwhelm
- // spellcasting in monsters that have both. This aims
- // to give them both roughly the same weight.
- 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))
- {
- // If it steps into you, cancel other targets.
- 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;
- }
- }
-
- // See if we move into (and fight) an unfriendly monster.
- monsters* targ = monster_at(monster->pos() + mmov);
- if (targ
- && targ != monster
- && !mons_aligned(monster->mindex(), targ->mindex())
- && monster_can_hit_monster(monster, targ))
- {
- // Maybe they can swap places?
- if (_swap_monsters(monster, targ))
- {
- _swim_or_move_energy(monster);
- continue;
- }
- // Figure out if they fight.
- else if (monsters_fight(monster, targ))
- {
- if (mons_is_batty(monster))
- {
- monster->behaviour = BEH_WANDER;
- _set_random_target(monster);
- // monster->speed_increment -= monster->speed;
- }
-
- 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);
-
- // Reevaluate behaviour, since the monster's surroundings have
- // changed (it may have moved, or died for that matter). Don't
- // bother for dead monsters. :)
- 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);
-}
-
-//---------------------------------------------------------------
-//
-// handle_monsters
-//
-// This is the routine that controls monster AI.
-//
-//---------------------------------------------------------------
-void handle_monsters()
-{
- // Keep track of monsters that have already moved and don't allow
- // them to move again.
- 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 the player got banished, discard pending monster actions.
- if (you.banished)
- {
- // Clear list of mesmerising monsters.
- if (you.duration[DUR_MESMERISED])
- {
- you.mesmerised_by.clear();
- you.duration[DUR_MESMERISED] = 0;
- }
- break;
- }
- }
-
- // Clear any summoning flags so that lower indiced
- // monsters get their actions in the next round.
- for (int i = 0; i < MAX_MONSTERS; i++)
- menv[i].flags &= ~MF_JUST_SUMMONED;
-}
-
-static bool _is_item_jelly_edible(const item_def &item)
-{
- // Don't eat artefacts.
- if (is_artefact(item))
- return (false);
-
- // Shouldn't eat stone things
- // - but what about wands and rings?
- if (item.base_type == OBJ_MISSILES
- && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
- {
- return (false);
- }
-
- // Don't eat special game items.
- 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);
-}
-
-// XXX: This function assumes that only jellies eat items.
-static bool _monster_eat_item(monsters *monster, bool nearby)
-{
- if (!mons_eats_items(monster))
- return (false);
-
- // Friendly jellies won't eat (unless worshipping Jiyva).
- 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
- {
- // Shouldn't be much trouble to digest a huge pile of gold!
- 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);
-
- // This is done manually instead of using heal_monster(),
- // because that function doesn't work quite this way. -- bwr
- 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;
-
- // Limited growth factor here - should 77 really be the cap? {dlb}:
- 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());
- }
-
- // Assume that eating a corpse requires butchering it. Use logic
- // from misc.cc:turn_corpse_into_chunks() and the butchery-related
- // delays in delay.cc:stop_delay().
-
- const int max_chunks = mons_weight(mt) / 150;
-
- // Only fresh corpses bleed enough to colour the ground.
- 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
- {
- // Assume that only undead can heal from eating corpses.
- if (_monster_eat_single_corpse(monster, *si,
- monster->holiness() == MH_UNDEAD,
- nearby))
- {
- eaten++;
- break;
- }
- }
- }
- }
-
- return (eaten > 0);
-}
-
-//---------------------------------------------------------------
-//
-// handle_pickup
-//
-// Returns false if monster doesn't spend any time picking something up.
-//
-//---------------------------------------------------------------
-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))
- {
- // Assume that only undead can heal from eating corpses.
- 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)
- {
- // Note: Monsters only look at stuff near the top of stacks.
- //
- // XXX: Need to put in something so that monster picks up
- // multiple items (e.g. ammunition) identical to those it's
- // carrying.
- //
- // Monsters may now pick up up to two items in the same turn.
- // (jpeg)
- 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;
-
- // note here, that this makes jellies "grow" {dlb}:
- 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);
-
- // Confused monsters can't be pushed past, sleeping monsters
- // can't push. Note that sleeping monsters can't be pushed
- // past, either, but they may be woken up by a crowd trying to
- // elbow past them, and the wake-up check happens downstream.
- 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);
- }
-
- // Batty monsters are unpushable.
- if (mons_is_batty(mpusher) || mons_is_batty(mpushee))
- return (false);
-
- if (!monster_shover(mpusher))
- return (false);
-
- // Fleeing monsters of the same type may push past higher ranking ones.
- 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);
- }
-
- // Check that both monsters will be happy at their proposed new locations.
- 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);
-
- // Okay, do the swap!
- _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);
-
- // The seen context no longer applies if the monster is moving normally.
- 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);
- }
-
- // This includes the case where the monster attacks itself.
- if (monsters* def = monster_at(f))
- {
- monsters_fight(monster, def);
- return (true);
- }
-
- // The monster gave a "comes into view" message and then immediately
- // moved back out of view, leaing the player nothing to see, so give
- // this message to avoid confusion.
- 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());
- }
-
- // The seen context no longer applies if the monster is moving normally.
- monster->seen_context.clear();
-
- // This appears to be the real one, ie where the movement occurs:
- _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)
{
@@ -8620,944 +3275,6 @@ void mons_check_pool(monsters *monster, const coord_def &oldpos,
}
}
-// Randomise potential damage.
-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);
- }
-}
-
-// Check whether a given trap (described by trap position) can be
-// regarded as safe. Takes into account monster intelligence and
-// allegiance.
-// (just_check is used for intelligent monsters trying to avoid traps.)
-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));
-
- // No friendly monsters will ever enter a Zot trap you know.
- if (player_knows_trap && mons_friendly(monster) && trap.type == TRAP_ZOT)
- return (false);
-
- // Dumb monsters don't care at all.
- 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);
- }
-
- // Hostile monsters are not afraid of non-mechanical traps.
- // Allies will try to avoid teleportation and zot traps.
- const bool mechanical = (trap.category() == DNGN_TRAP_MECHANICAL);
-
- if (trap.is_known(monster))
- {
- if (just_check)
- return (false); // Square is blocked.
- else
- {
- // Test for corridor-like environment.
- const int x = where.x - monster->pos().x;
- const int y = where.y - monster->pos().y;
-
- // The question is whether the monster (m) can easily reach its
- // presumable destination (x) without stepping on the trap. Traps
- // in corridors do not allow this. See e.g
- // #x# ##
- // #^# or m^x
- // m ##
- //
- // The same problem occurs if paths are blocked by monsters,
- // hostile terrain or other traps rather than walls.
- // What we do is check whether the squares with the relative
- // positions (-1,0)/(+1,0) or (0,-1)/(0,+1) form a "corridor"
- // (relative to the _trap_ position rather than the monster one).
- // If they don't, the trap square is marked as "unsafe" (because
- // there's a good alternative move for the monster to take),
- // otherwise the decision will be made according to later tests
- // (monster hp, trap type, ...)
- // If a monster still gets stuck in a corridor it will usually be
- // because it has less than half its maximum hp.
-
- 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);
- }
- }
- }
-
- // Friendlies will try not to be parted from you.
- if (intelligent_ally(monster) && trap.type == TRAP_TELEPORT
- && player_knows_trap && mons_near(monster))
- {
- return (false);
- }
-
- // Healthy monsters don't mind a little pain.
- if (mechanical && monster->hit_points >= monster->max_hit_points / 2
- && (intel == I_ANIMAL
- || monster->hit_points > _estimated_trap_damage(trap.type)))
- {
- return (true);
- }
-
- // Friendly and good neutral monsters don't enjoy Zot trap perks;
- // handle accordingly. In the arena Zot traps affect all monsters.
- 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);
-}
-
-// Check whether a monster can move to given square (described by its relative
-// coordinates to the current monster position). just_check is true only for
-// calls from is_trap_safe when checking the surrounding squares of a trap.
-static bool _mon_can_move_to_pos(const monsters *monster,
- const coord_def& delta, bool just_check)
-{
- const coord_def targ = monster->pos() + delta;
-
- // Bounds check: don't consider moving out of grid!
- if (!in_bounds(targ))
- return (false);
-
- // No monster may enter the open sea.
- if (grd(targ) == DNGN_OPEN_SEA)
- return (false);
-
- // Non-friendly and non-good neutral monsters won't enter
- // sanctuaries.
- if (!mons_wont_attack(monster)
- && is_sanctuary(targ)
- && !is_sanctuary(monster->pos()))
- {
- return (false);
- }
-
- // Inside a sanctuary don't attack anything!
- 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);
-
- // The kraken is so large it cannot enter shallow water.
- // Its tentacles can, and will, though.
- if (monster->type == MONS_KRAKEN && target_grid == DNGN_SHALLOW_WATER)
- return (false);
-
- // Effectively slows down monster movement across water.
- // Fire elementals can't cross at all.
- 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))
- {
- // Don't burrow out of bounds.
- if (!in_bounds(targ))
- return (false);
-
- // Don't burrow at an angle (legacy behaviour).
- 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 the monster somehow ended up in this habitat (and is
- // not dead by now), give it a chance to get out again.
- if (grd(monster->pos()) == target_grid
- && _no_habitable_adjacent_grids(monster))
- {
- return (true);
- }
-
- return (false);
- }
-
- // Wandering mushrooms don't move while you are looking.
- if (monster->type == MONS_WANDERING_MUSHROOM && see_cell(targ))
- return (false);
-
- // Water elementals avoid fire and heat.
- 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);
- }
-
- // Fire elementals avoid water and cold.
- if (monster->type == MONS_FIRE_ELEMENTAL
- && (feat_is_watery(target_grid)
- || targ_cloud_type == CLOUD_COLD))
- {
- return (false);
- }
-
- // Submerged water creatures avoid the shallows where
- // they would be forced to surface. -- bwr
- // [dshaligram] Monsters now prefer to head for deep water only if
- // they're low on hitpoints. No point in hiding if they want a
- // fight.
- 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);
- }
-
- // Smacking the player is always a good move if we're
- // hostile (even if we're heading somewhere else).
- // Also friendlies want to keep close to the player
- // so it's okay as well.
-
- // Smacking another monster is good, if the monsters
- // are aligned differently.
- if (monsters *targmonster = monster_at(targ))
- {
- if (just_check)
- {
- if (targ == monster->pos())
- return (true);
-
- return (false); // blocks square
- }
-
- if (mons_aligned(monster->mindex(), targmonster->mindex())
- && !_mons_can_displace(monster, targmonster))
- {
- return (false);
- }
- }
-
- // Friendlies shouldn't try to move onto the player's
- // location, if they are aiming for some other target.
- if (mons_wont_attack(monster)
- && monster->foe != MHITYOU
- && (monster->foe != MHITNOT || monster->is_patrolling())
- && targ == you.pos())
- {
- return (false);
- }
-
- // Wandering through a trap is OK if we're pretty healthy,
- // really stupid, or immune to the trap.
- if (!_is_trap_safe(monster, targ, just_check))
- return (false);
-
- // If we end up here the monster can safely move.
- return (true);
-}
-
-// Uses, and updates the global variable mmov.
-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;
- }
- }
-
- // Only handle if the original move is to an adjacent square.
- if (dir == -1)
- return;
-
- int dist[2];
-
- // First 1 away, then 2 (3 is silly).
- for (int j = 1; j <= 2; j++)
- {
- const int FAR_AWAY = 1000000;
-
- // Try both directions (but randomise which one is first).
- 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);
-
- // Now choose.
- if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY)
- continue;
-
- // Which one was better? -- depends on FLEEING or not.
- 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);
-
- // Trapdoor spiders hide if they can't see their foe.
- // (Note that friendly trapdoor spiders will thus hide even
- // if they can see you.)
- 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);
- }
- }
-
- // Berserking monsters make a lot of racket.
- 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
- {
- // Just be noisy without messaging the player.
- 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 a water monster is currently flopping around on land, it cannot
- // really control where it wants to move, though there's a 50% chance
- // of flopping into an adjacent water grid.
- 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());
- }
-
- // Let's not even bother with this if mmov is zero.
- 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;
-
- // Bounds check: don't consider moving out of grid!
- 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));
- }
-
- // Now we know where we _can_ move.
-
- const coord_def newpos = monster->pos() + mmov;
- // Normal/smart monsters know about secret doors, since they live in
- // the dungeon.
- if (grd(newpos) == DNGN_CLOSED_DOOR
- || feat_is_secret_door(grd(newpos)) && mons_intel(monster) >= I_NORMAL)
- {
- if (mons_is_zombified(monster))
- {
- // For zombies, monster type is kept in mon->base_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);
- }
- } // endif - secret/closed doors
-
- // Monsters that eat items (currently only jellies) also eat doors.
- // However, they don't realise that secret doors make good eating.
- if ((grd(newpos) == DNGN_CLOSED_DOOR || grd(newpos) == DNGN_OPEN_DOOR)
- && mons_itemeat(monster) == MONEAT_ITEMS
- // Doors with permarock marker cannot be eaten.
- && !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 );
- }
- }
- } // done door-eating jellies
-
- // Water creatures have a preference for water they can hide in -- bwr
- // [ds] Weakened the powerful attraction to deep water if the monster
- // is in good health.
- 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;
- }
- }
- }
- }
-
- // Now, if a monster can't move in its intended direction, try
- // either side. If they're both good, move in whichever dir
- // gets it closer (farther for fleeing monsters) to its target.
- // If neither does, do nothing.
- if (good_move[mmov.x + 1][mmov.y + 1] == false)
- _find_good_alternate_move(monster, good_move);
-
- // ------------------------------------------------------------------
- // If we haven't found a good move by this point, we're not going to.
- // ------------------------------------------------------------------
-
- // Take care of beetle burrowing.
- 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))
- {
- // Message depends on whether caused by boring beetle or
- // acid (Dissolution).
- 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())
- {
- // Check for attacking player.
- if (monster->pos() + mmov == you.pos())
- {
- ret = monster_attack(monster);
- mmov.reset();
- }
-
- // If we're following the player through stairs, the only valid
- // movement is towards the player. -- bwr
- 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
- }
- }
-
- // Check for attacking another monster.
- 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;
- }
-
- // If the monster swapped places, the work's already done.
- 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();
-
- // Fleeing monsters that can't move will panic and possibly
- // turn to face their attacker.
- _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);
- // If the monster got poisoned, wake it up.
- wake = true;
-
- hurted += (random2(8) * 10) / speed;
-
- if (monster->res_poison() < 0)
- hurted += (random2(4) * 10) / speed;
- break;
-
- case CLOUD_STEAM:
- {
- // FIXME: couldn't be bothered coding for armour of res fire
-
- 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; // 3
- 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!");
-
- // Will only polymorph a monster if they're not magic immune, can
- // mutate, aren't res asphyx, and pass the same check as meph cloud.
- if (monster->can_mutate() && !mons_immune_magic(monster)
- && 1 + random2(27) >= monster->hit_dice
- && !monster->res_asphyx())
- {
- if (monster->mutate())
- wake = true;
- }
- break;
-
- default: // 'harmless' clouds -- colored smoke, etc {dlb}.
- return;
- }
-
- // A sleeping monster that sustains damage will wake up.
- if ((wake || hurted > 0) && monster->asleep())
- {
- // We have no good coords to give the monster as the source of the
- // disturbance other than the cloud itself.
- 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)
@@ -9698,30 +3415,6 @@ bool heal_monster(monsters * patient, int health_boost,
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)
{
// If the monster is in the auto_exclude list, automatically
@@ -9839,3 +3532,369 @@ int dismiss_monsters(std::string pattern) {
}
return (ndismissed);
}
+
+bool is_item_jelly_edible(const item_def &item)
+{
+ // Don't eat artefacts.
+ if (is_artefact(item))
+ return (false);
+
+ // Shouldn't eat stone things
+ // - but what about wands and rings?
+ if (item.base_type == OBJ_MISSILES
+ && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
+ {
+ return (false);
+ }
+
+ // Don't eat special game items.
+ 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);
+}
+
+bool monster_random_space(const monsters *monster, coord_def& target,
+ bool forbid_sanctuary)
+{
+ int tries = 0;
+ while (tries++ < 1000)
+ {
+ target = random_in_bounds();
+
+ // Don't land on top of another monster.
+ if (actor_at(target))
+ continue;
+
+ if (is_sanctuary(target) && forbid_sanctuary)
+ continue;
+
+ if (monster_habitable_grid(monster, grd(target)))
+ return (true);
+ }
+
+ return (false);
+}
+
+bool monster_random_space(monster_type mon, coord_def& target,
+ bool forbid_sanctuary)
+{
+ monsters dummy;
+ dummy.type = mon;
+
+ return monster_random_space(&dummy, target, forbid_sanctuary);
+}
+
+void monster_teleport(monsters *monster, bool instan, bool silent)
+{
+ if (!instan)
+ {
+ if (monster->del_ench(ENCH_TP))
+ {
+ if (!silent)
+ simple_monster_message(monster, " seems more stable.");
+ }
+ else
+ {
+ if (!silent)
+ simple_monster_message(monster, " looks slightly unstable.");
+
+ monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER,
+ random_range(20, 30)) );
+ }
+
+ return;
+ }
+
+ bool was_seen = you.can_see(monster) && !mons_is_lurking(monster);
+
+ if (!silent)
+ simple_monster_message(monster, " disappears!");
+
+ const coord_def oldplace = monster->pos();
+
+ // Pick the monster up.
+ mgrd(oldplace) = NON_MONSTER;
+
+ coord_def newpos;
+ if (monster_random_space(monster, newpos, !mons_wont_attack(monster)))
+ monster->moveto(newpos);
+
+ mgrd(monster->pos()) = monster_index(monster);
+
+ // Mimics change form/colour when teleported.
+ if (mons_is_mimic(monster->type))
+ {
+ monster_type old_type = monster->type;
+ monster->type = static_cast<monster_type>(
+ MONS_GOLD_MIMIC + random2(5));
+ monster->colour = get_mimic_colour(monster);
+
+ // If it's changed form, you won't recognise it.
+ // This assumes that a non-gold mimic turning into another item of
+ // the same description is really, really unlikely.
+ if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC)
+ was_seen = false;
+ }
+
+ const bool now_visible = mons_near(monster);
+ if (!silent && now_visible)
+ {
+ if (was_seen)
+ simple_monster_message(monster, " reappears nearby!");
+ else
+ {
+ // Even if it doesn't interrupt an activity (the player isn't
+ // delayed, the monster isn't hostile) we still want to give
+ // a message.
+ activity_interrupt_data ai(monster, "thin air");
+ if (!interrupt_activity(AI_SEE_MONSTER, ai))
+ simple_monster_message(monster, " appears out of thin air!");
+ }
+ }
+
+ if (monster->visible_to(&you) && now_visible)
+ handle_seen_interrupt(monster);
+
+ // Leave a purple cloud.
+ place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3),
+ monster->kill_alignment());
+
+ monster->check_redraw(oldplace);
+ monster->apply_location_effects(oldplace);
+
+ mons_relocated(monster);
+
+ // Teleporting mimics change form - if they reappear out of LOS, they are
+ // no longer known.
+ if (mons_is_mimic(monster->type))
+ {
+ if (now_visible)
+ monster->flags |= MF_KNOWN_MIMIC;
+ else
+ monster->flags &= ~MF_KNOWN_MIMIC;
+ }
+}
+
+void mons_clear_trapping_net(monsters *mon)
+{
+ if (!mons_is_caught(mon))
+ return;
+
+ const int net = get_trapping_net(mon->pos());
+ if (net != NON_ITEM)
+ remove_item_stationary(mitm[net]);
+
+ mon->del_ench(ENCH_HELD, true);
+}
+
+bool mons_clonable(const monsters* mon, bool needs_adjacent)
+{
+ // No uniques or ghost demon monsters. Also, figuring out the name
+ // for the clone of a named monster isn't worth it.
+ if (mons_is_unique(mon->type)
+ || mons_is_ghost_demon(mon->type)
+ || mon->is_named())
+ {
+ return (false);
+ }
+
+ if (needs_adjacent)
+ {
+ // Is there space for the clone?
+ bool square_found = false;
+ for (int i = 0; i < 8; i++)
+ {
+ const coord_def p = mon->pos() + Compass[i];
+
+ if (in_bounds(p)
+ && !actor_at(p)
+ && monster_habitable_grid(mon, grd(p)))
+ {
+ square_found = true;
+ break;
+ }
+ }
+ if (!square_found)
+ return (false);
+ }
+
+ // Is the monster carrying an artefact?
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int index = mon->inv[i];
+
+ if (index == NON_ITEM)
+ continue;
+
+ if (is_artefact(mitm[index]))
+ return (false);
+ }
+
+ return (true);
+}
+
+int clone_mons(const monsters* orig, bool quiet, bool* obvious,
+ coord_def pos)
+{
+ // Is there an open slot in menv?
+ int midx = NON_MONSTER;
+ for (int i = 0; i < MAX_MONSTERS; i++)
+ if (menv[i].type == MONS_NO_MONSTER)
+ {
+ midx = i;
+ break;
+ }
+
+ if (midx == NON_MONSTER)
+ return (NON_MONSTER);
+
+ if (!in_bounds(pos))
+ {
+ // Find an adjacent square.
+ int squares = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ const coord_def p = orig->pos() + Compass[i];
+
+ if (in_bounds(p)
+ && !actor_at(p)
+ && monster_habitable_grid(orig, grd(p)))
+ {
+ if (one_chance_in(++squares))
+ pos = p;
+ }
+ }
+
+ if (squares == 0)
+ return (NON_MONSTER);
+ }
+
+ ASSERT( !actor_at(pos) );
+
+ monsters &mon(menv[midx]);
+
+ mon = *orig;
+ mon.position = pos;
+ mgrd(pos) = midx;
+
+ // Duplicate objects, or unequip them if they can't be duplicated.
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int old_index = orig->inv[i];
+
+ if (old_index == NON_ITEM)
+ continue;
+
+ const int new_index = get_item_slot(0);
+ if (new_index == NON_ITEM)
+ {
+ mon.unequip(mitm[old_index], i, 0, true);
+ mon.inv[i] = NON_ITEM;
+ continue;
+ }
+
+ mon.inv[i] = new_index;
+ mitm[new_index] = mitm[old_index];
+ mitm[new_index].set_holding_monster(midx);
+ }
+
+ bool _obvious;
+ if (obvious == NULL)
+ obvious = &_obvious;
+ *obvious = false;
+
+ if (you.can_see(orig) && you.can_see(&mon))
+ {
+ if (!quiet)
+ simple_monster_message(orig, " is duplicated!");
+ *obvious = true;
+ }
+
+ mark_interesting_monst(&mon, mon.behaviour);
+ if (you.can_see(&mon))
+ {
+ handle_seen_interrupt(&mon);
+ viewwindow(true, false);
+ }
+
+ if (crawl_state.arena)
+ arena_placed_monster(&mon);
+
+ return (midx);
+}
+
+std::string summoned_poof_msg(const monsters* monster, bool plural)
+{
+ int summon_type = 0;
+ bool valid_mon = false;
+ if (monster != NULL && !invalid_monster(monster))
+ {
+ (void) monster->is_summoned(NULL, &summon_type);
+ valid_mon = true;
+ }
+
+ std::string msg = "disappear%s in a puff of smoke";
+ bool no_chaos = false;
+
+ switch (summon_type)
+ {
+ case SPELL_SHADOW_CREATURES:
+ msg = "dissolve%s into shadows";
+ no_chaos = true;
+ break;
+
+ case MON_SUMM_CHAOS:
+ msg = "degenerate%s into a cloud of primal chaos";
+ break;
+
+ case MON_SUMM_WRATH:
+ case MON_SUMM_AID:
+ if (valid_mon && is_good_god(monster->god))
+ {
+ msg = "dissolve%s into sparkling lights";
+ no_chaos = true;
+ }
+ break;
+ }
+
+ if (valid_mon)
+ {
+ if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10)
+ || monster->type == MONS_CHAOS_SPAWN)
+ {
+ msg = "degenerate%s into a cloud of primal chaos";
+ }
+
+ if (mons_is_holy(monster) && summon_type != SPELL_SHADOW_CREATURES
+ && summon_type != MON_SUMM_CHAOS)
+ {
+ msg = "dissolve%s into sparkling lights";
+ }
+ }
+
+ // Conjugate.
+ msg = make_stringf(msg.c_str(), plural ? "" : "s");
+
+ return (msg);
+}
+
+std::string summoned_poof_msg(const int midx, const item_def &item)
+{
+ if (midx == NON_MONSTER)
+ return summoned_poof_msg(static_cast<const monsters*>(NULL), item);
+ else
+ return summoned_poof_msg(&menv[midx], item);
+}
+
+std::string summoned_poof_msg(const monsters* monster, const item_def &item)
+{
+ ASSERT(item.flags & ISFLAG_SUMMONED);
+
+ return summoned_poof_msg(monster, item.quantity > 1);
+}