summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/mon-act.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/mon-act.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/mon-act.cc')
-rw-r--r--crawl-ref/source/mon-act.cc3617
1 files changed, 3617 insertions, 0 deletions
diff --git a/crawl-ref/source/mon-act.cc b/crawl-ref/source/mon-act.cc
new file mode 100644
index 0000000000..42ead2a820
--- /dev/null
+++ b/crawl-ref/source/mon-act.cc
@@ -0,0 +1,3617 @@
+/*
+ * File: mon-act.cc
+ * Summary: Monsters doing stuff (monsters acting).
+ * Written by: Linley Henzell
+ */
+
+#include "AppHdr.h"
+#include "mon-act.h"
+
+#ifdef TARGET_OS_DOS
+#include <conio.h>
+#endif
+
+#include "arena.h"
+#include "beam.h"
+#include "cloud.h"
+#include "delay.h"
+#include "directn.h"
+#include "fight.h"
+#include "itemname.h"
+#include "itemprop.h"
+#include "items.h"
+#include "item_use.h"
+#include "mapmark.h"
+#include "message.h"
+#include "misc.h"
+#include "mon-abil.h"
+#include "mon-behv.h"
+#include "mon-cast.h"
+#include "monplace.h"
+#include "monstuff.h"
+#include "mutation.h"
+#include "notes.h"
+#include "player.h"
+#include "random.h"
+#include "religion.h"
+#include "shopping.h" // for item values
+#include "state.h"
+#include "terrain.h"
+#include "traps.h"
+#include "tutorial.h"
+#include "view.h"
+
+static bool _handle_pickup(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);
+
+// [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");
+
+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 bool _do_mon_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_mon_spell(monster, beem))
+ {
+ mmov.reset();
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+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 );
+}
+
+// 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);
+}
+
+// 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);
+}
+
+// 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();
+ }
+}
+
+//---------------------------------------------------------------
+//
+// 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;
+}
+
+//---------------------------------------------------------------
+//
+// _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);
+}
+
+static void _setup_generic_throw(struct monsters *monster, struct bolt &pbolt)
+{
+ // FIXME we should use a sensible range here
+ pbolt.range = LOS_RADIUS;
+ pbolt.beam_source = monster_index(monster);
+
+ pbolt.type = dchar_glyph(DCHAR_FIRED_MISSILE);
+ pbolt.flavour = BEAM_MISSILE;
+ pbolt.thrower = KILL_MON_MISSILE;
+ pbolt.aux_source.clear();
+ pbolt.is_beam = false;
+}
+
+static bool _mons_throw(struct monsters *monster, struct bolt &pbolt,
+ int hand_used)
+{
+ std::string ammo_name;
+
+ bool returning = false;
+
+ int baseHit = 0, baseDam = 0; // from thrown or ammo
+ int ammoHitBonus = 0, ammoDamBonus = 0; // from thrown or ammo
+ int lnchHitBonus = 0, lnchDamBonus = 0; // special add from launcher
+ int exHitBonus = 0, exDamBonus = 0; // 'extra' bonus from skill/dex/str
+ int lnchBaseDam = 0;
+
+ int hitMult = 0;
+ int damMult = 0;
+ int diceMult = 100;
+
+ // Some initial convenience & initializations.
+ int wepClass = mitm[hand_used].base_type;
+ int wepType = mitm[hand_used].sub_type;
+
+ int weapon = monster->inv[MSLOT_WEAPON];
+ int lnchType = (weapon != NON_ITEM) ? mitm[weapon].sub_type : 0;
+
+ mon_inv_type slot = get_mon_equip_slot(monster, mitm[hand_used]);
+ ASSERT(slot != NUM_MONSTER_SLOTS);
+
+ const bool skilled = mons_class_flag(monster->type, M_FIGHTER);
+
+ monster->lose_energy(EUT_MISSILE);
+ const int throw_energy = monster->action_energy(EUT_MISSILE);
+
+ // Dropping item copy, since the launched item might be different.
+ item_def item = mitm[hand_used];
+ item.quantity = 1;
+ if (mons_friendly(monster))
+ item.flags |= ISFLAG_DROPPED_BY_ALLY;
+
+ // FIXME we should actually determine a sensible range here
+ pbolt.range = LOS_RADIUS;
+
+ if (setup_missile_beam(monster, pbolt, item, ammo_name, returning))
+ return (false);
+
+ pbolt.aimed_at_spot = returning;
+
+ const launch_retval projected =
+ is_launched(monster, monster->mslot_item(MSLOT_WEAPON),
+ mitm[hand_used]);
+
+ // extract launcher bonuses due to magic
+ if (projected == LRET_LAUNCHED)
+ {
+ lnchHitBonus = mitm[weapon].plus;
+ lnchDamBonus = mitm[weapon].plus2;
+ lnchBaseDam = property(mitm[weapon], PWPN_DAMAGE);
+ }
+
+ // extract weapon/ammo bonuses due to magic
+ ammoHitBonus = item.plus;
+ ammoDamBonus = item.plus2;
+
+ // Archers get a boost from their melee attack.
+ if (mons_class_flag(monster->type, M_ARCHER))
+ {
+ const mon_attack_def attk = mons_attack_spec(monster, 0);
+ if (attk.type == AT_SHOOT)
+ ammoDamBonus += random2avg(attk.damage, 2);
+ }
+
+ if (projected == LRET_THROWN)
+ {
+ // Darts are easy.
+ if (wepClass == OBJ_MISSILES && wepType == MI_DART)
+ {
+ baseHit = 11;
+ hitMult = 40;
+ damMult = 25;
+ }
+ else
+ {
+ baseHit = 6;
+ hitMult = 30;
+ damMult = 25;
+ }
+
+ baseDam = property(item, PWPN_DAMAGE);
+
+ if (wepClass == OBJ_MISSILES) // throw missile
+ {
+ // ammo damage needs adjusting here - OBJ_MISSILES
+ // don't get separate tohit/damage bonuses!
+ ammoDamBonus = ammoHitBonus;
+
+ // [dshaligram] Thrown stones/darts do only half the damage of
+ // launched stones/darts. This matches 4.0 behaviour.
+ if (wepType == MI_DART || wepType == MI_STONE
+ || wepType == MI_SLING_BULLET)
+ {
+ baseDam = div_rand_round(baseDam, 2);
+ }
+ }
+
+ // give monster "skill" bonuses based on HD
+ exHitBonus = (hitMult * monster->hit_dice) / 10 + 1;
+ exDamBonus = (damMult * monster->hit_dice) / 10 + 1;
+ }
+
+ // Monsters no longer gain unfair advantages with weapons of
+ // fire/ice and incorrect ammo. They now have the same restrictions
+ // as players.
+
+ int bow_brand = SPWPN_NORMAL;
+ const int ammo_brand = get_ammo_brand(item);
+
+ if (projected == LRET_LAUNCHED)
+ {
+ bow_brand = get_weapon_brand(mitm[monster->inv[MSLOT_WEAPON]]);
+
+ switch (lnchType)
+ {
+ case WPN_BLOWGUN:
+ baseHit = 12;
+ hitMult = 60;
+ damMult = 0;
+ lnchDamBonus = 0;
+ break;
+ case WPN_BOW:
+ case WPN_LONGBOW:
+ baseHit = 0;
+ hitMult = 60;
+ damMult = 35;
+ // monsters get half the launcher damage bonus,
+ // which is about as fair as I can figure it.
+ lnchDamBonus = (lnchDamBonus + 1) / 2;
+ break;
+ case WPN_CROSSBOW:
+ baseHit = 4;
+ hitMult = 70;
+ damMult = 30;
+ break;
+ case WPN_HAND_CROSSBOW:
+ baseHit = 2;
+ hitMult = 50;
+ damMult = 20;
+ break;
+ case WPN_SLING:
+ baseHit = 10;
+ hitMult = 40;
+ damMult = 20;
+ // monsters get half the launcher damage bonus,
+ // which is about as fair as I can figure it.
+ lnchDamBonus /= 2;
+ break;
+ }
+
+ // Launcher is now more important than ammo for base damage.
+ baseDam = property(item, PWPN_DAMAGE);
+ if (lnchBaseDam)
+ baseDam = lnchBaseDam + random2(1 + baseDam);
+
+ // missiles don't have pluses2; use hit bonus
+ ammoDamBonus = ammoHitBonus;
+
+ exHitBonus = (hitMult * monster->hit_dice) / 10 + 1;
+ exDamBonus = (damMult * monster->hit_dice) / 10 + 1;
+
+ if (!baseDam && elemental_missile_beam(bow_brand, ammo_brand))
+ baseDam = 4;
+
+ // [dshaligram] This is a horrible hack - we force beam.cc to
+ // consider this beam "needle-like".
+ if (wepClass == OBJ_MISSILES && wepType == MI_NEEDLE)
+ pbolt.ench_power = AUTOMATIC_HIT;
+
+ // elven bow w/ elven arrow, also orcish
+ if (get_equip_race(mitm[monster->inv[MSLOT_WEAPON]])
+ == get_equip_race(mitm[monster->inv[MSLOT_MISSILE]]))
+ {
+ baseHit++;
+ baseDam++;
+
+ if (get_equip_race(mitm[monster->inv[MSLOT_WEAPON]]) == ISFLAG_ELVEN)
+ pbolt.hit++;
+ }
+
+ // POISON brand launchers poison ammo
+ if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_NORMAL)
+ set_item_ego_type(item, OBJ_MISSILES, SPMSL_POISONED);
+
+ // Vorpal brand increases damage dice size.
+ if (bow_brand == SPWPN_VORPAL)
+ diceMult = diceMult * 130 / 100;
+
+ // As do steel ammo.
+ if (ammo_brand == SPMSL_STEEL)
+ diceMult = diceMult * 150 / 100;
+
+ // Note: we already have throw_energy taken off. -- bwr
+ int speed_delta = 0;
+ if (lnchType == WPN_CROSSBOW)
+ {
+ if (bow_brand == SPWPN_SPEED)
+ {
+ // Speed crossbows take 50% less time to use than
+ // ordinary crossbows.
+ speed_delta = div_rand_round(throw_energy * 2, 5);
+ }
+ else
+ {
+ // Ordinary crossbows take 20% more time to use
+ // than ordinary bows.
+ speed_delta = -div_rand_round(throw_energy, 5);
+ }
+ }
+ else if (bow_brand == SPWPN_SPEED)
+ {
+ // Speed bows take 50% less time to use than
+ // ordinary bows.
+ speed_delta = div_rand_round(throw_energy, 2);
+ }
+
+ monster->speed_increment += speed_delta;
+ }
+
+ // Chaos overides flame and frost
+ if (pbolt.flavour != BEAM_MISSILE)
+ {
+ baseHit += 2;
+ exDamBonus += 6;
+ }
+
+ // monster intelligence bonus
+ if (mons_intel(monster) == I_HIGH)
+ exHitBonus += 10;
+
+ // Now, if a monster is, for some reason, throwing something really
+ // stupid, it will have baseHit of 0 and damage of 0. Ah well.
+ std::string msg = monster->name(DESC_CAP_THE);
+ msg += ((projected == LRET_LAUNCHED) ? " shoots " : " throws ");
+
+ if (!pbolt.name.empty() && projected == LRET_LAUNCHED)
+ msg += article_a(pbolt.name);
+ else
+ {
+ // build shoot message
+ msg += item.name(DESC_NOCAP_A);
+
+ // build beam name
+ pbolt.name = item.name(DESC_PLAIN, false, false, false);
+ }
+ msg += ".";
+
+ if (monster->observable())
+ {
+ mpr(msg.c_str());
+
+ if (projected == LRET_LAUNCHED
+ && item_type_known(mitm[monster->inv[MSLOT_WEAPON]])
+ || projected == LRET_THROWN
+ && mitm[hand_used].base_type == OBJ_MISSILES)
+ {
+ set_ident_flags(mitm[hand_used], ISFLAG_KNOW_TYPE);
+ }
+ }
+
+ // [dshaligram] When changing bolt names here, you must edit
+ // hiscores.cc (scorefile_entry::terse_missile_cause()) to match.
+ char throw_buff[ITEMNAME_SIZE];
+ if (projected == LRET_LAUNCHED)
+ {
+ snprintf(throw_buff, sizeof(throw_buff), "Shot with a%s %s by %s",
+ (is_vowel(pbolt.name[0]) ? "n" : ""), pbolt.name.c_str(),
+ monster->name(DESC_NOCAP_A).c_str());
+ }
+ else
+ {
+ snprintf(throw_buff, sizeof(throw_buff), "Hit by a%s %s thrown by %s",
+ (is_vowel(pbolt.name[0]) ? "n" : ""), pbolt.name.c_str(),
+ monster->name(DESC_NOCAP_A).c_str());
+ }
+
+ pbolt.aux_source = throw_buff;
+
+ // Add everything up.
+ pbolt.hit = baseHit + random2avg(exHitBonus, 2) + ammoHitBonus;
+ pbolt.damage =
+ dice_def(1, baseDam + random2avg(exDamBonus, 2) + ammoDamBonus);
+
+ if (projected == LRET_LAUNCHED)
+ {
+ pbolt.damage.size += lnchDamBonus;
+ pbolt.hit += lnchHitBonus;
+ }
+ pbolt.damage.size = diceMult * pbolt.damage.size / 100;
+
+ if (monster->has_ench(ENCH_BATTLE_FRENZY))
+ {
+ const mon_enchant ench = monster->get_ench(ENCH_BATTLE_FRENZY);
+
+#ifdef DEBUG_DIAGNOSTICS
+ const dice_def orig_damage = pbolt.damage;
+#endif
+
+ pbolt.damage.size = pbolt.damage.size * (115 + ench.degree * 15) / 100;
+
+#ifdef DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS, "%s frenzy damage: %dd%d -> %dd%d",
+ monster->name(DESC_PLAIN).c_str(),
+ orig_damage.num, orig_damage.size,
+ pbolt.damage.num, pbolt.damage.size);
+#endif
+ }
+
+ // Skilled archers get better to-hit and damage.
+ if (skilled)
+ {
+ pbolt.hit = pbolt.hit * 120 / 100;
+ pbolt.damage.size = pbolt.damage.size * 120 / 100;
+ }
+
+ scale_dice(pbolt.damage);
+
+ // decrease inventory
+ bool really_returns;
+ if (returning && !one_chance_in(mons_power(monster->type) + 3))
+ really_returns = true;
+ else
+ really_returns = false;
+
+ pbolt.drop_item = !really_returns;
+
+ // Redraw the screen before firing, in case the monster just
+ // came into view and the screen hasn't been updated yet.
+ viewwindow(true, false);
+ pbolt.fire();
+
+ // The item can be destroyed before returning.
+ if (really_returns && thrown_object_destroyed(&item, pbolt.target, true))
+ {
+ really_returns = false;
+ }
+
+ if (really_returns)
+ {
+ // Fire beam in reverse.
+ pbolt.setup_retrace();
+ viewwindow(true, false);
+ pbolt.fire();
+ msg::stream << "The weapon returns "
+ << (you.can_see(monster)?
+ ("to " + monster->name(DESC_NOCAP_THE))
+ : "whence it came from")
+ << "!" << std::endl;
+
+ // Player saw the item return.
+ if (!is_artefact(item))
+ {
+ // Since this only happens for non-artefacts, also mark properties
+ // as known.
+ set_ident_flags(mitm[hand_used],
+ ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES);
+ }
+ }
+ else if (dec_mitm_item_quantity(hand_used, 1))
+ monster->inv[returning ? slot : MSLOT_MISSILE] = NON_ITEM;
+
+ if (pbolt.special_explosion != NULL)
+ delete pbolt.special_explosion;
+
+ return (true);
+}
+
+//---------------------------------------------------------------
+//
+// 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 (player_or_mon_in_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);
+}
+
+// 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;
+ }
+}
+
+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;
+ }
+ }
+ }
+ }
+ mon_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() ? mon_special_ability(monster, beem)
+ || _do_mon_spell(monster, beem)
+ : _do_mon_spell(monster, beem)
+ || mon_special_ability(monster, beem))
+ {
+ DEBUG_ENERGY_USE("spell or special");
+ mmov.reset();
+ 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 _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);
+}
+
+// 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);
+}
+
+// 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 _habitat_okay( const monsters *monster, dungeon_feature_type targ )
+{
+ return (monster_habitable_grid(monster, targ));
+}
+
+static bool _no_habitable_adjacent_grids(const monsters *mon)
+{
+ for (adjacent_iterator ai(mon->pos()); ai; ++ai)
+ if (_habitat_okay(mon, grd(*ai)))
+ return (false);
+
+ return (true);
+}
+
+static bool _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);
+}
+
+// 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 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 _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);
+}
+
+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());
+ }
+ }
+}
+
+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;
+ }
+}
+