summaryrefslogtreecommitdiffstats
path: root/crawl-ref
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
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')
-rw-r--r--crawl-ref/source/acr.cc1
-rw-r--r--crawl-ref/source/arena.cc4
-rw-r--r--crawl-ref/source/beam.cc2
-rw-r--r--crawl-ref/source/delay.cc2
-rw-r--r--crawl-ref/source/effects.cc3
-rw-r--r--crawl-ref/source/fight.cc3
-rw-r--r--crawl-ref/source/files.cc2
-rw-r--r--crawl-ref/source/godabil.cc5
-rw-r--r--crawl-ref/source/item_use.cc2
-rw-r--r--crawl-ref/source/makefile.obj5
-rw-r--r--crawl-ref/source/mon-abil.cc1378
-rw-r--r--crawl-ref/source/mon-abil.h19
-rw-r--r--crawl-ref/source/mon-act.cc3617
-rw-r--r--crawl-ref/source/mon-act.h14
-rw-r--r--crawl-ref/source/mon-behv.cc1928
-rw-r--r--crawl-ref/source/mon-behv.h52
-rw-r--r--crawl-ref/source/mon-cast.cc2357
-rw-r--r--crawl-ref/source/mon-cast.h25
-rw-r--r--crawl-ref/source/mon-util.cc7
-rw-r--r--crawl-ref/source/mon-util.h13
-rw-r--r--crawl-ref/source/monplace.cc3
-rw-r--r--crawl-ref/source/monster.cc3
-rw-r--r--crawl-ref/source/monstuff.cc6685
-rw-r--r--crawl-ref/source/monstuff.h36
-rw-r--r--crawl-ref/source/mstuff2.cc3225
-rw-r--r--crawl-ref/source/mstuff2.h29
-rw-r--r--crawl-ref/source/religion.cc2
-rw-r--r--crawl-ref/source/spells2.cc1
-rw-r--r--crawl-ref/source/spells3.cc1
-rw-r--r--crawl-ref/source/spells4.cc2
-rw-r--r--crawl-ref/source/spl-cast.cc2
-rw-r--r--crawl-ref/source/spl-util.cc1
-rw-r--r--crawl-ref/source/terrain.cc2
-rw-r--r--crawl-ref/source/tutorial.cc2
-rw-r--r--crawl-ref/source/view.cc1
-rw-r--r--crawl-ref/source/xom.cc1
36 files changed, 9820 insertions, 9615 deletions
diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc
index 75a813d937..7928d56200 100644
--- a/crawl-ref/source/acr.cc
+++ b/crawl-ref/source/acr.cc
@@ -82,6 +82,7 @@
#include "maps.h"
#include "message.h"
#include "misc.h"
+#include "mon-act.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
diff --git a/crawl-ref/source/arena.cc b/crawl-ref/source/arena.cc
index bf6149f697..f4395c5336 100644
--- a/crawl-ref/source/arena.cc
+++ b/crawl-ref/source/arena.cc
@@ -20,11 +20,11 @@
#include "macro.h"
#include "maps.h"
#include "message.h"
+#include "mon-behv.h"
#include "mon-pick.h"
#include "mon-util.h"
-#include "monstuff.h"
#include "monplace.h"
-#include "mstuff2.h"
+#include "monstuff.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "state.h"
diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc
index e9e258b396..3291b82804 100644
--- a/crawl-ref/source/beam.cc
+++ b/crawl-ref/source/beam.cc
@@ -40,10 +40,10 @@
#include "los.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "mutation.h"
#include "ouch.h"
#include "player.h"
diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc
index 4de151f7a7..cbc7993a3f 100644
--- a/crawl-ref/source/delay.cc
+++ b/crawl-ref/source/delay.cc
@@ -30,9 +30,9 @@
#include "it_use2.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "notes.h"
#include "ouch.h"
#include "output.h"
diff --git a/crawl-ref/source/effects.cc b/crawl-ref/source/effects.cc
index 82de3a69f9..ed513fb2f6 100644
--- a/crawl-ref/source/effects.cc
+++ b/crawl-ref/source/effects.cc
@@ -38,10 +38,11 @@
#include "makeitem.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
+#include "mon-cast.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "mutation.h"
#include "notes.h"
#include "ouch.h"
diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc
index 058faf0cd2..33a86ca592 100644
--- a/crawl-ref/source/fight.cc
+++ b/crawl-ref/source/fight.cc
@@ -38,10 +38,11 @@
#include "macro.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
+#include "mon-cast.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "mutation.h"
#include "ouch.h"
#include "player.h"
diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc
index 30c4fa6e8f..82c308de6d 100644
--- a/crawl-ref/source/files.cc
+++ b/crawl-ref/source/files.cc
@@ -58,9 +58,9 @@
#include "mapmark.h"
#include "message.h"
#include "misc.h"
+#include "mon-act.h"
#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "mtransit.h"
#include "newgame.h"
#include "notes.h"
diff --git a/crawl-ref/source/godabil.cc b/crawl-ref/source/godabil.cc
index 2adfc984a6..1647678869 100644
--- a/crawl-ref/source/godabil.cc
+++ b/crawl-ref/source/godabil.cc
@@ -18,9 +18,10 @@
#include "kills.h"
#include "message.h"
#include "misc.h"
-#include "mon-util.h"
+#include "mon-act.h"
+#include "mon-behv.h"
#include "monstuff.h"
-#include "mstuff2.h"
+#include "mon-util.h"
#include "mutation.h"
#include "random.h"
#include "religion.h"
diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc
index b5393c488d..9ab4dd67cc 100644
--- a/crawl-ref/source/item_use.cc
+++ b/crawl-ref/source/item_use.cc
@@ -41,9 +41,9 @@
#include "macro.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
-#include "mstuff2.h"
#include "mon-util.h"
#include "notes.h"
#include "ouch.h"
diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj
index d9ffb8bd5a..3d6c68b246 100644
--- a/crawl-ref/source/makefile.obj
+++ b/crawl-ref/source/makefile.obj
@@ -84,6 +84,10 @@ menu.o \
message.o \
mgrow.o \
misc.o \
+mon-abil.o \
+mon-act.o \
+mon-behv.o \
+mon-cast.o \
mon-info.o \
mon-pick.o \
mon-util.o \
@@ -91,7 +95,6 @@ monplace.o \
monspeak.o \
monster.o \
monstuff.o \
-mstuff2.o \
mt19937ar.o \
mtransit.o \
mutation.o \
diff --git a/crawl-ref/source/mon-abil.cc b/crawl-ref/source/mon-abil.cc
new file mode 100644
index 0000000000..ed8b22d55c
--- /dev/null
+++ b/crawl-ref/source/mon-abil.cc
@@ -0,0 +1,1378 @@
+/*
+ * File: mon-abil.cc
+ * Summary: Monster abilities.
+ * Written by: Linley Henzell
+ */
+
+#include "AppHdr.h"
+#include "mon-abil.h"
+
+#include "externs.h"
+
+#ifdef TARGET_OS_DOS
+#include <conio.h>
+#endif
+
+#include "beam.h"
+#include "colour.h"
+#include "directn.h"
+#include "ghost.h"
+#include "misc.h"
+#include "mon-act.h"
+#include "mon-behv.h"
+#include "mon-cast.h"
+#include "monplace.h"
+#include "monspeak.h"
+#include "monstuff.h"
+#include "random.h"
+#include "spl-mis.h"
+#include "spl-util.h"
+#include "state.h"
+#include "stuff.h"
+#include "view.h"
+
+bool ugly_thing_mutate(monsters *ugly, bool proximity)
+{
+ bool success = false;
+
+ std::string src = "";
+
+ unsigned char mon_colour = BLACK;
+
+ if (!proximity)
+ success = true;
+ else if (one_chance_in(8))
+ {
+ int you_mutate_chance = 0;
+ int mon_mutate_chance = 0;
+
+ for (adjacent_iterator ri(ugly->pos()); ri; ++ri)
+ {
+ if (you.pos() == *ri)
+ you_mutate_chance = get_contamination_level();
+ else
+ {
+ monsters *ugly_near = monster_at(*ri);
+
+ if (!ugly_near
+ || ugly_near->type != MONS_UGLY_THING
+ && ugly_near->type != MONS_VERY_UGLY_THING)
+ {
+ continue;
+ }
+
+ for (int i = 0; i < 2; ++i)
+ {
+ if (coinflip())
+ {
+ mon_mutate_chance++;
+
+ if (coinflip())
+ {
+ const int ugly_colour =
+ make_low_colour(ugly->colour);
+ const int ugly_near_colour =
+ make_low_colour(ugly_near->colour);
+
+ if (ugly_colour != ugly_near_colour)
+ mon_colour = ugly_near_colour;
+ }
+ }
+
+ if (ugly_near->type != MONS_VERY_UGLY_THING)
+ break;
+ }
+ }
+ }
+
+ you_mutate_chance = std::min(16, you_mutate_chance);
+ mon_mutate_chance = std::min(16, mon_mutate_chance);
+
+ if (!one_chance_in(you_mutate_chance + mon_mutate_chance + 1))
+ {
+ const bool proximity_you =
+ (you_mutate_chance > mon_mutate_chance) ? true :
+ (you_mutate_chance == mon_mutate_chance) ? coinflip()
+ : false;
+
+ src = proximity_you ? " from you" : " from its kin";
+
+ success = true;
+ }
+ }
+
+ if (success)
+ {
+ simple_monster_message(ugly,
+ make_stringf(" basks in the mutagenic energy%s and changes!",
+ src.c_str()).c_str());
+
+ ugly->uglything_mutate(mon_colour);
+
+ return (true);
+ }
+
+ return (false);
+}
+
+// Inflict any enchantments the parent slime has on its offspring,
+// leaving durations unchanged, I guess. -cao
+static void _split_ench_durations(monsters *initial_slime, monsters *split_off)
+{
+ mon_enchant_list::iterator i;
+
+ for (i = initial_slime->enchantments.begin();
+ i != initial_slime->enchantments.end(); ++i)
+ {
+ split_off->add_ench(i->second);
+ }
+
+}
+
+// What to do about any enchantments these two slimes may have? For
+// now, we are averaging the durations. -cao
+static void _merge_ench_durations(monsters *initial_slime, monsters *merge_to)
+{
+ mon_enchant_list::iterator i;
+
+ int initial_count = initial_slime->number;
+ int merge_to_count = merge_to->number;
+ int total_count = initial_count + merge_to_count;
+
+ for (i = initial_slime->enchantments.begin();
+ i != initial_slime->enchantments.end(); ++i)
+ {
+ // Does the other slime have this enchantment as well?
+ mon_enchant temp = merge_to->get_ench(i->first);
+ // If not, use duration 0 for their part of the average.
+ int duration = temp.ench == ENCH_NONE ? 0 : temp.duration;
+
+ i->second.duration = (i->second.duration * initial_count
+ + duration * merge_to_count)/total_count;
+
+ if (!i->second.duration)
+ i->second.duration = 1;
+
+ merge_to->add_ench(i->second);
+ }
+
+ for (i = merge_to->enchantments.begin();
+ i != merge_to->enchantments.end(); ++i)
+ {
+ if (initial_slime->enchantments.find(i->first)
+ != initial_slime->enchantments.end()
+ && i->second.duration > 1)
+ {
+ i->second.duration = (merge_to_count * i->second.duration)
+ / total_count;
+
+ merge_to->update_ench(i->second);
+ }
+ }
+}
+
+// Calculate slime creature hp based on how many are merged.
+static void _stats_from_blob_count(monsters *slime, float hp_per_blob)
+{
+ slime->max_hit_points = (int)(slime->number * hp_per_blob);
+ slime->hit_points = slime->max_hit_points;
+}
+
+// Create a new slime creature at 'target', and split 'thing''s hp and
+// merge count with the new monster.
+static bool _do_split(monsters *thing, coord_def & target)
+{
+ // Create a new slime.
+ int slime_idx = create_monster(mgen_data(MONS_SLIME_CREATURE,
+ thing->behaviour,
+ 0,
+ 0,
+ target,
+ thing->foe,
+ MG_FORCE_PLACE));
+
+ if (slime_idx == -1)
+ return (false);
+
+ monsters *new_slime = &env.mons[slime_idx];
+
+ // Inflict the new slime with any enchantments on the parent.
+ _split_ench_durations(thing, new_slime);
+ new_slime->attitude = thing->attitude;
+ new_slime->flags = thing->flags;
+
+ if (!new_slime)
+ return (false);
+
+ if (you.can_see(thing))
+ mprf("%s splits.", thing->name(DESC_CAP_A).c_str());
+
+ int split_off = thing->number / 2;
+ float hp_per_blob = thing->max_hit_points / float(thing->number);
+
+ thing->number -= split_off;
+ new_slime->number = split_off;
+
+ new_slime->hit_dice = thing->hit_dice;
+
+ _stats_from_blob_count(thing, hp_per_blob);
+ _stats_from_blob_count(new_slime, hp_per_blob);
+
+ return (true);
+}
+
+// Actually merge two slime creature, pooling their hp, etc.
+// initial_slime is the one that gets killed off by this process.
+static bool _do_merge(monsters *initial_slime, monsters *merge_to)
+{
+ // Combine enchantment durations.
+ _merge_ench_durations(initial_slime, merge_to);
+
+ merge_to->number += initial_slime->number;
+ merge_to->max_hit_points += initial_slime->max_hit_points;
+ merge_to->hit_points += initial_slime->max_hit_points;
+
+ // Merge monster flags (mostly so that MF_CREATED_NEUTRAL, etc. are
+ // passed on if the merged slime subsequently splits. Hopefully
+ // this won't do anything weird.
+ merge_to->flags |= initial_slime->flags;
+
+ // Merging costs the combined slime some energy.
+ monsterentry* entry = get_monster_data(merge_to->type);
+ merge_to->speed_increment -= entry->energy_usage.move;
+
+ // This is dumb. With that said, the idea is that if 2 slimes merge
+ // you can gain a space by moving away the turn after (maybe this is
+ // too nice but there will probably be a lot of complaints about the
+ // damage on higher level slimes). So we subtracted some energy
+ // above, but if merge_to hasn't moved yet this turn, that will just
+ // cancel its turn in this round of world_reacts(). So we are going
+ // to see if merge_to has gone already by checking its mindex (this
+ // works because handle_monsters just iterates over env.mons in
+ // ascending order).
+ if (initial_slime->mindex() < merge_to->mindex())
+ merge_to->speed_increment -= entry->energy_usage.move;
+
+ // Overwrite the state of the slime getting merged into, because it
+ // might have been resting or something.
+ merge_to->behaviour = initial_slime->behaviour;
+ merge_to->foe = initial_slime->foe;
+
+ behaviour_event(merge_to, ME_EVAL);
+
+ // Messaging.
+ if (you.can_see(merge_to))
+ {
+ if (you.can_see(initial_slime))
+ {
+ mprf("Two slime creatures merge to form %s.",
+ merge_to->name(DESC_NOCAP_A).c_str());
+ }
+ else
+ {
+ mprf("A slime creature suddenly becomes %s.",
+ merge_to->name(DESC_NOCAP_A).c_str());
+ }
+
+ you.flash_colour = LIGHTGREEN;
+ viewwindow(true, false);
+
+ int flash_delay = 150;
+ // Scale delay to match change in arena_delay.
+ if (crawl_state.arena)
+ {
+ flash_delay *= Options.arena_delay;
+ flash_delay /= 600;
+ }
+
+ delay(flash_delay);
+ }
+ else if (you.can_see(initial_slime))
+ mpr("A slime creature suddenly disappears!");
+
+ // Have to 'kill' the slime doing the merging.
+ monster_die(initial_slime, KILL_MISC, NON_MONSTER, true);
+
+ return (true);
+}
+
+// Slime creatures can split but not merge under these conditions.
+static bool _unoccupied_slime(monsters *thing)
+{
+ return (thing->asleep()
+ || mons_is_wandering(thing)
+ || thing->foe == MHITNOT);
+}
+
+// Slime creatures cannot split or merge under these conditions.
+static bool _disabled_slime(monsters *thing)
+{
+ return (!thing
+ || mons_is_fleeing(thing)
+ || mons_is_confused(thing)
+ || mons_is_paralysed(thing));
+}
+
+// See if there are any appropriate adjacent slime creatures for 'thing'
+// to merge with. If so, carry out the merge.
+static bool _slime_merge(monsters *thing)
+{
+ if (!thing || _disabled_slime(thing) || _unoccupied_slime(thing))
+ return (false);
+
+ int max_slime_merge = 5;
+ int compass_idx[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+ std::random_shuffle(compass_idx, compass_idx + 8);
+ coord_def origin = thing->pos();
+
+ // Check for adjacent slime creatures.
+ for (int i = 0; i < 8; ++i)
+ {
+ coord_def target = origin + Compass[compass_idx[i]];
+ monsters *other_thing = monster_at(target);
+
+ // We found an adjacent monster. Is it another slime creature
+ // we can consider merging with?
+ if (other_thing
+ && other_thing->mons_species() == MONS_SLIME_CREATURE
+ && other_thing->attitude == thing->attitude
+ && other_thing->is_summoned() == thing->is_summoned()
+ && !mons_is_shapeshifter(other_thing)
+ && !_disabled_slime(other_thing))
+ {
+ // We can actually merge if doing so won't take us over the
+ // merge cap and the 'movement' would bring us closer to our
+ // target.
+ int new_blob_count = other_thing->number + thing->number;
+ if (new_blob_count <= max_slime_merge
+ && grid_distance(thing->target, thing->pos()) >
+ grid_distance(thing->target, target))
+ {
+ return (_do_merge(thing, other_thing));
+ }
+ }
+ }
+
+ // No adjacent slime creatures we could merge with.
+ return (false);
+}
+
+// See if slime creature 'thing' can split, and carry out the split if
+// we can find a square to place the new slime creature on.
+static bool _slime_split(monsters *thing)
+{
+ if (!thing
+ || !_unoccupied_slime(thing)
+ || _disabled_slime(thing)
+ || thing->number <= 1)
+ {
+ return (false);
+ }
+
+ int compass_idx[] = {0, 1, 2, 3, 4, 5, 6, 7};
+ std::random_shuffle(compass_idx, compass_idx + 8);
+ coord_def origin = thing->pos();
+
+ // Anywhere we can place an offspring?
+ for (int i = 0; i < 8; ++i)
+ {
+ coord_def target = origin + Compass[compass_idx[i]];
+
+ if (mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target))
+ && !actor_at(target))
+ {
+ // This can fail if placing a new monster fails. That
+ // probably means we have too many monsters on the level,
+ // so just return in that case.
+ return (_do_split(thing, target));
+ }
+ }
+
+ // No free squares.
+ return (false);
+}
+
+// See if a given slime creature can split or merge.
+bool slime_split_merge(monsters *thing)
+{
+ // No merging/splitting shapeshifters.
+ if (!thing
+ || mons_is_shapeshifter(thing)
+ || thing->mons_species() != MONS_SLIME_CREATURE)
+ {
+ return (false);
+ }
+
+ if (_slime_split(thing))
+ return (true);
+
+ return (_slime_merge(thing));
+}
+
+// Returns true if you resist the siren's call.
+static bool _siren_movement_effect(const monsters *monster)
+{
+ bool do_resist = (you.attribute[ATTR_HELD] || you_resist_magic(70));
+
+ if (!do_resist)
+ {
+ coord_def dir(coord_def(0,0));
+ if (monster->pos().x < you.pos().x)
+ dir.x = -1;
+ else if (monster->pos().x > you.pos().x)
+ dir.x = 1;
+ if (monster->pos().y < you.pos().y)
+ dir.y = -1;
+ else if (monster->pos().y > you.pos().y)
+ dir.y = 1;
+
+ const coord_def newpos = you.pos() + dir;
+
+ if (!in_bounds(newpos) || is_feat_dangerous(grd(newpos))
+ || !you.can_pass_through_feat(grd(newpos)))
+ {
+ do_resist = true;
+ }
+ else
+ {
+ bool swapping = false;
+ monsters *mon = monster_at(newpos);
+ if (mon)
+ {
+ if (mons_wont_attack(mon)
+ && !mons_is_stationary(mon)
+ && !mons_cannot_act(mon)
+ && !mon->asleep()
+ && swap_check(mon, you.pos(), true))
+ {
+ swapping = true;
+ }
+ else if (!mon->submerged())
+ do_resist = true;
+ }
+
+ if (!do_resist)
+ {
+ const coord_def oldpos = you.pos();
+ mprf("The pull of her song draws you forwards.");
+
+ if (swapping)
+ {
+ if (mgrd(oldpos) != NON_MONSTER)
+ {
+ mprf("Something prevents you from swapping places "
+ "with %s.",
+ mon->name(DESC_NOCAP_THE).c_str());
+ return (do_resist);
+ }
+
+ int swap_mon = mgrd(newpos);
+ // Pick the monster up.
+ mgrd(newpos) = NON_MONSTER;
+ mon->moveto(oldpos);
+
+ // Plunk it down.
+ mgrd(mon->pos()) = swap_mon;
+
+ mprf("You swap places with %s.",
+ mon->name(DESC_NOCAP_THE).c_str());
+ }
+ move_player_to_grid(newpos, true, true, true);
+
+ if (swapping)
+ mon->apply_location_effects(newpos);
+ }
+ }
+ }
+
+ return (do_resist);
+}
+
+static bool _silver_statue_effects(monsters *mons)
+{
+ actor *foe = mons->get_foe();
+ if (foe && mons->can_see(foe) && !one_chance_in(3))
+ {
+ const std::string msg =
+ "'s eyes glow " + weird_glowing_colour() + '.';
+ simple_monster_message(mons, msg.c_str(), MSGCH_WARN);
+
+ create_monster(
+ mgen_data(
+ summon_any_demon((coinflip() ? DEMON_COMMON
+ : DEMON_LESSER)),
+ SAME_ATTITUDE(mons), 5, 0, foe->pos(), mons->foe));
+ return (true);
+ }
+ return (false);
+}
+
+static bool _orange_statue_effects(monsters *mons)
+{
+ actor *foe = mons->get_foe();
+ if (foe && mons->can_see(foe) && !one_chance_in(3))
+ {
+ if (you.can_see(foe))
+ {
+ if (foe == &you)
+ mprf(MSGCH_WARN, "A hostile presence attacks your mind!");
+ else if (you.can_see(mons))
+ mprf(MSGCH_WARN, "%s fixes %s piercing gaze on %s.",
+ mons->name(DESC_CAP_THE).c_str(),
+ mons->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
+ foe->name(DESC_NOCAP_THE).c_str());
+ }
+
+ MiscastEffect(foe, monster_index(mons), SPTYP_DIVINATION,
+ random2(15), random2(150),
+ "an orange crystal statue");
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool _orc_battle_cry(monsters *chief)
+{
+ const actor *foe = chief->get_foe();
+ int affected = 0;
+
+ if (foe
+ && (foe != &you || !mons_friendly(chief))
+ && !silenced(chief->pos())
+ && chief->can_see(foe)
+ && coinflip())
+ {
+ const int boss_index = monster_index(chief);
+ const int level = chief->hit_dice > 12? 2 : 1;
+ std::vector<monsters*> seen_affected;
+ for (int i = 0; i < MAX_MONSTERS; ++i)
+ {
+ monsters *mon = &menv[i];
+ if (mon != chief
+ && mon->alive()
+ && mons_species(mon->type) == MONS_ORC
+ && mons_aligned(boss_index, i)
+ && mon->hit_dice < chief->hit_dice
+ && !mon->has_ench(ENCH_BERSERK)
+ && !mon->has_ench(ENCH_MIGHT)
+ && !mon->cannot_move()
+ && !mon->confused()
+ && chief->can_see(mon))
+ {
+ mon_enchant ench = mon->get_ench(ENCH_BATTLE_FRENZY);
+ if (ench.ench == ENCH_NONE || ench.degree < level)
+ {
+ const int dur =
+ random_range(12, 20) * speed_to_duration(mon->speed);
+
+ if (ench.ench != ENCH_NONE)
+ {
+ ench.degree = level;
+ ench.duration = std::max(ench.duration, dur);
+ mon->update_ench(ench);
+ }
+ else
+ {
+ mon->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, level,
+ KC_OTHER, dur));
+ }
+
+ affected++;
+ if (you.can_see(mon))
+ seen_affected.push_back(mon);
+
+ if (mon->asleep())
+ behaviour_event(mon, ME_DISTURB, MHITNOT, chief->pos());
+ }
+ }
+ }
+
+ if (affected)
+ {
+ if (you.can_see(chief) && player_can_hear(chief->pos()))
+ {
+ mprf(MSGCH_SOUND, "%s roars a battle-cry!",
+ chief->name(DESC_CAP_THE).c_str());
+ }
+
+ // The yell happens whether you happen to see it or not.
+ noisy(15, chief->pos(), chief->mindex());
+
+ // Disabling detailed frenzy announcement because it's so spammy.
+ const msg_channel_type channel =
+ mons_friendly_real(chief) ? MSGCH_MONSTER_ENCHANT
+ : MSGCH_FRIEND_ENCHANT;
+
+ if (!seen_affected.empty())
+ {
+ std::string who;
+ if (seen_affected.size() == 1)
+ {
+ who = seen_affected[0]->name(DESC_CAP_THE);
+ mprf(channel, "%s goes into a battle-frenzy!", who.c_str());
+ }
+ else
+ {
+ int type = seen_affected[0]->type;
+ for (unsigned int i = 0; i < seen_affected.size(); i++)
+ {
+ if (seen_affected[i]->type != type)
+ {
+ // just mention plain orcs
+ type = MONS_ORC;
+ break;
+ }
+ }
+ who = get_monster_data(type)->name;
+
+ mprf(channel, "%s %s go into a battle-frenzy!",
+ mons_friendly(chief) ? "Your" : "The",
+ pluralise(who).c_str());
+ }
+ }
+ }
+ }
+ // Orc battle cry doesn't cost the monster an action.
+ return (false);
+}
+
+static bool _make_monster_angry(const monsters *mon, monsters *targ)
+{
+ if (mons_friendly_real(mon) != mons_friendly_real(targ))
+ return (false);
+
+ // targ is guaranteed to have a foe (needs_berserk checks this).
+ // Now targ needs to be closer to *its* foe than mon is (otherwise
+ // mon might be in the way).
+
+ coord_def victim;
+ if (targ->foe == MHITYOU)
+ victim = you.pos();
+ else if (targ->foe != MHITNOT)
+ {
+ const monsters *vmons = &menv[targ->foe];
+ if (!vmons->alive())
+ return (false);
+ victim = vmons->pos();
+ }
+ else
+ {
+ // Should be impossible. needs_berserk should find this case.
+ ASSERT(false);
+ return (false);
+ }
+
+ // If mon may be blocking targ from its victim, don't try.
+ if (victim.distance_from(targ->pos()) > victim.distance_from(mon->pos()))
+ return (false);
+
+ if (you.can_see(mon))
+ {
+ mprf("%s goads %s on!", mon->name(DESC_CAP_THE).c_str(),
+ targ->name(DESC_NOCAP_THE).c_str());
+ }
+
+ targ->go_berserk(false);
+
+ return (true);
+}
+
+static bool _moth_incite_monsters(const monsters *mon)
+{
+ if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos()))
+ return false;
+
+ int goaded = 0;
+ for (int i = 0; i < MAX_MONSTERS; ++i)
+ {
+ monsters *targ = &menv[i];
+ if (targ == mon || !targ->alive() || !targ->needs_berserk())
+ continue;
+
+ if (mon->pos().distance_from(targ->pos()) > 3)
+ continue;
+
+ if (is_sanctuary(targ->pos()))
+ continue;
+
+ // Cannot goad other moths of wrath!
+ if (targ->type == MONS_MOTH_OF_WRATH)
+ continue;
+
+ if (_make_monster_angry(mon, targ) && !one_chance_in(3 * ++goaded))
+ return (true);
+ }
+
+ return (false);
+}
+
+//---------------------------------------------------------------
+//
+// mon_special_ability
+//
+//---------------------------------------------------------------
+bool mon_special_ability(monsters *monster, bolt & beem)
+{
+ bool used = false;
+
+ const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN)
+ ? draco_subspecies( monster )
+ : static_cast<monster_type>( monster->type );
+
+ // Slime creatures can split while out of sight.
+ if ((!mons_near(monster)
+ || monster->asleep()
+ || monster->submerged())
+ && monster->mons_species() != MONS_SLIME_CREATURE)
+ {
+ return (false);
+ }
+
+ const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL
+ : MSGCH_MONSTER_SPELL);
+
+ spell_type spell = SPELL_NO_SPELL;
+
+ switch (mclass)
+ {
+ case MONS_UGLY_THING:
+ case MONS_VERY_UGLY_THING:
+ // A (very) ugly thing's proximity to you if you're glowing, or
+ // to others of its kind, can mutate it into a different (very)
+ // ugly thing.
+ used = ugly_thing_mutate(monster, true);
+ break;
+
+ case MONS_SLIME_CREATURE:
+ // Slime creatures may split or merge depending on the
+ // situation.
+ used = slime_split_merge(monster);
+ if (!monster->alive())
+ return (true);
+ break;
+
+ case MONS_ORC_KNIGHT:
+ case MONS_ORC_WARLORD:
+ case MONS_SAINT_ROKA:
+ if (is_sanctuary(monster->pos()))
+ break;
+
+ used = _orc_battle_cry(monster);
+ break;
+
+ case MONS_ORANGE_STATUE:
+ if (player_or_mon_in_sanct(monster))
+ break;
+
+ used = _orange_statue_effects(monster);
+ break;
+
+ case MONS_SILVER_STATUE:
+ if (player_or_mon_in_sanct(monster))
+ break;
+
+ used = _silver_statue_effects(monster);
+ break;
+
+ case MONS_BALL_LIGHTNING:
+ if (is_sanctuary(monster->pos()))
+ break;
+
+ if (monster->attitude == ATT_HOSTILE
+ && distance(you.pos(), monster->pos()) <= 5)
+ {
+ monster->hit_points = -1;
+ used = true;
+ break;
+ }
+
+ for (int i = 0; i < MAX_MONSTERS; i++)
+ {
+ monsters *targ = &menv[i];
+
+ if (targ->type == MONS_NO_MONSTER)
+ continue;
+
+ if (distance(monster->pos(), targ->pos()) >= 5)
+ continue;
+
+ if (mons_atts_aligned(monster->attitude, targ->attitude))
+ continue;
+
+ // Faking LOS by checking the neighbouring square.
+ coord_def diff = targ->pos() - monster->pos();
+ coord_def sg(sgn(diff.x), sgn(diff.y));
+ coord_def t = monster->pos() + sg;
+
+ if (!inside_level_bounds(t))
+ continue;
+
+ if (!feat_is_solid(grd(t)))
+ {
+ monster->hit_points = -1;
+ used = true;
+ break;
+ }
+ }
+ break;
+
+ case MONS_LAVA_SNAKE:
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (!you.visible_to(monster))
+ break;
+
+ if (coinflip())
+ break;
+
+ // Setup tracer.
+ beem.name = "glob of lava";
+ beem.aux_source = "glob of lava";
+ beem.range = 6;
+ beem.damage = dice_def(3, 10);
+ beem.hit = 20;
+ beem.colour = RED;
+ beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
+ beem.flavour = BEAM_LAVA;
+ beem.beam_source = monster_index(monster);
+ beem.thrower = KILL_MON;
+
+ // Fire tracer.
+ fire_tracer(monster, beem);
+
+ // Good idea?
+ if (mons_should_fire(beem))
+ {
+ make_mons_stop_fleeing(monster);
+ simple_monster_message(monster, " spits lava!");
+ beem.fire();
+ used = true;
+ }
+ break;
+
+ case MONS_ELECTRIC_EEL:
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (!you.visible_to(monster))
+ break;
+
+ if (coinflip())
+ break;
+
+ // Setup tracer.
+ beem.name = "bolt of electricity";
+ beem.aux_source = "bolt of electricity";
+ beem.range = 8;
+ beem.damage = dice_def( 3, 6 );
+ beem.hit = 50;
+ beem.colour = LIGHTCYAN;
+ beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
+ beem.flavour = BEAM_ELECTRICITY;
+ beem.beam_source = monster_index(monster);
+ beem.thrower = KILL_MON;
+ beem.is_beam = true;
+
+ // Fire tracer.
+ fire_tracer(monster, beem);
+
+ // Good idea?
+ if (mons_should_fire(beem))
+ {
+ make_mons_stop_fleeing(monster);
+ simple_monster_message(monster,
+ " shoots out a bolt of electricity!");
+ beem.fire();
+ used = true;
+ }
+ break;
+
+ case MONS_ACID_BLOB:
+ case MONS_OKLOB_PLANT:
+ case MONS_YELLOW_DRACONIAN:
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (player_or_mon_in_sanct(monster))
+ break;
+
+ if (one_chance_in(3))
+ {
+ spell = SPELL_ACID_SPLASH;
+ setup_mons_cast(monster, beem, spell);
+
+ // Fire tracer.
+ fire_tracer(monster, beem);
+
+ // Good idea?
+ if (mons_should_fire(beem))
+ {
+ make_mons_stop_fleeing(monster);
+ mons_cast(monster, beem, spell);
+ used = true;
+ }
+ }
+ break;
+
+ case MONS_MOTH_OF_WRATH:
+ if (one_chance_in(3))
+ used = _moth_incite_monsters(monster);
+ break;
+
+ case MONS_SNORG:
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (monster->foe == MHITNOT
+ || monster->foe == MHITYOU && mons_friendly(monster))
+ {
+ break;
+ }
+
+ // There's a 5% chance of Snorg spontaneously going berserk that
+ // increases to 20% once he is wounded.
+ if (monster->hit_points == monster->max_hit_points && !one_chance_in(4))
+ break;
+
+ if (one_chance_in(5))
+ monster->go_berserk(true);
+ break;
+
+ case MONS_PIT_FIEND:
+ if (one_chance_in(3))
+ break;
+ // deliberate fall through
+ case MONS_FIEND:
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (player_or_mon_in_sanct(monster))
+ break;
+
+ // Friendly fiends won't use torment, preferring hellfire
+ // (right now there is no way a monster can predict how
+ // badly they'll damage the player with torment) -- GDL
+
+ // Well, I guess you could allow it if the player is torment
+ // resistant, but there's a very good reason torment resistant
+ // players can't cast Torment themselves, and allowing your
+ // allies to cast it would just introduce harmless Torment
+ // through the backdoor. Thus, shouldn't happen. (jpeg)
+ if (one_chance_in(4))
+ {
+ spell_type spell_cast = SPELL_NO_SPELL;
+
+ switch (random2(4))
+ {
+ case 0:
+ if (!mons_friendly(monster))
+ {
+ make_mons_stop_fleeing(monster);
+ spell_cast = SPELL_SYMBOL_OF_TORMENT;
+ mons_cast(monster, beem, spell_cast);
+ used = true;
+ break;
+ }
+ // deliberate fallthrough -- see above
+ case 1:
+ case 2:
+ case 3:
+ spell_cast = SPELL_HELLFIRE;
+ setup_mons_cast(monster, beem, spell_cast);
+
+ // Fire tracer.
+ fire_tracer(monster, beem);
+
+ // Good idea?
+ if (mons_should_fire(beem))
+ {
+ make_mons_stop_fleeing(monster);
+
+ mons_cast(monster, beem, spell_cast);
+ used = true;
+ }
+ break;
+ }
+ }
+ break;
+
+ case MONS_IMP:
+ case MONS_PHANTOM:
+ case MONS_INSUBSTANTIAL_WISP:
+ case MONS_BLINK_FROG:
+ case MONS_KILLER_KLOWN:
+ case MONS_PRINCE_RIBBIT:
+ if (one_chance_in(7) || mons_is_caught(monster) && one_chance_in(3))
+ used = monster_blink(monster);
+ break;
+
+ case MONS_MANTICORE:
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (!you.visible_to(monster))
+ break;
+
+ // The fewer spikes the manticore has left, the less
+ // likely it will use them.
+ if (random2(16) >= static_cast<int>(monster->number))
+ break;
+
+ // Do the throwing right here, since the beam is so
+ // easy to set up and doesn't involve inventory.
+
+ // Set up the beam.
+ beem.name = "volley of spikes";
+ beem.aux_source = "volley of spikes";
+ beem.range = 6;
+ beem.hit = 14;
+ beem.damage = dice_def( 2, 10 );
+ beem.beam_source = monster_index(monster);
+ beem.type = dchar_glyph(DCHAR_FIRED_MISSILE);
+ beem.colour = LIGHTGREY;
+ beem.flavour = BEAM_MISSILE;
+ beem.thrower = KILL_MON;
+ beem.is_beam = false;
+
+ // Fire tracer.
+ fire_tracer(monster, beem);
+
+ // Good idea?
+ if (mons_should_fire(beem))
+ {
+ make_mons_stop_fleeing(monster);
+ simple_monster_message(monster, " flicks its tail!");
+ beem.fire();
+ used = true;
+ // Decrement # of volleys left.
+ monster->number--;
+ }
+ break;
+
+ case MONS_PLAYER_GHOST:
+ {
+ const ghost_demon &ghost = *(monster->ghost);
+
+ if (ghost.species < SP_RED_DRACONIAN
+ || ghost.species == SP_GREY_DRACONIAN
+ || ghost.species >= SP_BASE_DRACONIAN
+ || ghost.xl < 7
+ || one_chance_in(ghost.xl - 5))
+ {
+ break;
+ }
+ }
+ // Intentional fallthrough
+
+ case MONS_WHITE_DRACONIAN:
+ case MONS_RED_DRACONIAN:
+ spell = SPELL_DRACONIAN_BREATH;
+ // Intentional fallthrough
+
+ case MONS_ICE_DRAGON:
+ if (spell == SPELL_NO_SPELL)
+ spell = SPELL_COLD_BREATH;
+ // Intentional fallthrough
+
+ // Dragon breath weapons:
+ case MONS_DRAGON:
+ case MONS_HELL_HOUND:
+ case MONS_LINDWURM:
+ case MONS_FIREDRAKE:
+ case MONS_XTAHUA:
+ if (spell == SPELL_NO_SPELL)
+ spell = SPELL_FIRE_BREATH;
+
+ if (monster->has_ench(ENCH_CONFUSION))
+ break;
+
+ if (!you.visible_to(monster))
+ break;
+
+ if (monster->type != MONS_HELL_HOUND && x_chance_in_y(3, 13)
+ || one_chance_in(10))
+ {
+ setup_mons_cast(monster, beem, spell);
+
+ // Fire tracer.
+ fire_tracer(monster, beem);
+
+ // Good idea?
+ if (mons_should_fire(beem))
+ {
+ make_mons_stop_fleeing(monster);
+ mons_cast(monster, beem, spell);
+ used = true;
+ }
+ }
+ break;
+
+ case MONS_MERMAID:
+ case MONS_SIREN:
+ {
+ // Don't behold observer in the arena.
+ if (crawl_state.arena)
+ break;
+
+ // Don't behold player already half down or up the stairs.
+ if (!you.delay_queue.empty())
+ {
+ delay_queue_item delay = you.delay_queue.front();
+
+ if (delay.type == DELAY_ASCENDING_STAIRS
+ || delay.type == DELAY_DESCENDING_STAIRS)
+ {
+#ifdef DEBUG_DIAGNOSTICS
+ mpr("Taking stairs, don't mesmerise.", MSGCH_DIAGNOSTICS);
+#endif
+ break;
+ }
+ }
+
+ // Won't sing if either of you silenced, or it's friendly,
+ // confused, fleeing, or leaving the level.
+ if (monster->has_ench(ENCH_CONFUSION)
+ || mons_is_fleeing(monster)
+ || mons_is_pacified(monster)
+ || mons_friendly(monster)
+ || !player_can_hear(monster->pos()))
+ {
+ break;
+ }
+
+ // Don't even try on berserkers. Mermaids know their limits.
+ if (you.duration[DUR_BERSERKER])
+ break;
+
+ // Reduce probability because of spamminess.
+ if (you.species == SP_MERFOLK && !one_chance_in(4))
+ break;
+
+ // A wounded invisible mermaid is less likely to give away her position.
+ if (monster->invisible()
+ && monster->hit_points <= monster->max_hit_points / 2
+ && !one_chance_in(3))
+ {
+ break;
+ }
+
+ bool already_mesmerised = player_mesmerised_by(monster);
+
+ if (one_chance_in(5)
+ || monster->foe == MHITYOU && !already_mesmerised && coinflip())
+ {
+ noisy(12, monster->pos(), monster->mindex(), true);
+
+ bool did_resist = false;
+ if (you.can_see(monster))
+ {
+ simple_monster_message(monster,
+ make_stringf(" chants %s song.",
+ already_mesmerised ? "her luring" : "a haunting").c_str(),
+ spl);
+
+ if (monster->type == MONS_SIREN)
+ {
+ if (_siren_movement_effect(monster))
+ {
+ canned_msg(MSG_YOU_RESIST); // flavour only
+ did_resist = true;
+ }
+ }
+ }
+ else
+ {
+ // If you're already mesmerised by an invisible mermaid she
+ // can still prolong the enchantment; otherwise you "resist".
+ if (already_mesmerised)
+ mpr("You hear a luring song.", MSGCH_SOUND);
+ else
+ {
+ if (one_chance_in(4)) // reduce spamminess
+ {
+ if (coinflip())
+ mpr("You hear a haunting song.", MSGCH_SOUND);
+ else
+ mpr("You hear an eerie melody.", MSGCH_SOUND);
+
+ canned_msg(MSG_YOU_RESIST); // flavour only
+ }
+ break;
+ }
+ }
+
+ // Once mesmerised by a particular monster, you cannot resist
+ // anymore.
+ if (!already_mesmerised
+ && (you.species == SP_MERFOLK || you_resist_magic(100)))
+ {
+ if (!did_resist)
+ canned_msg(MSG_YOU_RESIST);
+ break;
+ }
+
+ if (!you.duration[DUR_MESMERISED])
+ {
+ you.duration[DUR_MESMERISED] = 7;
+ you.mesmerised_by.push_back(monster_index(monster));
+ mprf(MSGCH_WARN, "You are mesmerised by %s!",
+ monster->name(DESC_NOCAP_THE).c_str());
+ }
+ else
+ {
+ you.duration[DUR_MESMERISED] += 5;
+ if (!already_mesmerised)
+ you.mesmerised_by.push_back(monster_index(monster));
+ }
+ used = true;
+
+ if (you.duration[DUR_MESMERISED] > 12)
+ you.duration[DUR_MESMERISED] = 12;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (used)
+ monster->lose_energy(EUT_SPECIAL);
+
+ return (used);
+}
+
+//---------------------------------------------------------------
+//
+// mon_nearby_ability
+//
+// Gives monsters a chance to use a special ability when they're
+// next to the player.
+//
+//---------------------------------------------------------------
+void mon_nearby_ability(monsters *monster)
+{
+ actor *foe = monster->get_foe();
+ if (!foe
+ || !monster->can_see(foe)
+ || monster->asleep()
+ || monster->submerged())
+ {
+ return;
+ }
+
+#define MON_SPEAK_CHANCE 21
+
+ if (monster->is_patrolling() || mons_is_wandering(monster)
+ || monster->attitude == ATT_NEUTRAL)
+ {
+ // Very fast wandering/patrolling monsters might, in one monster turn,
+ // move into the player's LOS and then back out (or the player
+ // might move into their LOS and the monster move back out before
+ // the player's view has a chance to update) so prevent them
+ // from speaking.
+ ;
+ }
+ else if ((mons_class_flag(monster->type, M_SPEAKS)
+ || !monster->mname.empty())
+ && one_chance_in(MON_SPEAK_CHANCE))
+ {
+ mons_speaks(monster);
+ }
+ else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED)
+ {
+ // Non-humanoid-ish monsters have a low chance of speaking
+ // without the M_SPEAKS flag, to give the dungeon some
+ // atmosphere/flavour.
+ int chance = MON_SPEAK_CHANCE * 4;
+
+ // Band members are a lot less likely to speak, since there's
+ // a lot of them.
+ if (testbits(monster->flags, MF_BAND_MEMBER))
+ chance *= 10;
+
+ // However, confused and fleeing monsters are more interesting.
+ if (mons_is_fleeing(monster))
+ chance /= 2;
+ if (monster->has_ench(ENCH_CONFUSION))
+ chance /= 2;
+
+ if (one_chance_in(chance))
+ mons_speaks(monster);
+ }
+ // Okay then, don't speak.
+
+ if (monster_can_submerge(monster, grd(monster->pos()))
+ && !monster->caught() // No submerging while caught.
+ && !player_mesmerised_by(monster) // No submerging if player entranced.
+ && !mons_is_lurking(monster) // Handled elsewhere.
+ && monster->wants_submerge())
+ {
+ monsterentry* entry = get_monster_data(monster->type);
+
+ monster->add_ench(ENCH_SUBMERGED);
+ monster->speed_increment -= ENERGY_SUBMERGE(entry);
+ update_beholders(monster);
+ return;
+ }
+
+ switch (monster->type)
+ {
+ case MONS_SPATIAL_VORTEX:
+ case MONS_KILLER_KLOWN:
+ // Choose random colour.
+ monster->colour = random_colour();
+ break;
+
+ case MONS_GIANT_EYEBALL:
+ if (coinflip()
+ && !mons_is_wandering(monster)
+ && !mons_is_fleeing(monster)
+ && !mons_is_pacified(monster)
+ && !player_or_mon_in_sanct(monster))
+ {
+ if (you.can_see(monster) && you.can_see(foe))
+ mprf("%s stares at %s.",
+ monster->name(DESC_CAP_THE).c_str(),
+ foe->name(DESC_NOCAP_THE).c_str());
+
+ // Subtly different from old paralysis behaviour, but
+ // it'll do.
+ foe->paralyse(monster, 2 + random2(3));
+ }
+ break;
+
+ case MONS_EYE_OF_DRAINING:
+ if (coinflip()
+ && foe->atype() == ACT_PLAYER
+ && !mons_is_wandering(monster)
+ && !mons_is_fleeing(monster)
+ && !mons_is_pacified(monster)
+ && !player_or_mon_in_sanct(monster))
+ {
+ simple_monster_message(monster, " stares at you.");
+
+ dec_mp(5 + random2avg(13, 3));
+
+ heal_monster(monster, 10, true); // heh heh {dlb}
+ }
+ break;
+
+ case MONS_AIR_ELEMENTAL:
+ if (one_chance_in(5))
+ monster->add_ench(ENCH_SUBMERGED);
+ break;
+
+ case MONS_PANDEMONIUM_DEMON:
+ if (monster->ghost->cycle_colours)
+ monster->colour = random_colour();
+ break;
+
+ default:
+ break;
+ }
+}
+
+
diff --git a/crawl-ref/source/mon-abil.h b/crawl-ref/source/mon-abil.h
new file mode 100644
index 0000000000..527f71dc3e
--- /dev/null
+++ b/crawl-ref/source/mon-abil.h
@@ -0,0 +1,19 @@
+/*
+ * File: mon-abil.h
+ * Summary: Monster abilities.
+ * Written by: Linley Henzell
+ */
+
+#ifndef MONABIL_H
+#define MONABIL_H
+
+class monsters;
+class bolt;
+
+bool mon_special_ability(monsters *monster, bolt & beem);
+void mon_nearby_ability(monsters *monster);
+
+bool ugly_thing_mutate(monsters *ugly, bool proximity = false);
+bool slime_split_merge(monsters *thing);
+
+#endif
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;
+ }
+}
+
diff --git a/crawl-ref/source/mon-act.h b/crawl-ref/source/mon-act.h
new file mode 100644
index 0000000000..026d8a2f4b
--- /dev/null
+++ b/crawl-ref/source/mon-act.h
@@ -0,0 +1,14 @@
+/*
+ * File: mon-act.h
+ * Summary: Monsters doing stuff (monsters acting).
+ * Written by: Linley Henzell
+ */
+
+#ifndef MONACT_H
+#define MONACT_H
+
+void handle_monsters(void);
+
+#define ENERGY_SUBMERGE(entry) (std::max(entry->energy_usage.swim / 2, 1))
+
+#endif
diff --git a/crawl-ref/source/mon-behv.cc b/crawl-ref/source/mon-behv.cc
new file mode 100644
index 0000000000..1644b57c8a
--- /dev/null
+++ b/crawl-ref/source/mon-behv.cc
@@ -0,0 +1,1928 @@
+/*
+ * File: mon-behv.h
+ * Summary: Monster behaviour functions.
+ * Written by: Linley Henzell
+ */
+
+#include "AppHdr.h"
+#include "mon-behv.h"
+
+#include "externs.h"
+
+#include "coord.h"
+#include "coordit.h"
+#include "exclude.h"
+#include "los.h"
+#include "monplace.h"
+#include "monstuff.h"
+#include "mon-util.h"
+#include "random.h"
+#include "state.h"
+#include "terrain.h"
+#include "traps.h"
+#include "tutorial.h"
+#include "view.h"
+
+static void _set_nearest_monster_foe(monsters *monster);
+
+// Check all grids in LoS and mark lava and/or water as seen if the
+// appropriate grids are encountered, so we later only need to do the
+// visibility check for monsters that can't pass a feature potentially in
+// the way. We don't care about shallow water as most monsters can safely
+// cross that, and fire elementals alone aren't really worth the extra
+// hassle. :)
+static void _check_lava_water_in_sight()
+{
+ you.lava_in_sight = you.water_in_sight = 0;
+ for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri)
+ {
+ // XXX: remove explicit coordinate translation.
+ const coord_def ep = *ri - you.pos() + coord_def(ENV_SHOW_OFFSET,
+ ENV_SHOW_OFFSET);
+ if (env.show(ep))
+ {
+ const dungeon_feature_type feat = grd(*ri);
+ if (feat == DNGN_LAVA)
+ {
+ you.lava_in_sight = 1;
+ if (you.water_in_sight > 0)
+ break;
+ }
+ else if (feat == DNGN_DEEP_WATER)
+ {
+ you.water_in_sight = 1;
+ if (you.lava_in_sight > 0)
+ break;
+ }
+ }
+ }
+}
+
+// If a monster can see but not directly reach the target, and then fails to
+// find a path to get there, mark all surrounding (in a radius of 2) monsters
+// of the same (or greater) movement restrictions as also being unable to
+// find a path, so we won't need to calculate again.
+// Should there be a direct path to the target for a monster thus marked, it
+// will still be able to come nearer (and the mark will then be cleared).
+static void _mark_neighbours_target_unreachable(monsters *mon)
+{
+ // Highly intelligent monsters are perfectly capable of pathfinding
+ // and don't need their neighbour's advice.
+ const mon_intel_type intel = mons_intel(mon);
+ if (intel > I_NORMAL)
+ return;
+
+ const bool flies = mons_flies(mon);
+ const bool amphibious = mons_amphibious(mon);
+ const habitat_type habit = mons_primary_habitat(mon);
+
+ for (radius_iterator ri(mon->pos(), 2, true, false); ri; ++ri)
+ {
+ if (*ri == mon->pos())
+ continue;
+
+ // Don't alert monsters out of sight (e.g. on the other side of
+ // a wall).
+ if (!mon->mon_see_cell(*ri))
+ continue;
+
+ monsters* const m = monster_at(*ri);
+ if (m == NULL)
+ continue;
+
+ // Don't restrict smarter monsters as they might find a path
+ // a dumber monster wouldn't.
+ if (mons_intel(m) > intel)
+ continue;
+
+ // Monsters of differing habitats might prefer different routes.
+ if (mons_primary_habitat(m) != habit)
+ continue;
+
+ // A flying monster has an advantage over a non-flying one.
+ if (!flies && mons_flies(m))
+ continue;
+
+ // Same for a swimming one, around water.
+ if (you.water_in_sight > 0 && !amphibious && mons_amphibious(m))
+ continue;
+
+ if (m->travel_target == MTRAV_NONE)
+ m->travel_target = MTRAV_UNREACHABLE;
+ }
+}
+
+static void _set_no_path_found(monsters *mon)
+{
+#ifdef DEBUG_PATHFIND
+ mpr("No path found!");
+#endif
+
+ mon->travel_target = MTRAV_UNREACHABLE;
+ // Pass information on to nearby monsters.
+ _mark_neighbours_target_unreachable(mon);
+}
+
+static bool _target_is_unreachable(monsters *mon)
+{
+ return (mon->travel_target == MTRAV_UNREACHABLE
+ || mon->travel_target == MTRAV_KNOWN_UNREACHABLE);
+}
+
+//#define DEBUG_PATHFIND
+
+// The monster is trying to get to the player (MHITYOU).
+// Check whether there's an unobstructed path to the player (in sight!),
+// either by using an existing travel_path or calculating a new one.
+// Returns true if no further handling necessary, else false.
+static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move,
+ bool potentially_blocking)
+{
+ // Just because we can *see* the player, that doesn't mean
+ // we can actually get there. To find about that, we first
+ // check for transparent walls. If there are transparent
+ // walls in the way we'll need pathfinding, no matter what.
+ // (Though monsters with a los attack don't need to get any
+ // closer to hurt the player.)
+ // If no walls are detected, there could still be a river
+ // or a pool of lava in the way. So we check whether there
+ // is water or lava in LoS (boolean) and if so, try to find
+ // a way around it. It's possible that the player can see
+ // lava but it actually has no influence on the monster's
+ // movement (because it's lying in the opposite direction)
+ // but if so, we'll find that out during path finding.
+ // In another attempt of optimization, don't bother with
+ // path finding if the monster in question has no trouble
+ // travelling through water or flying across lava.
+ // Also, if no path is found (too far away, perhaps) set a
+ // flag, so we don't directly calculate the whole thing again
+ // next turn, and even extend that flag to neighbouring
+ // monsters of similar movement restrictions.
+
+ // Smart monsters that can fire through walls won't use
+ // pathfinding, and it's also not necessary if the monster
+ // is already adjacent to you.
+ if (potentially_blocking && mons_intel(mon) >= I_NORMAL
+ && !mons_friendly(mon) && mons_has_los_ability(mon->type)
+ || grid_distance(mon->pos(), you.pos()) == 1)
+ {
+ potentially_blocking = false;
+ }
+ else
+ {
+ // If we don't already know whether there's water or lava
+ // in LoS of the player, find out now.
+ if (you.lava_in_sight == -1 || you.water_in_sight == -1)
+ _check_lava_water_in_sight();
+
+ // Flying monsters don't see water/lava as obstacle.
+ // Also don't use pathfinding if the monster can shoot
+ // across the blocking terrain, and is smart enough to
+ // realise that.
+ if (!potentially_blocking && !mons_flies(mon)
+ && (mons_intel(mon) < I_NORMAL
+ || mons_friendly(mon)
+ || (!mons_has_ranged_spell(mon, true)
+ && !mons_has_ranged_attack(mon))))
+ {
+ const habitat_type habit = mons_primary_habitat(mon);
+ if (you.lava_in_sight > 0 && habit != HT_LAVA
+ || you.water_in_sight > 0 && habit != HT_WATER
+ && can_move != DNGN_DEEP_WATER)
+ {
+ potentially_blocking = true;
+ }
+ }
+ }
+
+ if (!potentially_blocking
+ || can_go_straight(mon->pos(), you.pos(), can_move))
+ {
+ // The player is easily reachable.
+ // Clear travel path and target, if necessary.
+ if (mon->travel_target != MTRAV_PATROL
+ && mon->travel_target != MTRAV_NONE)
+ {
+ if (mon->is_travelling())
+ mon->travel_path.clear();
+ mon->travel_target = MTRAV_NONE;
+ }
+ return (false);
+ }
+
+ // Even if the target has been to "unreachable" (the monster already tried,
+ // and failed, to find a path) there's a chance of trying again.
+ if (!_target_is_unreachable(mon) || one_chance_in(12))
+ {
+#ifdef DEBUG_PATHFIND
+ mprf("%s: Player out of reach! What now?",
+ mon->name(DESC_PLAIN).c_str());
+#endif
+ // If we're already on our way, do nothing.
+ if (mon->is_travelling() && mon->travel_target == MTRAV_PLAYER)
+ {
+ const int len = mon->travel_path.size();
+ const coord_def targ = mon->travel_path[len - 1];
+
+ // Current target still valid?
+ if (can_go_straight(targ, you.pos(), can_move))
+ {
+ // Did we reach the target?
+ if (mon->pos() == mon->travel_path[0])
+ {
+ // Get next waypoint.
+ mon->travel_path.erase( mon->travel_path.begin() );
+
+ if (!mon->travel_path.empty())
+ {
+ mon->target = mon->travel_path[0];
+ return (true);
+ }
+ }
+ else if (can_go_straight(mon->pos(), mon->travel_path[0],
+ can_move))
+ {
+ mon->target = mon->travel_path[0];
+ return (true);
+ }
+ }
+ }
+
+ // Use pathfinding to find a (new) path to the player.
+ const int dist = grid_distance(mon->pos(), you.pos());
+
+#ifdef DEBUG_PATHFIND
+ mprf("Need to calculate a path... (dist = %d)", dist);
+#endif
+ const int range = mons_tracking_range(mon);
+ if (range > 0 && dist > range)
+ {
+ mon->travel_target = MTRAV_UNREACHABLE;
+#ifdef DEBUG_PATHFIND
+ mprf("Distance too great, don't attempt pathfinding! (%s)",
+ mon->name(DESC_PLAIN).c_str());
+#endif
+ return (false);
+ }
+
+#ifdef DEBUG_PATHFIND
+ mprf("Need a path for %s from (%d, %d) to (%d, %d), max. dist = %d",
+ mon->name(DESC_PLAIN).c_str(), mon->pos(), you.pos(), range);
+#endif
+ monster_pathfind mp;
+ if (range > 0)
+ mp.set_range(range);
+
+ if (mp.init_pathfind(mon, you.pos()))
+ {
+ mon->travel_path = mp.calc_waypoints();
+ if (!mon->travel_path.empty())
+ {
+ // Okay then, we found a path. Let's use it!
+ mon->target = mon->travel_path[0];
+ mon->travel_target = MTRAV_PLAYER;
+ return (true);
+ }
+ else
+ _set_no_path_found(mon);
+ }
+ else
+ _set_no_path_found(mon);
+ }
+
+ // We didn't find a path.
+ return (false);
+}
+
+static bool _is_level_exit(const coord_def& pos)
+{
+ // All types of stairs.
+ if (feat_is_stair(grd(pos)))
+ return (true);
+
+ // Teleportation and shaft traps.
+ const trap_type tt = get_trap_type(pos);
+ if (tt == TRAP_TELEPORT || tt == TRAP_SHAFT)
+ return (true);
+
+ return (false);
+}
+
+// Returns true if a monster left the level.
+static bool _pacified_leave_level(monsters *mon, std::vector<level_exit> e,
+ int e_index)
+{
+ // If a pacified monster is leaving the level, and has reached an
+ // exit (whether that exit was its target or not), handle it here.
+ // Likewise, if a pacified monster is far enough away from the
+ // player, make it leave the level.
+ if (_is_level_exit(mon->pos())
+ || (e_index != -1 && mon->pos() == e[e_index].target)
+ || grid_distance(mon->pos(), you.pos()) >= LOS_RADIUS * 4)
+ {
+ make_mons_leave_level(mon);
+ return (true);
+ }
+
+ return (false);
+}
+
+// Counts deep water twice.
+static int _count_water_neighbours(coord_def p)
+{
+ int water_count = 0;
+ for (adjacent_iterator ai(p); ai; ++ai)
+ {
+ if (grd(*ai) == DNGN_SHALLOW_WATER)
+ water_count++;
+ else if (grd(*ai) == DNGN_DEEP_WATER)
+ water_count += 2;
+ }
+ return (water_count);
+}
+
+// Pick the nearest water grid that is surrounded by the most
+// water squares within LoS.
+static bool _find_siren_water_target(monsters *mon)
+{
+ ASSERT(mon->type == MONS_SIREN);
+
+ // Moving away could break the entrancement, so don't do this.
+ if ((mon->pos() - you.pos()).rdist() >= 6)
+ return (false);
+
+ // Already completely surrounded by deep water.
+ if (_count_water_neighbours(mon->pos()) >= 16)
+ return (true);
+
+ if (mon->travel_target == MTRAV_SIREN)
+ {
+ coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]);
+#ifdef DEBUG_PATHFIND
+ mprf("siren target is (%d, %d), dist = %d",
+ targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist());
+#endif
+ if ((mon->pos() - targ_pos).rdist() > 2)
+ return (true);
+ }
+
+ int best_water_count = 0;
+ coord_def best_target;
+ bool first = true;
+
+ while (true)
+ {
+ int best_num = 0;
+ for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false);
+ ri; ++ri)
+ {
+ if (!feat_is_water(grd(*ri)))
+ continue;
+
+ // In the first iteration only count water grids that are
+ // not closer to the player than to the siren.
+ if (first && (mon->pos() - *ri).rdist() > (you.pos() - *ri).rdist())
+ continue;
+
+ // Counts deep water twice.
+ const int water_count = _count_water_neighbours(*ri);
+ if (water_count < best_water_count)
+ continue;
+
+ if (water_count > best_water_count)
+ {
+ best_water_count = water_count;
+ best_target = *ri;
+ best_num = 1;
+ }
+ else // water_count == best_water_count
+ {
+ const int old_dist = (mon->pos() - best_target).rdist();
+ const int new_dist = (mon->pos() - *ri).rdist();
+ if (new_dist > old_dist)
+ continue;
+
+ if (new_dist < old_dist)
+ {
+ best_target = *ri;
+ best_num = 1;
+ }
+ else if (one_chance_in(++best_num))
+ best_target = *ri;
+ }
+ }
+
+ if (!first || best_water_count > 0)
+ break;
+
+ // Else start the second iteration.
+ first = false;
+ }
+
+ if (!best_water_count)
+ return (false);
+
+ // We're already optimally placed.
+ if (best_target == mon->pos())
+ return (true);
+
+ monster_pathfind mp;
+#ifdef WIZARD
+ // Remove old highlighted areas to make place for the new ones.
+ for (rectangle_iterator ri(1); ri; ++ri)
+ env.map(*ri).property &= ~(FPROP_HIGHLIGHT);
+#endif
+
+ if (mp.init_pathfind(mon, best_target))
+ {
+ mon->travel_path = mp.calc_waypoints();
+
+ if (!mon->travel_path.empty())
+ {
+#ifdef WIZARD
+ for (unsigned int i = 0; i < mon->travel_path.size(); i++)
+ env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT;
+#endif
+#ifdef DEBUG_PATHFIND
+ mprf("Found a path to (%d, %d) with %d surrounding water squares",
+ best_target.x, best_target.y, best_water_count);
+#endif
+ // Okay then, we found a path. Let's use it!
+ mon->target = mon->travel_path[0];
+ mon->travel_target = MTRAV_SIREN;
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static bool _find_wall_target(monsters *mon)
+{
+ ASSERT(mons_wall_shielded(mon));
+
+ if (mon->travel_target == MTRAV_WALL)
+ {
+ coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]);
+
+ // Target grid might have changed since we started, like if the
+ // player destroys the wall the monster wants to hide in.
+ if (cell_is_solid(targ_pos)
+ && monster_habitable_grid(mon, grd(targ_pos)))
+ {
+ // Wall is still good.
+#ifdef DEBUG_PATHFIND
+ mprf("%s target is (%d, %d), dist = %d",
+ mon->name(DESC_PLAIN, true).c_str(),
+ targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist());
+#endif
+ return (true);
+ }
+
+ mon->travel_path.clear();
+ mon->travel_target = MTRAV_NONE;
+ }
+
+ int best_dist = INT_MAX;
+ bool best_closer_to_player = false;
+ coord_def best_target;
+
+ for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false);
+ ri; ++ri)
+ {
+ if (!cell_is_solid(*ri)
+ || !monster_habitable_grid(mon, grd(*ri)))
+ {
+ continue;
+ }
+
+ int dist = (mon->pos() - *ri).rdist();
+ bool closer_to_player = false;
+ if (dist > (you.pos() - *ri).rdist())
+ closer_to_player = true;
+
+ if (dist < best_dist)
+ {
+ best_dist = dist;
+ best_closer_to_player = closer_to_player;
+ best_target = *ri;
+ }
+ else if (best_closer_to_player && !closer_to_player
+ && dist == best_dist)
+ {
+ best_closer_to_player = false;
+ best_target = *ri;
+ }
+ }
+
+ if (best_dist == INT_MAX || !in_bounds(best_target))
+ return (false);
+
+ monster_pathfind mp;
+#ifdef WIZARD
+ // Remove old highlighted areas to make place for the new ones.
+ for (rectangle_iterator ri(1); ri; ++ri)
+ env.map(*ri).property &= ~(FPROP_HIGHLIGHT);
+#endif
+
+ if (mp.init_pathfind(mon, best_target))
+ {
+ mon->travel_path = mp.calc_waypoints();
+
+ if (!mon->travel_path.empty())
+ {
+#ifdef WIZARD
+ for (unsigned int i = 0; i < mon->travel_path.size(); i++)
+ env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT;
+#endif
+#ifdef DEBUG_PATHFIND
+ mprf("Found a path to (%d, %d)", best_target.x, best_target.y);
+#endif
+ // Okay then, we found a path. Let's use it!
+ mon->target = mon->travel_path[0];
+ mon->travel_target = MTRAV_WALL;
+ return (true);
+ }
+ }
+ return (false);
+}
+
+// Returns true if further handling neeeded.
+static bool _handle_monster_travelling(monsters *mon,
+ const dungeon_feature_type can_move)
+{
+#ifdef DEBUG_PATHFIND
+ mprf("Monster %s reached target (%d, %d)",
+ mon->name(DESC_PLAIN).c_str(), mon->target.x, mon->target.y);
+#endif
+
+ // Hey, we reached our first waypoint!
+ if (mon->pos() == mon->travel_path[0])
+ {
+#ifdef DEBUG_PATHFIND
+ mpr("Arrived at first waypoint.");
+#endif
+ mon->travel_path.erase( mon->travel_path.begin() );
+ if (mon->travel_path.empty())
+ {
+#ifdef DEBUG_PATHFIND
+ mpr("We reached the end of our path: stop travelling.");
+#endif
+ mon->travel_target = MTRAV_NONE;
+ return (true);
+ }
+ else
+ {
+ mon->target = mon->travel_path[0];
+#ifdef DEBUG_PATHFIND
+ mprf("Next waypoint: (%d, %d)", mon->target.x, mon->target.y);
+#endif
+ return (false);
+ }
+ }
+
+ // Can we still see our next waypoint?
+ if (!can_go_straight(mon->pos(), mon->travel_path[0], can_move))
+ {
+#ifdef DEBUG_PATHFIND
+ mpr("Can't see waypoint grid.");
+#endif
+ // Apparently we got sidetracked a bit.
+ // Check the waypoints vector backwards and pick the first waypoint
+ // we can see.
+
+ // XXX: Note that this might still not be the best thing to do
+ // since another path might be even *closer* to our actual target now.
+ // Not by much, though, since the original path was optimal (A*) and
+ // the distance between the waypoints is rather small.
+
+ int erase = -1; // Erase how many waypoints?
+ const int size = mon->travel_path.size();
+ for (int i = size - 1; i >= 0; --i)
+ {
+ if (can_go_straight(mon->pos(), mon->travel_path[i], can_move))
+ {
+ mon->target = mon->travel_path[i];
+ erase = i;
+ break;
+ }
+ }
+
+ if (erase > 0)
+ {
+#ifdef DEBUG_PATHFIND
+ mprf("Need to erase %d of %d waypoints.",
+ erase, size);
+#endif
+ // Erase all waypoints that came earlier:
+ // we don't need them anymore.
+ while (0 < erase--)
+ mon->travel_path.erase( mon->travel_path.begin() );
+ }
+ else
+ {
+ // We can't reach our old path from our current
+ // position, so calculate a new path instead.
+ monster_pathfind mp;
+
+ // The last coordinate in the path vector is our destination.
+ const int len = mon->travel_path.size();
+ if (mp.init_pathfind(mon, mon->travel_path[len-1]))
+ {
+ mon->travel_path = mp.calc_waypoints();
+ if (!mon->travel_path.empty())
+ {
+ mon->target = mon->travel_path[0];
+#ifdef DEBUG_PATHFIND
+ mprf("Next waypoint: (%d, %d)",
+ mon->target.x, mon->target.y);
+#endif
+ }
+ else
+ {
+ mon->travel_target = MTRAV_NONE;
+ return (true);
+ }
+ }
+ else
+ {
+ // Or just forget about the whole thing.
+ mon->travel_path.clear();
+ mon->travel_target = MTRAV_NONE;
+ return (true);
+ }
+ }
+ }
+
+ // Else, we can see the next waypoint and are making good progress.
+ // Carry on, then!
+ return (false);
+}
+
+static bool _choose_random_patrol_target_grid(monsters *mon)
+{
+ const int intel = mons_intel(mon);
+
+ // Zombies will occasionally just stand around.
+ // This does not mean that they don't move every second turn. Rather,
+ // once they reach their chosen target, there's a 50% chance they'll
+ // just remain there until next turn when this function is called
+ // again.
+ if (intel == I_PLANT && coinflip())
+ return (true);
+
+ // If there's no chance we'll find the patrol point, quit right away.
+ if (grid_distance(mon->pos(), mon->patrol_point) > 2 * LOS_RADIUS)
+ return (false);
+
+ // Can the monster see the patrol point from its current position?
+ const bool patrol_seen = mon->mon_see_cell(mon->patrol_point,
+ habitat2grid(mons_primary_habitat(mon)));
+
+ if (intel == I_PLANT && !patrol_seen)
+ {
+ // Really stupid monsters won't even try to get back into the
+ // patrol zone.
+ return (false);
+ }
+
+ // While the patrol point is in easy reach, monsters of insect/plant
+ // intelligence will only use a range of 5 (distance from the patrol point).
+ // Otherwise, try to get back using the full LOS.
+ const int rad = (intel >= I_ANIMAL || !patrol_seen) ? LOS_RADIUS : 5;
+ const bool is_smart = (intel >= I_NORMAL);
+
+ los_def patrol(mon->patrol_point, opacity_monmove(*mon), bounds_radius(rad));
+ patrol.update();
+ los_def lm(mon->pos(), opacity_monmove(*mon));
+ if (is_smart || !patrol_seen)
+ {
+ // For stupid monsters, don't bother if the patrol point is in sight.
+ lm.update();
+ }
+
+ int count_grids = 0;
+ for (radius_iterator ri(mon->patrol_point, LOS_RADIUS, true, false);
+ ri; ++ri)
+ {
+ // Don't bother for the current position. If everything fails,
+ // we'll stay here anyway.
+ if (*ri == mon->pos())
+ continue;
+
+ if (!mon->can_pass_through_feat(grd(*ri)))
+ continue;
+
+ // Don't bother moving to squares (currently) occupied by a
+ // monster. We'll usually be able to find other target squares
+ // (and if we're not, we couldn't move anyway), and this avoids
+ // monsters trying to move onto a grid occupied by a plant or
+ // sleeping monster.
+ if (monster_at(*ri))
+ continue;
+
+ if (patrol_seen)
+ {
+ // If the patrol point can be easily (within LOS) reached
+ // from the current position, it suffices if the target is
+ // within reach of the patrol point OR the current position:
+ // we can easily get there.
+ // Only smart monsters will even attempt to move out of the
+ // patrol area.
+ // NOTE: Either of these can take us into a position where the
+ // target cannot be easily reached (e.g. blocked by a wall)
+ // and the patrol point is out of sight, too. Such a case
+ // will be handled below, though it might take a while until
+ // a monster gets out of a deadlock. (5% chance per turn.)
+ if (!patrol.see_cell(*ri) &&
+ (!is_smart || !lm.see_cell(*ri)))
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // If, however, the patrol point is out of reach, we have to
+ // make sure the new target brings us into reach of it.
+ // This means that the target must be reachable BOTH from
+ // the patrol point AND the current position.
+ if (!patrol.see_cell(*ri) ||
+ !lm.see_cell(*ri))
+ {
+ continue;
+ }
+
+ // If this fails for all surrounding squares (probably because
+ // we're too far away), we fall back to heading directly for
+ // the patrol point.
+ }
+
+ bool set_target = false;
+ if (intel == I_PLANT && *ri == mon->patrol_point)
+ {
+ // Slightly greater chance to simply head for the centre.
+ count_grids += 3;
+ if (x_chance_in_y(3, count_grids))
+ set_target = true;
+ }
+ else if (one_chance_in(++count_grids))
+ set_target = true;
+
+ if (set_target)
+ mon->target = *ri;
+ }
+
+ return (count_grids);
+}// Returns true if further handling neeeded.
+static bool _handle_monster_patrolling(monsters *mon)
+{
+ if (!_choose_random_patrol_target_grid(mon))
+ {
+ // If we couldn't find a target that is within easy reach
+ // of the monster and close to the patrol point, depending
+ // on monster intelligence, do one of the following:
+ // * set current position as new patrol point
+ // * forget about patrolling
+ // * head back to patrol point
+
+ if (mons_intel(mon) == I_PLANT)
+ {
+ // Really stupid monsters forget where they're supposed to be.
+ if (mons_friendly(mon))
+ {
+ // Your ally was told to wait, and wait it will!
+ // (Though possibly not where you told it to.)
+ mon->patrol_point = mon->pos();
+ }
+ else
+ {
+ // Stop patrolling.
+ mon->patrol_point.reset();
+ mon->travel_target = MTRAV_NONE;
+ return (true);
+ }
+ }
+ else
+ {
+ // It's time to head back!
+ // Other than for tracking the player, there's currently
+ // no distinction between smart and stupid monsters when
+ // it comes to travelling back to the patrol point. This
+ // is in part due to the flavour of e.g. bees finding
+ // their way back to the Hive (and patrolling should
+ // really be restricted to cases like this), and for the
+ // other part it's not all that important because we
+ // calculate the path once and then follow it home, and
+ // the player won't ever see the orderly fashion the
+ // bees will trudge along.
+ // What he will see is them swarming back to the Hive
+ // entrance after some time, and that is what matters.
+ monster_pathfind mp;
+ if (mp.init_pathfind(mon, mon->patrol_point))
+ {
+ mon->travel_path = mp.calc_waypoints();
+ if (!mon->travel_path.empty())
+ {
+ mon->target = mon->travel_path[0];
+ mon->travel_target = MTRAV_PATROL;
+ }
+ else
+ {
+ // We're so close we don't even need a path.
+ mon->target = mon->patrol_point;
+ }
+ }
+ else
+ {
+ // Stop patrolling.
+ mon->patrol_point.reset();
+ mon->travel_target = MTRAV_NONE;
+ return (true);
+ }
+ }
+ }
+ else
+ {
+#ifdef DEBUG_PATHFIND
+ mprf("Monster %s (pp: %d, %d) is now patrolling to (%d, %d)",
+ mon->name(DESC_PLAIN).c_str(),
+ mon->patrol_point.x, mon->patrol_point.y,
+ mon->target.x, mon->target.y);
+#endif
+ }
+
+ return (false);
+}
+
+void set_random_target(monsters* mon)
+{
+ mon->target = random_in_bounds(); // If we don't find anything better.
+ for (int tries = 0; tries < 150; ++tries)
+ {
+ coord_def delta = coord_def(random2(13), random2(13)) - coord_def(6, 6);
+ if (delta.origin())
+ continue;
+
+ const coord_def newtarget = delta + mon->pos();
+ if (!in_bounds(newtarget))
+ continue;
+
+ mon->target = newtarget;
+ break;
+ }
+}
+
+static void _check_wander_target(monsters *mon, bool isPacified = false,
+ dungeon_feature_type can_move = DNGN_UNSEEN)
+{
+ // default wander behaviour
+ if (mon->pos() == mon->target
+ || mons_is_batty(mon) || !isPacified && one_chance_in(20))
+ {
+ bool need_target = true;
+
+ if (!can_move)
+ {
+ can_move = (mons_amphibious(mon) ? DNGN_DEEP_WATER
+ : DNGN_SHALLOW_WATER);
+ }
+
+ if (mon->is_travelling())
+ need_target = _handle_monster_travelling(mon, can_move);
+
+ // If we still need a target because we're not travelling
+ // (any more), check for patrol routes instead.
+ if (need_target && mon->is_patrolling())
+ need_target = _handle_monster_patrolling(mon);
+
+ // XXX: This is really dumb wander behaviour... instead of
+ // changing the goal square every turn, better would be to
+ // have the monster store a direction and have the monster
+ // head in that direction for a while, then shift the
+ // direction to the left or right. We're changing this so
+ // wandering monsters at least appear to have some sort of
+ // attention span. -- bwr
+ if (need_target)
+ set_random_target(mon);
+ }
+}
+
+static void _arena_set_foe(monsters *mons)
+{
+ const int mind = monster_index(mons);
+
+ int nearest = -1;
+ int best_distance = -1;
+
+ int nearest_unseen = -1;
+ int best_unseen_distance = -1;
+ for (int i = 0; i < MAX_MONSTERS; ++i)
+ {
+ if (mind == i)
+ continue;
+
+ const monsters *other(&menv[i]);
+ if (!other->alive() || mons_aligned(mind, i))
+ continue;
+
+ // Don't fight test spawners, since they're only pseudo-monsters
+ // placed to spawn real monsters, plus they're impossible to
+ // kill. But test spawners can fight each other, to give them a
+ // target to spawn against.
+ if (other->type == MONS_TEST_SPAWNER
+ && mons->type != MONS_TEST_SPAWNER)
+ {
+ continue;
+ }
+
+ const int distance = grid_distance(mons->pos(), other->pos());
+ const bool seen = mons->can_see(other);
+
+ if (seen)
+ {
+ if (best_distance == -1 || distance < best_distance)
+ {
+ best_distance = distance;
+ nearest = i;
+ }
+ }
+ else
+ {
+ if (best_unseen_distance == -1 || distance < best_unseen_distance)
+ {
+ best_unseen_distance = distance;
+ nearest_unseen = i;
+ }
+ }
+
+ if ((best_distance == -1 || distance < best_distance)
+ && mons->can_see(other))
+
+ {
+ best_distance = distance;
+ nearest = i;
+ }
+ }
+
+ if (nearest != -1)
+ {
+ mons->foe = nearest;
+ mons->target = menv[nearest].pos();
+ mons->behaviour = BEH_SEEK;
+ }
+ else if (nearest_unseen != -1)
+ {
+ mons->target = menv[nearest_unseen].pos();
+ if (mons->type == MONS_TEST_SPAWNER)
+ {
+ mons->foe = nearest_unseen;
+ mons->behaviour = BEH_SEEK;
+ }
+ else
+ mons->behaviour = BEH_WANDER;
+ }
+ else
+ {
+ mons->foe = MHITNOT;
+ mons->behaviour = BEH_WANDER;
+ }
+ if (mons->behaviour == BEH_WANDER)
+ _check_wander_target(mons);
+
+ ASSERT(mons->foe == MHITNOT || !mons->target.origin());
+}
+
+static void _find_all_level_exits(std::vector<level_exit> &e)
+{
+ e.clear();
+
+ for (rectangle_iterator ri(1); ri; ++ri)
+ {
+ if (!in_bounds(*ri))
+ continue;
+
+ if (_is_level_exit(*ri))
+ e.push_back(level_exit(*ri, false));
+ }
+}
+
+static int _mons_find_nearest_level_exit(const monsters *mon,
+ std::vector<level_exit> &e,
+ bool reset = false)
+{
+ if (e.empty() || reset)
+ _find_all_level_exits(e);
+
+ int retval = -1;
+ int old_dist = -1;
+
+ for (unsigned int i = 0; i < e.size(); ++i)
+ {
+ if (e[i].unreachable)
+ continue;
+
+ int dist = grid_distance(mon->pos(), e[i].target);
+
+ if (old_dist == -1 || old_dist >= dist)
+ {
+ // Ignore teleportation and shaft traps that the monster
+ // shouldn't know about.
+ if (!mons_is_native_in_branch(mon)
+ && grd(e[i].target) == DNGN_UNDISCOVERED_TRAP)
+ {
+ continue;
+ }
+
+ retval = i;
+ old_dist = dist;
+ }
+ }
+
+ return (retval);
+}
+
+static void _set_random_slime_target(monsters* mon)
+{
+ // Strictly neutral slimes will go for the nearest item.
+ int item_idx;
+ coord_def orig_target = mon->target;
+
+ for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false); ri; ++ri)
+ {
+ item_idx = igrd(*ri);
+ if (item_idx != NON_ITEM)
+ {
+ for (stack_iterator si(*ri); si; ++si)
+ {
+ item_def& item(*si);
+
+ if (is_item_jelly_edible(item))
+ {
+ mon->target = *ri;
+ break;
+ }
+ }
+ }
+ }
+
+ if (mon->target == mon->pos() || mon->target == you.pos())
+ set_random_target(mon);
+}
+
+//---------------------------------------------------------------
+//
+// handle_behaviour
+//
+// 1. Evaluates current AI state
+// 2. Sets monster target x,y based on current foe
+//
+// XXX: Monsters of I_NORMAL or above should select a new target
+// if their current target is another monster which is sitting in
+// a wall and is immune to most attacks while in a wall, unless
+// the monster has a spell or special/nearby ability which isn't
+// affected by the wall.
+//---------------------------------------------------------------
+void handle_behaviour(monsters *mon)
+{
+ bool changed = true;
+ bool isFriendly = mons_friendly(mon);
+ bool isNeutral = mons_neutral(mon);
+ bool wontAttack = mons_wont_attack_real(mon);
+
+ // Whether the player is in LOS of the monster and can see
+ // or has guessed the player's location.
+ bool proxPlayer = mons_near(mon) && !crawl_state.arena;
+
+ bool trans_wall_block = trans_wall_blocking(mon->pos());
+
+#ifdef WIZARD
+ // If stealth is greater than actually possible (wizmode level)
+ // pretend the player isn't there, but only for hostile monsters.
+ if (proxPlayer && you.skills[SK_STEALTH] > 27 && !mons_wont_attack(mon))
+ proxPlayer = false;
+#endif
+ bool proxFoe;
+ bool isHurt = (mon->hit_points <= mon->max_hit_points / 4 - 1);
+ bool isHealthy = (mon->hit_points > mon->max_hit_points / 2);
+ bool isSmart = (mons_intel(mon) > I_ANIMAL);
+ bool isScared = mon->has_ench(ENCH_FEAR);
+ bool isMobile = !mons_is_stationary(mon);
+ bool isPacified = mons_is_pacified(mon);
+ bool patrolling = mon->is_patrolling();
+ static std::vector<level_exit> e;
+ static int e_index = -1;
+
+ // Check for confusion -- early out.
+ if (mon->has_ench(ENCH_CONFUSION))
+ {
+ set_random_target(mon);
+ return;
+ }
+
+ if (mons_is_fleeing_sanctuary(mon)
+ && mons_is_fleeing(mon)
+ && is_sanctuary(you.pos()))
+ {
+ return;
+ }
+
+ if (crawl_state.arena)
+ {
+ if (Options.arena_force_ai)
+ {
+ if (!mon->get_foe() || mon->target.origin() || one_chance_in(3))
+ mon->foe = MHITNOT;
+ if (mon->foe == MHITNOT || mon->foe == MHITYOU)
+ _arena_set_foe(mon);
+ return;
+ }
+ // If we're not forcing monsters to attack, just make sure they're
+ // not targetting the player in arena mode.
+ else if (mon->foe == MHITYOU)
+ mon->foe = MHITNOT;
+ }
+
+ if (mons_wall_shielded(mon) && cell_is_solid(mon->pos()))
+ {
+ // Monster is safe, so its behaviour can be simplified to fleeing.
+ if (mon->behaviour == BEH_CORNERED || mon->behaviour == BEH_PANIC
+ || isScared)
+ {
+ mon->behaviour = BEH_FLEE;
+ }
+ }
+
+ const dungeon_feature_type can_move =
+ (mons_amphibious(mon)) ? DNGN_DEEP_WATER : DNGN_SHALLOW_WATER;
+
+ // Validate current target exists.
+ if (mon->foe != MHITNOT && mon->foe != MHITYOU)
+ {
+ const monsters& foe_monster = menv[mon->foe];
+ if (!foe_monster.alive())
+ mon->foe = MHITNOT;
+ if (mons_friendly(&foe_monster) == isFriendly)
+ mon->foe = MHITNOT;
+ }
+
+ // Change proxPlayer depending on invisibility and standing
+ // in shallow water.
+ if (proxPlayer && !you.visible_to(mon))
+ {
+ proxPlayer = false;
+
+ const int intel = mons_intel(mon);
+ // Sometimes, if a player is right next to a monster, they will 'see'.
+ if (grid_distance(you.pos(), mon->pos()) == 1
+ && one_chance_in(3))
+ {
+ proxPlayer = true;
+ }
+
+ // [dshaligram] Very smart monsters have a chance of clueing in to
+ // invisible players in various ways.
+ if (intel == I_NORMAL && one_chance_in(13)
+ || intel == I_HIGH && one_chance_in(6))
+ {
+ proxPlayer = true;
+ }
+ }
+
+ // Set friendly target, if they don't already have one.
+ // Berserking allies ignore your commands!
+ if (isFriendly
+ && you.pet_target != MHITNOT
+ && (mon->foe == MHITNOT || mon->foe == MHITYOU)
+ && !mon->has_ench(ENCH_BERSERK)
+ && mon->mons_species() != MONS_GIANT_SPORE )
+ {
+ mon->foe = you.pet_target;
+ }
+
+ // Instead, berserkers attack nearest monsters.
+ if ((mon->has_ench(ENCH_BERSERK) || mon->mons_species() == MONS_GIANT_SPORE)
+ && (mon->foe == MHITNOT || isFriendly && mon->foe == MHITYOU))
+ {
+ // Intelligent monsters prefer to attack the player,
+ // even when berserking.
+ if (!isFriendly && proxPlayer && mons_intel(mon) >= I_NORMAL)
+ mon->foe = MHITYOU;
+ else
+ _set_nearest_monster_foe(mon);
+ }
+
+ // Pacified monsters leaving the level prefer not to attack.
+ // Others choose the nearest foe.
+ if (!isPacified && mon->foe == MHITNOT)
+ _set_nearest_monster_foe(mon);
+
+ // Monsters do not attack themselves. {dlb}
+ if (mon->foe == monster_index(mon))
+ mon->foe = MHITNOT;
+
+ // Friendly and good neutral monsters do not attack other friendly
+ // and good neutral monsters.
+ if (mon->foe != MHITNOT && mon->foe != MHITYOU
+ && wontAttack && mons_wont_attack_real(&menv[mon->foe]))
+ {
+ mon->foe = MHITNOT;
+ }
+
+ // Neutral monsters prefer not to attack players, or other neutrals.
+ if (isNeutral && mon->foe != MHITNOT
+ && (mon->foe == MHITYOU || mons_neutral(&menv[mon->foe])))
+ {
+ mon->foe = MHITNOT;
+ }
+
+ // Unfriendly monsters fighting other monsters will usually
+ // target the player, if they're healthy.
+ if (!isFriendly && !isNeutral
+ && mon->foe != MHITYOU && mon->foe != MHITNOT
+ && proxPlayer && !(mon->has_ench(ENCH_BERSERK)) && isHealthy
+ && !one_chance_in(3))
+ {
+ mon->foe = MHITYOU;
+ }
+
+ // Validate current target again.
+ if (mon->foe != MHITNOT && mon->foe != MHITYOU)
+ {
+ const monsters& foe_monster = menv[mon->foe];
+ if (!foe_monster.alive())
+ mon->foe = MHITNOT;
+ if (mons_friendly(&foe_monster) == isFriendly)
+ mon->foe = MHITNOT;
+ }
+
+ while (changed)
+ {
+ actor* afoe = mon->get_foe();
+ proxFoe = afoe && mon->can_see(afoe);
+
+ coord_def foepos = coord_def(0,0);
+ if (afoe)
+ foepos = afoe->pos();
+
+ if (mon->foe == MHITYOU)
+ proxFoe = proxPlayer; // Take invis into account.
+
+ // Track changes to state; attitude never changes here.
+ beh_type new_beh = mon->behaviour;
+ unsigned short new_foe = mon->foe;
+
+ // Take care of monster state changes.
+ switch (mon->behaviour)
+ {
+ case BEH_SLEEP:
+ // default sleep state
+ mon->target = mon->pos();
+ new_foe = MHITNOT;
+ break;
+
+ case BEH_LURK:
+ case BEH_SEEK:
+ // No foe? Then wander or seek the player.
+ if (mon->foe == MHITNOT)
+ {
+ if (crawl_state.arena || !proxPlayer || isNeutral || patrolling)
+ new_beh = BEH_WANDER;
+ else
+ {
+ new_foe = MHITYOU;
+ mon->target = you.pos();
+ }
+ break;
+ }
+
+ // Foe gone out of LOS?
+ if (!proxFoe)
+ {
+ if (mon->travel_target == MTRAV_SIREN)
+ mon->travel_target = MTRAV_NONE;
+
+ if (mon->foe == MHITYOU && mon->is_travelling()
+ && mon->travel_target == MTRAV_PLAYER)
+ {
+ // We've got a target, so we'll continue on our way.
+#ifdef DEBUG_PATHFIND
+ mpr("Player out of LoS... start wandering.");
+#endif
+ new_beh = BEH_WANDER;
+ break;
+ }
+
+ if (isFriendly)
+ {
+ if (patrolling || crawl_state.arena)
+ {
+ new_foe = MHITNOT;
+ new_beh = BEH_WANDER;
+ }
+ else
+ {
+ new_foe = MHITYOU;
+ mon->target = foepos;
+ }
+ break;
+ }
+
+ ASSERT(mon->foe != MHITNOT);
+ if (mon->foe_memory > 0)
+ {
+ // If we've arrived at our target x,y
+ // do a stealth check. If the foe
+ // fails, monster will then start
+ // tracking foe's CURRENT position,
+ // but only for a few moves (smell and
+ // intuition only go so far).
+
+ if (mon->pos() == mon->target)
+ {
+ if (mon->foe == MHITYOU)
+ {
+ if (one_chance_in(you.skills[SK_STEALTH]/3))
+ mon->target = you.pos();
+ else
+ mon->foe_memory = 0;
+ }
+ else
+ {
+ if (coinflip()) // XXX: cheesy!
+ mon->target = menv[mon->foe].pos();
+ else
+ mon->foe_memory = 0;
+ }
+ }
+
+ // Either keep chasing, or start wandering.
+ if (mon->foe_memory < 2)
+ {
+ mon->foe_memory = 0;
+ new_beh = BEH_WANDER;
+ }
+ break;
+ }
+
+ ASSERT(mon->foe_memory == 0);
+ // Hack: smarter monsters will tend to pursue the player longer.
+ switch (mons_intel(mon))
+ {
+ case I_HIGH:
+ mon->foe_memory = 100 + random2(200);
+ break;
+ case I_NORMAL:
+ mon->foe_memory = 50 + random2(100);
+ break;
+ case I_ANIMAL:
+ case I_INSECT:
+ mon->foe_memory = 25 + random2(75);
+ break;
+ case I_PLANT:
+ mon->foe_memory = 10 + random2(50);
+ break;
+ }
+ break; // switch/case BEH_SEEK
+ }
+
+ ASSERT(proxFoe && mon->foe != MHITNOT);
+ // Monster can see foe: continue 'tracking'
+ // by updating target x,y.
+ if (mon->foe == MHITYOU)
+ {
+ // The foe is the player.
+ if (mon->type == MONS_SIREN
+ && player_mesmerised_by(mon)
+ && _find_siren_water_target(mon))
+ {
+ break;
+ }
+
+ if (_try_pathfind(mon, can_move, trans_wall_block))
+ break;
+
+ // Whew. If we arrived here, path finding didn't yield anything
+ // (or wasn't even attempted) and we need to set our target
+ // the traditional way.
+
+ // Sometimes, your friends will wander a bit.
+ if (isFriendly && one_chance_in(8))
+ {
+ set_random_target(mon);
+ mon->foe = MHITNOT;
+ new_beh = BEH_WANDER;
+ }
+ else
+ {
+ mon->target = you.pos();
+ }
+ }
+ else
+ {
+ // We have a foe but it's not the player.
+ mon->target = menv[mon->foe].pos();
+ }
+
+ // Smart monsters, zombified monsters other than spectral
+ // things, plants, and nonliving monsters cannot flee.
+ if (isHurt && !isSmart && isMobile
+ && (!mons_is_zombified(mon) || mon->type == MONS_SPECTRAL_THING)
+ && mon->holiness() != MH_PLANT
+ && mon->holiness() != MH_NONLIVING)
+ {
+ new_beh = BEH_FLEE;
+ }
+ break;
+
+ case BEH_WANDER:
+ if (isPacified)
+ {
+ // If a pacified monster isn't travelling toward
+ // someplace from which it can leave the level, make it
+ // start doing so. If there's no such place, either
+ // search the level for such a place again, or travel
+ // randomly.
+ if (mon->travel_target != MTRAV_PATROL)
+ {
+ new_foe = MHITNOT;
+ mon->travel_path.clear();
+
+ e_index = _mons_find_nearest_level_exit(mon, e);
+
+ if (e_index == -1 || one_chance_in(20))
+ e_index = _mons_find_nearest_level_exit(mon, e, true);
+
+ if (e_index != -1)
+ {
+ mon->travel_target = MTRAV_PATROL;
+ patrolling = true;
+ mon->patrol_point = e[e_index].target;
+ mon->target = e[e_index].target;
+ }
+ else
+ {
+ mon->travel_target = MTRAV_NONE;
+ patrolling = false;
+ mon->patrol_point.reset();
+ set_random_target(mon);
+ }
+ }
+
+ if (_pacified_leave_level(mon, e, e_index))
+ return;
+ }
+
+ if (mons_strict_neutral(mon) && mons_is_slime(mon)
+ && you.religion == GOD_JIYVA)
+ {
+ _set_random_slime_target(mon);
+ }
+
+ // Is our foe in LOS?
+ // Batty monsters don't automatically reseek so that
+ // they'll flitter away, we'll reset them just before
+ // they get movement in handle_monsters() instead. -- bwr
+ if (proxFoe && !mons_is_batty(mon))
+ {
+ new_beh = BEH_SEEK;
+ break;
+ }
+
+ _check_wander_target(mon, isPacified, can_move);
+
+ // During their wanderings, monsters will eventually relax
+ // their guard (stupid ones will do so faster, smart
+ // monsters have longer memories). Pacified monsters will
+ // also eventually switch the place from which they want to
+ // leave the level, in case their current choice is blocked.
+ if (!proxFoe && mon->foe != MHITNOT
+ && one_chance_in(isSmart ? 60 : 20)
+ || isPacified && one_chance_in(isSmart ? 40 : 120))
+ {
+ new_foe = MHITNOT;
+ if (mon->is_travelling() && mon->travel_target != MTRAV_PATROL
+ || isPacified)
+ {
+#ifdef DEBUG_PATHFIND
+ mpr("It's been too long! Stop travelling.");
+#endif
+ mon->travel_path.clear();
+ mon->travel_target = MTRAV_NONE;
+
+ if (isPacified && e_index != -1)
+ e[e_index].unreachable = true;
+ }
+ }
+ break;
+
+ case BEH_FLEE:
+ // Check for healed.
+ if (isHealthy && !isScared)
+ new_beh = BEH_SEEK;
+
+ // Smart monsters flee until they can flee no more...
+ // possible to get a 'CORNERED' event, at which point
+ // we can jump back to WANDER if the foe isn't present.
+
+ if (isFriendly)
+ {
+ // Special-cased below so that it will flee *towards* you.
+ if (mon->foe == MHITYOU)
+ mon->target = you.pos();
+ }
+ else if (mons_wall_shielded(mon) && _find_wall_target(mon))
+ ; // Wall target found.
+ else if (proxFoe)
+ {
+ // Special-cased below so that it will flee *from* the
+ // correct position.
+ mon->target = foepos;
+ }
+ break;
+
+ case BEH_CORNERED:
+ // Plants and nonliving monsters cannot fight back.
+ if (mon->holiness() == MH_PLANT
+ || mon->holiness() == MH_NONLIVING)
+ {
+ break;
+ }
+
+ if (isHealthy)
+ new_beh = BEH_SEEK;
+
+ // Foe gone out of LOS?
+ if (!proxFoe)
+ {
+ if ((isFriendly || proxPlayer) && !isNeutral && !patrolling)
+ new_foe = MHITYOU;
+ else
+ new_beh = BEH_WANDER;
+ }
+ else
+ {
+ mon->target = foepos;
+ }
+ break;
+
+ default:
+ return; // uh oh
+ }
+
+ changed = (new_beh != mon->behaviour || new_foe != mon->foe);
+ mon->behaviour = new_beh;
+
+ if (mon->foe != new_foe)
+ mon->foe_memory = 0;
+
+ mon->foe = new_foe;
+ }
+
+ if (mon->travel_target == MTRAV_WALL && cell_is_solid(mon->pos()))
+ {
+ if (mon->behaviour == BEH_FLEE)
+ {
+ // Monster is safe, so stay put.
+ mon->target = mon->pos();
+ mon->foe = MHITNOT;
+ }
+ }
+}
+
+static bool _mons_check_foe(monsters *mon, const coord_def& p,
+ bool friendly, bool neutral)
+{
+ if (!inside_level_bounds(p))
+ return (false);
+
+ if (!friendly && !neutral && p == you.pos()
+ && you.visible_to(mon) && !is_sanctuary(p))
+ {
+ return (true);
+ }
+
+ if (monsters *foe = monster_at(p))
+ {
+ if (foe != mon
+ && mon->can_see(foe)
+ && (friendly || !is_sanctuary(p))
+ && (mons_friendly(foe) != friendly
+ || (neutral && !mons_neutral(foe))))
+ {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+// Choose random nearest monster as a foe.
+void _set_nearest_monster_foe(monsters *mon)
+{
+ const bool friendly = mons_friendly_real(mon);
+ const bool neutral = mons_neutral(mon);
+
+ for (int k = 1; k <= LOS_RADIUS; ++k)
+ {
+ std::vector<coord_def> monster_pos;
+ for (int i = -k; i <= k; ++i)
+ for (int j = -k; j <= k; (abs(i) == k ? j++ : j += 2*k))
+ {
+ const coord_def p = mon->pos() + coord_def(i, j);
+ if (_mons_check_foe(mon, p, friendly, neutral))
+ monster_pos.push_back(p);
+ }
+ if (monster_pos.empty())
+ continue;
+
+ const coord_def mpos = monster_pos[random2(monster_pos.size())];
+ if (mpos == you.pos())
+ mon->foe = MHITYOU;
+ else
+ mon->foe = env.mgrid(mpos);
+ return;
+ }
+}
+
+///---------------------------------------------------------------
+//
+// behaviour_event
+//
+// 1. Change any of: monster state, foe, and attitude
+// 2. Call handle_behaviour to re-evaluate AI state and target x,y
+//
+//---------------------------------------------------------------
+void behaviour_event(monsters *mon, mon_event_type event, int src,
+ coord_def src_pos, bool allow_shout)
+{
+ ASSERT(src >= 0 && src <= MHITYOU);
+ ASSERT(!crawl_state.arena || src != MHITYOU);
+ ASSERT(in_bounds(src_pos) || src_pos.origin());
+
+ const beh_type old_behaviour = mon->behaviour;
+
+ bool isSmart = (mons_intel(mon) > I_ANIMAL);
+ bool wontAttack = mons_wont_attack_real(mon);
+ bool sourceWontAttack = false;
+ bool setTarget = false;
+ bool breakCharm = false;
+ bool was_sleeping = mon->asleep();
+
+ if (src == MHITYOU)
+ sourceWontAttack = true;
+ else if (src != MHITNOT)
+ sourceWontAttack = mons_wont_attack_real( &menv[src] );
+
+ if (is_sanctuary(mon->pos()) && mons_is_fleeing_sanctuary(mon))
+ {
+ mon->behaviour = BEH_FLEE;
+ mon->foe = MHITYOU;
+ mon->target = env.sanctuary_pos;
+ return;
+ }
+
+ switch (event)
+ {
+ case ME_DISTURB:
+ // Assumes disturbed by noise...
+ if (mon->asleep())
+ {
+ mon->behaviour = BEH_WANDER;
+
+ if (mons_near(mon))
+ remove_auto_exclude(mon, true);
+ }
+
+ // A bit of code to make Projected Noise actually do
+ // something again. Basically, dumb monsters and
+ // monsters who aren't otherwise occupied will at
+ // least consider the (apparent) source of the noise
+ // interesting for a moment. -- bwr
+ if (!isSmart || mon->foe == MHITNOT || mons_is_wandering(mon))
+ {
+ if (mon->is_patrolling())
+ break;
+
+ ASSERT(!src_pos.origin());
+ mon->target = src_pos;
+ }
+ break;
+
+ case ME_WHACK:
+ case ME_ANNOY:
+ // Will turn monster against <src>, unless they
+ // are BOTH friendly or good neutral AND stupid,
+ // or else fleeing anyway. Hitting someone over
+ // the head, of course, always triggers this code.
+ if (event == ME_WHACK
+ || ((wontAttack != sourceWontAttack || isSmart)
+ && !mons_is_fleeing(mon) && !mons_is_panicking(mon)))
+ {
+ // Monster types that you can't gain experience from cannot
+ // fight back, so don't bother having them do so. If you
+ // worship Feawn, create a ring of friendly plants, and try
+ // to break out of the ring by killing a plant, you'll get
+ // a warning prompt and penance only once. Without the
+ // hostility check, the plant will remain friendly until it
+ // dies, and you'll get a warning prompt and penance once
+ // *per hit*. This may not be the best way to address the
+ // issue, though. -cao
+ if (mons_class_flag(mon->type, M_NO_EXP_GAIN)
+ && mon->attitude != ATT_FRIENDLY
+ && mon->attitude != ATT_GOOD_NEUTRAL)
+ {
+ return;
+ }
+
+ mon->foe = src;
+
+ if (mon->asleep() && mons_near(mon))
+ remove_auto_exclude(mon, true);
+
+ if (!mons_is_cornered(mon))
+ mon->behaviour = BEH_SEEK;
+
+ if (src == MHITYOU)
+ {
+ mon->attitude = ATT_HOSTILE;
+ breakCharm = true;
+ }
+ }
+
+ // Now set target so that monster can whack back (once) at an
+ // invisible foe.
+ if (event == ME_WHACK)
+ setTarget = true;
+ break;
+
+ case ME_ALERT:
+ // Allow monsters falling asleep while patrolling (can happen if
+ // they're left alone for a long time) to be woken by this event.
+ if (mons_friendly(mon) && mon->is_patrolling()
+ && !mon->asleep())
+ {
+ break;
+ }
+
+ if (mon->asleep() && mons_near(mon))
+ remove_auto_exclude(mon, true);
+
+ // Will alert monster to <src> and turn them
+ // against them, unless they have a current foe.
+ // It won't turn friends hostile either.
+ if (!mons_is_fleeing(mon) && !mons_is_panicking(mon)
+ && !mons_is_cornered(mon))
+ {
+ mon->behaviour = BEH_SEEK;
+ }
+
+ if (mon->foe == MHITNOT)
+ mon->foe = src;
+
+ if (!src_pos.origin()
+ && (mon->foe == MHITNOT || mon->foe == src
+ || mons_is_wandering(mon)))
+ {
+ if (mon->is_patrolling())
+ break;
+
+ mon->target = src_pos;
+
+ // XXX: Should this be done in _handle_behaviour()?
+ if (src == MHITYOU && src_pos == you.pos()
+ && !see_cell(mon->pos()))
+ {
+ const dungeon_feature_type can_move =
+ (mons_amphibious(mon)) ? DNGN_DEEP_WATER
+ : DNGN_SHALLOW_WATER;
+
+ _try_pathfind(mon, can_move, true);
+ }
+ }
+ break;
+
+ case ME_SCARE:
+ // Stationary monsters can't flee, and berserking monsters
+ // are too enraged.
+ if (mons_is_stationary(mon) || mon->has_ench(ENCH_BERSERK))
+ {
+ mon->del_ench(ENCH_FEAR, true, true);
+ break;
+ }
+
+ // Neither do plants or nonliving beings.
+ if (mon->holiness() == MH_PLANT
+ || mon->holiness() == MH_NONLIVING)
+ {
+ mon->del_ench(ENCH_FEAR, true, true);
+ break;
+ }
+
+ // Assume monsters know where to run from, even if player is
+ // invisible.
+ mon->behaviour = BEH_FLEE;
+ mon->foe = src;
+ mon->target = src_pos;
+ if (src == MHITYOU)
+ {
+ // Friendly monsters don't become hostile if you read a
+ // scroll of fear, but enslaved ones will.
+ // Send friendlies off to a random target so they don't cling
+ // to you in fear.
+ if (mons_friendly(mon))
+ {
+ breakCharm = true;
+ mon->foe = MHITNOT;
+ set_random_target(mon);
+ }
+ else
+ setTarget = true;
+ }
+ else if (mons_friendly(mon) && !crawl_state.arena)
+ mon->foe = MHITYOU;
+
+ if (see_cell(mon->pos()))
+ learned_something_new(TUT_FLEEING_MONSTER);
+ break;
+
+ case ME_CORNERED:
+ // Some monsters can't flee.
+ if (mon->behaviour != BEH_FLEE && !mon->has_ench(ENCH_FEAR))
+ break;
+
+ // Pacified monsters shouldn't change their behaviour.
+ if (mons_is_pacified(mon))
+ break;
+
+ // Just set behaviour... foe doesn't change.
+ if (!mons_is_cornered(mon))
+ {
+ if (mons_friendly(mon) && !crawl_state.arena)
+ {
+ mon->foe = MHITYOU;
+ simple_monster_message(mon, " returns to your side!");
+ }
+ else
+ simple_monster_message(mon, " turns to fight!");
+ }
+
+ mon->behaviour = BEH_CORNERED;
+ break;
+
+ case ME_EVAL:
+ break;
+ }
+
+ if (setTarget)
+ {
+ if (src == MHITYOU)
+ {
+ mon->target = you.pos();
+ mon->attitude = ATT_HOSTILE;
+ }
+ else if (src != MHITNOT)
+ mon->target = menv[src].pos();
+ }
+
+ // Now, break charms if appropriate.
+ if (breakCharm)
+ mon->del_ench(ENCH_CHARM);
+
+ // Do any resultant foe or state changes.
+ handle_behaviour(mon);
+ ASSERT(in_bounds(mon->target) || mon->target.origin());
+
+ // If it woke up and you're its new foe, it might shout.
+ if (was_sleeping && !mon->asleep() && allow_shout
+ && mon->foe == MHITYOU && !mons_wont_attack(mon))
+ {
+ handle_monster_shouts(mon);
+ }
+
+ const bool wasLurking =
+ (old_behaviour == BEH_LURK && !mons_is_lurking(mon));
+ const bool isPacified = mons_is_pacified(mon);
+
+ if ((wasLurking || isPacified)
+ && (event == ME_DISTURB || event == ME_ALERT || event == ME_EVAL))
+ {
+ // Lurking monsters or pacified monsters leaving the level won't
+ // stop doing so just because they noticed something.
+ mon->behaviour = old_behaviour;
+ }
+ else if (wasLurking && mon->has_ench(ENCH_SUBMERGED)
+ && !mon->del_ench(ENCH_SUBMERGED))
+ {
+ // The same goes for lurking submerged monsters, if they can't
+ // unsubmerge.
+ mon->behaviour = BEH_LURK;
+ }
+
+ ASSERT(!crawl_state.arena
+ || mon->foe != MHITYOU && mon->target != you.pos());
+}
+
+void make_mons_stop_fleeing(monsters *mon)
+{
+ if (mons_is_fleeing(mon))
+ behaviour_event(mon, ME_CORNERED);
+}
diff --git a/crawl-ref/source/mon-behv.h b/crawl-ref/source/mon-behv.h
new file mode 100644
index 0000000000..9f3001657a
--- /dev/null
+++ b/crawl-ref/source/mon-behv.h
@@ -0,0 +1,52 @@
+/*
+ * File: mon-behv.h
+ * Summary: Monster behaviour functions.
+ * Written by: Linley Henzell
+ */
+
+#ifndef MONBEHV_H
+#define MONBEHV_H
+
+#include "enum.h"
+
+enum mon_event_type
+{
+ ME_EVAL, // 0, evaluate monster AI state
+ ME_DISTURB, // noisy
+ ME_ANNOY, // annoy at range
+ ME_ALERT, // alert to presence
+ ME_WHACK, // physical attack
+ ME_SCARE, // frighten monster
+ ME_CORNERED // cannot flee
+};
+
+class monsters;
+struct coord_def;
+
+void behaviour_event(monsters *mon, mon_event_type event_type,
+ int src = MHITNOT, coord_def src_pos = coord_def(),
+ bool allow_shout = true);
+
+// This function is somewhat low level; you should probably use
+// behaviour_event(mon, ME_EVAL) instead.
+void handle_behaviour(monsters *mon);
+
+void make_mons_stop_fleeing(monsters *mon);
+
+void set_random_target(monsters* mon);
+
+void make_mons_leave_level(monsters *mon);
+
+bool message_current_target(void);
+
+bool monster_can_hit_monster(monsters *monster, const monsters *targ);
+
+bool mons_avoids_cloud(const monsters *monster, cloud_type cl_type,
+ bool placement = false);
+
+// Like the above, but allow a monster to move from one damaging cloud
+// to another.
+bool mons_avoids_cloud(const monsters *monster, int cloud_num,
+ cloud_type *cl_type = NULL, bool placement = false);
+
+#endif
diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc
new file mode 100644
index 0000000000..1524d0cb44
--- /dev/null
+++ b/crawl-ref/source/mon-cast.cc
@@ -0,0 +1,2357 @@
+/*
+ * File: mon-cast.cc
+ * Summary: Monster spell casting.
+ * Written by: Linley Henzell
+ */
+
+#include "AppHdr.h"
+#include "mon-cast.h"
+
+#ifdef TARGET_OS_DOS
+#include <conio.h>
+#endif
+
+#include "beam.h"
+#include "colour.h"
+#include "database.h"
+#include "effects.h"
+#include "fight.h"
+#include "ghost.h"
+#include "los.h"
+#include "misc.h"
+#include "mon-behv.h"
+#include "monplace.h"
+#include "monspeak.h"
+#include "monstuff.h"
+#include "mon-util.h"
+#include "random.h"
+#include "religion.h"
+#include "spl-util.h"
+#include "spl-cast.h"
+#include "spells1.h"
+#include "spells3.h"
+#include "stuff.h"
+#include "view.h"
+
+static void _scale_draconian_breath(bolt& beam, int drac_type)
+{
+ int scaling = 100;
+ switch (drac_type)
+ {
+ case MONS_RED_DRACONIAN:
+ beam.name = "searing blast";
+ beam.aux_source = "blast of searing breath";
+ scaling = 65;
+ break;
+
+ case MONS_WHITE_DRACONIAN:
+ beam.name = "chilling blast";
+ beam.aux_source = "blast of chilling breath";
+ beam.short_name = "frost";
+ scaling = 65;
+ break;
+
+ case MONS_PLAYER_GHOST: // draconians only
+ beam.name = "blast of negative energy";
+ beam.aux_source = "blast of draining breath";
+ beam.flavour = BEAM_NEG;
+ beam.colour = DARKGREY;
+ scaling = 65;
+ break;
+ }
+ beam.damage.size = scaling * beam.damage.size / 100;
+}
+
+static spell_type _draco_type_to_breath(int drac_type)
+{
+ switch (drac_type)
+ {
+ case MONS_BLACK_DRACONIAN: return SPELL_LIGHTNING_BOLT;
+ case MONS_MOTTLED_DRACONIAN: return SPELL_STICKY_FLAME_SPLASH;
+ case MONS_YELLOW_DRACONIAN: return SPELL_ACID_SPLASH;
+ case MONS_GREEN_DRACONIAN: return SPELL_POISONOUS_CLOUD;
+ case MONS_PURPLE_DRACONIAN: return SPELL_ISKENDERUNS_MYSTIC_BLAST;
+ case MONS_RED_DRACONIAN: return SPELL_FIRE_BREATH;
+ case MONS_WHITE_DRACONIAN: return SPELL_COLD_BREATH;
+ case MONS_PALE_DRACONIAN: return SPELL_STEAM_BALL;
+
+ // Handled later.
+ case MONS_PLAYER_GHOST: return SPELL_DRACONIAN_BREATH;
+
+ default:
+ DEBUGSTR("Invalid monster using draconian breath spell");
+ break;
+ }
+
+ return (SPELL_DRACONIAN_BREATH);
+}
+
+bolt mons_spells( monsters *mons, spell_type spell_cast, int power )
+{
+ ASSERT(power > 0);
+
+ bolt beam;
+
+ // Initialise to some bogus values so we can catch problems.
+ beam.name = "****";
+ beam.colour = 1000;
+ beam.hit = -1;
+ beam.damage = dice_def( 1, 0 );
+ beam.ench_power = -1;
+ beam.type = 0;
+ beam.flavour = BEAM_NONE;
+ beam.thrower = KILL_MISC;
+ beam.is_beam = false;
+ beam.is_explosion = false;
+
+ // Sandblast is different, and gets range updated later
+ if (spell_cast != SPELL_SANDBLAST)
+ beam.range = spell_range(spell_cast, power, true, false);
+
+ const int drac_type = (mons_genus(mons->type) == MONS_DRACONIAN)
+ ? draco_subspecies(mons) : mons->type;
+
+ spell_type real_spell = spell_cast;
+
+ if (spell_cast == SPELL_DRACONIAN_BREATH)
+ real_spell = _draco_type_to_breath(drac_type);
+
+ beam.type = dchar_glyph(DCHAR_FIRED_ZAP); // default
+ beam.thrower = KILL_MON_MISSILE;
+
+ // FIXME: this should use the zap_data[] struct from beam.cc!
+ switch (real_spell)
+ {
+ case SPELL_MAGIC_DART:
+ beam.colour = LIGHTMAGENTA;
+ beam.name = "magic dart";
+ beam.damage = dice_def( 3, 4 + (power / 100) );
+ beam.hit = AUTOMATIC_HIT;
+ beam.flavour = BEAM_MMISSILE;
+ break;
+
+ case SPELL_THROW_FLAME:
+ beam.colour = RED;
+ beam.name = "puff of flame";
+ beam.damage = dice_def( 3, 5 + (power / 40) );
+ beam.hit = 25 + power / 40;
+ beam.flavour = BEAM_FIRE;
+ break;
+
+ case SPELL_THROW_FROST:
+ beam.colour = WHITE;
+ beam.name = "puff of frost";
+ beam.damage = dice_def( 3, 5 + (power / 40) );
+ beam.hit = 25 + power / 40;
+ beam.flavour = BEAM_COLD;
+ break;
+
+ case SPELL_SANDBLAST:
+ beam.colour = BROWN;
+ beam.name = "rocky blast";
+ beam.damage = dice_def( 3, 5 + (power / 40) );
+ beam.hit = 20 + power / 40;
+ beam.flavour = BEAM_FRAG;
+ beam.range = 2; // spell_range() is wrong here
+ break;
+
+ case SPELL_DISPEL_UNDEAD:
+ beam.flavour = BEAM_DISPEL_UNDEAD;
+ beam.damage = dice_def( 3, std::min(6 + power / 10, 40) );
+ beam.is_beam = true;
+ break;
+
+ case SPELL_PARALYSE:
+ beam.flavour = BEAM_PARALYSIS;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_SLOW:
+ beam.flavour = BEAM_SLOW;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_HASTE: // (self)
+ beam.flavour = BEAM_HASTE;
+ break;
+
+ case SPELL_BACKLIGHT:
+ beam.flavour = BEAM_BACKLIGHT;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_CONFUSE:
+ beam.flavour = BEAM_CONFUSION;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_SLEEP:
+ beam.flavour = BEAM_SLEEP;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_POLYMORPH_OTHER:
+ beam.flavour = BEAM_POLYMORPH;
+ beam.is_beam = true;
+ // Be careful with this one.
+ // Having allies mutate you is infuriating.
+ beam.foe_ratio = 1000;
+ break;
+
+ case SPELL_VENOM_BOLT:
+ beam.name = "bolt of poison";
+ beam.damage = dice_def( 3, 6 + power / 13 );
+ beam.colour = LIGHTGREEN;
+ beam.flavour = BEAM_POISON;
+ beam.hit = 19 + power / 20;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_POISON_ARROW:
+ beam.name = "poison arrow";
+ beam.damage = dice_def( 3, 7 + power / 12 );
+ beam.colour = LIGHTGREEN;
+ beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
+ beam.flavour = BEAM_POISON_ARROW;
+ beam.hit = 20 + power / 25;
+ break;
+
+ case SPELL_BOLT_OF_MAGMA:
+ beam.name = "bolt of magma";
+ beam.damage = dice_def( 3, 8 + power / 11 );
+ beam.colour = RED;
+ beam.flavour = BEAM_LAVA;
+ beam.hit = 17 + power / 25;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_BOLT_OF_FIRE:
+ beam.name = "bolt of fire";
+ beam.damage = dice_def( 3, 8 + power / 11 );
+ beam.colour = RED;
+ beam.flavour = BEAM_FIRE;
+ beam.hit = 17 + power / 25;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_FLING_ICICLE:
+ beam.name = "shard of ice";
+ beam.damage = dice_def( 3, 8 + power / 11 );
+ beam.colour = WHITE;
+ beam.flavour = BEAM_ICE;
+ beam.hit = 17 + power / 25;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_BOLT_OF_COLD:
+ beam.name = "bolt of cold";
+ beam.damage = dice_def( 3, 8 + power / 11 );
+ beam.colour = WHITE;
+ beam.flavour = BEAM_COLD;
+ beam.hit = 17 + power / 25;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_FREEZING_CLOUD:
+ beam.name = "freezing blast";
+ beam.damage = dice_def( 2, 9 + power / 11 );
+ beam.colour = WHITE;
+ beam.flavour = BEAM_COLD;
+ beam.hit = 17 + power / 25;
+ beam.is_beam = true;
+ beam.is_big_cloud = true;
+ break;
+
+ case SPELL_SHOCK:
+ beam.name = "zap";
+ beam.damage = dice_def( 1, 8 + (power / 20) );
+ beam.colour = LIGHTCYAN;
+ beam.flavour = BEAM_ELECTRICITY;
+ beam.hit = 17 + power / 20;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_LIGHTNING_BOLT:
+ beam.name = "bolt of lightning";
+ beam.damage = dice_def( 3, 10 + power / 17 );
+ beam.colour = LIGHTCYAN;
+ beam.flavour = BEAM_ELECTRICITY;
+ beam.hit = 16 + power / 40;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_INVISIBILITY:
+ beam.flavour = BEAM_INVISIBILITY;
+ break;
+
+ case SPELL_FIREBALL:
+ beam.colour = RED;
+ beam.name = "fireball";
+ beam.damage = dice_def( 3, 7 + power / 10 );
+ beam.hit = 40;
+ beam.flavour = BEAM_FIRE;
+ beam.foe_ratio = 60;
+ beam.is_explosion = true;
+ break;
+
+ case SPELL_FIRE_STORM:
+ setup_fire_storm(mons, power / 2, beam);
+ beam.foe_ratio = random_range(40, 55);
+ break;
+
+ case SPELL_ICE_STORM:
+ beam.name = "great blast of cold";
+ beam.colour = BLUE;
+ beam.damage = calc_dice( 10, 18 + power / 2 );
+ beam.hit = 20 + power / 10; // 50: 25 100: 30
+ beam.ench_power = power; // used for radius
+ beam.flavour = BEAM_ICE; // half resisted
+ beam.is_explosion = true;
+ beam.foe_ratio = random_range(40, 55);
+ break;
+
+ case SPELL_HELLFIRE_BURST:
+ beam.aux_source = "burst of hellfire";
+ beam.name = "burst of hellfire";
+ beam.ex_size = 1;
+ beam.flavour = BEAM_HELLFIRE;
+ beam.is_explosion = true;
+ beam.colour = RED;
+ beam.aux_source.clear();
+ beam.is_tracer = false;
+ beam.hit = 20;
+ beam.damage = mons_foe_is_mons(mons) ? dice_def(5, 7)
+ : dice_def(3, 20);
+ break;
+
+ case SPELL_MINOR_HEALING:
+ beam.flavour = BEAM_HEALING;
+ beam.hit = 25 + (power / 5);
+ break;
+
+ case SPELL_TELEPORT_SELF:
+ beam.flavour = BEAM_TELEPORT;
+ break;
+
+ case SPELL_TELEPORT_OTHER:
+ beam.flavour = BEAM_TELEPORT;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_LEHUDIBS_CRYSTAL_SPEAR: // was splinters
+ beam.name = "crystal spear";
+ beam.damage = dice_def( 3, 16 + power / 10 );
+ beam.colour = WHITE;
+ beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
+ beam.flavour = BEAM_MMISSILE;
+ beam.hit = 22 + power / 20;
+ break;
+
+ case SPELL_DIG:
+ beam.flavour = BEAM_DIGGING;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_BOLT_OF_DRAINING: // negative energy
+ beam.name = "bolt of negative energy";
+ beam.damage = dice_def( 3, 6 + power / 13 );
+ beam.colour = DARKGREY;
+ beam.flavour = BEAM_NEG;
+ beam.hit = 16 + power / 35;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_ISKENDERUNS_MYSTIC_BLAST: // mystic blast
+ beam.colour = LIGHTMAGENTA;
+ beam.name = "orb of energy";
+ beam.short_name = "energy";
+ beam.damage = dice_def( 3, 7 + (power / 14) );
+ beam.hit = 20 + (power / 20);
+ beam.flavour = BEAM_MMISSILE;
+ break;
+
+ case SPELL_STEAM_BALL:
+ beam.colour = LIGHTGREY;
+ beam.name = "ball of steam";
+ beam.damage = dice_def( 3, 7 + (power / 15) );
+ beam.hit = 20 + power / 20;
+ beam.flavour = BEAM_STEAM;
+ break;
+
+ case SPELL_PAIN:
+ beam.flavour = BEAM_PAIN;
+ beam.damage = dice_def( 1, 7 + (power / 20) );
+ beam.ench_power = std::max(50, 8 * mons->hit_dice);
+ beam.is_beam = true;
+ break;
+
+ case SPELL_STICKY_FLAME_SPLASH:
+ case SPELL_STICKY_FLAME:
+ beam.colour = RED;
+ beam.name = "sticky flame";
+ beam.damage = dice_def( 3, 3 + power / 50 );
+ beam.hit = 18 + power / 15;
+ beam.flavour = BEAM_FIRE;
+ break;
+
+ case SPELL_POISONOUS_CLOUD:
+ beam.name = "blast of poison";
+ beam.damage = dice_def( 3, 3 + power / 25 );
+ beam.colour = LIGHTGREEN;
+ beam.flavour = BEAM_POISON;
+ beam.hit = 18 + power / 25;
+ beam.is_beam = true;
+ beam.is_big_cloud = true;
+ break;
+
+ case SPELL_ENERGY_BOLT: // eye of devastation
+ beam.colour = YELLOW;
+ beam.name = "bolt of energy";
+ beam.short_name = "energy";
+ beam.damage = dice_def( 3, 20 );
+ beam.hit = 15 + power / 30;
+ beam.flavour = BEAM_NUKE; // a magical missile which destroys walls
+ beam.is_beam = true;
+ break;
+
+ case SPELL_STING: // sting
+ beam.colour = GREEN;
+ beam.name = "sting";
+ beam.damage = dice_def( 1, 6 + power / 25 );
+ beam.hit = 60;
+ beam.flavour = BEAM_POISON;
+ break;
+
+ case SPELL_IRON_SHOT:
+ beam.colour = LIGHTCYAN;
+ beam.name = "iron shot";
+ beam.damage = dice_def( 3, 8 + (power / 9) );
+ beam.hit = 20 + (power / 25);
+ beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
+ beam.flavour = BEAM_MMISSILE; // similarly unresisted thing
+ break;
+
+ case SPELL_STONE_ARROW:
+ beam.colour = LIGHTGREY;
+ beam.name = "stone arrow";
+ beam.damage = dice_def( 3, 5 + (power / 10) );
+ beam.hit = 14 + power / 35;
+ beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
+ beam.flavour = BEAM_MMISSILE; // similarly unresisted thing
+ break;
+
+ case SPELL_POISON_SPLASH:
+ beam.colour = GREEN;
+ beam.name = "splash of poison";
+ beam.damage = dice_def( 1, 4 + power / 10 );
+ beam.hit = 16 + power / 20;
+ beam.flavour = BEAM_POISON;
+ break;
+
+ case SPELL_ACID_SPLASH:
+ beam.colour = YELLOW;
+ beam.name = "splash of acid";
+ beam.damage = dice_def( 3, 7 );
+ beam.hit = 20 + (3 * mons->hit_dice);
+ beam.flavour = BEAM_ACID;
+ break;
+
+ case SPELL_DISINTEGRATE:
+ beam.flavour = BEAM_DISINTEGRATION;
+ beam.ench_power = 50;
+ beam.damage = dice_def( 1, 30 + (power / 10) );
+ beam.is_beam = true;
+ break;
+
+ case SPELL_MEPHITIC_CLOUD: // swamp drake, player ghost
+ beam.name = "foul vapour";
+ beam.damage = dice_def(1,0);
+ beam.colour = GREEN;
+ // Well, it works, even if the name isn't quite intuitive.
+ beam.flavour = BEAM_POTION_STINKING_CLOUD;
+ beam.hit = 14 + power / 30;
+ beam.ench_power = power; // probably meaningless
+ beam.is_explosion = true;
+ beam.is_big_cloud = true;
+ break;
+
+ case SPELL_MIASMA: // death drake
+ beam.name = "foul vapour";
+ beam.damage = dice_def( 3, 5 + power / 24 );
+ beam.colour = DARKGREY;
+ beam.flavour = BEAM_MIASMA;
+ beam.hit = 17 + power / 20;
+ beam.is_beam = true;
+ beam.is_big_cloud = true;
+ break;
+
+ case SPELL_QUICKSILVER_BOLT: // Quicksilver dragon
+ beam.colour = random_colour();
+ beam.name = "bolt of energy";
+ beam.short_name = "energy";
+ beam.damage = dice_def( 3, 25 );
+ beam.hit = 16 + power / 25;
+ beam.flavour = BEAM_MMISSILE;
+ break;
+
+ case SPELL_HELLFIRE: // fiend's hellfire
+ beam.name = "blast of hellfire";
+ beam.aux_source = "blast of hellfire";
+ beam.colour = RED;
+ beam.damage = dice_def( 3, 25 );
+ beam.hit = 24;
+ beam.flavour = BEAM_HELLFIRE;
+ beam.is_beam = true;
+ beam.is_explosion = true;
+ break;
+
+ case SPELL_METAL_SPLINTERS:
+ beam.name = "spray of metal splinters";
+ beam.short_name = "metal splinters";
+ beam.damage = dice_def( 3, 20 + power / 20 );
+ beam.colour = CYAN;
+ beam.flavour = BEAM_FRAG;
+ beam.hit = 19 + power / 30;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_BANISHMENT:
+ beam.flavour = BEAM_BANISH;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_BLINK_OTHER:
+ beam.flavour = BEAM_BLINK;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_FIRE_BREATH:
+ beam.name = "blast of flame";
+ beam.aux_source = "blast of fiery breath";
+ beam.damage = dice_def( 3, (mons->hit_dice * 2) );
+ beam.colour = RED;
+ beam.hit = 30;
+ beam.flavour = BEAM_FIRE;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_COLD_BREATH:
+ beam.name = "blast of cold";
+ beam.aux_source = "blast of icy breath";
+ beam.short_name = "frost";
+ beam.damage = dice_def( 3, (mons->hit_dice * 2) );
+ beam.colour = WHITE;
+ beam.hit = 30;
+ beam.flavour = BEAM_COLD;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_DRACONIAN_BREATH:
+ beam.damage = dice_def( 3, (mons->hit_dice * 2) );
+ beam.hit = 30;
+ beam.is_beam = true;
+ break;
+
+ case SPELL_PORKALATOR:
+ beam.name = "porkalator";
+ beam.type = 0;
+ beam.flavour = BEAM_PORKALATOR;
+ beam.thrower = KILL_MON_MISSILE;
+ beam.is_beam = true;
+ break;
+
+ default:
+ if (!is_valid_spell(real_spell))
+ DEBUGSTR("Invalid spell #%d cast by %s", (int) real_spell,
+ mons->name(DESC_PLAIN, true).c_str());
+
+ DEBUGSTR("Unknown monster spell '%s' cast by %s",
+ spell_title(real_spell),
+ mons->name(DESC_PLAIN, true).c_str());
+
+ return (beam);
+ }
+
+ if (beam.is_enchantment())
+ {
+ beam.type = dchar_glyph(DCHAR_SPACE);
+ beam.name = "0";
+ }
+
+ if (spell_cast == SPELL_DRACONIAN_BREATH)
+ _scale_draconian_breath(beam, drac_type);
+
+ // Accuracy is lowered by one quarter if the dragon is attacking
+ // a target that is wielding a weapon of dragon slaying (which
+ // makes the dragon/draconian avoid looking at the foe).
+ // FIXME: This effect is not yet implemented for player draconians
+ // or characters in dragon form breathing at monsters wielding a
+ // weapon with this brand.
+ if (is_dragonkind(mons))
+ {
+ if (actor *foe = mons->get_foe())
+ {
+ if (const item_def *weapon = foe->weapon())
+ {
+ if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING)
+ {
+ beam.hit *= 3;
+ beam.hit /= 4;
+ }
+ }
+ }
+ }
+
+ return (beam);
+}
+
+static bool _los_free_spell(spell_type spell_cast)
+{
+ return (spell_cast == SPELL_HELLFIRE_BURST
+ || spell_cast == SPELL_BRAIN_FEED
+ || spell_cast == SPELL_SMITING
+ || spell_cast == SPELL_HAUNT
+ || spell_cast == SPELL_FIRE_STORM
+ || spell_cast == SPELL_AIRSTRIKE);
+}
+
+// Set up bolt structure for monster spell casting.
+void setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast)
+{
+ // always set these -- used by things other than fire_beam()
+
+ // [ds] Used to be 12 * MHD and later buggily forced to -1 downstairs.
+ // Setting this to a more realistic number now that that bug is
+ // squashed.
+ pbolt.ench_power = 4 * monster->hit_dice;
+
+ if (spell_cast == SPELL_TELEPORT_SELF)
+ pbolt.ench_power = 2000;
+
+ pbolt.beam_source = monster_index(monster);
+
+ // Convenience for the hapless innocent who assumes that this
+ // damn function does all possible setup. [ds]
+ if (pbolt.target.origin())
+ pbolt.target = monster->target;
+
+ // Set bolt type and range.
+ if (_los_free_spell(spell_cast))
+ {
+ pbolt.range = 0;
+ switch (spell_cast)
+ {
+ case SPELL_BRAIN_FEED:
+ pbolt.type = DMNBM_BRAIN_FEED;
+ return;
+ case SPELL_SMITING:
+ case SPELL_AIRSTRIKE:
+ pbolt.type = DMNBM_SMITING;
+ return;
+ default:
+ // Other spells get normal setup:
+ break;
+ }
+ }
+
+ // The below are no-ops since they don't involve direct_effect,
+ // fire_tracer, or beam.
+ switch (spell_cast)
+ {
+ case SPELL_SUMMON_SMALL_MAMMALS:
+ case SPELL_MAJOR_HEALING:
+ case SPELL_VAMPIRE_SUMMON:
+ case SPELL_SHADOW_CREATURES: // summon anything appropriate for level
+ case SPELL_FAKE_RAKSHASA_SUMMON:
+ case SPELL_SUMMON_DEMON:
+ case SPELL_SUMMON_UGLY_THING:
+ case SPELL_ANIMATE_DEAD:
+ case SPELL_CALL_IMP:
+ case SPELL_SUMMON_SCORPIONS:
+ case SPELL_SUMMON_UFETUBUS:
+ case SPELL_SUMMON_BEAST: // Geryon
+ case SPELL_SUMMON_UNDEAD: // summon undead around player
+ case SPELL_SUMMON_ICE_BEAST:
+ case SPELL_SUMMON_MUSHROOMS:
+ case SPELL_CONJURE_BALL_LIGHTNING:
+ case SPELL_SUMMON_DRAKES:
+ case SPELL_SUMMON_HORRIBLE_THINGS:
+ case SPELL_HAUNT:
+ case SPELL_SYMBOL_OF_TORMENT:
+ case SPELL_SUMMON_GREATER_DEMON:
+ case SPELL_CANTRIP:
+ case SPELL_BERSERKER_RAGE:
+ case SPELL_WATER_ELEMENTALS:
+ case SPELL_KRAKEN_TENTACLES:
+ case SPELL_BLINK:
+ case SPELL_CONTROLLED_BLINK:
+ case SPELL_TOMB_OF_DOROKLOHE:
+ return;
+ default:
+ break;
+ }
+
+ // Need to correct this for power of spellcaster
+ int power = 12 * monster->hit_dice;
+
+ bolt theBeam = mons_spells(monster, spell_cast, power);
+
+ pbolt.colour = theBeam.colour;
+ pbolt.range = theBeam.range;
+ pbolt.hit = theBeam.hit;
+ pbolt.damage = theBeam.damage;
+
+ if (theBeam.ench_power != -1)
+ pbolt.ench_power = theBeam.ench_power;
+
+ pbolt.type = theBeam.type;
+ pbolt.flavour = theBeam.flavour;
+ pbolt.thrower = theBeam.thrower;
+ pbolt.name = theBeam.name;
+ pbolt.short_name = theBeam.short_name;
+ pbolt.is_beam = theBeam.is_beam;
+ pbolt.source = monster->pos();
+ pbolt.is_tracer = false;
+ pbolt.is_explosion = theBeam.is_explosion;
+ pbolt.ex_size = theBeam.ex_size;
+
+ pbolt.foe_ratio = theBeam.foe_ratio;
+
+ if (!pbolt.is_enchantment())
+ pbolt.aux_source = pbolt.name;
+ else
+ pbolt.aux_source.clear();
+
+ if (spell_cast == SPELL_HASTE
+ || spell_cast == SPELL_INVISIBILITY
+ || spell_cast == SPELL_MINOR_HEALING
+ || spell_cast == SPELL_TELEPORT_SELF)
+ {
+ pbolt.target = monster->pos();
+ }
+ else if (spell_cast == SPELL_PORKALATOR && one_chance_in(3))
+ {
+ int target = -1;
+ int count = 0;
+ monster_type hog_type = MONS_HOG;
+ for (int i = 0; i < MAX_MONSTERS; i++)
+ {
+ monsters *targ = &menv[i];
+
+ if (!monster->can_see(targ))
+ continue;
+
+ hog_type = MONS_HOG;
+ if (targ->holiness() == MH_DEMONIC)
+ hog_type = MONS_HELL_HOG;
+ else if (targ->holiness() != MH_NATURAL)
+ continue;
+
+ if (targ->type != hog_type
+ && mons_atts_aligned(monster->attitude, targ->attitude)
+ && mons_power(hog_type) + random2(4) >= mons_power(targ->type)
+ && (!mons_class_flag(targ->type, M_SPELLCASTER) || coinflip())
+ && one_chance_in(++count))
+ {
+ target = i;
+ }
+ }
+
+ if (target != -1)
+ {
+ monsters *targ = &menv[target];
+ pbolt.target = targ->pos();
+#if DEBUG_DIAGNOSTICS
+ mprf("Porkalator: targetting %s instead",
+ targ->name(DESC_PLAIN).c_str());
+#endif
+ monster_polymorph(targ, hog_type);
+ }
+ // else target remains as specified
+ }
+}
+
+// Returns a suitable breath weapon for the draconian; does not handle all
+// draconians, does fire a tracer.
+static spell_type _get_draconian_breath_spell( monsters *monster )
+{
+ spell_type draco_breath = SPELL_NO_SPELL;
+
+ if (mons_genus( monster->type ) == MONS_DRACONIAN)
+ {
+ switch (draco_subspecies( monster ))
+ {
+ case MONS_DRACONIAN:
+ case MONS_YELLOW_DRACONIAN: // already handled as ability
+ break;
+ default:
+ draco_breath = SPELL_DRACONIAN_BREATH;
+ break;
+ }
+ }
+
+
+ if (draco_breath != SPELL_NO_SPELL)
+ {
+ // [ds] Check line-of-fire here. It won't happen elsewhere.
+ bolt beem;
+ setup_mons_cast(monster, beem, draco_breath);
+
+ fire_tracer(monster, beem);
+
+ if (!mons_should_fire(beem))
+ draco_breath = SPELL_NO_SPELL;
+ }
+
+ return (draco_breath);
+}
+
+static bool _mon_has_spells(monsters *monster)
+{
+ for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
+ if (monster->spells[i] != SPELL_NO_SPELL)
+ return (true);
+
+ return (false);
+}
+
+static bool _is_emergency_spell(const monster_spells &msp, int spell)
+{
+ // If the emergency spell appears early, it's probably not a dedicated
+ // escape spell.
+ for (int i = 0; i < 5; ++i)
+ if (msp[i] == spell)
+ return (false);
+
+ return (msp[5] == spell);
+}
+
+//---------------------------------------------------------------
+//
+// handle_spell
+//
+// Give the monster a chance to cast a spell. Returns true if
+// a spell was cast.
+//
+//---------------------------------------------------------------
+bool handle_mon_spell(monsters *monster, bolt &beem)
+{
+ bool monsterNearby = mons_near(monster);
+ bool finalAnswer = false; // as in: "Is that your...?" {dlb}
+ const spell_type draco_breath = _get_draconian_breath_spell(monster);
+
+ // A polymorphed unique will retain his or her spells even in another
+ // form. If the new form has the SPELLCASTER flag, casting happens as
+ // normally, otherwise we need to enforce it, but it only happens with
+ // a 50% chance.
+ const bool spellcasting_poly
+ = !mons_class_flag(monster->type, M_SPELLCASTER)
+ && mons_class_flag(monster->type, M_SPEAKS)
+ && _mon_has_spells(monster);
+
+ if (is_sanctuary(monster->pos()) && !mons_wont_attack(monster))
+ return (false);
+
+ // Yes, there is a logic to this ordering {dlb}:
+ if (monster->asleep()
+ || monster->submerged()
+ || !mons_class_flag(monster->type, M_SPELLCASTER)
+ && !spellcasting_poly
+ && draco_breath == SPELL_NO_SPELL)
+ {
+ return (false);
+ }
+
+ // If the monster's a priest, assume summons come from priestly
+ // abilities, in which case they'll have the same god. If the
+ // monster is neither a priest nor a wizard, assume summons come
+ // from intrinsic abilities, in which case they'll also have the
+ // same god.
+ const bool priest = mons_class_flag(monster->type, M_PRIEST);
+ const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
+ god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD;
+
+ if (silenced(monster->pos())
+ && (priest || wizard || spellcasting_poly
+ || mons_class_flag(monster->type, M_SPELL_NO_SILENT)))
+ {
+ return (false);
+ }
+
+ // Shapeshifters don't get spells.
+ if (mons_is_shapeshifter(monster) && (priest || wizard))
+ return (false);
+ else if (monster->has_ench(ENCH_CONFUSION)
+ && !mons_class_flag(monster->type, M_CONFUSED))
+ {
+ return (false);
+ }
+ else if (monster->type == MONS_PANDEMONIUM_DEMON
+ && !monster->ghost->spellcaster)
+ {
+ return (false);
+ }
+ else if (random2(200) > monster->hit_dice + 50
+ || monster->type == MONS_BALL_LIGHTNING && coinflip())
+ {
+ return (false);
+ }
+ else if (spellcasting_poly && coinflip()) // 50% chance of not casting
+ return (false);
+ else
+ {
+ spell_type spell_cast = SPELL_NO_SPELL;
+ monster_spells hspell_pass(monster->spells);
+
+ // 1KB: the following code is never used for unfriendlies!
+ if (!mon_enemies_around(monster))
+ {
+ // Force the casting of dig when the player is not visible -
+ // this is EVIL!
+ if (monster->has_spell(SPELL_DIG)
+ && mons_is_seeking(monster))
+ {
+ spell_cast = SPELL_DIG;
+ finalAnswer = true;
+ }
+ else if ((monster->has_spell(SPELL_MINOR_HEALING)
+ || monster->has_spell(SPELL_MAJOR_HEALING))
+ && monster->hit_points < monster->max_hit_points)
+ {
+ // The player's out of sight!
+ // Quick, let's take a turn to heal ourselves. -- bwr
+ spell_cast = monster->has_spell(SPELL_MAJOR_HEALING) ?
+ SPELL_MAJOR_HEALING : SPELL_MINOR_HEALING;
+ finalAnswer = true;
+ }
+ else if (mons_is_fleeing(monster) || mons_is_pacified(monster))
+ {
+ // Since the player isn't around, we'll extend the monster's
+ // normal choices to include the self-enchant slot.
+ int foundcount = 0;
+ for (int i = NUM_MONSTER_SPELL_SLOTS - 1; i >= 0; --i)
+ {
+ if (ms_useful_fleeing_out_of_sight(monster, hspell_pass[i])
+ && one_chance_in(++foundcount))
+ {
+ spell_cast = hspell_pass[i];
+ finalAnswer = true;
+ }
+ }
+ }
+ else if (monster->foe == MHITYOU && !monsterNearby)
+ return (false);
+ }
+
+ // Monsters caught in a net try to get away.
+ // This is only urgent if enemies are around.
+ if (!finalAnswer && mon_enemies_around(monster)
+ && mons_is_caught(monster) && one_chance_in(4))
+ {
+ for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
+ {
+ if (ms_quick_get_away(monster, hspell_pass[i]))
+ {
+ spell_cast = hspell_pass[i];
+ finalAnswer = true;
+ break;
+ }
+ }
+ }
+
+ // Promote the casting of useful spells for low-HP monsters.
+ if (!finalAnswer
+ && monster->hit_points < monster->max_hit_points / 4
+ && !one_chance_in(4))
+ {
+ // Note: There should always be at least some chance we don't
+ // get here... even if the monster is on its last HP. That
+ // way we don't have to worry about monsters infinitely casting
+ // Healing on themselves (e.g. orc high priests).
+ if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
+ && ms_low_hitpoint_cast(monster, hspell_pass[5]))
+ {
+ spell_cast = hspell_pass[5];
+ finalAnswer = true;
+ }
+
+ if (!finalAnswer)
+ {
+ int found_spell = 0;
+ for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
+ {
+ if (ms_low_hitpoint_cast(monster, hspell_pass[i])
+ && one_chance_in(++found_spell))
+ {
+ spell_cast = hspell_pass[i];
+ finalAnswer = true;
+ }
+ }
+ }
+ }
+
+ if (!finalAnswer)
+ {
+ // If nothing found by now, safe friendlies and good
+ // neutrals will rarely cast.
+ if (mons_wont_attack(monster) && !mon_enemies_around(monster)
+ && !one_chance_in(10))
+ {
+ return (false);
+ }
+
+ // Remove healing/invis/haste if we don't need them.
+ int num_no_spell = 0;
+
+ for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
+ {
+ if (hspell_pass[i] == SPELL_NO_SPELL)
+ num_no_spell++;
+ else if (ms_waste_of_time(monster, hspell_pass[i])
+ || hspell_pass[i] == SPELL_DIG)
+ {
+ // Should monster not have selected dig by now,
+ // it never will.
+ hspell_pass[i] = SPELL_NO_SPELL;
+ num_no_spell++;
+ }
+ }
+
+ // If no useful spells... cast no spell.
+ if (num_no_spell == NUM_MONSTER_SPELL_SLOTS
+ && draco_breath == SPELL_NO_SPELL)
+ {
+ return (false);
+ }
+
+ const bolt orig_beem = beem;
+ // Up to four tries to pick a spell.
+ for (int loopy = 0; loopy < 4; ++loopy)
+ {
+ beem = orig_beem;
+
+ bool spellOK = false;
+
+ // Setup spell - monsters that are fleeing or pacified
+ // and leaving the level will always try to choose their
+ // emergency spell.
+ if (mons_is_fleeing(monster) || mons_is_pacified(monster))
+ {
+ spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL
+ : hspell_pass[5]);
+
+ // Pacified monsters leaving the level won't choose
+ // emergency spells harmful to the area.
+ if (spell_cast != SPELL_NO_SPELL
+ && mons_is_pacified(monster)
+ && spell_harms_area(spell_cast))
+ {
+ spell_cast = SPELL_NO_SPELL;
+ }
+ }
+ else
+ {
+ // Randomly picking one of the non-emergency spells:
+ spell_cast = hspell_pass[random2(5)];
+ }
+
+ if (spell_cast == SPELL_NO_SPELL)
+ continue;
+
+ // Setup the spell.
+ setup_mons_cast(monster, beem, spell_cast);
+
+ // beam-type spells requiring tracers
+ if (spell_needs_tracer(spell_cast))
+ {
+ const bool explode =
+ spell_is_direct_explosion(spell_cast);
+ fire_tracer(monster, beem, explode);
+ // Good idea?
+ if (mons_should_fire(beem))
+ spellOK = true;
+ }
+ else
+ {
+ // All direct-effect/summoning/self-enchantments/etc.
+ spellOK = true;
+
+ if (ms_direct_nasty(spell_cast)
+ && mons_aligned(monster_index(monster),
+ monster->foe))
+ {
+ spellOK = false;
+ }
+ else if (monster->foe == MHITYOU || monster->foe == MHITNOT)
+ {
+ // XXX: Note the crude hack so that monsters can
+ // use ME_ALERT to target (we should really have
+ // a measure of time instead of peeking to see
+ // if the player is still there). -- bwr
+ if (!you.visible_to(monster)
+ && (monster->target != you.pos() || coinflip()))
+ {
+ spellOK = false;
+ }
+ }
+ else if (!monster->can_see(&menv[monster->foe]))
+ {
+ spellOK = false;
+ }
+ else if (monster->type == MONS_DAEVA
+ && monster->god == GOD_SHINING_ONE)
+ {
+ const monsters *mon = &menv[monster->foe];
+
+ // Don't allow TSO-worshipping daevas to make
+ // unchivalric magic attacks, except against
+ // appropriate monsters.
+ if (is_unchivalric_attack(monster, mon)
+ && !tso_unchivalric_attack_safe_monster(mon))
+ {
+ spellOK = false;
+ }
+ }
+ }
+
+ // If not okay, then maybe we'll cast a defensive spell.
+ if (!spellOK)
+ {
+ spell_cast = (coinflip() ? hspell_pass[2]
+ : SPELL_NO_SPELL);
+ }
+
+ if (spell_cast != SPELL_NO_SPELL)
+ break;
+ }
+ }
+
+ // If there's otherwise no ranged attack use the breath weapon.
+ // The breath weapon is also occasionally used.
+ if (draco_breath != SPELL_NO_SPELL
+ && (spell_cast == SPELL_NO_SPELL
+ || !_is_emergency_spell(hspell_pass, spell_cast)
+ && one_chance_in(4))
+ && !player_or_mon_in_sanct(monster))
+ {
+ spell_cast = draco_breath;
+ finalAnswer = true;
+ }
+
+ // Should the monster *still* not have a spell, well, too bad {dlb}:
+ if (spell_cast == SPELL_NO_SPELL)
+ return (false);
+
+ // Friendly monsters don't use polymorph other, for fear of harming
+ // the player.
+ if (spell_cast == SPELL_POLYMORPH_OTHER && mons_friendly(monster))
+ return (false);
+
+ // Try to animate dead: if nothing rises, pretend we didn't cast it.
+ if (spell_cast == SPELL_ANIMATE_DEAD
+ && !animate_dead(monster, 100, SAME_ATTITUDE(monster),
+ monster->foe, god, false))
+ {
+ return (false);
+ }
+
+ if (monster->type == MONS_BALL_LIGHTNING)
+ monster->hit_points = -1;
+
+ // FINALLY! determine primary spell effects {dlb}:
+ if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK)
+ {
+ // Why only cast blink if nearby? {dlb}
+ if (monsterNearby)
+ {
+ mons_cast_noise(monster, beem, spell_cast);
+ monster_blink(monster);
+
+ monster->lose_energy(EUT_SPELL);
+ }
+ else
+ return (false);
+ }
+ else
+ {
+ if (spell_needs_foe(spell_cast))
+ make_mons_stop_fleeing(monster);
+
+ mons_cast(monster, beem, spell_cast);
+ monster->lose_energy(EUT_SPELL);
+ }
+ } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ...
+
+ return (true);
+}
+
+static int _monster_abjure_square(const coord_def &pos,
+ int pow, int actual,
+ int wont_attack)
+{
+ monsters *target = monster_at(pos);
+ if (target == NULL)
+ return (0);
+
+ if (!target->alive()
+ || ((bool)wont_attack == mons_wont_attack_real(target)))
+ {
+ return (0);
+ }
+
+ int duration;
+
+ if (!target->is_summoned(&duration))
+ return (0);
+
+ pow = std::max(20, fuzz_value(pow, 40, 25));
+
+ if (!actual)
+ return (pow > 40 || pow >= duration);
+
+ // TSO and Trog's abjuration protection.
+ bool shielded = false;
+ if (you.religion == GOD_SHINING_ONE)
+ {
+ pow = pow * (30 - target->hit_dice) / 30;
+ if (pow < duration)
+ {
+ simple_god_message(" protects your fellow warrior from evil "
+ "magic!");
+ shielded = true;
+ }
+ }
+ else if (you.religion == GOD_TROG)
+ {
+ pow = pow * 4 / 5;
+ if (pow < duration)
+ {
+ simple_god_message(" shields your ally from puny magic!");
+ shielded = true;
+ }
+ }
+ else if (is_sanctuary(target->pos()))
+ {
+ pow = 0;
+ mpr("Zin's power protects your fellow warrior from evil magic!",
+ MSGCH_GOD);
+ shielded = true;
+ }
+
+#ifdef DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS, "Abj: dur: %d, pow: %d, ndur: %d",
+ duration, pow, duration - pow);
+#endif
+
+ mon_enchant abj = target->get_ench(ENCH_ABJ);
+ if (!target->lose_ench_duration(abj, pow))
+ {
+ if (!shielded)
+ simple_monster_message(target, " shudders.");
+ return (1);
+ }
+
+ return (0);
+}
+
+static int _apply_radius_around_square( const coord_def &c, int radius,
+ int (*fn)(const coord_def &, int, int, int),
+ int pow, int par1, int par2)
+{
+ int res = 0;
+ for (int yi = -radius; yi <= radius; ++yi)
+ {
+ const coord_def c1(c.x - radius, c.y + yi);
+ const coord_def c2(c.x + radius, c.y + yi);
+ if (in_bounds(c1))
+ res += fn(c1, pow, par1, par2);
+ if (in_bounds(c2))
+ res += fn(c2, pow, par1, par2);
+ }
+
+ for (int xi = -radius + 1; xi < radius; ++xi)
+ {
+ const coord_def c1(c.x + xi, c.y - radius);
+ const coord_def c2(c.x + xi, c.y + radius);
+ if (in_bounds(c1))
+ res += fn(c1, pow, par1, par2);
+ if (in_bounds(c2))
+ res += fn(c2, pow, par1, par2);
+ }
+ return (res);
+}
+
+static int _monster_abjuration(const monsters *caster, bool actual)
+{
+ const bool wont_attack = mons_wont_attack_real(caster);
+ int maffected = 0;
+
+ if (actual)
+ mpr("Send 'em back where they came from!");
+
+ int pow = std::min(caster->hit_dice * 90, 2500);
+
+ // Abjure radius.
+ for (int rad = 1; rad < 5 && pow >= 30; ++rad)
+ {
+ int number_hit =
+ _apply_radius_around_square(caster->pos(), rad,
+ _monster_abjure_square,
+ pow, actual, wont_attack);
+
+ maffected += number_hit;
+
+ // Each affected monster drops power.
+ //
+ // We could further tune this by the actual amount of abjuration
+ // damage done to each summon, but the player will probably never
+ // notice. :-)
+ while (number_hit-- > 0)
+ pow = pow * 90 / 100;
+
+ pow /= 2;
+ }
+ return (maffected);
+}
+
+
+static bool _mons_abjured(monsters *monster, bool nearby)
+{
+ if (nearby && _monster_abjuration(monster, false) > 0
+ && coinflip())
+ {
+ _monster_abjuration(monster, true);
+ return (true);
+ }
+
+ return (false);
+}
+
+static monster_type _pick_random_wraith()
+{
+ static monster_type wraiths[] =
+ {
+ MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
+ MONS_SPECTRAL_WARRIOR, MONS_PHANTOM, MONS_HUNGRY_GHOST,
+ MONS_FLAYED_GHOST
+ };
+
+ return (RANDOM_ELEMENT(wraiths));
+}
+
+static monster_type _pick_horrible_thing()
+{
+ return (one_chance_in(4) ? MONS_TENTACLED_MONSTROSITY
+ : MONS_ABOMINATION_LARGE);
+}
+
+static monster_type _pick_undead_summon()
+{
+ static monster_type undead[] =
+ {
+ MONS_NECROPHAGE, MONS_GHOUL, MONS_HUNGRY_GHOST, MONS_FLAYED_GHOST,
+ MONS_ZOMBIE_SMALL, MONS_SKELETON_SMALL, MONS_SIMULACRUM_SMALL,
+ MONS_FLYING_SKULL, MONS_FLAMING_CORPSE, MONS_MUMMY, MONS_VAMPIRE,
+ MONS_WIGHT, MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
+ MONS_SPECTRAL_WARRIOR, MONS_ZOMBIE_LARGE, MONS_SKELETON_LARGE,
+ MONS_SIMULACRUM_LARGE, MONS_SHADOW
+ };
+
+ return (RANDOM_ELEMENT(undead));
+}
+
+static void _do_high_level_summon(monsters *monster, bool monsterNearby,
+ spell_type spell_cast,
+ monster_type (*mpicker)(), int nsummons,
+ god_type god, coord_def *target = NULL)
+{
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ const int duration = std::min(2 + monster->hit_dice / 5, 6);
+
+ for (int i = 0; i < nsummons; ++i)
+ {
+ monster_type which_mons = mpicker();
+
+ if (which_mons == MONS_NO_MONSTER)
+ continue;
+
+ create_monster(
+ mgen_data(which_mons, SAME_ATTITUDE(monster),
+ duration, spell_cast, target ? *target : monster->pos(),
+ monster->foe, 0, god));
+ }
+}
+
+// Returns true if a message referring to the player's legs makes sense.
+static bool _legs_msg_applicable()
+{
+ return (you.species != SP_NAGA
+ && (you.species != SP_MERFOLK || !player_is_swimming()));
+}
+
+void mons_cast_haunt(monsters *monster)
+{
+ coord_def fpos;
+
+ switch (monster->foe)
+ {
+ case MHITNOT:
+ return;
+
+ case MHITYOU:
+ fpos = you.pos();
+ break;
+
+ default:
+ fpos = menv[monster->foe].pos();
+ }
+
+ _do_high_level_summon(monster, mons_near(monster), SPELL_HAUNT,
+ _pick_random_wraith, random_range(3, 6), GOD_NO_GOD, &fpos);
+}
+
+void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
+ bool do_noise)
+{
+ // Always do setup. It might be done already, but it doesn't hurt
+ // to do it again (cheap).
+ setup_mons_cast(monster, pbolt, spell_cast);
+
+ // single calculation permissible {dlb}
+ bool monsterNearby = mons_near(monster);
+
+ int sumcount = 0;
+ int sumcount2;
+ int duration = 0;
+
+#if DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS, "Mon #%d casts %s (#%d)",
+ monster_index(monster), spell_title(spell_cast), spell_cast);
+#endif
+
+ if (spell_cast == SPELL_CANTRIP)
+ do_noise = false; // Spell itself does the messaging.
+
+ if (_los_free_spell(spell_cast) && !spell_is_direct_explosion(spell_cast))
+ {
+ if (monster->foe == MHITYOU || monster->foe == MHITNOT)
+ {
+ if (monsterNearby)
+ {
+ if (do_noise)
+ mons_cast_noise(monster, pbolt, spell_cast);
+ direct_effect(monster, spell_cast, pbolt, &you);
+ }
+ return;
+ }
+
+ if (do_noise)
+ mons_cast_noise(monster, pbolt, spell_cast);
+ direct_effect(monster, spell_cast, pbolt, monster->get_foe());
+ return;
+ }
+
+#ifdef DEBUG
+ const unsigned int flags = get_spell_flags(spell_cast);
+
+ ASSERT(!(flags & (SPFLAG_TESTING | SPFLAG_MAPPING)));
+
+ // Targeted spells need a valid target.
+ ASSERT(!(flags & SPFLAG_TARGETTING_MASK) || in_bounds(pbolt.target));
+#endif
+
+ if (do_noise)
+ mons_cast_noise(monster, pbolt, spell_cast);
+
+ // If the monster's a priest, assume summons come from priestly
+ // abilities, in which case they'll have the same god. If the
+ // monster is neither a priest nor a wizard, assume summons come
+ // from intrinsic abilities, in which case they'll also have the
+ // same god.
+ const bool priest = mons_class_flag(monster->type, M_PRIEST);
+ const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
+ god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD;
+
+ switch (spell_cast)
+ {
+ default:
+ break;
+
+ case SPELL_MAJOR_HEALING:
+ if (heal_monster(monster, 50 + random2avg(monster->hit_dice * 10, 2),
+ false))
+ {
+ simple_monster_message(monster, " is healed.");
+ }
+ return;
+
+ case SPELL_BERSERKER_RAGE:
+ monster->go_berserk(true);
+ return;
+
+ case SPELL_SUMMON_SMALL_MAMMALS:
+ case SPELL_VAMPIRE_SUMMON:
+ if (spell_cast == SPELL_SUMMON_SMALL_MAMMALS)
+ sumcount2 = 1 + random2(4);
+ else
+ sumcount2 = 3 + random2(3) + monster->hit_dice / 5;
+
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ const monster_type rats[] = { MONS_ORANGE_RAT, MONS_GREEN_RAT,
+ MONS_GREY_RAT, MONS_RAT };
+ const monster_type mon = (one_chance_in(3) ? MONS_GIANT_BAT
+ : RANDOM_ELEMENT(rats));
+ create_monster(
+ mgen_data(mon, SAME_ATTITUDE(monster),
+ 5, spell_cast, monster->pos(), monster->foe, 0, god));
+ }
+ return;
+
+ case SPELL_SHADOW_CREATURES: // summon anything appropriate for level
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1);
+
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ create_monster(
+ mgen_data(RANDOM_MONSTER, SAME_ATTITUDE(monster),
+ 5, spell_cast, monster->pos(), monster->foe, 0, god));
+ }
+ return;
+
+ case SPELL_WATER_ELEMENTALS:
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1);
+
+ for (sumcount = 0; sumcount < sumcount2; sumcount++)
+ {
+ create_monster(
+ mgen_data(MONS_WATER_ELEMENTAL, SAME_ATTITUDE(monster),
+ 3, spell_cast, monster->pos(), monster->foe, 0, god));
+ }
+ return;
+
+ case SPELL_KRAKEN_TENTACLES:
+ {
+ int kraken_index = monster_index(monster);
+ if (invalid_monster_index(duration))
+ {
+ mpr("Error! Kraken is not a part of the current environment!",
+ MSGCH_ERROR);
+ return;
+ }
+ sumcount2 = std::max(random2(9), random2(9)); // up to eight tentacles
+ if (sumcount2 == 0)
+ return;
+
+ for (sumcount = 0; sumcount < MAX_MONSTERS; ++sumcount)
+ if (menv[sumcount].type == MONS_KRAKEN_TENTACLE
+ && (int)menv[sumcount].number == kraken_index)
+ {
+ // Reduce by tentacles already placed.
+ sumcount2--;
+ }
+
+ for (sumcount = sumcount2; sumcount > 0; --sumcount)
+ {
+ // Tentacles aren't really summoned (controlled by spell_cast
+ // being passed to summon_type), so I'm not sure what the
+ // abjuration value (3) is doing there. (jpeg)
+ if (create_monster(
+ mgen_data(MONS_KRAKEN_TENTACLE, SAME_ATTITUDE(monster),
+ 3, spell_cast, monster->pos(), monster->foe, 0, god,
+ MONS_NO_MONSTER, kraken_index, monster->colour,
+ you.your_level, PROX_CLOSE_TO_PLAYER,
+ you.level_type)) == -1)
+ {
+ sumcount2--;
+ }
+ }
+ if (sumcount2 == 1)
+ mpr("A tentacle rises from the water!");
+ else if (sumcount2 > 1)
+ mpr("Tentacles burst out of the water!");
+ return;
+ }
+ case SPELL_FAKE_RAKSHASA_SUMMON:
+ sumcount2 = (coinflip() ? 2 : 3);
+
+ for (sumcount = 0; sumcount < sumcount2; sumcount++)
+ {
+ create_monster(
+ mgen_data(MONS_RAKSHASA_FAKE, SAME_ATTITUDE(monster),
+ 3, spell_cast, monster->pos(), monster->foe, 0, god));
+ }
+ return;
+
+ case SPELL_SUMMON_DEMON: // class 2-4 demons
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 10, 6);
+ for (sumcount = 0; sumcount < sumcount2; sumcount++)
+ {
+ create_monster(
+ mgen_data(summon_any_demon(DEMON_COMMON),
+ SAME_ATTITUDE(monster), duration, spell_cast,
+ monster->pos(), monster->foe, 0, god));
+ }
+ return;
+
+ case SPELL_SUMMON_UGLY_THING:
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 10, 6);
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ const int chance = std::max(6 - (monster->hit_dice / 6), 1);
+ monster_type mon = (one_chance_in(chance) ? MONS_VERY_UGLY_THING
+ : MONS_UGLY_THING);
+
+ create_monster(
+ mgen_data(mon, SAME_ATTITUDE(monster),
+ duration, spell_cast, monster->pos(), monster->foe, 0,
+ god));
+ }
+ return;
+
+ case SPELL_ANIMATE_DEAD:
+ // see special handling in monstuff::handle_spell() {dlb}
+ animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster),
+ monster->foe, god);
+ return;
+
+ case SPELL_CALL_IMP: // class 5 demons
+ sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 5, 6);
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ create_monster(
+ mgen_data(summon_any_demon(DEMON_LESSER),
+ SAME_ATTITUDE(monster),
+ duration, spell_cast, monster->pos(), monster->foe, 0,
+ god));
+ }
+ return;
+
+ case SPELL_SUMMON_SCORPIONS:
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 5, 6);
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ create_monster(
+ mgen_data(MONS_SCORPION, SAME_ATTITUDE(monster),
+ duration, spell_cast, monster->pos(), monster->foe, 0,
+ god));
+ }
+ return;
+
+ case SPELL_SUMMON_UFETUBUS:
+ sumcount2 = 2 + random2(2) + random2(monster->hit_dice / 5 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 5, 6);
+
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ create_monster(
+ mgen_data(MONS_UFETUBUS, SAME_ATTITUDE(monster),
+ duration, spell_cast, monster->pos(), monster->foe, 0,
+ god));
+ }
+ return;
+
+ case SPELL_SUMMON_BEAST: // Geryon
+ create_monster(
+ mgen_data(MONS_BEAST, SAME_ATTITUDE(monster),
+ 4, spell_cast, monster->pos(), monster->foe, 0, god));
+ return;
+
+ case SPELL_SUMMON_ICE_BEAST:
+ create_monster(
+ mgen_data(MONS_ICE_BEAST, SAME_ATTITUDE(monster),
+ 5, spell_cast, monster->pos(), monster->foe, 0, god));
+ return;
+
+ case SPELL_SUMMON_MUSHROOMS: // Summon swarms of icky crawling fungi.
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 4 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 5, 6);
+ for (int i = 0; i < sumcount2; ++i)
+ {
+ create_monster(
+ mgen_data(MONS_WANDERING_MUSHROOM, SAME_ATTITUDE(monster),
+ duration, spell_cast, monster->pos(), monster->foe, 0,
+ god));
+ }
+ return;
+
+ case SPELL_SUMMON_HORRIBLE_THINGS:
+ _do_high_level_summon(monster, monsterNearby, spell_cast,
+ _pick_horrible_thing, random_range(3, 5), god);
+ return;
+
+ case SPELL_CONJURE_BALL_LIGHTNING:
+ {
+ const int n = 2 + random2(monster->hit_dice / 4);
+ for (int i = 0; i < n; ++i)
+ {
+ create_monster(
+ mgen_data(MONS_BALL_LIGHTNING, SAME_ATTITUDE(monster),
+ 2, spell_cast, monster->pos(), monster->foe, 0, god));
+ }
+ return;
+ }
+
+ case SPELL_SUMMON_UNDEAD: // Summon undead around player.
+ _do_high_level_summon(monster, monsterNearby, spell_cast,
+ _pick_undead_summon,
+ 2 + random2(2)
+ + random2(monster->hit_dice / 4 + 1), god);
+ return;
+
+ case SPELL_SYMBOL_OF_TORMENT:
+ if (!monsterNearby || mons_friendly(monster))
+ return;
+
+ torment(monster_index(monster), monster->pos());
+ return;
+
+ case SPELL_SUMMON_GREATER_DEMON:
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(monster->hit_dice / 10 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 10, 6);
+ for (sumcount = 0; sumcount < sumcount2; ++sumcount)
+ {
+ create_monster(
+ mgen_data(summon_any_demon(DEMON_GREATER),
+ SAME_ATTITUDE(monster),
+ duration, spell_cast, monster->pos(), monster->foe,
+ 0, god));
+ }
+ return;
+
+ // Journey -- Added in Summon Lizards and Draconian
+ case SPELL_SUMMON_DRAKES:
+ if (_mons_abjured(monster, monsterNearby))
+ return;
+
+ sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
+
+ duration = std::min(2 + monster->hit_dice / 10, 6);
+
+ {
+ std::vector<monster_type> monsters;
+
+ for (sumcount = 0; sumcount < sumcount2; sumcount++)
+ {
+ monster_type mon = summon_any_dragon(DRAGON_LIZARD);
+
+ if (mon == MONS_DRAGON)
+ {
+ monsters.clear();
+ monsters.push_back(summon_any_dragon(DRAGON_DRAGON));
+ break;
+ }
+
+ monsters.push_back(mon);
+ }
+
+ for (int i = 0, size = monsters.size(); i < size; ++i)
+ {
+ create_monster(
+ mgen_data(monsters[i], SAME_ATTITUDE(monster),
+ duration, spell_cast,
+ monster->pos(), monster->foe, 0, god));
+ }
+ }
+ return;
+
+ // TODO: Outsource the cantrip messages and allow specification of
+ // special cantrip spells per monster, like for speech, both as
+ // "self buffs" and "player enchantments".
+ case SPELL_CANTRIP:
+ {
+ // Monster spell of uselessness, just prints a message.
+ // This spell exists so that some monsters with really strong
+ // spells (ie orc priest) can be toned down a bit. -- bwr
+ //
+ // XXX: Needs expansion, and perhaps different priest/mage flavours.
+
+ // Don't give any message if the monster isn't nearby.
+ // (Otherwise you could get them from halfway across the level.)
+ if (!mons_near(monster))
+ return;
+
+ const bool friendly = mons_friendly(monster);
+ const bool buff_only = !friendly && is_sanctuary(you.pos());
+ const msg_channel_type channel = (friendly) ? MSGCH_FRIEND_ENCHANT
+ : MSGCH_MONSTER_ENCHANT;
+
+ if (monster->type == MONS_GASTRONOK)
+ {
+ bool has_mon_foe = !invalid_monster_index(monster->foe);
+ std::string slugform = "";
+ if (buff_only || crawl_state.arena && !has_mon_foe
+ || friendly && !has_mon_foe || coinflip())
+ {
+ slugform = getSpeakString("gastronok_self_buff");
+ if (!slugform.empty())
+ {
+ slugform = replace_all(slugform, "@The_monster@",
+ monster->name(DESC_CAP_THE));
+ mpr(slugform.c_str(), channel);
+ }
+ }
+ else if (!friendly && !has_mon_foe)
+ {
+ mons_cast_noise(monster, pbolt, spell_cast);
+
+ // "Enchant" the player.
+ slugform = getSpeakString("gastronok_debuff");
+ if (!slugform.empty()
+ && (slugform.find("legs") == std::string::npos
+ || _legs_msg_applicable()))
+ {
+ mpr(slugform.c_str());
+ }
+ }
+ else
+ {
+ // "Enchant" another monster.
+ const monsters* foe
+ = dynamic_cast<const monsters*>(monster->get_foe());
+ slugform = getSpeakString("gastronok_other_buff");
+ if (!slugform.empty())
+ {
+ slugform = replace_all(slugform, "@The_monster@",
+ foe->name(DESC_CAP_THE));
+ mpr(slugform.c_str(), MSGCH_MONSTER_ENCHANT);
+ }
+ }
+ }
+ else
+ {
+ // Messages about the monster influencing itself.
+ const char* buff_msgs[] = { " glows brightly for a moment.",
+ " looks stronger.",
+ " becomes somewhat translucent.",
+ "'s eyes start to glow." };
+
+ // Messages about the monster influencing you.
+ const char* other_msgs[] = {
+ "You feel troubled.",
+ "You feel a wave of unholy energy pass over you."
+ };
+
+ if (buff_only || crawl_state.arena || x_chance_in_y(2,3))
+ {
+ simple_monster_message(monster, RANDOM_ELEMENT(buff_msgs),
+ channel);
+ }
+ else if (friendly)
+ {
+ simple_monster_message(monster, " shimmers for a moment.",
+ channel);
+ }
+ else // "Enchant" the player.
+ {
+ mons_cast_noise(monster, pbolt, spell_cast);
+ mpr(RANDOM_ELEMENT(other_msgs));
+ }
+ }
+ return;
+ }
+ case SPELL_BLINK_OTHER:
+ {
+ // Allow the caster to comment on moving the foe.
+ std::string msg = getSpeakString(monster->name(DESC_PLAIN)
+ + " blink_other");
+ if (!msg.empty() && msg != "__NONE")
+ {
+ mons_speaks_msg(monster, msg, MSGCH_TALK,
+ silenced(you.pos()) || silenced(monster->pos()));
+ }
+ break;
+ }
+ case SPELL_TOMB_OF_DOROKLOHE:
+ {
+ sumcount = 0;
+ for (adjacent_iterator ai(monster->pos()); ai; ++ai)
+ {
+ // we can blink away the crowd, but only our allies
+ if (mgrd(*ai) != NON_MONSTER
+ && monster_at(*ai)->attitude != monster->attitude)
+ sumcount++;
+ if (grd(*ai) != DNGN_FLOOR && grd(*ai) > DNGN_MAX_NONREACH
+ && !feat_is_trap(grd(*ai)))
+ sumcount++;
+ }
+ if (abs(you.pos().x-monster->pos().x)<=1 &&
+ abs(you.pos().y-monster->pos().y)<=1)
+ sumcount++;
+ if (sumcount)
+ {
+ monster->blink();
+ return;
+ }
+
+ sumcount = 0;
+ for (adjacent_iterator ai(monster->pos()); ai; ++ai)
+ {
+ if (mgrd(*ai) != NON_MONSTER && monster_at(*ai) != monster)
+ {
+ monster_at(*ai)->blink();
+ if (mgrd(*ai) != NON_MONSTER)
+ {
+ monster_at(*ai)->teleport(true);
+ if (mgrd(*ai) != NON_MONSTER)
+ continue;
+ }
+ }
+ if (grd(*ai) == DNGN_FLOOR || feat_is_trap(grd(*ai)))
+ {
+ grd(*ai) = DNGN_ROCK_WALL;
+ sumcount++;
+ }
+ }
+ if (sumcount)
+ mpr("Walls emerge from the floor!");
+ monster->number = 1; // mark Khufu as entombed
+ return;
+ }
+ }
+
+ // If a monster just came into view and immediately cast a spell,
+ // we need to refresh the screen before drawing the beam.
+ viewwindow(true, false);
+ if (spell_is_direct_explosion(spell_cast))
+ {
+ const actor *foe = monster->get_foe();
+ const bool need_more = foe && (foe == &you || see_cell(foe->pos()));
+ pbolt.in_explosion_phase = false;
+ pbolt.explode(need_more);
+ }
+ else
+ pbolt.fire();
+}
+
+void mons_cast_noise(monsters *monster, bolt &pbolt, spell_type spell_cast)
+{
+ bool force_silent = false;
+
+ spell_type real_spell = spell_cast;
+
+ if (spell_cast == SPELL_DRACONIAN_BREATH)
+ {
+ int type = monster->type;
+ if (mons_genus(type) == MONS_DRACONIAN)
+ type = draco_subspecies(monster);
+
+ switch (type)
+ {
+ case MONS_MOTTLED_DRACONIAN:
+ real_spell = SPELL_STICKY_FLAME_SPLASH;
+ break;
+
+ case MONS_YELLOW_DRACONIAN:
+ real_spell = SPELL_ACID_SPLASH;
+ break;
+
+ case MONS_PLAYER_GHOST:
+ // Draining breath is silent.
+ force_silent = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (monster->type == MONS_SHADOW_DRAGON)
+ // Draining breath is silent.
+ force_silent = true;
+
+ const bool unseen = !you.can_see(monster);
+ const bool silent = silenced(monster->pos()) || force_silent;
+ const bool no_silent = mons_class_flag(monster->type, M_SPELL_NO_SILENT);
+
+ if (unseen && silent)
+ return;
+
+ const unsigned int flags = get_spell_flags(real_spell);
+
+ const bool priest = mons_class_flag(monster->type, M_PRIEST);
+ const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
+ const bool innate = !(priest || wizard || no_silent)
+ || (flags & SPFLAG_INNATE);
+
+ int noise;
+ if (silent
+ || (innate
+ && !mons_class_flag(monster->type, M_NOISY_SPELLS)
+ && !(flags & SPFLAG_NOISY)
+ && mons_genus(monster->type) != MONS_DRAGON))
+ {
+ noise = 0;
+ }
+ else
+ {
+ if (mons_genus(monster->type) == MONS_DRAGON)
+ noise = get_shout_noise_level(S_ROAR);
+ else
+ noise = spell_noise(real_spell);
+ }
+
+ const std::string cast_str = " cast";
+
+ const std::string spell_name = spell_title(real_spell);
+ const mon_body_shape shape = get_mon_shape(monster);
+
+ std::vector<std::string> key_list;
+
+ // First try the spells name.
+ if (shape <= MON_SHAPE_NAGA)
+ {
+ if (!innate && (priest || wizard))
+ key_list.push_back(spell_name + cast_str + " real");
+ if (mons_intel(monster) >= I_NORMAL)
+ key_list.push_back(spell_name + cast_str + " gestures");
+ }
+ key_list.push_back(spell_name + cast_str);
+
+ const unsigned int num_spell_keys = key_list.size();
+
+ // Next the monster type name, then species name, then genus name.
+ key_list.push_back(mons_type_name(monster->type, DESC_PLAIN) + cast_str);
+ key_list.push_back(mons_type_name(mons_species(monster->type), DESC_PLAIN)
+ + cast_str);
+ key_list.push_back(mons_type_name(mons_genus(monster->type), DESC_PLAIN)
+ + cast_str);
+
+ // Last, generic wizard, priest or demon.
+ if (wizard)
+ key_list.push_back("wizard" + cast_str);
+ else if (priest)
+ key_list.push_back("priest" + cast_str);
+ else if (mons_is_demon(monster->type))
+ key_list.push_back("demon" + cast_str);
+
+ const bool visible_beam = pbolt.type != 0 && pbolt.type != ' '
+ && pbolt.name[0] != '0'
+ && !pbolt.is_enchantment();
+
+ const bool targeted = (flags & SPFLAG_TARGETTING_MASK)
+ && (pbolt.target != monster->pos() || visible_beam);
+
+ if (targeted)
+ {
+ // For targeted spells, try with the targeted suffix first.
+ for (unsigned int i = key_list.size() - 1; i >= num_spell_keys; i--)
+ {
+ std::string str = key_list[i] + " targeted";
+ key_list.insert(key_list.begin() + i, str);
+ }
+
+ // Generic beam messages.
+ if (visible_beam)
+ {
+ key_list.push_back(pbolt.get_short_name() + " beam " + cast_str);
+ key_list.push_back("beam catchall cast");
+ }
+ }
+
+ std::string prefix;
+ if (silent)
+ prefix = "silent ";
+ else if (unseen)
+ prefix = "unseen ";
+
+ std::string msg;
+ for (unsigned int i = 0; i < key_list.size(); i++)
+ {
+ const std::string key = key_list[i];
+
+ msg = getSpeakString(prefix + key);
+ if (msg == "__NONE")
+ {
+ msg = "";
+ break;
+ }
+ else if (msg == "__NEXT")
+ {
+ msg = "";
+ if (i < num_spell_keys)
+ i = num_spell_keys - 1;
+ else if (ends_with(key, " targeted"))
+ i++;
+ continue;
+ }
+ else if (!msg.empty())
+ break;
+
+ // If we got no message and we're using the silent prefix, then
+ // try again without the prefix.
+ if (prefix != "silent")
+ continue;
+
+ msg = getSpeakString(key);
+ if (msg == "__NONE")
+ {
+ msg = "";
+ break;
+ }
+ else if (msg == "__NEXT")
+ {
+ msg = "";
+ if (i < num_spell_keys)
+ i = num_spell_keys - 1;
+ else if (ends_with(key, " targeted"))
+ i++;
+ continue;
+ }
+ else if (!msg.empty())
+ break;
+ }
+
+ if (msg.empty())
+ {
+ if (silent)
+ return;
+
+ noisy(noise, monster->pos(), monster->mindex());
+ return;
+ }
+
+ /////////////////////
+ // We have a message.
+ /////////////////////
+
+ const bool gestured = msg.find("Gesture") != std::string::npos
+ || msg.find(" gesture") != std::string::npos
+ || msg.find("Point") != std::string::npos
+ || msg.find(" point") != std::string::npos;
+
+ bolt tracer = pbolt;
+ if (targeted)
+ {
+ // For a targeted but rangeless spell make the range positive so that
+ // fire_tracer() will fill out path_taken.
+ if (pbolt.range == 0 && pbolt.target != monster->pos())
+ tracer.range = ENV_SHOW_DIAMETER;
+
+ fire_tracer(monster, tracer);
+ }
+
+ std::string targ_prep = "at";
+ std::string target = "nothing";
+
+ if (!targeted)
+ target = "NO TARGET";
+ else if (pbolt.target == you.pos())
+ target = "you";
+ else if (pbolt.target == monster->pos())
+ target = monster->pronoun(PRONOUN_REFLEXIVE);
+ // Monsters should only use targeted spells while foe == MHITNOT
+ // if they're targetting themselves.
+ else if (monster->foe == MHITNOT && !monster->confused())
+ target = "NONEXISTENT FOE";
+ else if (!invalid_monster_index(monster->foe)
+ && menv[monster->foe].type == MONS_NO_MONSTER)
+ {
+ target = "DEAD FOE";
+ }
+ else if (in_bounds(pbolt.target) && see_cell(pbolt.target))
+ {
+ if (const monsters* mtarg = monster_at(pbolt.target))
+ {
+ if (you.can_see(mtarg))
+ target = mtarg->name(DESC_NOCAP_THE);
+ }
+ }
+
+ // Monster might be aiming past the real target, or maybe some fuzz has
+ // been applied because the target is invisible.
+ if (target == "nothing" && targeted)
+ {
+ if (pbolt.aimed_at_spot)
+ {
+ int count = 0;
+ for (adjacent_iterator ai(pbolt.target); ai; ++ai)
+ {
+ const actor* act = actor_at(*ai);
+ if (act && act != monster && you.can_see(act))
+ {
+ targ_prep = "next to";
+
+ if (act->atype() == ACT_PLAYER || one_chance_in(++count))
+ target = act->name(DESC_NOCAP_THE);
+
+ if (act->atype() == ACT_PLAYER)
+ break;
+ }
+ }
+ }
+
+ const bool visible_path = visible_beam || gestured;
+ bool mons_targ_aligned = false;
+
+ const std::vector<coord_def> &path = tracer.path_taken;
+ for (unsigned int i = 0; i < path.size(); i++)
+ {
+ const coord_def pos = path[i];
+
+ if (pos == monster->pos())
+ continue;
+
+ const monsters *m = monster_at(pos);
+ if (pos == you.pos())
+ {
+ // Be egotistical and assume that the monster is aiming at
+ // the player, rather than the player being in the path of
+ // a beam aimed at an ally.
+ if (!mons_wont_attack(monster))
+ {
+ targ_prep = "at";
+ target = "you";
+ break;
+ }
+ // If the ally is confused or aiming at an invisible enemy,
+ // with the player in the path, act like it's targeted at
+ // the player if there isn't any visible target earlier
+ // in the path.
+ else if (target == "nothing")
+ {
+ targ_prep = "at";
+ target = "you";
+ mons_targ_aligned = true;
+ }
+ }
+ else if (visible_path && m && you.can_see(m))
+ {
+ bool is_aligned = mons_aligned(m->mindex(), monster->mindex());
+ std::string name = m->name(DESC_NOCAP_THE);
+
+ if (target == "nothing")
+ {
+ mons_targ_aligned = is_aligned;
+ target = name;
+ }
+ // If the first target was aligned with the beam source then
+ // the first subsequent non-aligned monster in the path will
+ // take it's place.
+ else if (mons_targ_aligned && !is_aligned)
+ {
+ mons_targ_aligned = false;
+ target = name;
+ }
+ targ_prep = "at";
+ }
+ else if (visible_path && target == "nothing")
+ {
+ int count = 0;
+ for (adjacent_iterator ai(pbolt.target); ai; ++ai)
+ {
+ const actor* act = monster_at(*ai);
+ if (act && act != monster && you.can_see(act))
+ {
+ targ_prep = "past";
+ if (act->atype() == ACT_PLAYER
+ || one_chance_in(++count))
+ {
+ target = act->name(DESC_NOCAP_THE);
+ }
+
+ if (act->atype() == ACT_PLAYER)
+ break;
+ }
+ }
+ }
+ } // for (unsigned int i = 0; i < path.size(); i++)
+ } // if (target == "nothing" && targeted)
+
+ const actor* foe = monster->get_foe();
+
+ // If we still can't find what appears to be the target, and the
+ // monster isn't just throwing the spell in a random direction,
+ // we should be able to tell what the monster was aiming for if
+ // we can see the monster's foe and the beam (or the beam path
+ // implied by gesturing). But only if the beam didn't actually hit
+ // anything (but if it did hit something, why didn't that monster
+ // show up in the beam's path?)
+ if (targeted
+ && target == "nothing"
+ && (tracer.foe_info.count + tracer.friend_info.count) == 0
+ && foe != NULL
+ && you.can_see(foe)
+ && !monster->confused()
+ && (visible_beam || gestured))
+ {
+ target = foe->name(DESC_NOCAP_THE);
+ targ_prep = (pbolt.aimed_at_spot ? "next to" : "past");
+ }
+
+ // If the monster gestures to create an invisible beam then
+ // assume that anything close to the beam is the intended target.
+ // Also, if the monster gestures to create a visible beam but it
+ // misses still say that the monster gestured "at" the target,
+ // rather than "past".
+ if (gestured || target == "nothing")
+ targ_prep = "at";
+
+ msg = replace_all(msg, "@at@", targ_prep);
+ msg = replace_all(msg, "@target@", target);
+
+ std::string beam_name;
+ if (!targeted)
+ beam_name = "NON TARGETED BEAM";
+ else if (pbolt.name.empty())
+ beam_name = "INVALID BEAM";
+ else if (!tracer.seen)
+ beam_name = "UNSEEN BEAM";
+ else
+ beam_name = pbolt.get_short_name();
+
+ msg = replace_all(msg, "@beam@", beam_name);
+
+ const msg_channel_type chan =
+ (unseen ? MSGCH_SOUND :
+ mons_friendly_real(monster) ? MSGCH_FRIEND_SPELL
+ : MSGCH_MONSTER_SPELL);
+
+ if (silent)
+ mons_speaks_msg(monster, msg, chan, true);
+ else if (noisy(noise, monster->pos(), monster->mindex()) || !unseen)
+ {
+ // noisy() returns true if the player heard the noise.
+ mons_speaks_msg(monster, msg, chan);
+ }
+}
+
+
+
+
+
+
+
diff --git a/crawl-ref/source/mon-cast.h b/crawl-ref/source/mon-cast.h
new file mode 100644
index 0000000000..5d10125d98
--- /dev/null
+++ b/crawl-ref/source/mon-cast.h
@@ -0,0 +1,25 @@
+/*
+ * File: mon-cast.h
+ * Summary: Monster spell casting.
+ * Written by: Linley Henzell
+ */
+
+#ifndef MONCAST_H
+#define MONCAST_H
+
+#include "enum.h"
+
+class monsters;
+class bolt;
+
+bool handle_mon_spell(monsters *monster, bolt &beem);
+
+bolt mons_spells(monsters *mons, spell_type spell_cast, int power);
+void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
+ bool do_noise = true);
+void mons_cast_noise(monsters *monster, bolt &pbolt, spell_type spell_cast);
+void setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast);
+
+void mons_cast_haunt(monsters *monster);
+
+#endif
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc
index 381fce5e8e..dd7940fd58 100644
--- a/crawl-ref/source/mon-util.cc
+++ b/crawl-ref/source/mon-util.cc
@@ -21,6 +21,7 @@
#include "itemname.h"
#include "kills.h"
#include "los.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
#include "random.h"
@@ -3813,3 +3814,9 @@ mon_resist_def mon_resist_def::operator | (const mon_resist_def &o) const
mon_resist_def c(*this);
return (c |= o);
}
+
+bool player_or_mon_in_sanct(const monsters* monster)
+{
+ return (is_sanctuary(you.pos())
+ || is_sanctuary(monster->pos()));
+}
diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h
index c6470c9169..3b6eb54433 100644
--- a/crawl-ref/source/mon-util.h
+++ b/crawl-ref/source/mon-util.h
@@ -157,17 +157,6 @@ enum mons_class_flags
M_NO_EXP_GAIN = (1<<31) // worth 0 xp
};
-enum mon_event_type
-{
- ME_EVAL, // 0, evaluate monster AI state
- ME_DISTURB, // noisy
- ME_ANNOY, // annoy at range
- ME_ALERT, // alert to presence
- ME_WHACK, // physical attack
- ME_SCARE, // frighten monster
- ME_CORNERED // cannot flee
-};
-
enum mon_intel_type // Must be in increasing intelligence order
{
I_PLANT = 0,
@@ -894,4 +883,6 @@ mon_inv_type item_to_mslot(const item_def &item);
int scan_mon_inv_randarts(const monsters *mon,
artefact_prop_type ra_prop);
+
+bool player_or_mon_in_sanct(const monsters* monster);
#endif
diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc
index 33ec1450e2..8f0eed6800 100644
--- a/crawl-ref/source/monplace.cc
+++ b/crawl-ref/source/monplace.cc
@@ -19,8 +19,9 @@
#include "los.h"
#include "makeitem.h"
#include "message.h"
-#include "monstuff.h"
+#include "mon-behv.h"
#include "mon-pick.h"
+#include "monstuff.h"
#include "mon-util.h"
#include "player.h"
#include "random.h"
diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc
index a12666dc2e..637fb3c078 100644
--- a/crawl-ref/source/monster.cc
+++ b/crawl-ref/source/monster.cc
@@ -17,9 +17,10 @@
#include "items.h"
#include "kills.h"
#include "misc.h"
+#include "mon-abil.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
-#include "mstuff2.h"
#include "mtransit.h"
#include "random.h"
#include "religion.h"
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index 3f4e706d27..0f01faf0e4 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -7,55 +7,36 @@
#include "AppHdr.h"
#include "monstuff.h"
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <algorithm>
+//#include <stdlib.h>
+//#include <string.h>
+//#include <stdio.h>
+//#include <algorithm>
#ifdef TARGET_OS_DOS
#include <conio.h>
#endif
-#include "externs.h"
-
#include "arena.h"
#include "artefact.h"
-#include "beam.h"
#include "cloud.h"
-#include "colour.h"
-#include "database.h"
-#include "debug.h"
#include "delay.h"
-#include "describe.h"
#include "dgnevent.h"
#include "directn.h"
-#include "exclude.h"
-#include "fight.h"
#include "files.h"
-#include "ghost.h"
#include "godabil.h"
-#include "hiscores.h"
-#include "it_use2.h"
-#include "itemname.h"
#include "items.h"
-#include "itemprop.h"
#include "kills.h"
-#include "los.h"
-#include "makeitem.h"
-#include "mapmark.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monspeak.h"
-#include "mon-util.h"
-#include "mutation.h"
-#include "mstuff2.h"
#include "notes.h"
#include "player.h"
+#include "random.h"
#include "religion.h"
#include "spl-mis.h"
#include "spl-util.h"
-#include "spells3.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
@@ -67,38 +48,6 @@
#include "xom.h"
static bool _wounded_damaged(monster_type mon_type);
-static bool _handle_special_ability(monsters *monster, bolt & beem);
-static bool _handle_pickup(monsters *monster);
-static void _handle_behaviour(monsters *monster);
-static void _set_nearest_monster_foe(monsters *monster);
-static void _mons_in_cloud(monsters *monster);
-static bool _mon_can_move_to_pos(const monsters *monster,
- const coord_def& delta,
- bool just_check = false);
-static bool _is_trap_safe(const monsters *monster, const coord_def& where,
- bool just_check = false);
-static bool _monster_move(monsters *monster);
-static spell_type _map_wand_to_mspell(int wand_type);
-static bool _is_item_jelly_edible(const item_def &item);
-
-static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move,
- bool potentially_blocking);
-
-// [dshaligram] Doesn't need to be extern.
-static coord_def mmov;
-
-static const coord_def mon_compass[8] = {
- coord_def(-1,-1), coord_def(0,-1), coord_def(1,-1), coord_def(1,0),
- coord_def( 1, 1), coord_def(0, 1), coord_def(-1,1), coord_def(-1,0)
-};
-
-static bool immobile_monster[MAX_MONSTERS];
-
-// A probably needless optimization: convert the C string "just seen" to
-// a C++ string just once, instead of twice every time a monster moves.
-static const std::string _just_seen("just seen");
-
-#define ENERGY_SUBMERGE(entry) (std::max(entry->energy_usage.swim / 2, 1))
// This function creates an artificial item to represent a mimic's appearance.
// Eventually, mimics could be redone to be more like dancing weapons...
@@ -2012,70 +1961,6 @@ void monster_cleanup(monsters *monster)
you.pet_target = MHITNOT;
}
-static bool _jelly_divide(monsters *parent)
-{
- if (!mons_class_flag(parent->type, M_SPLITS))
- return (false);
-
- const int reqd = std::max(parent->hit_dice * 8, 50);
- if (parent->hit_points < reqd)
- return (false);
-
- monsters *child = NULL;
- coord_def child_spot;
- int num_spots = 0;
-
- // First, find a suitable spot for the child {dlb}:
- for (adjacent_iterator ai(parent->pos()); ai; ++ai)
- if (actor_at(*ai) == NULL && parent->can_pass_through(*ai))
- if ( one_chance_in(++num_spots) )
- child_spot = *ai;
-
- if ( num_spots == 0 )
- return (false);
-
- int k = 0;
-
- // Now that we have a spot, find a monster slot {dlb}:
- for (k = 0; k < MAX_MONSTERS; k++)
- {
- child = &menv[k];
-
- if (child->type == -1)
- break;
- else if (k == MAX_MONSTERS - 1)
- return (false);
- }
-
- // Handle impact of split on parent {dlb}:
- parent->max_hit_points /= 2;
-
- if (parent->hit_points > parent->max_hit_points)
- parent->hit_points = parent->max_hit_points;
-
- parent->init_experience();
- parent->experience = parent->experience * 3 / 5 + 1;
-
- // Create child {dlb}:
- // This is terribly partial and really requires
- // more thought as to generation ... {dlb}
- *child = *parent;
- child->max_hit_points = child->hit_points;
- child->speed_increment = 70 + random2(5);
- child->moveto(child_spot);
-
- mgrd(child->pos()) = k;
-
- if (!simple_monster_message(parent, " splits in two!"))
- if (player_can_hear(parent->pos()) || player_can_hear(child->pos()))
- mpr("You hear a squelching noise.", MSGCH_SOUND);
-
- if (crawl_state.arena)
- arena_placed_monster(child);
-
- return (true);
-}
-
// If you're invis and throw/zap whatever, alerts menv to your position.
void alert_nearby_monsters(void)
{
@@ -2593,52 +2478,6 @@ void slimify_monster(monsters *mon, bool hostile)
mons_make_god_gift(mon, GOD_JIYVA);
}
-static void _set_random_target(monsters* mon)
-{
- mon->target = random_in_bounds(); // If we don't find anything better.
- for (int tries = 0; tries < 150; ++tries)
- {
- coord_def delta = coord_def(random2(13), random2(13)) - coord_def(6, 6);
- if (delta.origin())
- continue;
-
- const coord_def newtarget = delta + mon->pos();
- if (!in_bounds(newtarget))
- continue;
-
- mon->target = newtarget;
- break;
- }
-}
-
-static void _set_random_slime_target(monsters* mon)
-{
- // Strictly neutral slimes will go for the nearest item.
- int item_idx;
- coord_def orig_target = mon->target;
-
- for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false); ri; ++ri)
- {
- item_idx = igrd(*ri);
- if (item_idx != NON_ITEM)
- {
- for (stack_iterator si(*ri); si; ++si)
- {
- item_def& item(*si);
-
- if (_is_item_jelly_edible(item))
- {
- mon->target = *ri;
- break;
- }
- }
- }
- }
-
- if (mon->target == mon->pos() || mon->target == you.pos())
- _set_random_target(mon);
-}
-
// allow_adjacent: allow target to be adjacent to origin.
// restrict_LOS: restrict target to be within PLAYER line of sight.
bool random_near_space(const coord_def& origin, coord_def& target,
@@ -2958,552 +2797,6 @@ static bool _wounded_damaged(monster_type mon_type)
return (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT);
}
-//---------------------------------------------------------------
-//
-// behaviour_event
-//
-// 1. Change any of: monster state, foe, and attitude
-// 2. Call handle_behaviour to re-evaluate AI state and target x,y
-//
-//---------------------------------------------------------------
-void behaviour_event(monsters *mon, mon_event_type event, int src,
- coord_def src_pos, bool allow_shout)
-{
- ASSERT(src >= 0 && src <= MHITYOU);
- ASSERT(!crawl_state.arena || src != MHITYOU);
- ASSERT(in_bounds(src_pos) || src_pos.origin());
-
- const beh_type old_behaviour = mon->behaviour;
-
- bool isSmart = (mons_intel(mon) > I_ANIMAL);
- bool wontAttack = mons_wont_attack_real(mon);
- bool sourceWontAttack = false;
- bool setTarget = false;
- bool breakCharm = false;
- bool was_sleeping = mon->asleep();
-
- if (src == MHITYOU)
- sourceWontAttack = true;
- else if (src != MHITNOT)
- sourceWontAttack = mons_wont_attack_real( &menv[src] );
-
- if (is_sanctuary(mon->pos()) && mons_is_fleeing_sanctuary(mon))
- {
- mon->behaviour = BEH_FLEE;
- mon->foe = MHITYOU;
- mon->target = env.sanctuary_pos;
- return;
- }
-
- switch (event)
- {
- case ME_DISTURB:
- // Assumes disturbed by noise...
- if (mon->asleep())
- {
- mon->behaviour = BEH_WANDER;
-
- if (mons_near(mon))
- remove_auto_exclude(mon, true);
- }
-
- // A bit of code to make Projected Noise actually do
- // something again. Basically, dumb monsters and
- // monsters who aren't otherwise occupied will at
- // least consider the (apparent) source of the noise
- // interesting for a moment. -- bwr
- if (!isSmart || mon->foe == MHITNOT || mons_is_wandering(mon))
- {
- if (mon->is_patrolling())
- break;
-
- ASSERT(!src_pos.origin());
- mon->target = src_pos;
- }
- break;
-
- case ME_WHACK:
- case ME_ANNOY:
- // Will turn monster against <src>, unless they
- // are BOTH friendly or good neutral AND stupid,
- // or else fleeing anyway. Hitting someone over
- // the head, of course, always triggers this code.
- if (event == ME_WHACK
- || ((wontAttack != sourceWontAttack || isSmart)
- && !mons_is_fleeing(mon) && !mons_is_panicking(mon)))
- {
- // Monster types that you can't gain experience from cannot
- // fight back, so don't bother having them do so. If you
- // worship Feawn, create a ring of friendly plants, and try
- // to break out of the ring by killing a plant, you'll get
- // a warning prompt and penance only once. Without the
- // hostility check, the plant will remain friendly until it
- // dies, and you'll get a warning prompt and penance once
- // *per hit*. This may not be the best way to address the
- // issue, though. -cao
- if (mons_class_flag(mon->type, M_NO_EXP_GAIN)
- && mon->attitude != ATT_FRIENDLY
- && mon->attitude != ATT_GOOD_NEUTRAL)
- {
- return;
- }
-
- mon->foe = src;
-
- if (mon->asleep() && mons_near(mon))
- remove_auto_exclude(mon, true);
-
- if (!mons_is_cornered(mon))
- mon->behaviour = BEH_SEEK;
-
- if (src == MHITYOU)
- {
- mon->attitude = ATT_HOSTILE;
- breakCharm = true;
- }
- }
-
- // Now set target so that monster can whack back (once) at an
- // invisible foe.
- if (event == ME_WHACK)
- setTarget = true;
- break;
-
- case ME_ALERT:
- // Allow monsters falling asleep while patrolling (can happen if
- // they're left alone for a long time) to be woken by this event.
- if (mons_friendly(mon) && mon->is_patrolling()
- && !mon->asleep())
- {
- break;
- }
-
- if (mon->asleep() && mons_near(mon))
- remove_auto_exclude(mon, true);
-
- // Will alert monster to <src> and turn them
- // against them, unless they have a current foe.
- // It won't turn friends hostile either.
- if (!mons_is_fleeing(mon) && !mons_is_panicking(mon)
- && !mons_is_cornered(mon))
- {
- mon->behaviour = BEH_SEEK;
- }
-
- if (mon->foe == MHITNOT)
- mon->foe = src;
-
- if (!src_pos.origin()
- && (mon->foe == MHITNOT || mon->foe == src
- || mons_is_wandering(mon)))
- {
- if (mon->is_patrolling())
- break;
-
- mon->target = src_pos;
-
- // XXX: Should this be done in _handle_behaviour()?
- if (src == MHITYOU && src_pos == you.pos()
- && !see_cell(mon->pos()))
- {
- const dungeon_feature_type can_move =
- (mons_amphibious(mon)) ? DNGN_DEEP_WATER
- : DNGN_SHALLOW_WATER;
-
- _try_pathfind(mon, can_move, true);
- }
- }
- break;
-
- case ME_SCARE:
- // Stationary monsters can't flee, and berserking monsters
- // are too enraged.
- if (mons_is_stationary(mon) || mon->has_ench(ENCH_BERSERK))
- {
- mon->del_ench(ENCH_FEAR, true, true);
- break;
- }
-
- // Neither do plants or nonliving beings.
- if (mon->holiness() == MH_PLANT
- || mon->holiness() == MH_NONLIVING)
- {
- mon->del_ench(ENCH_FEAR, true, true);
- break;
- }
-
- // Assume monsters know where to run from, even if player is
- // invisible.
- mon->behaviour = BEH_FLEE;
- mon->foe = src;
- mon->target = src_pos;
- if (src == MHITYOU)
- {
- // Friendly monsters don't become hostile if you read a
- // scroll of fear, but enslaved ones will.
- // Send friendlies off to a random target so they don't cling
- // to you in fear.
- if (mons_friendly(mon))
- {
- breakCharm = true;
- mon->foe = MHITNOT;
- _set_random_target(mon);
- }
- else
- setTarget = true;
- }
- else if (mons_friendly(mon) && !crawl_state.arena)
- mon->foe = MHITYOU;
-
- if (see_cell(mon->pos()))
- learned_something_new(TUT_FLEEING_MONSTER);
- break;
-
- case ME_CORNERED:
- // Some monsters can't flee.
- if (mon->behaviour != BEH_FLEE && !mon->has_ench(ENCH_FEAR))
- break;
-
- // Pacified monsters shouldn't change their behaviour.
- if (mons_is_pacified(mon))
- break;
-
- // Just set behaviour... foe doesn't change.
- if (!mons_is_cornered(mon))
- {
- if (mons_friendly(mon) && !crawl_state.arena)
- {
- mon->foe = MHITYOU;
- simple_monster_message(mon, " returns to your side!");
- }
- else
- simple_monster_message(mon, " turns to fight!");
- }
-
- mon->behaviour = BEH_CORNERED;
- break;
-
- case ME_EVAL:
- break;
- }
-
- if (setTarget)
- {
- if (src == MHITYOU)
- {
- mon->target = you.pos();
- mon->attitude = ATT_HOSTILE;
- }
- else if (src != MHITNOT)
- mon->target = menv[src].pos();
- }
-
- // Now, break charms if appropriate.
- if (breakCharm)
- mon->del_ench(ENCH_CHARM);
-
- // Do any resultant foe or state changes.
- _handle_behaviour(mon);
- ASSERT(in_bounds(mon->target) || mon->target.origin());
-
- // If it woke up and you're its new foe, it might shout.
- if (was_sleeping && !mon->asleep() && allow_shout
- && mon->foe == MHITYOU && !mons_wont_attack(mon))
- {
- handle_monster_shouts(mon);
- }
-
- const bool wasLurking =
- (old_behaviour == BEH_LURK && !mons_is_lurking(mon));
- const bool isPacified = mons_is_pacified(mon);
-
- if ((wasLurking || isPacified)
- && (event == ME_DISTURB || event == ME_ALERT || event == ME_EVAL))
- {
- // Lurking monsters or pacified monsters leaving the level won't
- // stop doing so just because they noticed something.
- mon->behaviour = old_behaviour;
- }
- else if (wasLurking && mon->has_ench(ENCH_SUBMERGED)
- && !mon->del_ench(ENCH_SUBMERGED))
- {
- // The same goes for lurking submerged monsters, if they can't
- // unsubmerge.
- mon->behaviour = BEH_LURK;
- }
-
- ASSERT(!crawl_state.arena
- || mon->foe != MHITYOU && mon->target != you.pos());
-}
-
-static bool _choose_random_patrol_target_grid(monsters *mon)
-{
- const int intel = mons_intel(mon);
-
- // Zombies will occasionally just stand around.
- // This does not mean that they don't move every second turn. Rather,
- // once they reach their chosen target, there's a 50% chance they'll
- // just remain there until next turn when this function is called
- // again.
- if (intel == I_PLANT && coinflip())
- return (true);
-
- // If there's no chance we'll find the patrol point, quit right away.
- if (grid_distance(mon->pos(), mon->patrol_point) > 2 * LOS_RADIUS)
- return (false);
-
- // Can the monster see the patrol point from its current position?
- const bool patrol_seen = mon->mon_see_cell(mon->patrol_point,
- habitat2grid(mons_primary_habitat(mon)));
-
- if (intel == I_PLANT && !patrol_seen)
- {
- // Really stupid monsters won't even try to get back into the
- // patrol zone.
- return (false);
- }
-
- // While the patrol point is in easy reach, monsters of insect/plant
- // intelligence will only use a range of 5 (distance from the patrol point).
- // Otherwise, try to get back using the full LOS.
- const int rad = (intel >= I_ANIMAL || !patrol_seen) ? LOS_RADIUS : 5;
- const bool is_smart = (intel >= I_NORMAL);
-
- los_def patrol(mon->patrol_point, opacity_monmove(*mon), bounds_radius(rad));
- patrol.update();
- los_def lm(mon->pos(), opacity_monmove(*mon));
- if (is_smart || !patrol_seen)
- {
- // For stupid monsters, don't bother if the patrol point is in sight.
- lm.update();
- }
-
- int count_grids = 0;
- for (radius_iterator ri(mon->patrol_point, LOS_RADIUS, true, false);
- ri; ++ri)
- {
- // Don't bother for the current position. If everything fails,
- // we'll stay here anyway.
- if (*ri == mon->pos())
- continue;
-
- if (!mon->can_pass_through_feat(grd(*ri)))
- continue;
-
- // Don't bother moving to squares (currently) occupied by a
- // monster. We'll usually be able to find other target squares
- // (and if we're not, we couldn't move anyway), and this avoids
- // monsters trying to move onto a grid occupied by a plant or
- // sleeping monster.
- if (monster_at(*ri))
- continue;
-
- if (patrol_seen)
- {
- // If the patrol point can be easily (within LOS) reached
- // from the current position, it suffices if the target is
- // within reach of the patrol point OR the current position:
- // we can easily get there.
- // Only smart monsters will even attempt to move out of the
- // patrol area.
- // NOTE: Either of these can take us into a position where the
- // target cannot be easily reached (e.g. blocked by a wall)
- // and the patrol point is out of sight, too. Such a case
- // will be handled below, though it might take a while until
- // a monster gets out of a deadlock. (5% chance per turn.)
- if (!patrol.see_cell(*ri) &&
- (!is_smart || !lm.see_cell(*ri)))
- {
- continue;
- }
- }
- else
- {
- // If, however, the patrol point is out of reach, we have to
- // make sure the new target brings us into reach of it.
- // This means that the target must be reachable BOTH from
- // the patrol point AND the current position.
- if (!patrol.see_cell(*ri) ||
- !lm.see_cell(*ri))
- {
- continue;
- }
-
- // If this fails for all surrounding squares (probably because
- // we're too far away), we fall back to heading directly for
- // the patrol point.
- }
-
- bool set_target = false;
- if (intel == I_PLANT && *ri == mon->patrol_point)
- {
- // Slightly greater chance to simply head for the centre.
- count_grids += 3;
- if (x_chance_in_y(3, count_grids))
- set_target = true;
- }
- else if (one_chance_in(++count_grids))
- set_target = true;
-
- if (set_target)
- mon->target = *ri;
- }
-
- return (count_grids);
-}
-
-//#define DEBUG_PATHFIND
-
-// Check all grids in LoS and mark lava and/or water as seen if the
-// appropriate grids are encountered, so we later only need to do the
-// visibility check for monsters that can't pass a feature potentially in
-// the way. We don't care about shallow water as most monsters can safely
-// cross that, and fire elementals alone aren't really worth the extra
-// hassle. :)
-static void _check_lava_water_in_sight()
-{
- you.lava_in_sight = you.water_in_sight = 0;
- for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri)
- {
- // XXX: remove explicit coordinate translation.
- const coord_def ep = *ri - you.pos() + coord_def(ENV_SHOW_OFFSET,
- ENV_SHOW_OFFSET);
- if (env.show(ep))
- {
- const dungeon_feature_type feat = grd(*ri);
- if (feat == DNGN_LAVA)
- {
- you.lava_in_sight = 1;
- if (you.water_in_sight > 0)
- break;
- }
- else if (feat == DNGN_DEEP_WATER)
- {
- you.water_in_sight = 1;
- if (you.lava_in_sight > 0)
- break;
- }
- }
- }
-}
-
-// If a monster can see but not directly reach the target, and then fails to
-// find a path to get there, mark all surrounding (in a radius of 2) monsters
-// of the same (or greater) movement restrictions as also being unable to
-// find a path, so we won't need to calculate again.
-// Should there be a direct path to the target for a monster thus marked, it
-// will still be able to come nearer (and the mark will then be cleared).
-static void _mark_neighbours_target_unreachable(monsters *mon)
-{
- // Highly intelligent monsters are perfectly capable of pathfinding
- // and don't need their neighbour's advice.
- const mon_intel_type intel = mons_intel(mon);
- if (intel > I_NORMAL)
- return;
-
- const bool flies = mons_flies(mon);
- const bool amphibious = mons_amphibious(mon);
- const habitat_type habit = mons_primary_habitat(mon);
-
- for (radius_iterator ri(mon->pos(), 2, true, false); ri; ++ri)
- {
- if (*ri == mon->pos())
- continue;
-
- // Don't alert monsters out of sight (e.g. on the other side of
- // a wall).
- if (!mon->mon_see_cell(*ri))
- continue;
-
- monsters* const m = monster_at(*ri);
- if (m == NULL)
- continue;
-
- // Don't restrict smarter monsters as they might find a path
- // a dumber monster wouldn't.
- if (mons_intel(m) > intel)
- continue;
-
- // Monsters of differing habitats might prefer different routes.
- if (mons_primary_habitat(m) != habit)
- continue;
-
- // A flying monster has an advantage over a non-flying one.
- if (!flies && mons_flies(m))
- continue;
-
- // Same for a swimming one, around water.
- if (you.water_in_sight > 0 && !amphibious && mons_amphibious(m))
- continue;
-
- if (m->travel_target == MTRAV_NONE)
- m->travel_target = MTRAV_UNREACHABLE;
- }
-}
-
-static bool _is_level_exit(const coord_def& pos)
-{
- // All types of stairs.
- if (feat_is_stair(grd(pos)))
- return (true);
-
- // Teleportation and shaft traps.
- const trap_type tt = get_trap_type(pos);
- if (tt == TRAP_TELEPORT || tt == TRAP_SHAFT)
- return (true);
-
- return (false);
-}
-
-static void _find_all_level_exits(std::vector<level_exit> &e)
-{
- e.clear();
-
- for (rectangle_iterator ri(1); ri; ++ri)
- {
- if (!in_bounds(*ri))
- continue;
-
- if (_is_level_exit(*ri))
- e.push_back(level_exit(*ri, false));
- }
-}
-
-static int _mons_find_nearest_level_exit(const monsters *mon,
- std::vector<level_exit> &e,
- bool reset = false)
-{
- if (e.empty() || reset)
- _find_all_level_exits(e);
-
- int retval = -1;
- int old_dist = -1;
-
- for (unsigned int i = 0; i < e.size(); ++i)
- {
- if (e[i].unreachable)
- continue;
-
- int dist = grid_distance(mon->pos(), e[i].target);
-
- if (old_dist == -1 || old_dist >= dist)
- {
- // Ignore teleportation and shaft traps that the monster
- // shouldn't know about.
- if (!mons_is_native_in_branch(mon)
- && grd(e[i].target) == DNGN_UNDISCOVERED_TRAP)
- {
- continue;
- }
-
- retval = i;
- old_dist = dist;
- }
- }
-
- return (retval);
-}
-
// If _mons_find_level_exits() is ever expanded to handle more grid
// types, this should be expanded along with it.
static void _mons_indicate_level_exit(const monsters *mon)
@@ -3547,23 +2840,6 @@ void make_mons_leave_level(monsters *mon)
}
}
-static void _set_no_path_found(monsters *mon)
-{
-#ifdef DEBUG_PATHFIND
- mpr("No path found!");
-#endif
-
- mon->travel_target = MTRAV_UNREACHABLE;
- // Pass information on to nearby monsters.
- _mark_neighbours_target_unreachable(mon);
-}
-
-static bool _target_is_unreachable(monsters *mon)
-{
- return (mon->travel_target == MTRAV_UNREACHABLE
- || mon->travel_target == MTRAV_KNOWN_UNREACHABLE);
-}
-
// Checks whether there is a straight path from p1 to p2 that passes
// through features >= allowed.
// If it exists, such a path may be missed; on the other hand, it
@@ -3583,1295 +2859,6 @@ bool can_go_straight(const coord_def& p1, const coord_def& p2,
true, true));
}
-// The monster is trying to get to the player (MHITYOU).
-// Check whether there's an unobstructed path to the player (in sight!),
-// either by using an existing travel_path or calculating a new one.
-// Returns true if no further handling necessary, else false.
-static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move,
- bool potentially_blocking)
-{
- // Just because we can *see* the player, that doesn't mean
- // we can actually get there. To find about that, we first
- // check for transparent walls. If there are transparent
- // walls in the way we'll need pathfinding, no matter what.
- // (Though monsters with a los attack don't need to get any
- // closer to hurt the player.)
- // If no walls are detected, there could still be a river
- // or a pool of lava in the way. So we check whether there
- // is water or lava in LoS (boolean) and if so, try to find
- // a way around it. It's possible that the player can see
- // lava but it actually has no influence on the monster's
- // movement (because it's lying in the opposite direction)
- // but if so, we'll find that out during path finding.
- // In another attempt of optimization, don't bother with
- // path finding if the monster in question has no trouble
- // travelling through water or flying across lava.
- // Also, if no path is found (too far away, perhaps) set a
- // flag, so we don't directly calculate the whole thing again
- // next turn, and even extend that flag to neighbouring
- // monsters of similar movement restrictions.
-
- // Smart monsters that can fire through walls won't use
- // pathfinding, and it's also not necessary if the monster
- // is already adjacent to you.
- if (potentially_blocking && mons_intel(mon) >= I_NORMAL
- && !mons_friendly(mon) && mons_has_los_ability(mon->type)
- || grid_distance(mon->pos(), you.pos()) == 1)
- {
- potentially_blocking = false;
- }
- else
- {
- // If we don't already know whether there's water or lava
- // in LoS of the player, find out now.
- if (you.lava_in_sight == -1 || you.water_in_sight == -1)
- _check_lava_water_in_sight();
-
- // Flying monsters don't see water/lava as obstacle.
- // Also don't use pathfinding if the monster can shoot
- // across the blocking terrain, and is smart enough to
- // realise that.
- if (!potentially_blocking && !mons_flies(mon)
- && (mons_intel(mon) < I_NORMAL
- || mons_friendly(mon)
- || (!mons_has_ranged_spell(mon, true)
- && !mons_has_ranged_attack(mon))))
- {
- const habitat_type habit = mons_primary_habitat(mon);
- if (you.lava_in_sight > 0 && habit != HT_LAVA
- || you.water_in_sight > 0 && habit != HT_WATER
- && can_move != DNGN_DEEP_WATER)
- {
- potentially_blocking = true;
- }
- }
- }
-
- if (!potentially_blocking
- || can_go_straight(mon->pos(), you.pos(), can_move))
- {
- // The player is easily reachable.
- // Clear travel path and target, if necessary.
- if (mon->travel_target != MTRAV_PATROL
- && mon->travel_target != MTRAV_NONE)
- {
- if (mon->is_travelling())
- mon->travel_path.clear();
- mon->travel_target = MTRAV_NONE;
- }
- return (false);
- }
-
- // Even if the target has been to "unreachable" (the monster already tried,
- // and failed, to find a path) there's a chance of trying again.
- if (!_target_is_unreachable(mon) || one_chance_in(12))
- {
-#ifdef DEBUG_PATHFIND
- mprf("%s: Player out of reach! What now?",
- mon->name(DESC_PLAIN).c_str());
-#endif
- // If we're already on our way, do nothing.
- if (mon->is_travelling() && mon->travel_target == MTRAV_PLAYER)
- {
- const int len = mon->travel_path.size();
- const coord_def targ = mon->travel_path[len - 1];
-
- // Current target still valid?
- if (can_go_straight(targ, you.pos(), can_move))
- {
- // Did we reach the target?
- if (mon->pos() == mon->travel_path[0])
- {
- // Get next waypoint.
- mon->travel_path.erase( mon->travel_path.begin() );
-
- if (!mon->travel_path.empty())
- {
- mon->target = mon->travel_path[0];
- return (true);
- }
- }
- else if (can_go_straight(mon->pos(), mon->travel_path[0],
- can_move))
- {
- mon->target = mon->travel_path[0];
- return (true);
- }
- }
- }
-
- // Use pathfinding to find a (new) path to the player.
- const int dist = grid_distance(mon->pos(), you.pos());
-
-#ifdef DEBUG_PATHFIND
- mprf("Need to calculate a path... (dist = %d)", dist);
-#endif
- const int range = mons_tracking_range(mon);
- if (range > 0 && dist > range)
- {
- mon->travel_target = MTRAV_UNREACHABLE;
-#ifdef DEBUG_PATHFIND
- mprf("Distance too great, don't attempt pathfinding! (%s)",
- mon->name(DESC_PLAIN).c_str());
-#endif
- return (false);
- }
-
-#ifdef DEBUG_PATHFIND
- mprf("Need a path for %s from (%d, %d) to (%d, %d), max. dist = %d",
- mon->name(DESC_PLAIN).c_str(), mon->pos(), you.pos(), range);
-#endif
- monster_pathfind mp;
- if (range > 0)
- mp.set_range(range);
-
- if (mp.init_pathfind(mon, you.pos()))
- {
- mon->travel_path = mp.calc_waypoints();
- if (!mon->travel_path.empty())
- {
- // Okay then, we found a path. Let's use it!
- mon->target = mon->travel_path[0];
- mon->travel_target = MTRAV_PLAYER;
- return (true);
- }
- else
- _set_no_path_found(mon);
- }
- else
- _set_no_path_found(mon);
- }
-
- // We didn't find a path.
- return (false);
-}
-
-// Returns true if a monster left the level.
-static bool _pacified_leave_level(monsters *mon, std::vector<level_exit> e,
- int e_index)
-{
- // If a pacified monster is leaving the level, and has reached an
- // exit (whether that exit was its target or not), handle it here.
- // Likewise, if a pacified monster is far enough away from the
- // player, make it leave the level.
- if (_is_level_exit(mon->pos())
- || (e_index != -1 && mon->pos() == e[e_index].target)
- || grid_distance(mon->pos(), you.pos()) >= LOS_RADIUS * 4)
- {
- make_mons_leave_level(mon);
- return (true);
- }
-
- return (false);
-}
-
-// Counts deep water twice.
-static int _count_water_neighbours(coord_def p)
-{
- int water_count = 0;
- for (adjacent_iterator ai(p); ai; ++ai)
- {
- if (grd(*ai) == DNGN_SHALLOW_WATER)
- water_count++;
- else if (grd(*ai) == DNGN_DEEP_WATER)
- water_count += 2;
- }
- return (water_count);
-}
-
-// Pick the nearest water grid that is surrounded by the most
-// water squares within LoS.
-static bool _find_siren_water_target(monsters *mon)
-{
- ASSERT(mon->type == MONS_SIREN);
-
- // Moving away could break the entrancement, so don't do this.
- if ((mon->pos() - you.pos()).rdist() >= 6)
- return (false);
-
- // Already completely surrounded by deep water.
- if (_count_water_neighbours(mon->pos()) >= 16)
- return (true);
-
- if (mon->travel_target == MTRAV_SIREN)
- {
- coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]);
-#ifdef DEBUG_PATHFIND
- mprf("siren target is (%d, %d), dist = %d",
- targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist());
-#endif
- if ((mon->pos() - targ_pos).rdist() > 2)
- return (true);
- }
-
- int best_water_count = 0;
- coord_def best_target;
- bool first = true;
-
- while (true)
- {
- int best_num = 0;
- for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false);
- ri; ++ri)
- {
- if (!feat_is_water(grd(*ri)))
- continue;
-
- // In the first iteration only count water grids that are
- // not closer to the player than to the siren.
- if (first && (mon->pos() - *ri).rdist() > (you.pos() - *ri).rdist())
- continue;
-
- // Counts deep water twice.
- const int water_count = _count_water_neighbours(*ri);
- if (water_count < best_water_count)
- continue;
-
- if (water_count > best_water_count)
- {
- best_water_count = water_count;
- best_target = *ri;
- best_num = 1;
- }
- else // water_count == best_water_count
- {
- const int old_dist = (mon->pos() - best_target).rdist();
- const int new_dist = (mon->pos() - *ri).rdist();
- if (new_dist > old_dist)
- continue;
-
- if (new_dist < old_dist)
- {
- best_target = *ri;
- best_num = 1;
- }
- else if (one_chance_in(++best_num))
- best_target = *ri;
- }
- }
-
- if (!first || best_water_count > 0)
- break;
-
- // Else start the second iteration.
- first = false;
- }
-
- if (!best_water_count)
- return (false);
-
- // We're already optimally placed.
- if (best_target == mon->pos())
- return (true);
-
- monster_pathfind mp;
-#ifdef WIZARD
- // Remove old highlighted areas to make place for the new ones.
- for (rectangle_iterator ri(1); ri; ++ri)
- env.map(*ri).property &= ~(FPROP_HIGHLIGHT);
-#endif
-
- if (mp.init_pathfind(mon, best_target))
- {
- mon->travel_path = mp.calc_waypoints();
-
- if (!mon->travel_path.empty())
- {
-#ifdef WIZARD
- for (unsigned int i = 0; i < mon->travel_path.size(); i++)
- env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT;
-#endif
-#ifdef DEBUG_PATHFIND
- mprf("Found a path to (%d, %d) with %d surrounding water squares",
- best_target.x, best_target.y, best_water_count);
-#endif
- // Okay then, we found a path. Let's use it!
- mon->target = mon->travel_path[0];
- mon->travel_target = MTRAV_SIREN;
- return (true);
- }
- }
-
- return (false);
-}
-
-static bool _find_wall_target(monsters *mon)
-{
- ASSERT(mons_wall_shielded(mon));
-
- if (mon->travel_target == MTRAV_WALL)
- {
- coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]);
-
- // Target grid might have changed since we started, like if the
- // player destroys the wall the monster wants to hide in.
- if (cell_is_solid(targ_pos)
- && monster_habitable_grid(mon, grd(targ_pos)))
- {
- // Wall is still good.
-#ifdef DEBUG_PATHFIND
- mprf("%s target is (%d, %d), dist = %d",
- mon->name(DESC_PLAIN, true).c_str(),
- targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist());
-#endif
- return (true);
- }
-
- mon->travel_path.clear();
- mon->travel_target = MTRAV_NONE;
- }
-
- int best_dist = INT_MAX;
- bool best_closer_to_player = false;
- coord_def best_target;
-
- for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false);
- ri; ++ri)
- {
- if (!cell_is_solid(*ri)
- || !monster_habitable_grid(mon, grd(*ri)))
- {
- continue;
- }
-
- int dist = (mon->pos() - *ri).rdist();
- bool closer_to_player = false;
- if (dist > (you.pos() - *ri).rdist())
- closer_to_player = true;
-
- if (dist < best_dist)
- {
- best_dist = dist;
- best_closer_to_player = closer_to_player;
- best_target = *ri;
- }
- else if (best_closer_to_player && !closer_to_player
- && dist == best_dist)
- {
- best_closer_to_player = false;
- best_target = *ri;
- }
- }
-
- if (best_dist == INT_MAX || !in_bounds(best_target))
- return (false);
-
- monster_pathfind mp;
-#ifdef WIZARD
- // Remove old highlighted areas to make place for the new ones.
- for (rectangle_iterator ri(1); ri; ++ri)
- env.map(*ri).property &= ~(FPROP_HIGHLIGHT);
-#endif
-
- if (mp.init_pathfind(mon, best_target))
- {
- mon->travel_path = mp.calc_waypoints();
-
- if (!mon->travel_path.empty())
- {
-#ifdef WIZARD
- for (unsigned int i = 0; i < mon->travel_path.size(); i++)
- env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT;
-#endif
-#ifdef DEBUG_PATHFIND
- mprf("Found a path to (%d, %d)", best_target.x, best_target.y);
-#endif
- // Okay then, we found a path. Let's use it!
- mon->target = mon->travel_path[0];
- mon->travel_target = MTRAV_WALL;
- return (true);
- }
- }
- return (false);
-}
-
-// Returns true if further handling neeeded.
-static bool _handle_monster_travelling(monsters *mon,
- const dungeon_feature_type can_move)
-{
-#ifdef DEBUG_PATHFIND
- mprf("Monster %s reached target (%d, %d)",
- mon->name(DESC_PLAIN).c_str(), mon->target.x, mon->target.y);
-#endif
-
- // Hey, we reached our first waypoint!
- if (mon->pos() == mon->travel_path[0])
- {
-#ifdef DEBUG_PATHFIND
- mpr("Arrived at first waypoint.");
-#endif
- mon->travel_path.erase( mon->travel_path.begin() );
- if (mon->travel_path.empty())
- {
-#ifdef DEBUG_PATHFIND
- mpr("We reached the end of our path: stop travelling.");
-#endif
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- else
- {
- mon->target = mon->travel_path[0];
-#ifdef DEBUG_PATHFIND
- mprf("Next waypoint: (%d, %d)", mon->target.x, mon->target.y);
-#endif
- return (false);
- }
- }
-
- // Can we still see our next waypoint?
- if (!can_go_straight(mon->pos(), mon->travel_path[0], can_move))
- {
-#ifdef DEBUG_PATHFIND
- mpr("Can't see waypoint grid.");
-#endif
- // Apparently we got sidetracked a bit.
- // Check the waypoints vector backwards and pick the first waypoint
- // we can see.
-
- // XXX: Note that this might still not be the best thing to do
- // since another path might be even *closer* to our actual target now.
- // Not by much, though, since the original path was optimal (A*) and
- // the distance between the waypoints is rather small.
-
- int erase = -1; // Erase how many waypoints?
- const int size = mon->travel_path.size();
- for (int i = size - 1; i >= 0; --i)
- {
- if (can_go_straight(mon->pos(), mon->travel_path[i], can_move))
- {
- mon->target = mon->travel_path[i];
- erase = i;
- break;
- }
- }
-
- if (erase > 0)
- {
-#ifdef DEBUG_PATHFIND
- mprf("Need to erase %d of %d waypoints.",
- erase, size);
-#endif
- // Erase all waypoints that came earlier:
- // we don't need them anymore.
- while (0 < erase--)
- mon->travel_path.erase( mon->travel_path.begin() );
- }
- else
- {
- // We can't reach our old path from our current
- // position, so calculate a new path instead.
- monster_pathfind mp;
-
- // The last coordinate in the path vector is our destination.
- const int len = mon->travel_path.size();
- if (mp.init_pathfind(mon, mon->travel_path[len-1]))
- {
- mon->travel_path = mp.calc_waypoints();
- if (!mon->travel_path.empty())
- {
- mon->target = mon->travel_path[0];
-#ifdef DEBUG_PATHFIND
- mprf("Next waypoint: (%d, %d)",
- mon->target.x, mon->target.y);
-#endif
- }
- else
- {
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- }
- else
- {
- // Or just forget about the whole thing.
- mon->travel_path.clear();
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- }
- }
-
- // Else, we can see the next waypoint and are making good progress.
- // Carry on, then!
- return (false);
-}
-
-// Returns true if further handling neeeded.
-static bool _handle_monster_patrolling(monsters *mon)
-{
- if (!_choose_random_patrol_target_grid(mon))
- {
- // If we couldn't find a target that is within easy reach
- // of the monster and close to the patrol point, depending
- // on monster intelligence, do one of the following:
- // * set current position as new patrol point
- // * forget about patrolling
- // * head back to patrol point
-
- if (mons_intel(mon) == I_PLANT)
- {
- // Really stupid monsters forget where they're supposed to be.
- if (mons_friendly(mon))
- {
- // Your ally was told to wait, and wait it will!
- // (Though possibly not where you told it to.)
- mon->patrol_point = mon->pos();
- }
- else
- {
- // Stop patrolling.
- mon->patrol_point.reset();
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- }
- else
- {
- // It's time to head back!
- // Other than for tracking the player, there's currently
- // no distinction between smart and stupid monsters when
- // it comes to travelling back to the patrol point. This
- // is in part due to the flavour of e.g. bees finding
- // their way back to the Hive (and patrolling should
- // really be restricted to cases like this), and for the
- // other part it's not all that important because we
- // calculate the path once and then follow it home, and
- // the player won't ever see the orderly fashion the
- // bees will trudge along.
- // What he will see is them swarming back to the Hive
- // entrance after some time, and that is what matters.
- monster_pathfind mp;
- if (mp.init_pathfind(mon, mon->patrol_point))
- {
- mon->travel_path = mp.calc_waypoints();
- if (!mon->travel_path.empty())
- {
- mon->target = mon->travel_path[0];
- mon->travel_target = MTRAV_PATROL;
- }
- else
- {
- // We're so close we don't even need a path.
- mon->target = mon->patrol_point;
- }
- }
- else
- {
- // Stop patrolling.
- mon->patrol_point.reset();
- mon->travel_target = MTRAV_NONE;
- return (true);
- }
- }
- }
- else
- {
-#ifdef DEBUG_PATHFIND
- mprf("Monster %s (pp: %d, %d) is now patrolling to (%d, %d)",
- mon->name(DESC_PLAIN).c_str(),
- mon->patrol_point.x, mon->patrol_point.y,
- mon->target.x, mon->target.y);
-#endif
- }
-
- return (false);
-}
-
-static void _check_wander_target(monsters *mon, bool isPacified = false,
- dungeon_feature_type can_move = DNGN_UNSEEN)
-{
- // default wander behaviour
- if (mon->pos() == mon->target
- || mons_is_batty(mon) || !isPacified && one_chance_in(20))
- {
- bool need_target = true;
-
- if (!can_move)
- {
- can_move = (mons_amphibious(mon) ? DNGN_DEEP_WATER
- : DNGN_SHALLOW_WATER);
- }
-
- if (mon->is_travelling())
- need_target = _handle_monster_travelling(mon, can_move);
-
- // If we still need a target because we're not travelling
- // (any more), check for patrol routes instead.
- if (need_target && mon->is_patrolling())
- need_target = _handle_monster_patrolling(mon);
-
- // XXX: This is really dumb wander behaviour... instead of
- // changing the goal square every turn, better would be to
- // have the monster store a direction and have the monster
- // head in that direction for a while, then shift the
- // direction to the left or right. We're changing this so
- // wandering monsters at least appear to have some sort of
- // attention span. -- bwr
- if (need_target)
- _set_random_target(mon);
- }
-}
-
-static void _arena_set_foe(monsters *mons)
-{
- const int mind = monster_index(mons);
-
- int nearest = -1;
- int best_distance = -1;
-
- int nearest_unseen = -1;
- int best_unseen_distance = -1;
- for (int i = 0; i < MAX_MONSTERS; ++i)
- {
- if (mind == i)
- continue;
-
- const monsters *other(&menv[i]);
- if (!other->alive() || mons_aligned(mind, i))
- continue;
-
- // Don't fight test spawners, since they're only pseudo-monsters
- // placed to spawn real monsters, plus they're impossible to
- // kill. But test spawners can fight each other, to give them a
- // target to spawn against.
- if (other->type == MONS_TEST_SPAWNER
- && mons->type != MONS_TEST_SPAWNER)
- {
- continue;
- }
-
- const int distance = grid_distance(mons->pos(), other->pos());
- const bool seen = mons->can_see(other);
-
- if (seen)
- {
- if (best_distance == -1 || distance < best_distance)
- {
- best_distance = distance;
- nearest = i;
- }
- }
- else
- {
- if (best_unseen_distance == -1 || distance < best_unseen_distance)
- {
- best_unseen_distance = distance;
- nearest_unseen = i;
- }
- }
-
- if ((best_distance == -1 || distance < best_distance)
- && mons->can_see(other))
-
- {
- best_distance = distance;
- nearest = i;
- }
- }
-
- if (nearest != -1)
- {
- mons->foe = nearest;
- mons->target = menv[nearest].pos();
- mons->behaviour = BEH_SEEK;
- }
- else if (nearest_unseen != -1)
- {
- mons->target = menv[nearest_unseen].pos();
- if (mons->type == MONS_TEST_SPAWNER)
- {
- mons->foe = nearest_unseen;
- mons->behaviour = BEH_SEEK;
- }
- else
- mons->behaviour = BEH_WANDER;
- }
- else
- {
- mons->foe = MHITNOT;
- mons->behaviour = BEH_WANDER;
- }
- if (mons->behaviour == BEH_WANDER)
- _check_wander_target(mons);
-
- ASSERT(mons->foe == MHITNOT || !mons->target.origin());
-}
-
-//---------------------------------------------------------------
-//
-// handle_behaviour
-//
-// 1. Evaluates current AI state
-// 2. Sets monster target x,y based on current foe
-//
-// XXX: Monsters of I_NORMAL or above should select a new target
-// if their current target is another monster which is sitting in
-// a wall and is immune to most attacks while in a wall, unless
-// the monster has a spell or special/nearby ability which isn't
-// affected by the wall.
-//---------------------------------------------------------------
-static void _handle_behaviour(monsters *mon)
-{
- bool changed = true;
- bool isFriendly = mons_friendly(mon);
- bool isNeutral = mons_neutral(mon);
- bool wontAttack = mons_wont_attack_real(mon);
-
- // Whether the player is in LOS of the monster and can see
- // or has guessed the player's location.
- bool proxPlayer = mons_near(mon) && !crawl_state.arena;
-
- bool trans_wall_block = trans_wall_blocking(mon->pos());
-
-#ifdef WIZARD
- // If stealth is greater than actually possible (wizmode level)
- // pretend the player isn't there, but only for hostile monsters.
- if (proxPlayer && you.skills[SK_STEALTH] > 27 && !mons_wont_attack(mon))
- proxPlayer = false;
-#endif
- bool proxFoe;
- bool isHurt = (mon->hit_points <= mon->max_hit_points / 4 - 1);
- bool isHealthy = (mon->hit_points > mon->max_hit_points / 2);
- bool isSmart = (mons_intel(mon) > I_ANIMAL);
- bool isScared = mon->has_ench(ENCH_FEAR);
- bool isMobile = !mons_is_stationary(mon);
- bool isPacified = mons_is_pacified(mon);
- bool patrolling = mon->is_patrolling();
- static std::vector<level_exit> e;
- static int e_index = -1;
-
- // Check for confusion -- early out.
- if (mon->has_ench(ENCH_CONFUSION))
- {
- _set_random_target(mon);
- return;
- }
-
- if (mons_is_fleeing_sanctuary(mon)
- && mons_is_fleeing(mon)
- && is_sanctuary(you.pos()))
- {
- return;
- }
-
- if (crawl_state.arena)
- {
- if (Options.arena_force_ai)
- {
- if (!mon->get_foe() || mon->target.origin() || one_chance_in(3))
- mon->foe = MHITNOT;
- if (mon->foe == MHITNOT || mon->foe == MHITYOU)
- _arena_set_foe(mon);
- return;
- }
- // If we're not forcing monsters to attack, just make sure they're
- // not targetting the player in arena mode.
- else if (mon->foe == MHITYOU)
- mon->foe = MHITNOT;
- }
-
- if (mons_wall_shielded(mon) && cell_is_solid(mon->pos()))
- {
- // Monster is safe, so its behaviour can be simplified to fleeing.
- if (mon->behaviour == BEH_CORNERED || mon->behaviour == BEH_PANIC
- || isScared)
- {
- mon->behaviour = BEH_FLEE;
- }
- }
-
- const dungeon_feature_type can_move =
- (mons_amphibious(mon)) ? DNGN_DEEP_WATER : DNGN_SHALLOW_WATER;
-
- // Validate current target exists.
- if (mon->foe != MHITNOT && mon->foe != MHITYOU)
- {
- const monsters& foe_monster = menv[mon->foe];
- if (!foe_monster.alive())
- mon->foe = MHITNOT;
- if (mons_friendly(&foe_monster) == isFriendly)
- mon->foe = MHITNOT;
- }
-
- // Change proxPlayer depending on invisibility and standing
- // in shallow water.
- if (proxPlayer && !you.visible_to(mon))
- {
- proxPlayer = false;
-
- const int intel = mons_intel(mon);
- // Sometimes, if a player is right next to a monster, they will 'see'.
- if (grid_distance(you.pos(), mon->pos()) == 1
- && one_chance_in(3))
- {
- proxPlayer = true;
- }
-
- // [dshaligram] Very smart monsters have a chance of clueing in to
- // invisible players in various ways.
- if (intel == I_NORMAL && one_chance_in(13)
- || intel == I_HIGH && one_chance_in(6))
- {
- proxPlayer = true;
- }
- }
-
- // Set friendly target, if they don't already have one.
- // Berserking allies ignore your commands!
- if (isFriendly
- && you.pet_target != MHITNOT
- && (mon->foe == MHITNOT || mon->foe == MHITYOU)
- && !mon->has_ench(ENCH_BERSERK)
- && mon->mons_species() != MONS_GIANT_SPORE )
- {
- mon->foe = you.pet_target;
- }
-
- // Instead, berserkers attack nearest monsters.
- if ((mon->has_ench(ENCH_BERSERK) || mon->mons_species() == MONS_GIANT_SPORE)
- && (mon->foe == MHITNOT || isFriendly && mon->foe == MHITYOU))
- {
- // Intelligent monsters prefer to attack the player,
- // even when berserking.
- if (!isFriendly && proxPlayer && mons_intel(mon) >= I_NORMAL)
- mon->foe = MHITYOU;
- else
- _set_nearest_monster_foe(mon);
- }
-
- // Pacified monsters leaving the level prefer not to attack.
- // Others choose the nearest foe.
- if (!isPacified && mon->foe == MHITNOT)
- _set_nearest_monster_foe(mon);
-
- // Monsters do not attack themselves. {dlb}
- if (mon->foe == monster_index(mon))
- mon->foe = MHITNOT;
-
- // Friendly and good neutral monsters do not attack other friendly
- // and good neutral monsters.
- if (mon->foe != MHITNOT && mon->foe != MHITYOU
- && wontAttack && mons_wont_attack_real(&menv[mon->foe]))
- {
- mon->foe = MHITNOT;
- }
-
- // Neutral monsters prefer not to attack players, or other neutrals.
- if (isNeutral && mon->foe != MHITNOT
- && (mon->foe == MHITYOU || mons_neutral(&menv[mon->foe])))
- {
- mon->foe = MHITNOT;
- }
-
- // Unfriendly monsters fighting other monsters will usually
- // target the player, if they're healthy.
- if (!isFriendly && !isNeutral
- && mon->foe != MHITYOU && mon->foe != MHITNOT
- && proxPlayer && !(mon->has_ench(ENCH_BERSERK)) && isHealthy
- && !one_chance_in(3))
- {
- mon->foe = MHITYOU;
- }
-
- // Validate current target again.
- if (mon->foe != MHITNOT && mon->foe != MHITYOU)
- {
- const monsters& foe_monster = menv[mon->foe];
- if (!foe_monster.alive())
- mon->foe = MHITNOT;
- if (mons_friendly(&foe_monster) == isFriendly)
- mon->foe = MHITNOT;
- }
-
- while (changed)
- {
- actor* afoe = mon->get_foe();
- proxFoe = afoe && mon->can_see(afoe);
-
- coord_def foepos = coord_def(0,0);
- if (afoe)
- foepos = afoe->pos();
-
- if (mon->foe == MHITYOU)
- proxFoe = proxPlayer; // Take invis into account.
-
- // Track changes to state; attitude never changes here.
- beh_type new_beh = mon->behaviour;
- unsigned short new_foe = mon->foe;
-
- // Take care of monster state changes.
- switch (mon->behaviour)
- {
- case BEH_SLEEP:
- // default sleep state
- mon->target = mon->pos();
- new_foe = MHITNOT;
- break;
-
- case BEH_LURK:
- case BEH_SEEK:
- // No foe? Then wander or seek the player.
- if (mon->foe == MHITNOT)
- {
- if (crawl_state.arena || !proxPlayer || isNeutral || patrolling)
- new_beh = BEH_WANDER;
- else
- {
- new_foe = MHITYOU;
- mon->target = you.pos();
- }
- break;
- }
-
- // Foe gone out of LOS?
- if (!proxFoe)
- {
- if (mon->travel_target == MTRAV_SIREN)
- mon->travel_target = MTRAV_NONE;
-
- if (mon->foe == MHITYOU && mon->is_travelling()
- && mon->travel_target == MTRAV_PLAYER)
- {
- // We've got a target, so we'll continue on our way.
-#ifdef DEBUG_PATHFIND
- mpr("Player out of LoS... start wandering.");
-#endif
- new_beh = BEH_WANDER;
- break;
- }
-
- if (isFriendly)
- {
- if (patrolling || crawl_state.arena)
- {
- new_foe = MHITNOT;
- new_beh = BEH_WANDER;
- }
- else
- {
- new_foe = MHITYOU;
- mon->target = foepos;
- }
- break;
- }
-
- ASSERT(mon->foe != MHITNOT);
- if (mon->foe_memory > 0)
- {
- // If we've arrived at our target x,y
- // do a stealth check. If the foe
- // fails, monster will then start
- // tracking foe's CURRENT position,
- // but only for a few moves (smell and
- // intuition only go so far).
-
- if (mon->pos() == mon->target)
- {
- if (mon->foe == MHITYOU)
- {
- if (one_chance_in(you.skills[SK_STEALTH]/3))
- mon->target = you.pos();
- else
- mon->foe_memory = 0;
- }
- else
- {
- if (coinflip()) // XXX: cheesy!
- mon->target = menv[mon->foe].pos();
- else
- mon->foe_memory = 0;
- }
- }
-
- // Either keep chasing, or start wandering.
- if (mon->foe_memory < 2)
- {
- mon->foe_memory = 0;
- new_beh = BEH_WANDER;
- }
- break;
- }
-
- ASSERT(mon->foe_memory == 0);
- // Hack: smarter monsters will tend to pursue the player longer.
- switch (mons_intel(mon))
- {
- case I_HIGH:
- mon->foe_memory = 100 + random2(200);
- break;
- case I_NORMAL:
- mon->foe_memory = 50 + random2(100);
- break;
- case I_ANIMAL:
- case I_INSECT:
- mon->foe_memory = 25 + random2(75);
- break;
- case I_PLANT:
- mon->foe_memory = 10 + random2(50);
- break;
- }
- break; // switch/case BEH_SEEK
- }
-
- ASSERT(proxFoe && mon->foe != MHITNOT);
- // Monster can see foe: continue 'tracking'
- // by updating target x,y.
- if (mon->foe == MHITYOU)
- {
- // The foe is the player.
- if (mon->type == MONS_SIREN
- && player_mesmerised_by(mon)
- && _find_siren_water_target(mon))
- {
- break;
- }
-
- if (_try_pathfind(mon, can_move, trans_wall_block))
- break;
-
- // Whew. If we arrived here, path finding didn't yield anything
- // (or wasn't even attempted) and we need to set our target
- // the traditional way.
-
- // Sometimes, your friends will wander a bit.
- if (isFriendly && one_chance_in(8))
- {
- _set_random_target(mon);
- mon->foe = MHITNOT;
- new_beh = BEH_WANDER;
- }
- else
- {
- mon->target = you.pos();
- }
- }
- else
- {
- // We have a foe but it's not the player.
- mon->target = menv[mon->foe].pos();
- }
-
- // Smart monsters, zombified monsters other than spectral
- // things, plants, and nonliving monsters cannot flee.
- if (isHurt && !isSmart && isMobile
- && (!mons_is_zombified(mon) || mon->type == MONS_SPECTRAL_THING)
- && mon->holiness() != MH_PLANT
- && mon->holiness() != MH_NONLIVING)
- {
- new_beh = BEH_FLEE;
- }
- break;
-
- case BEH_WANDER:
- if (isPacified)
- {
- // If a pacified monster isn't travelling toward
- // someplace from which it can leave the level, make it
- // start doing so. If there's no such place, either
- // search the level for such a place again, or travel
- // randomly.
- if (mon->travel_target != MTRAV_PATROL)
- {
- new_foe = MHITNOT;
- mon->travel_path.clear();
-
- e_index = _mons_find_nearest_level_exit(mon, e);
-
- if (e_index == -1 || one_chance_in(20))
- e_index = _mons_find_nearest_level_exit(mon, e, true);
-
- if (e_index != -1)
- {
- mon->travel_target = MTRAV_PATROL;
- patrolling = true;
- mon->patrol_point = e[e_index].target;
- mon->target = e[e_index].target;
- }
- else
- {
- mon->travel_target = MTRAV_NONE;
- patrolling = false;
- mon->patrol_point.reset();
- _set_random_target(mon);
- }
- }
-
- if (_pacified_leave_level(mon, e, e_index))
- return;
- }
-
- if (mons_strict_neutral(mon) && mons_is_slime(mon)
- && you.religion == GOD_JIYVA)
- {
- _set_random_slime_target(mon);
- }
-
- // Is our foe in LOS?
- // Batty monsters don't automatically reseek so that
- // they'll flitter away, we'll reset them just before
- // they get movement in handle_monsters() instead. -- bwr
- if (proxFoe && !mons_is_batty(mon))
- {
- new_beh = BEH_SEEK;
- break;
- }
-
- _check_wander_target(mon, isPacified, can_move);
-
- // During their wanderings, monsters will eventually relax
- // their guard (stupid ones will do so faster, smart
- // monsters have longer memories). Pacified monsters will
- // also eventually switch the place from which they want to
- // leave the level, in case their current choice is blocked.
- if (!proxFoe && mon->foe != MHITNOT
- && one_chance_in(isSmart ? 60 : 20)
- || isPacified && one_chance_in(isSmart ? 40 : 120))
- {
- new_foe = MHITNOT;
- if (mon->is_travelling() && mon->travel_target != MTRAV_PATROL
- || isPacified)
- {
-#ifdef DEBUG_PATHFIND
- mpr("It's been too long! Stop travelling.");
-#endif
- mon->travel_path.clear();
- mon->travel_target = MTRAV_NONE;
-
- if (isPacified && e_index != -1)
- e[e_index].unreachable = true;
- }
- }
- break;
-
- case BEH_FLEE:
- // Check for healed.
- if (isHealthy && !isScared)
- new_beh = BEH_SEEK;
-
- // Smart monsters flee until they can flee no more...
- // possible to get a 'CORNERED' event, at which point
- // we can jump back to WANDER if the foe isn't present.
-
- if (isFriendly)
- {
- // Special-cased below so that it will flee *towards* you.
- if (mon->foe == MHITYOU)
- mon->target = you.pos();
- }
- else if (mons_wall_shielded(mon) && _find_wall_target(mon))
- ; // Wall target found.
- else if (proxFoe)
- {
- // Special-cased below so that it will flee *from* the
- // correct position.
- mon->target = foepos;
- }
- break;
-
- case BEH_CORNERED:
- // Plants and nonliving monsters cannot fight back.
- if (mon->holiness() == MH_PLANT
- || mon->holiness() == MH_NONLIVING)
- {
- break;
- }
-
- if (isHealthy)
- new_beh = BEH_SEEK;
-
- // Foe gone out of LOS?
- if (!proxFoe)
- {
- if ((isFriendly || proxPlayer) && !isNeutral && !patrolling)
- new_foe = MHITYOU;
- else
- new_beh = BEH_WANDER;
- }
- else
- {
- mon->target = foepos;
- }
- break;
-
- default:
- return; // uh oh
- }
-
- changed = (new_beh != mon->behaviour || new_foe != mon->foe);
- mon->behaviour = new_beh;
-
- if (mon->foe != new_foe)
- mon->foe_memory = 0;
-
- mon->foe = new_foe;
- }
-
- if (mon->travel_target == MTRAV_WALL && cell_is_solid(mon->pos()))
- {
- if (mon->behaviour == BEH_FLEE)
- {
- // Monster is safe, so stay put.
- mon->target = mon->pos();
- mon->foe = MHITNOT;
- }
- }
-}
-
-static bool _mons_check_foe(monsters *mon, const coord_def& p,
- bool friendly, bool neutral)
-{
- if (!inside_level_bounds(p))
- return (false);
-
- if (!friendly && !neutral && p == you.pos()
- && you.visible_to(mon) && !is_sanctuary(p))
- {
- return (true);
- }
-
- if (monsters *foe = monster_at(p))
- {
- if (foe != mon
- && mon->can_see(foe)
- && (friendly || !is_sanctuary(p))
- && (mons_friendly(foe) != friendly
- || (neutral && !mons_neutral(foe))))
- {
- return (true);
- }
- }
- return (false);
-}
-
-// Choose random nearest monster as a foe.
-void _set_nearest_monster_foe(monsters *mon)
-{
- const bool friendly = mons_friendly_real(mon);
- const bool neutral = mons_neutral(mon);
-
- for (int k = 1; k <= LOS_RADIUS; ++k)
- {
- std::vector<coord_def> monster_pos;
- for (int i = -k; i <= k; ++i)
- for (int j = -k; j <= k; (abs(i) == k ? j++ : j += 2*k))
- {
- const coord_def p = mon->pos() + coord_def(i, j);
- if (_mons_check_foe(mon, p, friendly, neutral))
- monster_pos.push_back(p);
- }
- if (monster_pos.empty())
- continue;
-
- const coord_def mpos = monster_pos[random2(monster_pos.size())];
- if (mpos == you.pos())
- mon->foe = MHITYOU;
- else
- mon->foe = env.mgrid(mpos);
- return;
- }
-}
-
// The default suitable() function for choose_random_nearby_monster().
bool choose_any_monster(const monsters* mon)
{
@@ -4969,512 +2956,6 @@ bool simple_monster_message(const monsters *monster, const char *event,
return (false);
}
-// Altars as well as branch entrances are considered interesting for
-// some monster types.
-static bool _mon_on_interesting_grid(monsters *mon)
-{
- // Patrolling shouldn't happen all the time.
- if (one_chance_in(4))
- return (false);
-
- const dungeon_feature_type feat = grd(mon->pos());
-
- switch (feat)
- {
- // Holy beings will tend to patrol around altars to the good gods.
- case DNGN_ALTAR_ELYVILON:
- if (!one_chance_in(3))
- return (false);
- // else fall through
- case DNGN_ALTAR_ZIN:
- case DNGN_ALTAR_SHINING_ONE:
- return (mons_is_holy(mon));
-
- // Orcs will tend to patrol around altars to Beogh, and guard the
- // stairway from and to the Orcish Mines.
- case DNGN_ALTAR_BEOGH:
- case DNGN_ENTER_ORCISH_MINES:
- case DNGN_RETURN_FROM_ORCISH_MINES:
- return (mons_is_native_in_branch(mon, BRANCH_ORCISH_MINES));
-
- // Same for elves and the Elven Halls.
- case DNGN_ENTER_ELVEN_HALLS:
- case DNGN_RETURN_FROM_ELVEN_HALLS:
- return (mons_is_native_in_branch(mon, BRANCH_ELVEN_HALLS));
-
- // Killer bees always return to their hive.
- case DNGN_ENTER_HIVE:
- return (mons_is_native_in_branch(mon, BRANCH_HIVE));
-
- default:
- return (false);
- }
-}
-
-// If a hostile monster finds itself on a grid of an "interesting" feature,
-// while unoccupied, it will remain in that area, and try to return to it
-// if it left it for fighting, seeking etc.
-static void _maybe_set_patrol_route(monsters *monster)
-{
- if (mons_is_wandering(monster)
- && !mons_friendly(monster)
- && !monster->is_patrolling()
- && _mon_on_interesting_grid(monster))
- {
- monster->patrol_point = monster->pos();
- }
-}
-
-// Check whether there's a monster of the same type and alignment adjacent
-// to the given monster in at least one of three given directions (relative to
-// the monster position).
-static bool _allied_monster_at(monsters *mon, coord_def a, coord_def b,
- coord_def c)
-{
- std::vector<coord_def> pos;
- pos.push_back(mon->pos() + a);
- pos.push_back(mon->pos() + b);
- pos.push_back(mon->pos() + c);
-
- for (unsigned int i = 0; i < pos.size(); i++)
- {
- if (!in_bounds(pos[i]))
- continue;
-
- const monsters *ally = monster_at(pos[i]);
- if (ally == NULL)
- continue;
-
- if (mons_is_stationary(ally))
- continue;
-
- // Hostile monsters of normal intelligence only move aside for
- // monsters of the same genus.
- if (mons_intel(mon) <= I_NORMAL && !mons_wont_attack_real(mon)
- && mons_genus(mon->type) != mons_genus(ally->type))
- {
- continue;
- }
-
- if (mons_aligned(mon->mindex(), ally->mindex()))
- return (true);
- }
-
- return (false);
-}
-
-// Check up to eight grids in the given direction for whether there's a
-// monster of the same alignment as the given monster that happens to
-// have a ranged attack. If this is true for the first monster encountered,
-// returns true. Otherwise returns false.
-static bool _ranged_allied_monster_in_dir(monsters *mon, coord_def p)
-{
- coord_def pos = mon->pos();
-
- for (int i = 1; i <= LOS_RADIUS; i++)
- {
- pos += p;
- if (!in_bounds(pos))
- break;
-
- const monsters* ally = monster_at(pos);
- if (ally == NULL)
- continue;
-
- if (mons_aligned(mon->mindex(), ally->mindex()))
- {
- // Hostile monsters of normal intelligence only move aside for
- // monsters of the same type.
- if (mons_intel(mon) <= I_NORMAL && !mons_wont_attack(mon)
- && mons_genus(mon->type) != mons_genus(ally->type))
- {
- return (false);
- }
-
- if (mons_has_ranged_attack(ally)
- || mons_has_ranged_spell(ally, true))
- {
- return (true);
- }
- }
- break;
- }
- return (false);
-}
-
-//---------------------------------------------------------------
-//
-// handle_movement
-//
-// Move the monster closer to its target square.
-//
-//---------------------------------------------------------------
-static void _handle_movement(monsters *monster)
-{
- coord_def delta;
-
- _maybe_set_patrol_route(monster);
-
- // Monsters will try to flee out of a sanctuary.
- if (is_sanctuary(monster->pos())
- && mons_is_influenced_by_sanctuary(monster)
- && !mons_is_fleeing_sanctuary(monster))
- {
- mons_start_fleeing_from_sanctuary(monster);
- }
- else if (mons_is_fleeing_sanctuary(monster)
- && !is_sanctuary(monster->pos()))
- {
- // Once outside there's a chance they'll regain their courage.
- // Nonliving and berserking monsters always stop immediately,
- // since they're only being forced out rather than actually
- // scared.
- if (monster->holiness() == MH_NONLIVING
- || monster->has_ench(ENCH_BERSERK)
- || x_chance_in_y(2, 5))
- {
- mons_stop_fleeing_from_sanctuary(monster);
- }
- }
-
- // Some calculations.
- if (mons_class_flag(monster->type, M_BURROWS) && monster->foe == MHITYOU)
- {
- // Boring beetles always move in a straight line in your
- // direction.
- delta = you.pos() - monster->pos();
- }
- else
- {
- delta = monster->target - monster->pos();
-
- if (crawl_state.arena && Options.arena_force_ai
- && !mons_is_stationary(monster))
- {
- const bool ranged = (mons_has_ranged_attack(monster)
- || mons_has_ranged_spell(monster));
-
- // Smiters are happy if they have clear visibility through
- // glass, but other monsters must go around.
- const bool glass_ok = mons_has_smite_attack(monster);
-
- // Monsters in the arena are smarter than the norm and
- // always pathfind to their targets.
- if (delta.abs() > 2
- && (!ranged
- || !monster->mon_see_cell(monster->target, !glass_ok)))
- {
- monster_pathfind mp;
- if (mp.init_pathfind(monster, monster->target))
- delta = mp.next_pos(monster->pos()) - monster->pos();
- }
- }
- }
-
- // Move the monster.
- mmov.x = (delta.x > 0) ? 1 : ((delta.x < 0) ? -1 : 0);
- mmov.y = (delta.y > 0) ? 1 : ((delta.y < 0) ? -1 : 0);
-
- if (mons_is_fleeing(monster) && monster->travel_target != MTRAV_WALL
- && (!mons_friendly(monster)
- || monster->target != you.pos()))
- {
- mmov *= -1;
- }
-
- // Don't allow monsters to enter a sanctuary or attack you inside a
- // sanctuary, even if you're right next to them.
- if (is_sanctuary(monster->pos() + mmov)
- && (!is_sanctuary(monster->pos())
- || monster->pos() + mmov == you.pos()))
- {
- mmov.reset();
- }
-
- // Bounds check: don't let fleeing monsters try to run off the grid.
- const coord_def s = monster->pos() + mmov;
- if (!in_bounds_x(s.x))
- mmov.x = 0;
- if (!in_bounds_y(s.y))
- mmov.y = 0;
-
- // Now quit if we can't move.
- if (mmov.origin())
- return;
-
- if (delta.rdist() > 3)
- {
- // Reproduced here is some semi-legacy code that makes monsters
- // move somewhat randomly along oblique paths. It is an
- // exceedingly good idea, given crawl's unique line of sight
- // properties.
- //
- // Added a check so that oblique movement paths aren't used when
- // close to the target square. -- bwr
-
- // Sometimes we'll just move parallel the x axis.
- if (abs(delta.x) > abs(delta.y) && coinflip())
- mmov.y = 0;
-
- // Sometimes we'll just move parallel the y axis.
- if (abs(delta.y) > abs(delta.x) && coinflip())
- mmov.x = 0;
- }
-
- const coord_def newpos(monster->pos() + mmov);
- FixedArray < bool, 3, 3 > good_move;
-
- for (int count_x = 0; count_x < 3; count_x++)
- for (int count_y = 0; count_y < 3; count_y++)
- {
- const int targ_x = monster->pos().x + count_x - 1;
- const int targ_y = monster->pos().y + count_y - 1;
-
- // Bounds check: don't consider moving out of grid!
- if (!in_bounds(targ_x, targ_y))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- good_move[count_x][count_y] =
- _mon_can_move_to_pos(monster, coord_def(count_x-1, count_y-1));
- }
-
- if (mons_wall_shielded(monster))
- {
- // The rock worm will try to move along through rock for as long as
- // possible. If the player is walking through a corridor, for example,
- // moving along in the wall beside him is much preferable to actually
- // leaving the wall.
- // This might cause the rock worm to take detours but it still
- // comes off as smarter than otherwise.
- if (mmov.x != 0 && mmov.y != 0) // diagonal movement
- {
- bool updown = false;
- bool leftright = false;
-
- coord_def t = monster->pos() + coord_def(mmov.x, 0);
- if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
- updown = true;
-
- t = monster->pos() + coord_def(0, mmov.y);
- if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
- leftright = true;
-
- if (updown && (!leftright || coinflip()))
- mmov.y = 0;
- else if (leftright)
- mmov.x = 0;
- }
- else if (mmov.x == 0 && monster->target.x == monster->pos().x)
- {
- bool left = false;
- bool right = false;
- coord_def t = monster->pos() + coord_def(-1, mmov.y);
- if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
- left = true;
-
- t = monster->pos() + coord_def(1, mmov.y);
- if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
- right = true;
-
- if (left && (!right || coinflip()))
- mmov.x = -1;
- else if (right)
- mmov.x = 1;
- }
- else if (mmov.y == 0 && monster->target.y == monster->pos().y)
- {
- bool up = false;
- bool down = false;
- coord_def t = monster->pos() + coord_def(mmov.x, -1);
- if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
- up = true;
-
- t = monster->pos() + coord_def(mmov.x, 1);
- if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t)))
- down = true;
-
- if (up && (!down || coinflip()))
- mmov.y = -1;
- else if (down)
- mmov.y = 1;
- }
- }
-
- // If the monster is moving in your direction, whether to attack or
- // protect you, or towards a monster it intends to attack, check
- // whether we first need to take a step to the side to make sure the
- // reinforcement can follow through. Only do this with 50% chance,
- // though, so it's not completely predictable.
-
- // First, check whether the monster is smart enough to even consider
- // this.
- if ((newpos == you.pos()
- || mgrd(newpos) != NON_MONSTER && monster->foe == mgrd(newpos))
- && mons_intel(monster) >= I_ANIMAL
- && coinflip()
- && !mons_is_confused(monster) && !mons_is_caught(monster)
- && !monster->has_ench(ENCH_BERSERK))
- {
- // If the monster is moving parallel to the x or y axis, check
- // whether
- //
- // a) the neighbouring grids are blocked
- // b) there are other unblocked grids adjacent to the target
- // c) there's at least one allied monster waiting behind us.
- //
- // (For really smart monsters, also check whether there's a
- // monster farther back in the corridor that has some kind of
- // ranged attack.)
- if (mmov.y == 0)
- {
- if (!good_move[1][0] && !good_move[1][2]
- && (good_move[mmov.x+1][0] || good_move[mmov.x+1][2])
- && (_allied_monster_at(monster, coord_def(-mmov.x, -1),
- coord_def(-mmov.x, 0),
- coord_def(-mmov.x, 1))
- || mons_intel(monster) >= I_NORMAL
- && !mons_wont_attack_real(monster)
- && _ranged_allied_monster_in_dir(monster,
- coord_def(-mmov.x, 0))))
- {
- if (good_move[mmov.x+1][0])
- mmov.y = -1;
- if (good_move[mmov.x+1][2] && (mmov.y == 0 || coinflip()))
- mmov.y = 1;
- }
- }
- else if (mmov.x == 0)
- {
- if (!good_move[0][1] && !good_move[2][1]
- && (good_move[0][mmov.y+1] || good_move[2][mmov.y+1])
- && (_allied_monster_at(monster, coord_def(-1, -mmov.y),
- coord_def(0, -mmov.y),
- coord_def(1, -mmov.y))
- || mons_intel(monster) >= I_NORMAL
- && !mons_wont_attack_real(monster)
- && _ranged_allied_monster_in_dir(monster,
- coord_def(0, -mmov.y))))
- {
- if (good_move[0][mmov.y+1])
- mmov.x = -1;
- if (good_move[2][mmov.y+1] && (mmov.x == 0 || coinflip()))
- mmov.x = 1;
- }
- }
- else // We're moving diagonally.
- {
- if (good_move[mmov.x+1][1])
- {
- if (!good_move[1][mmov.y+1]
- && (_allied_monster_at(monster, coord_def(-mmov.x, -1),
- coord_def(-mmov.x, 0),
- coord_def(-mmov.x, 1))
- || mons_intel(monster) >= I_NORMAL
- && !mons_wont_attack_real(monster)
- && _ranged_allied_monster_in_dir(monster,
- coord_def(-mmov.x, -mmov.y))))
- {
- mmov.y = 0;
- }
- }
- else if (good_move[1][mmov.y+1]
- && (_allied_monster_at(monster, coord_def(-1, -mmov.y),
- coord_def(0, -mmov.y),
- coord_def(1, -mmov.y))
- || mons_intel(monster) >= I_NORMAL
- && !mons_wont_attack_real(monster)
- && _ranged_allied_monster_in_dir(monster,
- coord_def(-mmov.x, -mmov.y))))
- {
- mmov.x = 0;
- }
- }
- }
-
- // Now quit if we can't move.
- if (mmov.origin())
- return;
-
- // Try to stay in sight of the player if we're moving towards
- // him/her, in order to avoid the monster coming into view,
- // shouting, and then taking a step in a path to the player which
- // temporarily takes it out of view, which can lead to the player
- // getting "comes into view" and shout messages with no monster in
- // view.
-
- // Doesn't matter for arena mode.
- if (crawl_state.arena)
- return;
-
- // Did we just come into view?
- if (monster->seen_context != _just_seen)
- return;
-
- monster->seen_context.clear();
-
- // If the player can't see us, it doesn't matter.
- if (!(monster->flags & MF_WAS_IN_VIEW))
- return;
-
- const coord_def old_pos = monster->pos();
- const int old_dist = grid_distance(you.pos(), old_pos);
-
- // We're not moving towards the player.
- if (grid_distance(you.pos(), old_pos + mmov) >= old_dist)
- {
- // Give a message if we move back out of view.
- monster->seen_context = _just_seen;
- return;
- }
-
- // We're already staying in the player's LOS.
- if (see_cell(old_pos + mmov))
- return;
-
- // Try to find a move that brings us closer to the player while
- // keeping us in view.
- int matches = 0;
- for (int i = 0; i < 3; i++)
- for (int j = 0; j < 3; j++)
- {
- if (i == 0 && j == 0)
- continue;
-
- if (!good_move[i][j])
- continue;
-
- delta.set(i - 1, j - 1);
- coord_def tmp = old_pos + delta;
-
- if (grid_distance(you.pos(), tmp) < old_dist && see_cell(tmp))
- {
- if (one_chance_in(++matches))
- mmov = delta;
- break;
- }
- }
-
- // The only way to get closer to the player is to step out of view;
- // give a message so they player isn't confused about its being
- // announced as coming into view but not being seen.
- monster->seen_context = _just_seen;
-}
-
-static void _make_mons_stop_fleeing(monsters *mon)
-{
- if (mons_is_fleeing(mon))
- behaviour_event(mon, ME_CORNERED);
-}
-
-static bool _is_player_or_mon_sanct(const monsters* monster)
-{
- return (is_sanctuary(you.pos())
- || is_sanctuary(monster->pos()));
-}
-
bool mons_avoids_cloud(const monsters *monster, cloud_struct cloud,
bool placement)
{
@@ -5639,1544 +3120,6 @@ bool mons_avoids_cloud(const monsters *monster, int cloud_num,
return (!mons_avoids_cloud(monster, our_cloud, true));
}
-//---------------------------------------------------------------
-//
-// handle_nearby_ability
-//
-// Gives monsters a chance to use a special ability when they're
-// next to the player.
-//
-//---------------------------------------------------------------
-static void _handle_nearby_ability(monsters *monster)
-{
- actor *foe = monster->get_foe();
- if (!foe
- || !monster->can_see(foe)
- || monster->asleep()
- || monster->submerged())
- {
- return;
- }
-
-#define MON_SPEAK_CHANCE 21
-
- if (monster->is_patrolling() || mons_is_wandering(monster)
- || monster->attitude == ATT_NEUTRAL)
- {
- // Very fast wandering/patrolling monsters might, in one monster turn,
- // move into the player's LOS and then back out (or the player
- // might move into their LOS and the monster move back out before
- // the player's view has a chance to update) so prevent them
- // from speaking.
- ;
- }
- else if ((mons_class_flag(monster->type, M_SPEAKS)
- || !monster->mname.empty())
- && one_chance_in(MON_SPEAK_CHANCE))
- {
- mons_speaks(monster);
- }
- else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED)
- {
- // Non-humanoid-ish monsters have a low chance of speaking
- // without the M_SPEAKS flag, to give the dungeon some
- // atmosphere/flavour.
- int chance = MON_SPEAK_CHANCE * 4;
-
- // Band members are a lot less likely to speak, since there's
- // a lot of them.
- if (testbits(monster->flags, MF_BAND_MEMBER))
- chance *= 10;
-
- // However, confused and fleeing monsters are more interesting.
- if (mons_is_fleeing(monster))
- chance /= 2;
- if (monster->has_ench(ENCH_CONFUSION))
- chance /= 2;
-
- if (one_chance_in(chance))
- mons_speaks(monster);
- }
- // Okay then, don't speak.
-
- if (monster_can_submerge(monster, grd(monster->pos()))
- && !monster->caught() // No submerging while caught.
- && !player_mesmerised_by(monster) // No submerging if player entranced.
- && !mons_is_lurking(monster) // Handled elsewhere.
- && monster->wants_submerge())
- {
- monsterentry* entry = get_monster_data(monster->type);
-
- monster->add_ench(ENCH_SUBMERGED);
- monster->speed_increment -= ENERGY_SUBMERGE(entry);
- update_beholders(monster);
- return;
- }
-
- switch (monster->type)
- {
- case MONS_SPATIAL_VORTEX:
- case MONS_KILLER_KLOWN:
- // Choose random colour.
- monster->colour = random_colour();
- break;
-
- case MONS_GIANT_EYEBALL:
- if (coinflip()
- && !mons_is_wandering(monster)
- && !mons_is_fleeing(monster)
- && !mons_is_pacified(monster)
- && !_is_player_or_mon_sanct(monster))
- {
- if (you.can_see(monster) && you.can_see(foe))
- mprf("%s stares at %s.",
- monster->name(DESC_CAP_THE).c_str(),
- foe->name(DESC_NOCAP_THE).c_str());
-
- // Subtly different from old paralysis behaviour, but
- // it'll do.
- foe->paralyse(monster, 2 + random2(3));
- }
- break;
-
- case MONS_EYE_OF_DRAINING:
- if (coinflip()
- && foe->atype() == ACT_PLAYER
- && !mons_is_wandering(monster)
- && !mons_is_fleeing(monster)
- && !mons_is_pacified(monster)
- && !_is_player_or_mon_sanct(monster))
- {
- simple_monster_message(monster, " stares at you.");
-
- dec_mp(5 + random2avg(13, 3));
-
- heal_monster(monster, 10, true); // heh heh {dlb}
- }
- break;
-
- case MONS_AIR_ELEMENTAL:
- if (one_chance_in(5))
- monster->add_ench(ENCH_SUBMERGED);
- break;
-
- case MONS_PANDEMONIUM_DEMON:
- if (monster->ghost->cycle_colours)
- monster->colour = random_colour();
- break;
-
- default:
- break;
- }
-}
-
-// Returns true if you resist the siren's call.
-static bool _siren_movement_effect(const monsters *monster)
-{
- bool do_resist = (you.attribute[ATTR_HELD] || you_resist_magic(70));
-
- if (!do_resist)
- {
- coord_def dir(coord_def(0,0));
- if (monster->pos().x < you.pos().x)
- dir.x = -1;
- else if (monster->pos().x > you.pos().x)
- dir.x = 1;
- if (monster->pos().y < you.pos().y)
- dir.y = -1;
- else if (monster->pos().y > you.pos().y)
- dir.y = 1;
-
- const coord_def newpos = you.pos() + dir;
-
- if (!in_bounds(newpos) || is_feat_dangerous(grd(newpos))
- || !you.can_pass_through_feat(grd(newpos)))
- {
- do_resist = true;
- }
- else
- {
- bool swapping = false;
- monsters *mon = monster_at(newpos);
- if (mon)
- {
- if (mons_wont_attack(mon)
- && !mons_is_stationary(mon)
- && !mons_cannot_act(mon)
- && !mon->asleep()
- && swap_check(mon, you.pos(), true))
- {
- swapping = true;
- }
- else if (!mon->submerged())
- do_resist = true;
- }
-
- if (!do_resist)
- {
- const coord_def oldpos = you.pos();
- mprf("The pull of her song draws you forwards.");
-
- if (swapping)
- {
- if (mgrd(oldpos) != NON_MONSTER)
- {
- mprf("Something prevents you from swapping places "
- "with %s.",
- mon->name(DESC_NOCAP_THE).c_str());
- return (do_resist);
- }
-
- int swap_mon = mgrd(newpos);
- // Pick the monster up.
- mgrd(newpos) = NON_MONSTER;
- mon->moveto(oldpos);
-
- // Plunk it down.
- mgrd(mon->pos()) = swap_mon;
-
- mprf("You swap places with %s.",
- mon->name(DESC_NOCAP_THE).c_str());
- }
- move_player_to_grid(newpos, true, true, true);
-
- if (swapping)
- mon->apply_location_effects(newpos);
- }
- }
- }
-
- return (do_resist);
-}
-
-//---------------------------------------------------------------
-//
-// handle_special_ability
-//
-//---------------------------------------------------------------
-static bool _handle_special_ability(monsters *monster, bolt & beem)
-{
- bool used = false;
-
- const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN)
- ? draco_subspecies( monster )
- : static_cast<monster_type>( monster->type );
-
- // Slime creatures can split while out of sight.
- if ((!mons_near(monster)
- || monster->asleep()
- || monster->submerged())
- && monster->mons_species() != MONS_SLIME_CREATURE)
- {
- return (false);
- }
-
- const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL
- : MSGCH_MONSTER_SPELL);
-
- spell_type spell = SPELL_NO_SPELL;
-
- switch (mclass)
- {
- case MONS_UGLY_THING:
- case MONS_VERY_UGLY_THING:
- // A (very) ugly thing's proximity to you if you're glowing, or
- // to others of its kind, can mutate it into a different (very)
- // ugly thing.
- used = ugly_thing_mutate(monster, true);
- break;
-
- case MONS_SLIME_CREATURE:
- // Slime creatures may split or merge depending on the
- // situation.
- used = slime_split_merge(monster);
- if (!monster->alive())
- return (true);
- break;
-
- case MONS_ORC_KNIGHT:
- case MONS_ORC_WARLORD:
- case MONS_SAINT_ROKA:
- if (is_sanctuary(monster->pos()))
- break;
-
- used = orc_battle_cry(monster);
- break;
-
- case MONS_ORANGE_STATUE:
- if (_is_player_or_mon_sanct(monster))
- break;
-
- used = orange_statue_effects(monster);
- break;
-
- case MONS_SILVER_STATUE:
- if (_is_player_or_mon_sanct(monster))
- break;
-
- used = silver_statue_effects(monster);
- break;
-
- case MONS_BALL_LIGHTNING:
- if (is_sanctuary(monster->pos()))
- break;
-
- if (monster->attitude == ATT_HOSTILE
- && distance(you.pos(), monster->pos()) <= 5)
- {
- monster->hit_points = -1;
- used = true;
- break;
- }
-
- for (int i = 0; i < MAX_MONSTERS; i++)
- {
- monsters *targ = &menv[i];
-
- if (targ->type == MONS_NO_MONSTER)
- continue;
-
- if (distance(monster->pos(), targ->pos()) >= 5)
- continue;
-
- if (mons_atts_aligned(monster->attitude, targ->attitude))
- continue;
-
- // Faking LOS by checking the neighbouring square.
- coord_def diff = targ->pos() - monster->pos();
- coord_def sg(sgn(diff.x), sgn(diff.y));
- coord_def t = monster->pos() + sg;
-
- if (!inside_level_bounds(t))
- continue;
-
- if (!feat_is_solid(grd(t)))
- {
- monster->hit_points = -1;
- used = true;
- break;
- }
- }
- break;
-
- case MONS_LAVA_SNAKE:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (!you.visible_to(monster))
- break;
-
- if (coinflip())
- break;
-
- // Setup tracer.
- beem.name = "glob of lava";
- beem.aux_source = "glob of lava";
- beem.range = 6;
- beem.damage = dice_def(3, 10);
- beem.hit = 20;
- beem.colour = RED;
- beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
- beem.flavour = BEAM_LAVA;
- beem.beam_source = monster_index(monster);
- beem.thrower = KILL_MON;
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
- simple_monster_message(monster, " spits lava!");
- beem.fire();
- used = true;
- }
- break;
-
- case MONS_ELECTRIC_EEL:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (!you.visible_to(monster))
- break;
-
- if (coinflip())
- break;
-
- // Setup tracer.
- beem.name = "bolt of electricity";
- beem.aux_source = "bolt of electricity";
- beem.range = 8;
- beem.damage = dice_def( 3, 6 );
- beem.hit = 50;
- beem.colour = LIGHTCYAN;
- beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
- beem.flavour = BEAM_ELECTRICITY;
- beem.beam_source = monster_index(monster);
- beem.thrower = KILL_MON;
- beem.is_beam = true;
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
- simple_monster_message(monster,
- " shoots out a bolt of electricity!");
- beem.fire();
- used = true;
- }
- break;
-
- case MONS_ACID_BLOB:
- case MONS_OKLOB_PLANT:
- case MONS_YELLOW_DRACONIAN:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (_is_player_or_mon_sanct(monster))
- break;
-
- if (one_chance_in(3))
- {
- spell = SPELL_ACID_SPLASH;
- setup_mons_cast(monster, beem, spell);
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
- mons_cast(monster, beem, spell);
- mmov.reset();
- used = true;
- }
- }
- break;
-
- case MONS_MOTH_OF_WRATH:
- if (one_chance_in(3))
- used = moth_incite_monsters(monster);
- break;
-
- case MONS_SNORG:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (monster->foe == MHITNOT
- || monster->foe == MHITYOU && mons_friendly(monster))
- {
- break;
- }
-
- // There's a 5% chance of Snorg spontaneously going berserk that
- // increases to 20% once he is wounded.
- if (monster->hit_points == monster->max_hit_points && !one_chance_in(4))
- break;
-
- if (one_chance_in(5))
- monster->go_berserk(true);
- break;
-
- case MONS_PIT_FIEND:
- if (one_chance_in(3))
- break;
- // deliberate fall through
- case MONS_FIEND:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (_is_player_or_mon_sanct(monster))
- break;
-
- // Friendly fiends won't use torment, preferring hellfire
- // (right now there is no way a monster can predict how
- // badly they'll damage the player with torment) -- GDL
-
- // Well, I guess you could allow it if the player is torment
- // resistant, but there's a very good reason torment resistant
- // players can't cast Torment themselves, and allowing your
- // allies to cast it would just introduce harmless Torment
- // through the backdoor. Thus, shouldn't happen. (jpeg)
- if (one_chance_in(4))
- {
- spell_type spell_cast = SPELL_NO_SPELL;
-
- switch (random2(4))
- {
- case 0:
- if (!mons_friendly(monster))
- {
- _make_mons_stop_fleeing(monster);
- spell_cast = SPELL_SYMBOL_OF_TORMENT;
- mons_cast(monster, beem, spell_cast);
- used = true;
- break;
- }
- // deliberate fallthrough -- see above
- case 1:
- case 2:
- case 3:
- spell_cast = SPELL_HELLFIRE;
- setup_mons_cast(monster, beem, spell_cast);
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
-
- mons_cast(monster, beem, spell_cast);
- used = true;
- }
- break;
- }
-
- mmov.reset();
- }
- break;
-
- case MONS_IMP:
- case MONS_PHANTOM:
- case MONS_INSUBSTANTIAL_WISP:
- case MONS_BLINK_FROG:
- case MONS_KILLER_KLOWN:
- case MONS_PRINCE_RIBBIT:
- if (one_chance_in(7) || mons_is_caught(monster) && one_chance_in(3))
- used = monster_blink(monster);
- break;
-
- case MONS_MANTICORE:
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (!you.visible_to(monster))
- break;
-
- // The fewer spikes the manticore has left, the less
- // likely it will use them.
- if (random2(16) >= static_cast<int>(monster->number))
- break;
-
- // Do the throwing right here, since the beam is so
- // easy to set up and doesn't involve inventory.
-
- // Set up the beam.
- beem.name = "volley of spikes";
- beem.aux_source = "volley of spikes";
- beem.range = 6;
- beem.hit = 14;
- beem.damage = dice_def( 2, 10 );
- beem.beam_source = monster_index(monster);
- beem.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beem.colour = LIGHTGREY;
- beem.flavour = BEAM_MISSILE;
- beem.thrower = KILL_MON;
- beem.is_beam = false;
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
- simple_monster_message(monster, " flicks its tail!");
- beem.fire();
- used = true;
- // Decrement # of volleys left.
- monster->number--;
- }
- break;
-
- case MONS_PLAYER_GHOST:
- {
- const ghost_demon &ghost = *(monster->ghost);
-
- if (ghost.species < SP_RED_DRACONIAN
- || ghost.species == SP_GREY_DRACONIAN
- || ghost.species >= SP_BASE_DRACONIAN
- || ghost.xl < 7
- || one_chance_in(ghost.xl - 5))
- {
- break;
- }
- }
- // Intentional fallthrough
-
- case MONS_WHITE_DRACONIAN:
- case MONS_RED_DRACONIAN:
- spell = SPELL_DRACONIAN_BREATH;
- // Intentional fallthrough
-
- case MONS_ICE_DRAGON:
- if (spell == SPELL_NO_SPELL)
- spell = SPELL_COLD_BREATH;
- // Intentional fallthrough
-
- // Dragon breath weapons:
- case MONS_DRAGON:
- case MONS_HELL_HOUND:
- case MONS_LINDWURM:
- case MONS_FIREDRAKE:
- case MONS_XTAHUA:
- if (spell == SPELL_NO_SPELL)
- spell = SPELL_FIRE_BREATH;
-
- if (monster->has_ench(ENCH_CONFUSION))
- break;
-
- if (!you.visible_to(monster))
- break;
-
- if (monster->type != MONS_HELL_HOUND && x_chance_in_y(3, 13)
- || one_chance_in(10))
- {
- setup_mons_cast(monster, beem, spell);
-
- // Fire tracer.
- fire_tracer(monster, beem);
-
- // Good idea?
- if (mons_should_fire(beem))
- {
- _make_mons_stop_fleeing(monster);
- mons_cast(monster, beem, spell);
- mmov.reset();
- used = true;
- }
- }
- break;
-
- case MONS_MERMAID:
- case MONS_SIREN:
- {
- // Don't behold observer in the arena.
- if (crawl_state.arena)
- break;
-
- // Don't behold player already half down or up the stairs.
- if (!you.delay_queue.empty())
- {
- delay_queue_item delay = you.delay_queue.front();
-
- if (delay.type == DELAY_ASCENDING_STAIRS
- || delay.type == DELAY_DESCENDING_STAIRS)
- {
-#ifdef DEBUG_DIAGNOSTICS
- mpr("Taking stairs, don't mesmerise.", MSGCH_DIAGNOSTICS);
-#endif
- break;
- }
- }
-
- // Won't sing if either of you silenced, or it's friendly,
- // confused, fleeing, or leaving the level.
- if (monster->has_ench(ENCH_CONFUSION)
- || mons_is_fleeing(monster)
- || mons_is_pacified(monster)
- || mons_friendly(monster)
- || !player_can_hear(monster->pos()))
- {
- break;
- }
-
- // Don't even try on berserkers. Mermaids know their limits.
- if (you.duration[DUR_BERSERKER])
- break;
-
- // Reduce probability because of spamminess.
- if (you.species == SP_MERFOLK && !one_chance_in(4))
- break;
-
- // A wounded invisible mermaid is less likely to give away her position.
- if (monster->invisible()
- && monster->hit_points <= monster->max_hit_points / 2
- && !one_chance_in(3))
- {
- break;
- }
-
- bool already_mesmerised = player_mesmerised_by(monster);
-
- if (one_chance_in(5)
- || monster->foe == MHITYOU && !already_mesmerised && coinflip())
- {
- noisy(12, monster->pos(), monster->mindex(), true);
-
- bool did_resist = false;
- if (you.can_see(monster))
- {
- simple_monster_message(monster,
- make_stringf(" chants %s song.",
- already_mesmerised ? "her luring" : "a haunting").c_str(),
- spl);
-
- if (monster->type == MONS_SIREN)
- {
- if (_siren_movement_effect(monster))
- {
- canned_msg(MSG_YOU_RESIST); // flavour only
- did_resist = true;
- }
- }
- }
- else
- {
- // If you're already mesmerised by an invisible mermaid she
- // can still prolong the enchantment; otherwise you "resist".
- if (already_mesmerised)
- mpr("You hear a luring song.", MSGCH_SOUND);
- else
- {
- if (one_chance_in(4)) // reduce spamminess
- {
- if (coinflip())
- mpr("You hear a haunting song.", MSGCH_SOUND);
- else
- mpr("You hear an eerie melody.", MSGCH_SOUND);
-
- canned_msg(MSG_YOU_RESIST); // flavour only
- }
- break;
- }
- }
-
- // Once mesmerised by a particular monster, you cannot resist
- // anymore.
- if (!already_mesmerised
- && (you.species == SP_MERFOLK || you_resist_magic(100)))
- {
- if (!did_resist)
- canned_msg(MSG_YOU_RESIST);
- break;
- }
-
- if (!you.duration[DUR_MESMERISED])
- {
- you.duration[DUR_MESMERISED] = 7;
- you.mesmerised_by.push_back(monster_index(monster));
- mprf(MSGCH_WARN, "You are mesmerised by %s!",
- monster->name(DESC_NOCAP_THE).c_str());
- }
- else
- {
- you.duration[DUR_MESMERISED] += 5;
- if (!already_mesmerised)
- you.mesmerised_by.push_back(monster_index(monster));
- }
- used = true;
-
- if (you.duration[DUR_MESMERISED] > 12)
- you.duration[DUR_MESMERISED] = 12;
- }
- break;
- }
-
- default:
- break;
- }
-
- if (used)
- monster->lose_energy(EUT_SPECIAL);
-
- return (used);
-}
-
-//---------------------------------------------------------------
-//
-// _handle_potion
-//
-// Give the monster a chance to quaff a potion. Returns true if
-// the monster imbibed.
-//
-//---------------------------------------------------------------
-static bool _handle_potion(monsters *monster, bolt & beem)
-{
- if (monster->asleep()
- || monster->inv[MSLOT_POTION] == NON_ITEM
- || !one_chance_in(3))
- {
- return (false);
- }
-
- bool rc = false;
-
- const int potion_idx = monster->inv[MSLOT_POTION];
- item_def& potion = mitm[potion_idx];
- const potion_type ptype = static_cast<potion_type>(potion.sub_type);
-
- if (monster->can_drink_potion(ptype) && monster->should_drink_potion(ptype))
- {
- const bool was_visible = you.can_see(monster);
-
- // Drink the potion.
- const item_type_id_state_type id = monster->drink_potion_effect(ptype);
-
- // Give ID if necessary.
- if (was_visible && id != ID_UNKNOWN_TYPE)
- set_ident_type(OBJ_POTIONS, ptype, id);
-
- // Remove it from inventory.
- if (dec_mitm_item_quantity(potion_idx, 1))
- monster->inv[MSLOT_POTION] = NON_ITEM;
- else if (is_blood_potion(potion))
- remove_oldest_blood_potion(potion);
-
- monster->lose_energy(EUT_ITEM);
- rc = true;
- }
-
- return (rc);
-}
-
-static bool _handle_reaching(monsters *monster)
-{
- bool ret = false;
- const int wpn = monster->inv[MSLOT_WEAPON];
-
- if (monster->submerged())
- return (false);
-
- if (mons_aligned(monster_index(monster), monster->foe))
- return (false);
-
- if (wpn != NON_ITEM && get_weapon_brand(mitm[wpn]) == SPWPN_REACHING)
- {
- if (monster->foe == MHITYOU)
- {
- const coord_def delta = monster->pos() - you.pos();
- const int x_middle = std::max(monster->pos().x, you.pos().x)
- - (abs(delta.x) / 2);
- const int y_middle = std::max(monster->pos().y, you.pos().y)
- - (abs(delta.y) / 2);
- const coord_def middle(x_middle, y_middle);
-
- // This check isn't redundant -- player may be invisible.
- if (monster->target == you.pos()
- && grid_distance(monster->pos(), you.pos()) == 2
- && (see_cell_no_trans(monster->pos())
- || grd(middle) > DNGN_MAX_NONREACH))
- {
- ret = true;
- monster_attack(monster, false);
- }
- }
- else if (monster->foe != MHITNOT)
- {
- monsters& mfoe = menv[monster->foe];
- coord_def foepos = mfoe.pos();
- // Same comments as to invisibility as above.
- if (monster->target == foepos
- && monster->mon_see_cell(foepos, true)
- && grid_distance(monster->pos(), foepos) == 2)
- {
- ret = true;
- monsters_fight(monster, &mfoe, false);
- }
- }
- }
-
- // Player saw the item reach.
- if (ret && !is_artefact(mitm[wpn]) && you.can_see(monster))
- set_ident_flags(mitm[wpn], ISFLAG_KNOW_TYPE);
-
- return (ret);
-}
-
-//---------------------------------------------------------------
-//
-// handle_scroll
-//
-// Give the monster a chance to read a scroll. Returns true if
-// the monster read something.
-//
-//---------------------------------------------------------------
-static bool _handle_scroll(monsters *monster)
-{
- // Yes, there is a logic to this ordering {dlb}:
- if (monster->asleep()
- || mons_is_confused(monster)
- || monster->submerged()
- || monster->inv[MSLOT_SCROLL] == NON_ITEM
- || !one_chance_in(3))
- {
- return (false);
- }
-
- // Make sure the item actually is a scroll.
- if (mitm[monster->inv[MSLOT_SCROLL]].base_type != OBJ_SCROLLS)
- return (false);
-
- bool read = false;
- item_type_id_state_type ident = ID_UNKNOWN_TYPE;
- bool was_visible = you.can_see(monster);
-
- // Notice how few cases are actually accounted for here {dlb}:
- const int scroll_type = mitm[monster->inv[MSLOT_SCROLL]].sub_type;
- switch (scroll_type)
- {
- case SCR_TELEPORTATION:
- if (!monster->has_ench(ENCH_TP))
- {
- if (mons_is_caught(monster) || mons_is_fleeing(monster)
- || mons_is_pacified(monster))
- {
- simple_monster_message(monster, " reads a scroll.");
- monster_teleport(monster, false);
- read = true;
- ident = ID_KNOWN_TYPE;
- }
- }
- break;
-
- case SCR_BLINKING:
- if (mons_is_caught(monster) || mons_is_fleeing(monster)
- || mons_is_pacified(monster))
- {
- if (mons_near(monster))
- {
- simple_monster_message(monster, " reads a scroll.");
- monster_blink(monster);
- read = true;
- ident = ID_KNOWN_TYPE;
- }
- }
- break;
-
- case SCR_SUMMONING:
- if (mons_near(monster))
- {
- simple_monster_message(monster, " reads a scroll.");
- const int mon = create_monster(
- mgen_data(MONS_ABOMINATION_SMALL, SAME_ATTITUDE(monster),
- 0, 0, monster->pos(), monster->foe, MG_FORCE_BEH));
-
- read = true;
- if (mon != -1)
- {
- if (you.can_see(&menv[mon]))
- {
- mprf("%s appears!", menv[mon].name(DESC_CAP_A).c_str());
- ident = ID_KNOWN_TYPE;
- }
- player_angers_monster(&menv[mon]);
- }
- else if (you.can_see(monster))
- canned_msg(MSG_NOTHING_HAPPENS);
- }
- break;
- }
-
- if (read)
- {
- if (dec_mitm_item_quantity(monster->inv[MSLOT_SCROLL], 1))
- monster->inv[MSLOT_SCROLL] = NON_ITEM;
-
- if (ident != ID_UNKNOWN_TYPE && was_visible)
- set_ident_type(OBJ_SCROLLS, scroll_type, ident);
-
- monster->lose_energy(EUT_ITEM);
- }
-
- return read;
-}
-
-//---------------------------------------------------------------
-//
-// handle_wand
-//
-// Give the monster a chance to zap a wand. Returns true if the
-// monster zapped.
-//
-//---------------------------------------------------------------
-static bool _handle_wand(monsters *monster, bolt &beem)
-{
- // Yes, there is a logic to this ordering {dlb}:
- if (!mons_near(monster)
- || monster->asleep()
- || monster->has_ench(ENCH_SUBMERGED)
- || monster->inv[MSLOT_WAND] == NON_ITEM
- || mitm[monster->inv[MSLOT_WAND]].plus <= 0
- || coinflip())
- {
- return (false);
- }
-
- bool niceWand = false;
- bool zap = false;
- bool was_visible = you.can_see(monster);
-
- item_def &wand(mitm[monster->inv[MSLOT_WAND]]);
-
- // map wand type to monster spell type
- const spell_type mzap = _map_wand_to_mspell(wand.sub_type);
- if (mzap == SPELL_NO_SPELL)
- return (false);
-
- // set up the beam
- int power = 30 + monster->hit_dice;
- bolt theBeam = mons_spells(monster, mzap, power);
-
- beem.name = theBeam.name;
- beem.beam_source = monster_index(monster);
- beem.source = monster->pos();
- beem.colour = theBeam.colour;
- beem.range = theBeam.range;
- beem.damage = theBeam.damage;
- beem.ench_power = theBeam.ench_power;
- beem.hit = theBeam.hit;
- beem.type = theBeam.type;
- beem.flavour = theBeam.flavour;
- beem.thrower = theBeam.thrower;
- beem.is_beam = theBeam.is_beam;
- beem.is_explosion = theBeam.is_explosion;
-
-#if HISCORE_WEAPON_DETAIL
- beem.aux_source =
- wand.name(DESC_QUALNAME, false, true, false, false);
-#else
- beem.aux_source =
- wand.name(DESC_QUALNAME, false, true, false, false,
- ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES);
-#endif
-
- const int wand_type = wand.sub_type;
- switch (wand_type)
- {
- case WAND_DISINTEGRATION:
- // Dial down damage from wands of disintegration, since
- // disintegration beams can do large amounts of damage.
- beem.damage.size = beem.damage.size * 2 / 3;
- break;
-
- case WAND_ENSLAVEMENT:
- case WAND_DIGGING:
- case WAND_RANDOM_EFFECTS:
- // These have been deemed "too tricky" at this time {dlb}:
- return (false);
-
- case WAND_POLYMORPH_OTHER:
- // Monsters can be very trigger happy with wands, reduce this
- // for polymorph.
- if (!one_chance_in(5))
- return (false);
- break;
-
- // These are wands that monsters will aim at themselves {dlb}:
- case WAND_HASTING:
- if (!monster->has_ench(ENCH_HASTE))
- {
- beem.target = monster->pos();
- niceWand = true;
- break;
- }
- return (false);
-
- case WAND_HEALING:
- if (monster->hit_points <= monster->max_hit_points / 2)
- {
- beem.target = monster->pos();
- niceWand = true;
- break;
- }
- return (false);
-
- case WAND_INVISIBILITY:
- if (!monster->has_ench(ENCH_INVIS)
- && !monster->has_ench(ENCH_SUBMERGED)
- && (!mons_friendly(monster) || you.can_see_invisible(false)))
- {
- beem.target = monster->pos();
- niceWand = true;
- break;
- }
- return (false);
-
- case WAND_TELEPORTATION:
- if (monster->hit_points <= monster->max_hit_points / 2
- || mons_is_caught(monster))
- {
- if (!monster->has_ench(ENCH_TP)
- && !one_chance_in(20))
- {
- beem.target = monster->pos();
- niceWand = true;
- break;
- }
- // This break causes the wand to be tried on the player.
- break;
- }
- return (false);
- }
-
- // Fire tracer, if necessary.
- if (!niceWand)
- {
- fire_tracer( monster, beem );
-
- // Good idea?
- zap = mons_should_fire(beem);
- }
-
- if (niceWand || zap)
- {
- if (!niceWand)
- _make_mons_stop_fleeing(monster);
-
- if (!simple_monster_message(monster, " zaps a wand."))
- {
- if (!silenced(you.pos()))
- mpr("You hear a zap.", MSGCH_SOUND);
- }
-
- // charge expenditure {dlb}
- wand.plus--;
- beem.is_tracer = false;
- beem.fire();
-
- if (was_visible)
- {
- if (niceWand || !beem.is_enchantment() || beem.obvious_effect)
- set_ident_type(OBJ_WANDS, wand_type, ID_KNOWN_TYPE);
- else
- set_ident_type(OBJ_WANDS, wand_type, ID_MON_TRIED_TYPE);
-
- // Increment zap count.
- if (wand.plus2 >= 0)
- wand.plus2++;
- }
-
- monster->lose_energy(EUT_ITEM);
-
- return (true);
- }
-
- return (false);
-}
-
-// Returns a suitable breath weapon for the draconian; does not handle all
-// draconians, does fire a tracer.
-static spell_type _get_draconian_breath_spell( monsters *monster )
-{
- spell_type draco_breath = SPELL_NO_SPELL;
-
- if (mons_genus( monster->type ) == MONS_DRACONIAN)
- {
- switch (draco_subspecies( monster ))
- {
- case MONS_DRACONIAN:
- case MONS_YELLOW_DRACONIAN: // already handled as ability
- break;
- default:
- draco_breath = SPELL_DRACONIAN_BREATH;
- break;
- }
- }
-
-
- if (draco_breath != SPELL_NO_SPELL)
- {
- // [ds] Check line-of-fire here. It won't happen elsewhere.
- bolt beem;
- setup_mons_cast(monster, beem, draco_breath);
-
- fire_tracer(monster, beem);
-
- if (!mons_should_fire(beem))
- draco_breath = SPELL_NO_SPELL;
- }
-
- return (draco_breath);
-}
-
-static bool _is_emergency_spell(const monster_spells &msp, int spell)
-{
- // If the emergency spell appears early, it's probably not a dedicated
- // escape spell.
- for (int i = 0; i < 5; ++i)
- if (msp[i] == spell)
- return (false);
-
- return (msp[5] == spell);
-}
-
-static bool _mon_has_spells(monsters *monster)
-{
- for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
- if (monster->spells[i] != SPELL_NO_SPELL)
- return (true);
-
- return (false);
-}
-
-//---------------------------------------------------------------
-//
-// handle_spell
-//
-// Give the monster a chance to cast a spell. Returns true if
-// a spell was cast.
-//
-//---------------------------------------------------------------
-static bool _handle_spell(monsters *monster, bolt &beem)
-{
- bool monsterNearby = mons_near(monster);
- bool finalAnswer = false; // as in: "Is that your...?" {dlb}
- const spell_type draco_breath = _get_draconian_breath_spell(monster);
-
- // A polymorphed unique will retain his or her spells even in another
- // form. If the new form has the SPELLCASTER flag, casting happens as
- // normally, otherwise we need to enforce it, but it only happens with
- // a 50% chance.
- const bool spellcasting_poly
- = !mons_class_flag(monster->type, M_SPELLCASTER)
- && mons_class_flag(monster->type, M_SPEAKS)
- && _mon_has_spells(monster);
-
- if (is_sanctuary(monster->pos()) && !mons_wont_attack(monster))
- return (false);
-
- // Yes, there is a logic to this ordering {dlb}:
- if (monster->asleep()
- || monster->submerged()
- || !mons_class_flag(monster->type, M_SPELLCASTER)
- && !spellcasting_poly
- && draco_breath == SPELL_NO_SPELL)
- {
- return (false);
- }
-
- // If the monster's a priest, assume summons come from priestly
- // abilities, in which case they'll have the same god. If the
- // monster is neither a priest nor a wizard, assume summons come
- // from intrinsic abilities, in which case they'll also have the
- // same god.
- const bool priest = mons_class_flag(monster->type, M_PRIEST);
- const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
- god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD;
-
- if (silenced(monster->pos())
- && (priest || wizard || spellcasting_poly
- || mons_class_flag(monster->type, M_SPELL_NO_SILENT)))
- {
- return (false);
- }
-
- // Shapeshifters don't get spells.
- if (mons_is_shapeshifter(monster) && (priest || wizard))
- return (false);
- else if (monster->has_ench(ENCH_CONFUSION)
- && !mons_class_flag(monster->type, M_CONFUSED))
- {
- return (false);
- }
- else if (monster->type == MONS_PANDEMONIUM_DEMON
- && !monster->ghost->spellcaster)
- {
- return (false);
- }
- else if (random2(200) > monster->hit_dice + 50
- || monster->type == MONS_BALL_LIGHTNING && coinflip())
- {
- return (false);
- }
- else if (spellcasting_poly && coinflip()) // 50% chance of not casting
- return (false);
- else
- {
- spell_type spell_cast = SPELL_NO_SPELL;
- monster_spells hspell_pass(monster->spells);
-
- // 1KB: the following code is never used for unfriendlies!
- if (!mon_enemies_around(monster))
- {
- // Force the casting of dig when the player is not visible -
- // this is EVIL!
- if (monster->has_spell(SPELL_DIG)
- && mons_is_seeking(monster))
- {
- spell_cast = SPELL_DIG;
- finalAnswer = true;
- }
- else if ((monster->has_spell(SPELL_MINOR_HEALING)
- || monster->has_spell(SPELL_MAJOR_HEALING))
- && monster->hit_points < monster->max_hit_points)
- {
- // The player's out of sight!
- // Quick, let's take a turn to heal ourselves. -- bwr
- spell_cast = monster->has_spell(SPELL_MAJOR_HEALING) ?
- SPELL_MAJOR_HEALING : SPELL_MINOR_HEALING;
- finalAnswer = true;
- }
- else if (mons_is_fleeing(monster) || mons_is_pacified(monster))
- {
- // Since the player isn't around, we'll extend the monster's
- // normal choices to include the self-enchant slot.
- int foundcount = 0;
- for (int i = NUM_MONSTER_SPELL_SLOTS - 1; i >= 0; --i)
- {
- if (ms_useful_fleeing_out_of_sight(monster, hspell_pass[i])
- && one_chance_in(++foundcount))
- {
- spell_cast = hspell_pass[i];
- finalAnswer = true;
- }
- }
- }
- else if (monster->foe == MHITYOU && !monsterNearby)
- return (false);
- }
-
- // Monsters caught in a net try to get away.
- // This is only urgent if enemies are around.
- if (!finalAnswer && mon_enemies_around(monster)
- && mons_is_caught(monster) && one_chance_in(4))
- {
- for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
- {
- if (ms_quick_get_away(monster, hspell_pass[i]))
- {
- spell_cast = hspell_pass[i];
- finalAnswer = true;
- break;
- }
- }
- }
-
- // Promote the casting of useful spells for low-HP monsters.
- if (!finalAnswer
- && monster->hit_points < monster->max_hit_points / 4
- && !one_chance_in(4))
- {
- // Note: There should always be at least some chance we don't
- // get here... even if the monster is on its last HP. That
- // way we don't have to worry about monsters infinitely casting
- // Healing on themselves (e.g. orc high priests).
- if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
- && ms_low_hitpoint_cast(monster, hspell_pass[5]))
- {
- spell_cast = hspell_pass[5];
- finalAnswer = true;
- }
-
- if (!finalAnswer)
- {
- int found_spell = 0;
- for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
- {
- if (ms_low_hitpoint_cast(monster, hspell_pass[i])
- && one_chance_in(++found_spell))
- {
- spell_cast = hspell_pass[i];
- finalAnswer = true;
- }
- }
- }
- }
-
- if (!finalAnswer)
- {
- // If nothing found by now, safe friendlies and good
- // neutrals will rarely cast.
- if (mons_wont_attack(monster) && !mon_enemies_around(monster)
- && !one_chance_in(10))
- {
- return (false);
- }
-
- // Remove healing/invis/haste if we don't need them.
- int num_no_spell = 0;
-
- for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
- {
- if (hspell_pass[i] == SPELL_NO_SPELL)
- num_no_spell++;
- else if (ms_waste_of_time(monster, hspell_pass[i])
- || hspell_pass[i] == SPELL_DIG)
- {
- // Should monster not have selected dig by now,
- // it never will.
- hspell_pass[i] = SPELL_NO_SPELL;
- num_no_spell++;
- }
- }
-
- // If no useful spells... cast no spell.
- if (num_no_spell == NUM_MONSTER_SPELL_SLOTS
- && draco_breath == SPELL_NO_SPELL)
- {
- return (false);
- }
-
- const bolt orig_beem = beem;
- // Up to four tries to pick a spell.
- for (int loopy = 0; loopy < 4; ++loopy)
- {
- beem = orig_beem;
-
- bool spellOK = false;
-
- // Setup spell - monsters that are fleeing or pacified
- // and leaving the level will always try to choose their
- // emergency spell.
- if (mons_is_fleeing(monster) || mons_is_pacified(monster))
- {
- spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL
- : hspell_pass[5]);
-
- // Pacified monsters leaving the level won't choose
- // emergency spells harmful to the area.
- if (spell_cast != SPELL_NO_SPELL
- && mons_is_pacified(monster)
- && spell_harms_area(spell_cast))
- {
- spell_cast = SPELL_NO_SPELL;
- }
- }
- else
- {
- // Randomly picking one of the non-emergency spells:
- spell_cast = hspell_pass[random2(5)];
- }
-
- if (spell_cast == SPELL_NO_SPELL)
- continue;
-
- // Setup the spell.
- setup_mons_cast(monster, beem, spell_cast);
-
- // beam-type spells requiring tracers
- if (spell_needs_tracer(spell_cast))
- {
- const bool explode =
- spell_is_direct_explosion(spell_cast);
- fire_tracer(monster, beem, explode);
- // Good idea?
- if (mons_should_fire(beem))
- spellOK = true;
- }
- else
- {
- // All direct-effect/summoning/self-enchantments/etc.
- spellOK = true;
-
- if (ms_direct_nasty(spell_cast)
- && mons_aligned(monster_index(monster),
- monster->foe))
- {
- spellOK = false;
- }
- else if (monster->foe == MHITYOU || monster->foe == MHITNOT)
- {
- // XXX: Note the crude hack so that monsters can
- // use ME_ALERT to target (we should really have
- // a measure of time instead of peeking to see
- // if the player is still there). -- bwr
- if (!you.visible_to(monster)
- && (monster->target != you.pos() || coinflip()))
- {
- spellOK = false;
- }
- }
- else if (!monster->can_see(&menv[monster->foe]))
- {
- spellOK = false;
- }
- else if (monster->type == MONS_DAEVA
- && monster->god == GOD_SHINING_ONE)
- {
- const monsters *mon = &menv[monster->foe];
-
- // Don't allow TSO-worshipping daevas to make
- // unchivalric magic attacks, except against
- // appropriate monsters.
- if (is_unchivalric_attack(monster, mon)
- && !tso_unchivalric_attack_safe_monster(mon))
- {
- spellOK = false;
- }
- }
- }
-
- // If not okay, then maybe we'll cast a defensive spell.
- if (!spellOK)
- {
- spell_cast = (coinflip() ? hspell_pass[2]
- : SPELL_NO_SPELL);
- }
-
- if (spell_cast != SPELL_NO_SPELL)
- break;
- }
- }
-
- // If there's otherwise no ranged attack use the breath weapon.
- // The breath weapon is also occasionally used.
- if (draco_breath != SPELL_NO_SPELL
- && (spell_cast == SPELL_NO_SPELL
- || !_is_emergency_spell(hspell_pass, spell_cast)
- && one_chance_in(4))
- && !_is_player_or_mon_sanct(monster))
- {
- spell_cast = draco_breath;
- finalAnswer = true;
- }
-
- // Should the monster *still* not have a spell, well, too bad {dlb}:
- if (spell_cast == SPELL_NO_SPELL)
- return (false);
-
- // Friendly monsters don't use polymorph other, for fear of harming
- // the player.
- if (spell_cast == SPELL_POLYMORPH_OTHER && mons_friendly(monster))
- return (false);
-
- // Try to animate dead: if nothing rises, pretend we didn't cast it.
- if (spell_cast == SPELL_ANIMATE_DEAD
- && !animate_dead(monster, 100, SAME_ATTITUDE(monster),
- monster->foe, god, false))
- {
- return (false);
- }
-
- if (monster->type == MONS_BALL_LIGHTNING)
- monster->hit_points = -1;
-
- // FINALLY! determine primary spell effects {dlb}:
- if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK)
- {
- // Why only cast blink if nearby? {dlb}
- if (monsterNearby)
- {
- mons_cast_noise(monster, beem, spell_cast);
- monster_blink(monster);
-
- monster->lose_energy(EUT_SPELL);
- }
- else
- return (false);
- }
- else
- {
- if (spell_needs_foe(spell_cast))
- _make_mons_stop_fleeing(monster);
-
- mons_cast(monster, beem, spell_cast);
- mmov.reset();
- monster->lose_energy(EUT_SPELL);
- }
- } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ...
-
- return (true);
-}
-
// Returns a rough estimate of damage from throwing the wielded weapon.
int mons_thrown_weapon_damage(const item_def *weap)
{
@@ -7244,135 +3187,6 @@ int mons_pick_best_missile(monsters *mons, item_def **launcher,
}
}
-//---------------------------------------------------------------
-//
-// handle_throw
-//
-// Give the monster a chance to throw something. Returns true if
-// the monster hurled.
-//
-//---------------------------------------------------------------
-static bool _handle_throw(monsters *monster, bolt & beem)
-{
- // Yes, there is a logic to this ordering {dlb}:
- if (monster->incapacitated()
- || monster->asleep()
- || monster->submerged())
- {
- return (false);
- }
-
- if (mons_itemuse(monster) < MONUSE_STARTING_EQUIPMENT)
- return (false);
-
- const bool archer = mons_class_flag(monster->type, M_ARCHER);
- // Highly-specialised archers are more likely to shoot than talk.
- if (one_chance_in(archer? 9 : 5))
- return (false);
-
- // Don't allow offscreen throwing for now.
- if (monster->foe == MHITYOU && !mons_near(monster))
- return (false);
-
- // Monsters won't shoot in melee range, largely for balance reasons.
- // Specialist archers are an exception to this rule.
- if (!archer && adjacent(beem.target, monster->pos()))
- return (false);
-
- // Greatly lowered chances if the monster is fleeing or pacified and
- // leaving the level.
- if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
- && !one_chance_in(8))
- {
- return (false);
- }
-
- item_def *launcher = NULL;
- const item_def *weapon = NULL;
- const int mon_item = mons_pick_best_missile(monster, &launcher);
-
- if (mon_item == NON_ITEM || !is_valid_item(mitm[mon_item]))
- return (false);
-
- if (_is_player_or_mon_sanct(monster))
- return (false);
-
- item_def *missile = &mitm[mon_item];
-
- // Throwing a net at a target that is already caught would be
- // completely useless, so bail out.
- const actor *act = actor_at(beem.target);
- if (missile->base_type == OBJ_MISSILES
- && missile->sub_type == MI_THROWING_NET
- && act && act->caught())
- {
- return (false);
- }
-
- // If the attack needs a launcher that we can't wield, bail out.
- if (launcher)
- {
- weapon = monster->mslot_item(MSLOT_WEAPON);
- if (weapon && weapon != launcher && weapon->cursed())
- return (false);
- }
-
- // Ok, we'll try it.
- setup_generic_throw( monster, beem );
-
- // Set fake damage for the tracer.
- beem.damage = dice_def(10, 10);
-
- // Set item for tracer, even though it probably won't be used
- beem.item = missile;
-
- // Fire tracer.
- fire_tracer( monster, beem );
-
- // Clear fake damage (will be set correctly in mons_throw).
- beem.damage = 0;
-
- // Good idea?
- if (mons_should_fire( beem ))
- {
- // Monsters shouldn't shoot if fleeing, so let them "turn to attack".
- _make_mons_stop_fleeing(monster);
-
- if (launcher && launcher != weapon)
- monster->swap_weapons();
-
- beem.name.clear();
- return (mons_throw( monster, beem, mon_item ));
- }
-
- return (false);
-}
-
-static bool _handle_monster_spell(monsters *monster, bolt &beem)
-{
- // Shapeshifters don't get spells.
- if (!mons_is_shapeshifter(monster)
- || !mons_class_flag(monster->type, M_ACTUAL_SPELLS))
- {
- if (_handle_spell(monster, beem))
- return (true);
- }
-
- return (false);
-}
-
-// Give the monster its action energy (aka speed_increment).
-static void _monster_add_energy(monsters *monster)
-{
- if (monster->speed > 0)
- {
- // Randomise to make counting off monster moves harder:
- const int energy_gained =
- std::max(1, div_rand_round(monster->speed * you.time_taken, 10));
- monster->speed_increment += energy_gained;
- }
-}
-
int mons_natural_regen_rate(monsters *monster)
{
// A HD divider ranging from 3 (at 1 HD) to 1 (at 8 HD).
@@ -7398,1165 +3212,6 @@ int mons_natural_regen_rate(monsters *monster)
return (std::max(div_rand_round(monster->hit_dice, divider), 1));
}
-static inline bool _mons_natural_regen_roll(monsters *monster)
-{
- const int regen_rate = mons_natural_regen_rate(monster);
- return (x_chance_in_y(regen_rate, 25));
-}
-
-// Do natural regeneration for monster.
-static void _monster_regenerate(monsters *monster)
-{
- if (monster->has_ench(ENCH_SICK) || !mons_can_regenerate(monster))
- return;
-
- // Non-land creatures out of their element cannot regenerate.
- if (mons_primary_habitat(monster) != HT_LAND
- && !monster_habitable_grid(monster, grd(monster->pos())))
- {
- return;
- }
-
- if (monster_descriptor(monster->type, MDSC_REGENERATES)
- || (monster->type == MONS_FIRE_ELEMENTAL
- && (grd(monster->pos()) == DNGN_LAVA
- || cloud_type_at(monster->pos()) == CLOUD_FIRE))
-
- || (monster->type == MONS_WATER_ELEMENTAL
- && feat_is_watery(grd(monster->pos())))
-
- || (monster->type == MONS_AIR_ELEMENTAL
- && env.cgrid(monster->pos()) == EMPTY_CLOUD
- && one_chance_in(3))
-
- || _mons_natural_regen_roll(monster))
- {
- heal_monster(monster, 1, false);
- }
-}
-
-static bool _swap_monsters(monsters* mover, monsters* moved)
-{
- // Can't swap with a stationary monster.
- if (mons_is_stationary(moved))
- return (false);
-
- // Swapping is a purposeful action.
- if (mover->confused())
- return (false);
-
- // Right now just happens in sanctuary.
- if (!is_sanctuary(mover->pos()) || !is_sanctuary(moved->pos()))
- return (false);
-
- // A friendly or good-neutral monster moving past a fleeing hostile
- // or neutral monster, or vice versa.
- if (mons_wont_attack_real(mover) == mons_wont_attack_real(moved)
- || mons_is_fleeing(mover) == mons_is_fleeing(moved))
- {
- return (false);
- }
-
- // Don't swap places if the player explicitly ordered their pet to
- // attack monsters.
- if ((mons_friendly(mover) || mons_friendly(moved))
- && you.pet_target != MHITYOU && you.pet_target != MHITNOT)
- {
- return (false);
- }
-
- if (!mover->can_pass_through(moved->pos())
- || !moved->can_pass_through(mover->pos()))
- {
- return (false);
- }
-
- if (!monster_habitable_grid(mover, grd(moved->pos()))
- || !monster_habitable_grid(moved, grd(mover->pos())))
- {
- return (false);
- }
-
- // Okay, we can do the swap.
- const coord_def mover_pos = mover->pos();
- const coord_def moved_pos = moved->pos();
-
- mover->pos() = moved_pos;
- moved->pos() = mover_pos;
-
- mgrd(mover->pos()) = mover->mindex();
- mgrd(moved->pos()) = moved->mindex();
-
- if (you.can_see(mover) && you.can_see(moved))
- {
- mprf("%s and %s swap places.", mover->name(DESC_CAP_THE).c_str(),
- moved->name(DESC_NOCAP_THE).c_str());
- }
-
- return (true);
-}
-
-static void _swim_or_move_energy(monsters *mon)
-{
- const dungeon_feature_type feat = grd(mon->pos());
-
- // FIXME: Replace check with mons_is_swimming()?
- mon->lose_energy( (feat >= DNGN_LAVA && feat <= DNGN_SHALLOW_WATER
- && !mon->airborne()) ? EUT_SWIM
- : EUT_MOVE );
-}
-
-static void _khufu_drop_tomb(monsters *monster)
-{
- int count = 0;
-
- monster->behaviour = BEH_SEEK; // don't wander on duty!
- for (adjacent_iterator ai(monster->pos()); ai; ++ai)
- {
- if (grd(*ai) == DNGN_ROCK_WALL)
- {
- grd(*ai) = DNGN_FLOOR;
- count++;
- }
- }
- if (count)
- if (mons_near(monster))
- mpr("The walls disappear!");
- else
- mpr("You hear a deep rumble.");
- monster->number = 0;
- monster->lose_energy(EUT_SPELL);
-}
-
-#ifdef DEBUG
-# define DEBUG_ENERGY_USE(problem) \
- if (monster->speed_increment == old_energy && monster->alive()) \
- mprf(MSGCH_DIAGNOSTICS, \
- problem " for monster '%s' consumed no energy", \
- monster->name(DESC_PLAIN).c_str(), true);
-#else
-# define DEBUG_ENERGY_USE(problem) ((void) 0)
-#endif
-
-static void _handle_monster_move(monsters *monster)
-{
- monster->hit_points = std::min(monster->max_hit_points,
- monster->hit_points);
-
- // Monster just summoned (or just took stairs), skip this action.
- if (testbits( monster->flags, MF_JUST_SUMMONED ))
- {
- monster->flags &= ~MF_JUST_SUMMONED;
- return;
- }
-
- mon_acting mact(monster);
-
- _monster_add_energy(monster);
-
- // Handle clouds on nonmoving monsters.
- if (monster->speed == 0
- && env.cgrid(monster->pos()) != EMPTY_CLOUD
- && !monster->submerged())
- {
- _mons_in_cloud( monster );
- }
-
- // Apply monster enchantments once for every normal-speed
- // player turn.
- monster->ench_countdown -= you.time_taken;
- while (monster->ench_countdown < 0)
- {
- monster->ench_countdown += 10;
- monster->apply_enchantments();
-
- // If the monster *merely* died just break from the loop
- // rather than quit altogether, since we have to deal with
- // giant spores and ball lightning exploding at the end of the
- // function, but do return if the monster's data has been
- // reset, since then the monster type is invalid.
- if (monster->type == MONS_NO_MONSTER)
- return;
- else if (monster->hit_points < 1)
- break;
- }
-
- // Memory is decremented here for a reason -- we only want it
- // decrementing once per monster "move".
- if (monster->foe_memory > 0)
- monster->foe_memory--;
-
- // Otherwise there are potential problems with summonings.
- if (monster->type == MONS_GLOWING_SHAPESHIFTER)
- monster->add_ench(ENCH_GLOWING_SHAPESHIFTER);
-
- if (monster->type == MONS_SHAPESHIFTER)
- monster->add_ench(ENCH_SHAPESHIFTER);
-
- // We reset batty monsters from wander to seek here, instead
- // of in handle_behaviour() since that will be called with
- // every single movement, and we want these monsters to
- // hit and run. -- bwr
- if (monster->foe != MHITNOT && mons_is_wandering(monster)
- && mons_is_batty(monster))
- {
- monster->behaviour = BEH_SEEK;
- }
-
- monster->check_speed();
-
- monsterentry* entry = get_monster_data(monster->type);
- if (!entry)
- return;
-
- int old_energy = INT_MAX;
- int non_move_energy = std::min(entry->energy_usage.move,
- entry->energy_usage.swim);
-
-#if DEBUG_MONS_SCAN
- bool monster_was_floating = mgrd(monster->pos()) != monster->mindex();
-#endif
-
- while (monster->has_action_energy())
- {
- // The continues & breaks are WRT this.
- if (!monster->alive())
- break;
-
- const coord_def old_pos = monster->pos();
-
-#if DEBUG_MONS_SCAN
- if (!monster_was_floating
- && mgrd(monster->pos()) != monster->mindex())
- {
- mprf(MSGCH_ERROR, "Monster %s became detached from mgrd "
- "in _handle_monster_move() loop",
- monster->name(DESC_PLAIN, true).c_str());
- mpr("[[[[[[[[[[[[[[[[[[", MSGCH_WARN);
- debug_mons_scan();
- mpr("]]]]]]]]]]]]]]]]]]", MSGCH_WARN);
- monster_was_floating = true;
- }
- else if (monster_was_floating
- && mgrd(monster->pos()) == monster->mindex())
- {
- mprf(MSGCH_DIAGNOSTICS, "Monster %s re-attached itself to mgrd "
- "in _handle_monster_move() loop",
- monster->name(DESC_PLAIN, true).c_str());
- monster_was_floating = false;
- }
-#endif
-
- if (monster->speed_increment >= old_energy)
- {
-#ifdef DEBUG
- if (monster->speed_increment == old_energy)
- {
- mprf(MSGCH_DIAGNOSTICS, "'%s' has same energy as last loop",
- monster->name(DESC_PLAIN, true).c_str());
- }
- else
- {
- mprf(MSGCH_DIAGNOSTICS, "'%s' has MORE energy than last loop",
- monster->name(DESC_PLAIN, true).c_str());
- }
-#endif
- monster->speed_increment = old_energy - 10;
- old_energy = monster->speed_increment;
- continue;
- }
- old_energy = monster->speed_increment;
-
- monster->shield_blocks = 0;
-
- cloud_type cl_type;
- const int cloud_num = env.cgrid(monster->pos());
- const bool avoid_cloud = mons_avoids_cloud(monster, cloud_num,
- &cl_type);
- if (cl_type != CLOUD_NONE)
- {
- if (avoid_cloud)
- {
- if (monster->submerged())
- {
- monster->speed_increment -= entry->energy_usage.swim;
- break;
- }
-
- if (monster->type == MONS_NO_MONSTER)
- {
- monster->speed_increment -= entry->energy_usage.move;
- break; // problem with vortices
- }
- }
-
- _mons_in_cloud(monster);
-
- if (monster->type == MONS_NO_MONSTER)
- {
- monster->speed_increment = 1;
- break;
- }
- }
-
- if (monster->type == MONS_TIAMAT && one_chance_in(3))
- {
- const int cols[] = { RED, WHITE, DARKGREY, GREEN, MAGENTA };
- monster->colour = RANDOM_ELEMENT(cols);
- }
-
- _monster_regenerate(monster);
-
- if (mons_cannot_act(monster))
- {
- monster->speed_increment -= non_move_energy;
- continue;
- }
-
- _handle_behaviour(monster);
-
- // _handle_behaviour() could make the monster leave the level.
- if (!monster->alive())
- break;
-
- ASSERT(!crawl_state.arena || monster->foe != MHITYOU);
- ASSERT(in_bounds(monster->target) || monster->target.origin());
-
- // Submerging monsters will hide from clouds.
- if (avoid_cloud
- && monster_can_submerge(monster, grd(monster->pos()))
- && !monster->caught()
- && !monster->submerged())
- {
- monster->add_ench(ENCH_SUBMERGED);
- monster->speed_increment -= ENERGY_SUBMERGE(entry);
- continue;
- }
-
- if (monster->speed >= 100)
- {
- monster->speed_increment -= non_move_energy;
- continue;
- }
-
- if (igrd(monster->pos()) != NON_ITEM
- && (mons_itemuse(monster) >= MONUSE_WEAPONS_ARMOUR
- || mons_itemeat(monster) != MONEAT_NOTHING))
- {
- // Keep neutral and charmed monsters from picking up stuff.
- // Same for friendlies if friendly_pickup is set to "none".
- if (!mons_neutral(monster) && !monster->has_ench(ENCH_CHARM)
- || (you.religion == GOD_JIYVA && mons_is_slime(monster))
- && (!mons_friendly(monster)
- || you.friendly_pickup != FRIENDLY_PICKUP_NONE))
- {
- if (_handle_pickup(monster))
- {
- DEBUG_ENERGY_USE("handle_pickup()");
- continue;
- }
- }
- }
-
- // Lurking monsters only stop lurking if their target is right
- // next to them, otherwise they just sit there.
- // However, if the monster is involuntarily submerged but
- // still alive (e.g., nonbreathing which had water poured
- // on top of it), this doesn't apply.
- if (mons_is_lurking(monster) || monster->has_ench(ENCH_SUBMERGED))
- {
- if (monster->foe != MHITNOT
- && grid_distance(monster->target, monster->pos()) <= 1)
- {
- if (monster->submerged())
- {
- // Don't unsubmerge if the monster is too damaged or
- // if the monster is afraid, or if it's avoiding the
- // cloud on top of the water.
- if (monster->hit_points <= monster->max_hit_points / 2
- || monster->has_ench(ENCH_FEAR)
- || avoid_cloud)
- {
- monster->speed_increment -= non_move_energy;
- continue;
- }
-
- if (!monster->del_ench(ENCH_SUBMERGED))
- {
- // Couldn't unsubmerge.
- monster->speed_increment -= non_move_energy;
- continue;
- }
- }
- monster->behaviour = BEH_SEEK;
- }
- else
- {
- monster->speed_increment -= non_move_energy;
- continue;
- }
- }
-
- if (mons_is_caught(monster))
- {
- // Struggling against the net takes time.
- _swim_or_move_energy(monster);
- }
- else if (!mons_is_petrified(monster))
- {
- // Calculates mmov based on monster target.
- _handle_movement(monster);
-
- if (mons_is_confused(monster)
- || monster->type == MONS_AIR_ELEMENTAL
- && monster->submerged())
- {
- mmov.reset();
- int pfound = 0;
- for (adjacent_iterator ai(monster->pos(), false); ai; ++ai)
- if (monster->can_pass_through(*ai))
- if (one_chance_in(++pfound))
- mmov = *ai - monster->pos();
-
- // OK, mmov determined.
- const coord_def newcell = mmov + monster->pos();
- monsters* enemy = monster_at(newcell);
- if (enemy
- && newcell != monster->pos()
- && !is_sanctuary(monster->pos()))
- {
- if (monsters_fight(monster, enemy))
- {
- mmov.reset();
- DEBUG_ENERGY_USE("monsters_fight()");
- continue;
- }
- else
- {
- // FIXME: None of these work!
- // Instead run away!
- if (monster->add_ench(mon_enchant(ENCH_FEAR)))
- {
- behaviour_event(monster, ME_SCARE,
- MHITNOT, newcell);
- }
- break;
- }
- }
- }
- }
- _handle_nearby_ability(monster);
-
- if (monster->type == MONS_KHUFU && monster->number
- && monster->hit_points==monster->max_hit_points)
- _khufu_drop_tomb(monster);
-
- if (!monster->asleep() && !mons_is_wandering(monster)
- // Berserking monsters are limited to running up and
- // hitting their foes.
- && !monster->has_ench(ENCH_BERSERK)
- // Slime creatures can split while wandering or resting.
- || monster->type == MONS_SLIME_CREATURE)
- {
- bolt beem;
-
- beem.source = monster->pos();
- beem.target = monster->target;
- beem.beam_source = monster->mindex();
-
- // Prevents unfriendlies from nuking you from offscreen.
- // How nice!
- const bool friendly_or_near =
- mons_friendly(monster) || monster->near_foe();
- if (friendly_or_near
- || monster->type == MONS_TEST_SPAWNER
- // Slime creatures can split when offscreen.
- || monster->type == MONS_SLIME_CREATURE)
- {
- // [ds] Special abilities shouldn't overwhelm
- // spellcasting in monsters that have both. This aims
- // to give them both roughly the same weight.
- if (coinflip() ? _handle_special_ability(monster, beem)
- || _handle_monster_spell(monster, beem)
- : _handle_monster_spell(monster, beem)
- || _handle_special_ability(monster, beem))
- {
- DEBUG_ENERGY_USE("spell or special");
- continue;
- }
- }
-
- if (friendly_or_near)
- {
- if (_handle_potion(monster, beem))
- {
- DEBUG_ENERGY_USE("_handle_potion()");
- continue;
- }
-
- if (_handle_scroll(monster))
- {
- DEBUG_ENERGY_USE("_handle_scroll()");
- continue;
- }
-
- if (_handle_wand(monster, beem))
- {
- DEBUG_ENERGY_USE("_handle_wand()");
- continue;
- }
-
- if (_handle_reaching(monster))
- {
- DEBUG_ENERGY_USE("_handle_reaching()");
- continue;
- }
- }
-
- if (_handle_throw(monster, beem))
- {
- DEBUG_ENERGY_USE("_handle_throw()");
- continue;
- }
- }
-
- if (!mons_is_caught(monster))
- {
- if (monster->pos() + mmov == you.pos())
- {
- ASSERT(!crawl_state.arena);
-
- if (!mons_friendly(monster))
- {
- // If it steps into you, cancel other targets.
- monster->foe = MHITYOU;
- monster->target = you.pos();
-
- monster_attack(monster);
-
- if (mons_is_batty(monster))
- {
- monster->behaviour = BEH_WANDER;
- _set_random_target(monster);
- }
- DEBUG_ENERGY_USE("monster_attack()");
- mmov.reset();
- continue;
- }
- }
-
- // See if we move into (and fight) an unfriendly monster.
- monsters* targ = monster_at(monster->pos() + mmov);
- if (targ
- && targ != monster
- && !mons_aligned(monster->mindex(), targ->mindex())
- && monster_can_hit_monster(monster, targ))
- {
- // Maybe they can swap places?
- if (_swap_monsters(monster, targ))
- {
- _swim_or_move_energy(monster);
- continue;
- }
- // Figure out if they fight.
- else if (monsters_fight(monster, targ))
- {
- if (mons_is_batty(monster))
- {
- monster->behaviour = BEH_WANDER;
- _set_random_target(monster);
- // monster->speed_increment -= monster->speed;
- }
-
- mmov.reset();
- DEBUG_ENERGY_USE("monsters_fight()");
- continue;
- }
- }
-
- if (invalid_monster(monster) || mons_is_stationary(monster))
- {
- if (monster->speed_increment == old_energy)
- monster->speed_increment -= non_move_energy;
- continue;
- }
-
- if (mons_cannot_move(monster) || !_monster_move(monster))
- monster->speed_increment -= non_move_energy;
- }
- update_beholders(monster);
-
- // Reevaluate behaviour, since the monster's surroundings have
- // changed (it may have moved, or died for that matter). Don't
- // bother for dead monsters. :)
- if (monster->alive())
- {
- _handle_behaviour(monster);
- ASSERT(in_bounds(monster->target) || monster->target.origin());
- }
- }
-
- if (monster->type != MONS_NO_MONSTER && monster->hit_points < 1)
- monster_die(monster, KILL_MISC, NON_MONSTER);
-}
-
-//---------------------------------------------------------------
-//
-// handle_monsters
-//
-// This is the routine that controls monster AI.
-//
-//---------------------------------------------------------------
-void handle_monsters()
-{
- // Keep track of monsters that have already moved and don't allow
- // them to move again.
- memset(immobile_monster, 0, sizeof immobile_monster);
-
- for (int i = 0; i < MAX_MONSTERS; ++i)
- {
- monsters *monster = &menv[i];
-
- if (!monster->alive() || immobile_monster[i])
- continue;
-
- const coord_def oldpos = monster->pos();
-
- _handle_monster_move(monster);
-
- if (!invalid_monster(monster) && monster->pos() != oldpos)
- immobile_monster[i] = true;
-
- // If the player got banished, discard pending monster actions.
- if (you.banished)
- {
- // Clear list of mesmerising monsters.
- if (you.duration[DUR_MESMERISED])
- {
- you.mesmerised_by.clear();
- you.duration[DUR_MESMERISED] = 0;
- }
- break;
- }
- }
-
- // Clear any summoning flags so that lower indiced
- // monsters get their actions in the next round.
- for (int i = 0; i < MAX_MONSTERS; i++)
- menv[i].flags &= ~MF_JUST_SUMMONED;
-}
-
-static bool _is_item_jelly_edible(const item_def &item)
-{
- // Don't eat artefacts.
- if (is_artefact(item))
- return (false);
-
- // Shouldn't eat stone things
- // - but what about wands and rings?
- if (item.base_type == OBJ_MISSILES
- && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
- {
- return (false);
- }
-
- // Don't eat special game items.
- if (item.base_type == OBJ_ORBS
- || (item.base_type == OBJ_MISCELLANY
- && (item.sub_type == MISC_RUNE_OF_ZOT
- || item.sub_type == MISC_HORN_OF_GERYON)))
- {
- return (false);
- }
-
- return (true);
-}
-
-// XXX: This function assumes that only jellies eat items.
-static bool _monster_eat_item(monsters *monster, bool nearby)
-{
- if (!mons_eats_items(monster))
- return (false);
-
- // Friendly jellies won't eat (unless worshipping Jiyva).
- if (mons_friendly(monster) && you.religion != GOD_JIYVA)
- return (false);
-
- int hps_gained = 0;
- int max_eat = roll_dice(1, 10);
- int eaten = 0;
- bool eaten_net = false;
-
- for (stack_iterator si(monster->pos());
- si && eaten < max_eat && hps_gained < 50; ++si)
- {
- if (!_is_item_jelly_edible(*si))
- continue;
-
-#if DEBUG_DIAGNOSTICS || DEBUG_EATERS
- mprf(MSGCH_DIAGNOSTICS,
- "%s eating %s", monster->name(DESC_PLAIN, true).c_str(),
- si->name(DESC_PLAIN).c_str());
-#endif
-
- int quant = si->quantity;
-
- if (si->base_type != OBJ_GOLD)
- {
- quant = std::min(quant, max_eat - eaten);
-
- hps_gained += (quant * item_mass(*si)) / 20 + quant;
- eaten += quant;
-
- if (mons_is_caught(monster)
- && si->base_type == OBJ_MISSILES
- && si->sub_type == MI_THROWING_NET
- && item_is_stationary(*si))
- {
- monster->del_ench(ENCH_HELD, true);
- eaten_net = true;
- }
- }
- else
- {
- // Shouldn't be much trouble to digest a huge pile of gold!
- if (quant > 500)
- quant = 500 + roll_dice(2, (quant - 500) / 2);
-
- hps_gained += quant / 10 + 1;
- eaten++;
- }
-
- if (you.religion == GOD_JIYVA)
- {
- const int quantity = si->quantity;
- const int value = item_value(*si) / quantity;
- int pg = 0;
- int timeout = 0;
-
- for (int m = 0; m < quantity; ++m)
- {
- if (x_chance_in_y(value / 2 + 1, 30 + you.piety / 4))
- {
- if (timeout <= 0)
- pg += random2(item_value(*si) / 6);
- else
- timeout -= value / 5;
- }
- }
-
- if (pg > 0)
- {
- simple_god_message(" appreciates your sacrifice.");
- gain_piety(pg);
- }
-
- if (you.piety > 80 && random2(you.piety) > 50 && one_chance_in(4))
- {
- if (you.can_safely_mutate())
- {
- simple_god_message(" alters your body.");
-
- bool success = false;
- const int rand = random2(100);
-
- if (rand < 40)
- success = mutate(RANDOM_MUTATION, true, false, true);
- else if (rand < 60)
- {
- success = delete_mutation(RANDOM_MUTATION, true, false,
- true);
- }
- else
- {
- success = mutate(RANDOM_GOOD_MUTATION, true, false,
- true);
- }
-
- if (success)
- {
- timeout = (100 + roll_dice(2, 4));
- you.num_gifts[you.religion]++;
- take_note(Note(NOTE_GOD_GIFT, you.religion));
- }
- else
- mpr("You feel as though nothing has changed.");
- }
- }
- }
-
- if (quant >= si->quantity)
- item_was_destroyed(*si, monster->mindex());
-
- dec_mitm_item_quantity(si.link(), quant);
- }
-
- if (eaten > 0)
- {
- hps_gained = std::max(hps_gained, 1);
- hps_gained = std::min(hps_gained, 50);
-
- // This is done manually instead of using heal_monster(),
- // because that function doesn't work quite this way. -- bwr
- monster->hit_points += hps_gained;
- monster->max_hit_points = std::max(monster->hit_points,
- monster->max_hit_points);
-
- if (player_can_hear(monster->pos()))
- {
- mprf(MSGCH_SOUND, "You hear a%s slurping noise.",
- nearby ? "" : " distant");
- }
-
- if (eaten_net)
- simple_monster_message(monster, " devours the net!");
-
- _jelly_divide(monster);
- }
-
- return (eaten > 0);
-}
-
-static bool _monster_eat_single_corpse(monsters *monster, item_def& item,
- bool do_heal, bool nearby)
-{
- if (item.base_type != OBJ_CORPSES || item.sub_type != CORPSE_BODY)
- return (false);
-
- monster_type mt = static_cast<monster_type>(item.plus);
- if (do_heal)
- {
- monster->hit_points += 1 + random2(mons_weight(mt)) / 100;
-
- // Limited growth factor here - should 77 really be the cap? {dlb}:
- monster->hit_points = std::min(100, monster->hit_points);
- monster->max_hit_points = std::max(monster->hit_points,
- monster->max_hit_points);
- }
-
- if (nearby)
- {
- mprf("%s eats %s.", monster->name(DESC_CAP_THE).c_str(),
- item.name(DESC_NOCAP_THE).c_str());
- }
-
- // Assume that eating a corpse requires butchering it. Use logic
- // from misc.cc:turn_corpse_into_chunks() and the butchery-related
- // delays in delay.cc:stop_delay().
-
- const int max_chunks = mons_weight(mt) / 150;
-
- // Only fresh corpses bleed enough to colour the ground.
- if (!food_is_rotten(item))
- bleed_onto_floor(monster->pos(), mt, max_chunks, true);
-
- if (mons_skeleton(mt) && one_chance_in(3))
- turn_corpse_into_skeleton(item);
- else
- destroy_item(item.index());
-
- return (true);
-}
-
-static bool _monster_eat_corpse(monsters *monster, bool do_heal, bool nearby)
-{
- if (!mons_eats_corpses(monster))
- return (false);
-
- int eaten = 0;
-
- for (stack_iterator si(monster->pos()); si; ++si)
- {
- if (_monster_eat_single_corpse(monster, *si, do_heal, nearby))
- {
- eaten++;
- break;
- }
- }
-
- return (eaten > 0);
-}
-
-static bool _monster_eat_food(monsters *monster, bool nearby)
-{
- if (!mons_eats_food(monster))
- return (false);
-
- if (mons_is_fleeing(monster))
- return (false);
-
- int eaten = 0;
-
- for (stack_iterator si(monster->pos()); si; ++si)
- {
- const bool is_food = (si->base_type == OBJ_FOOD);
- const bool is_corpse = (si->base_type == OBJ_CORPSES
- && si->sub_type == CORPSE_BODY);
-
- if (!is_food && !is_corpse)
- continue;
-
- if ((mons_wont_attack(monster)
- || grid_distance(monster->pos(), you.pos()) > 1)
- && coinflip())
- {
- if (is_food)
- {
- if (nearby)
- {
- mprf("%s eats %s.", monster->name(DESC_CAP_THE).c_str(),
- quant_name(*si, 1, DESC_NOCAP_THE).c_str());
- }
-
- dec_mitm_item_quantity(si.link(), 1);
-
- eaten++;
- break;
- }
- else
- {
- // Assume that only undead can heal from eating corpses.
- if (_monster_eat_single_corpse(monster, *si,
- monster->holiness() == MH_UNDEAD,
- nearby))
- {
- eaten++;
- break;
- }
- }
- }
- }
-
- return (eaten > 0);
-}
-
-//---------------------------------------------------------------
-//
-// handle_pickup
-//
-// Returns false if monster doesn't spend any time picking something up.
-//
-//---------------------------------------------------------------
-static bool _handle_pickup(monsters *monster)
-{
- if (monster->asleep() || monster->submerged())
- return (false);
-
- const bool nearby = mons_near(monster);
- int count_pickup = 0;
-
- if (mons_itemeat(monster) != MONEAT_NOTHING)
- {
- if (mons_eats_items(monster))
- {
- if (_monster_eat_item(monster, nearby))
- return (false);
- }
- else if (mons_eats_corpses(monster))
- {
- // Assume that only undead can heal from eating corpses.
- if (_monster_eat_corpse(monster, monster->holiness() == MH_UNDEAD,
- nearby))
- {
- return (false);
- }
- }
- else if (mons_eats_food(monster))
- {
- if (_monster_eat_food(monster, nearby))
- return (false);
- }
- }
-
- if (mons_itemuse(monster) >= MONUSE_WEAPONS_ARMOUR)
- {
- // Note: Monsters only look at stuff near the top of stacks.
- //
- // XXX: Need to put in something so that monster picks up
- // multiple items (e.g. ammunition) identical to those it's
- // carrying.
- //
- // Monsters may now pick up up to two items in the same turn.
- // (jpeg)
- for (stack_iterator si(monster->pos()); si; ++si)
- {
- if (monster->pickup_item(*si, nearby))
- count_pickup++;
-
- if (count_pickup > 1 || coinflip())
- break;
- }
- }
-
- return (count_pickup > 0);
-}
-
-static void _jelly_grows(monsters *monster)
-{
- if (player_can_hear(monster->pos()))
- {
- mprf(MSGCH_SOUND, "You hear a%s slurping noise.",
- mons_near(monster) ? "" : " distant");
- }
-
- monster->hit_points += 5;
-
- // note here, that this makes jellies "grow" {dlb}:
- if (monster->hit_points > monster->max_hit_points)
- monster->max_hit_points = monster->hit_points;
-
- _jelly_divide(monster);
-}
-
-static bool _mons_can_displace(const monsters *mpusher, const monsters *mpushee)
-{
- if (invalid_monster(mpusher) || invalid_monster(mpushee))
- return (false);
-
- const int ipushee = monster_index(mpushee);
- if (invalid_monster_index(ipushee))
- return (false);
-
- if (immobile_monster[ipushee])
- return (false);
-
- // Confused monsters can't be pushed past, sleeping monsters
- // can't push. Note that sleeping monsters can't be pushed
- // past, either, but they may be woken up by a crowd trying to
- // elbow past them, and the wake-up check happens downstream.
- if (mons_is_confused(mpusher) || mons_is_confused(mpushee)
- || mons_cannot_move(mpusher) || mons_cannot_move(mpushee)
- || mons_is_stationary(mpusher) || mons_is_stationary(mpushee)
- || mpusher->asleep())
- {
- return (false);
- }
-
- // Batty monsters are unpushable.
- if (mons_is_batty(mpusher) || mons_is_batty(mpushee))
- return (false);
-
- if (!monster_shover(mpusher))
- return (false);
-
- // Fleeing monsters of the same type may push past higher ranking ones.
- if (!monster_senior(mpusher, mpushee, mons_is_fleeing(mpusher)))
- return (false);
-
- return (true);
-}
-
-static bool _monster_swaps_places( monsters *mon, const coord_def& delta )
-{
- if (delta.origin())
- return (false);
-
- monsters* const m2 = monster_at(mon->pos() + delta);
-
- if (!m2)
- return (false);
-
- if (!_mons_can_displace(mon, m2))
- return (false);
-
- if (m2->asleep())
- {
- if (coinflip())
- {
-#ifdef DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS,
- "Alerting monster %s at (%d,%d)",
- m2->name(DESC_PLAIN).c_str(), m2->pos().x, m2->pos().y);
-#endif
- behaviour_event(m2, ME_ALERT, MHITNOT);
- }
- return (false);
- }
-
- // Check that both monsters will be happy at their proposed new locations.
- const coord_def c = mon->pos();
- const coord_def n = mon->pos() + delta;
-
- if (!_habitat_okay(mon, grd(n)) || !_habitat_okay(m2, grd(c)))
- return (false);
-
- // Okay, do the swap!
- _swim_or_move_energy(mon);
-
- mon->pos() = n;
- mgrd(n) = monster_index(mon);
- m2->pos() = c;
- const int m2i = monster_index(m2);
- ASSERT(m2i >= 0 && m2i < MAX_MONSTERS);
- mgrd(c) = m2i;
- immobile_monster[m2i] = true;
-
- mon->check_redraw(c);
- mon->apply_location_effects(c);
- m2->check_redraw(c);
- m2->apply_location_effects(n);
-
- // The seen context no longer applies if the monster is moving normally.
- mon->seen_context.clear();
- m2->seen_context.clear();
-
- return (false);
-}
-
-static bool _do_move_monster(monsters *monster, const coord_def& delta)
-{
- const coord_def f = monster->pos() + delta;
-
- if (!in_bounds(f))
- return (false);
-
- if (f == you.pos())
- {
- monster_attack(monster);
- return (true);
- }
-
- // This includes the case where the monster attacks itself.
- if (monsters* def = monster_at(f))
- {
- monsters_fight(monster, def);
- return (true);
- }
-
- // The monster gave a "comes into view" message and then immediately
- // moved back out of view, leaing the player nothing to see, so give
- // this message to avoid confusion.
- if (monster->seen_context == _just_seen && !see_cell(f))
- simple_monster_message(monster, " moves out of view.");
- else if (Options.tutorial_left && (monster->flags & MF_WAS_IN_VIEW)
- && !see_cell(f))
- {
- learned_something_new(TUT_MONSTER_LEFT_LOS, monster->pos());
- }
-
- // The seen context no longer applies if the monster is moving normally.
- monster->seen_context.clear();
-
- // This appears to be the real one, ie where the movement occurs:
- _swim_or_move_energy(monster);
-
- if (grd(monster->pos()) == DNGN_DEEP_WATER && grd(f) != DNGN_DEEP_WATER
- && !monster_habitable_grid(monster, DNGN_DEEP_WATER))
- {
- monster->seen_context = "emerges from the water";
- }
- mgrd(monster->pos()) = NON_MONSTER;
-
- monster->pos() = f;
-
- mgrd(monster->pos()) = monster_index(monster);
-
- monster->check_redraw(monster->pos() - delta);
- monster->apply_location_effects(monster->pos() - delta);
-
- return (true);
-}
-
void mons_check_pool(monsters *monster, const coord_def &oldpos,
killer_type killer, int killnum)
{
@@ -8620,944 +3275,6 @@ void mons_check_pool(monsters *monster, const coord_def &oldpos,
}
}
-// Randomise potential damage.
-static int _estimated_trap_damage(trap_type trap)
-{
- switch (trap)
- {
- case TRAP_BLADE: return (10 + random2(30));
- case TRAP_DART: return (random2(4));
- case TRAP_ARROW: return (random2(7));
- case TRAP_SPEAR: return (random2(10));
- case TRAP_BOLT: return (random2(13));
- case TRAP_AXE: return (random2(15));
- default: return (0);
- }
-}
-
-// Check whether a given trap (described by trap position) can be
-// regarded as safe. Takes into account monster intelligence and
-// allegiance.
-// (just_check is used for intelligent monsters trying to avoid traps.)
-static bool _is_trap_safe(const monsters *monster, const coord_def& where,
- bool just_check)
-{
- const int intel = mons_intel(monster);
-
- const trap_def *ptrap = find_trap(where);
- if (!ptrap)
- return (true);
- const trap_def& trap = *ptrap;
-
- const bool player_knows_trap = (trap.is_known(&you));
-
- // No friendly monsters will ever enter a Zot trap you know.
- if (player_knows_trap && mons_friendly(monster) && trap.type == TRAP_ZOT)
- return (false);
-
- // Dumb monsters don't care at all.
- if (intel == I_PLANT)
- return (true);
-
- if (trap.type == TRAP_SHAFT && monster->will_trigger_shaft())
- {
- if (mons_is_fleeing(monster) && intel >= I_NORMAL
- || mons_is_pacified(monster))
- {
- return (true);
- }
- return (false);
- }
-
- // Hostile monsters are not afraid of non-mechanical traps.
- // Allies will try to avoid teleportation and zot traps.
- const bool mechanical = (trap.category() == DNGN_TRAP_MECHANICAL);
-
- if (trap.is_known(monster))
- {
- if (just_check)
- return (false); // Square is blocked.
- else
- {
- // Test for corridor-like environment.
- const int x = where.x - monster->pos().x;
- const int y = where.y - monster->pos().y;
-
- // The question is whether the monster (m) can easily reach its
- // presumable destination (x) without stepping on the trap. Traps
- // in corridors do not allow this. See e.g
- // #x# ##
- // #^# or m^x
- // m ##
- //
- // The same problem occurs if paths are blocked by monsters,
- // hostile terrain or other traps rather than walls.
- // What we do is check whether the squares with the relative
- // positions (-1,0)/(+1,0) or (0,-1)/(0,+1) form a "corridor"
- // (relative to the _trap_ position rather than the monster one).
- // If they don't, the trap square is marked as "unsafe" (because
- // there's a good alternative move for the monster to take),
- // otherwise the decision will be made according to later tests
- // (monster hp, trap type, ...)
- // If a monster still gets stuck in a corridor it will usually be
- // because it has less than half its maximum hp.
-
- if ((_mon_can_move_to_pos(monster, coord_def(x-1, y), true)
- || _mon_can_move_to_pos(monster, coord_def(x+1,y), true))
- && (_mon_can_move_to_pos(monster, coord_def(x,y-1), true)
- || _mon_can_move_to_pos(monster, coord_def(x,y+1), true)))
- {
- return (false);
- }
- }
- }
-
- // Friendlies will try not to be parted from you.
- if (intelligent_ally(monster) && trap.type == TRAP_TELEPORT
- && player_knows_trap && mons_near(monster))
- {
- return (false);
- }
-
- // Healthy monsters don't mind a little pain.
- if (mechanical && monster->hit_points >= monster->max_hit_points / 2
- && (intel == I_ANIMAL
- || monster->hit_points > _estimated_trap_damage(trap.type)))
- {
- return (true);
- }
-
- // Friendly and good neutral monsters don't enjoy Zot trap perks;
- // handle accordingly. In the arena Zot traps affect all monsters.
- if (mons_wont_attack(monster) || crawl_state.arena)
- {
- return (mechanical ? mons_flies(monster)
- : !trap.is_known(monster) || trap.type != TRAP_ZOT);
- }
- else
- return (!mechanical || mons_flies(monster));
-}
-
-static void _mons_open_door(monsters* monster, const coord_def &pos)
-{
- dungeon_feature_type grid = grd(pos);
- const char *adj = "", *noun = "door";
-
- bool was_secret = false;
- bool was_seen = false;
-
- std::set<coord_def> all_door;
- find_connected_range(pos, DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR, all_door);
- get_door_description(all_door.size(), &adj, &noun);
-
- for (std::set<coord_def>::iterator i = all_door.begin();
- i != all_door.end(); ++i)
- {
- const coord_def& dc = *i;
- if (grd(dc) == DNGN_SECRET_DOOR && see_cell(dc))
- {
- grid = grid_secret_door_appearance(dc);
- was_secret = true;
- }
-
- if (see_cell(dc))
- was_seen = true;
- else
- set_terrain_changed(dc);
-
- grd[dc.x][dc.y] = DNGN_OPEN_DOOR;
- }
-
- if (was_seen)
- {
- viewwindow(true, false);
-
- if (was_secret)
- {
- mprf("%s was actually a secret door!",
- feature_description(grid, NUM_TRAPS, false,
- DESC_CAP_THE, false).c_str());
- learned_something_new(TUT_SEEN_SECRET_DOOR, pos);
- }
-
- std::string open_str = "opens the ";
- open_str += adj;
- open_str += noun;
- open_str += ".";
-
- monster->seen_context = open_str;
-
- if (!you.can_see(monster))
- {
- mprf("Something unseen %s", open_str.c_str());
- interrupt_activity(AI_FORCE_INTERRUPT);
- }
- else if (!you_are_delayed())
- {
- mprf("%s %s", monster->name(DESC_CAP_A).c_str(),
- open_str.c_str());
- }
- }
-
- monster->lose_energy(EUT_MOVE);
-}
-
-static bool _no_habitable_adjacent_grids(const monsters *mon)
-{
- for (adjacent_iterator ai(mon->pos()); ai; ++ai)
- if (_habitat_okay(mon, grd(*ai)))
- return (false);
-
- return (true);
-}
-
-// Check whether a monster can move to given square (described by its relative
-// coordinates to the current monster position). just_check is true only for
-// calls from is_trap_safe when checking the surrounding squares of a trap.
-static bool _mon_can_move_to_pos(const monsters *monster,
- const coord_def& delta, bool just_check)
-{
- const coord_def targ = monster->pos() + delta;
-
- // Bounds check: don't consider moving out of grid!
- if (!in_bounds(targ))
- return (false);
-
- // No monster may enter the open sea.
- if (grd(targ) == DNGN_OPEN_SEA)
- return (false);
-
- // Non-friendly and non-good neutral monsters won't enter
- // sanctuaries.
- if (!mons_wont_attack(monster)
- && is_sanctuary(targ)
- && !is_sanctuary(monster->pos()))
- {
- return (false);
- }
-
- // Inside a sanctuary don't attack anything!
- if (is_sanctuary(monster->pos()) && actor_at(targ))
- return (false);
-
- const dungeon_feature_type target_grid = grd(targ);
- const habitat_type habitat = mons_primary_habitat(monster);
-
- // The kraken is so large it cannot enter shallow water.
- // Its tentacles can, and will, though.
- if (monster->type == MONS_KRAKEN && target_grid == DNGN_SHALLOW_WATER)
- return (false);
-
- // Effectively slows down monster movement across water.
- // Fire elementals can't cross at all.
- bool no_water = false;
- if (monster->type == MONS_FIRE_ELEMENTAL || one_chance_in(5))
- no_water = true;
-
- cloud_type targ_cloud_type;
- const int targ_cloud_num = env.cgrid(targ);
-
- if (mons_avoids_cloud(monster, targ_cloud_num, &targ_cloud_type))
- return (false);
-
- if (mons_class_flag(monster->type, M_BURROWS)
- && (target_grid == DNGN_ROCK_WALL
- || target_grid == DNGN_CLEAR_ROCK_WALL))
- {
- // Don't burrow out of bounds.
- if (!in_bounds(targ))
- return (false);
-
- // Don't burrow at an angle (legacy behaviour).
- if (delta.x != 0 && delta.y != 0)
- return (false);
- }
- else if (!monster->can_pass_through_feat(target_grid)
- || no_water && feat_is_water(target_grid))
- {
- return (false);
- }
- else if (!_habitat_okay(monster, target_grid))
- {
- // If the monster somehow ended up in this habitat (and is
- // not dead by now), give it a chance to get out again.
- if (grd(monster->pos()) == target_grid
- && _no_habitable_adjacent_grids(monster))
- {
- return (true);
- }
-
- return (false);
- }
-
- // Wandering mushrooms don't move while you are looking.
- if (monster->type == MONS_WANDERING_MUSHROOM && see_cell(targ))
- return (false);
-
- // Water elementals avoid fire and heat.
- if (monster->type == MONS_WATER_ELEMENTAL
- && (target_grid == DNGN_LAVA
- || targ_cloud_type == CLOUD_FIRE
- || targ_cloud_type == CLOUD_FOREST_FIRE
- || targ_cloud_type == CLOUD_STEAM))
- {
- return (false);
- }
-
- // Fire elementals avoid water and cold.
- if (monster->type == MONS_FIRE_ELEMENTAL
- && (feat_is_watery(target_grid)
- || targ_cloud_type == CLOUD_COLD))
- {
- return (false);
- }
-
- // Submerged water creatures avoid the shallows where
- // they would be forced to surface. -- bwr
- // [dshaligram] Monsters now prefer to head for deep water only if
- // they're low on hitpoints. No point in hiding if they want a
- // fight.
- if (habitat == HT_WATER
- && targ != you.pos()
- && target_grid != DNGN_DEEP_WATER
- && grd(monster->pos()) == DNGN_DEEP_WATER
- && monster->hit_points < (monster->max_hit_points * 3) / 4)
- {
- return (false);
- }
-
- // Smacking the player is always a good move if we're
- // hostile (even if we're heading somewhere else).
- // Also friendlies want to keep close to the player
- // so it's okay as well.
-
- // Smacking another monster is good, if the monsters
- // are aligned differently.
- if (monsters *targmonster = monster_at(targ))
- {
- if (just_check)
- {
- if (targ == monster->pos())
- return (true);
-
- return (false); // blocks square
- }
-
- if (mons_aligned(monster->mindex(), targmonster->mindex())
- && !_mons_can_displace(monster, targmonster))
- {
- return (false);
- }
- }
-
- // Friendlies shouldn't try to move onto the player's
- // location, if they are aiming for some other target.
- if (mons_wont_attack(monster)
- && monster->foe != MHITYOU
- && (monster->foe != MHITNOT || monster->is_patrolling())
- && targ == you.pos())
- {
- return (false);
- }
-
- // Wandering through a trap is OK if we're pretty healthy,
- // really stupid, or immune to the trap.
- if (!_is_trap_safe(monster, targ, just_check))
- return (false);
-
- // If we end up here the monster can safely move.
- return (true);
-}
-
-// Uses, and updates the global variable mmov.
-static void _find_good_alternate_move(monsters *monster,
- const FixedArray<bool, 3, 3>& good_move)
-{
- const int current_distance = distance(monster->pos(), monster->target);
-
- int dir = -1;
- for (int i = 0; i < 8; i++)
- {
- if (mon_compass[i] == mmov)
- {
- dir = i;
- break;
- }
- }
-
- // Only handle if the original move is to an adjacent square.
- if (dir == -1)
- return;
-
- int dist[2];
-
- // First 1 away, then 2 (3 is silly).
- for (int j = 1; j <= 2; j++)
- {
- const int FAR_AWAY = 1000000;
-
- // Try both directions (but randomise which one is first).
- const int sdir = coinflip() ? j : -j;
- const int inc = -2 * sdir;
-
- for (int mod = sdir, i = 0; i < 2; mod += inc, i++)
- {
- const int newdir = (dir + 8 + mod) % 8;
- if (good_move[mon_compass[newdir].x+1][mon_compass[newdir].y+1])
- {
- dist[i] = distance(monster->pos()+mon_compass[newdir],
- monster->target);
- }
- else
- {
- dist[i] = (mons_is_fleeing(monster)) ? (-FAR_AWAY) : FAR_AWAY;
- }
- }
-
- const int dir0 = ((dir + 8 + sdir) % 8);
- const int dir1 = ((dir + 8 - sdir) % 8);
-
- // Now choose.
- if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY)
- continue;
-
- // Which one was better? -- depends on FLEEING or not.
- if (mons_is_fleeing(monster))
- {
- if (dist[0] >= dist[1] && dist[0] >= current_distance)
- {
- mmov = mon_compass[dir0];
- break;
- }
- if (dist[1] >= dist[0] && dist[1] >= current_distance)
- {
- mmov = mon_compass[dir1];
- break;
- }
- }
- else
- {
- if (dist[0] <= dist[1] && dist[0] <= current_distance)
- {
- mmov = mon_compass[dir0];
- break;
- }
- if (dist[1] <= dist[0] && dist[1] <= current_distance)
- {
- mmov = mon_compass[dir1];
- break;
- }
- }
- }
-}
-
-static bool _monster_move(monsters *monster)
-{
- FixedArray<bool, 3, 3> good_move;
-
- const habitat_type habitat = mons_primary_habitat(monster);
- bool deep_water_available = false;
-
- if (monster->type == MONS_TRAPDOOR_SPIDER)
- {
- if (monster->submerged())
- return (false);
-
- // Trapdoor spiders hide if they can't see their foe.
- // (Note that friendly trapdoor spiders will thus hide even
- // if they can see you.)
- const actor *foe = monster->get_foe();
- const bool can_see = foe && monster->can_see(foe);
-
- if (monster_can_submerge(monster, grd(monster->pos()))
- && !can_see && !mons_is_confused(monster)
- && !monster->caught()
- && !monster->has_ench(ENCH_BERSERK))
- {
- monster->add_ench(ENCH_SUBMERGED);
- monster->behaviour = BEH_LURK;
- return (false);
- }
- }
-
- // Berserking monsters make a lot of racket.
- if (monster->has_ench(ENCH_BERSERK))
- {
- int noise_level = get_shout_noise_level(mons_shouts(monster->type));
- if (noise_level > 0)
- {
- if (you.can_see(monster))
- {
- if (one_chance_in(10))
- {
- mprf(MSGCH_TALK_VISUAL, "%s rages.",
- monster->name(DESC_CAP_THE).c_str());
- }
- noisy(noise_level, monster->pos(), monster->mindex());
- }
- else if (one_chance_in(5))
- handle_monster_shouts(monster, true);
- else
- {
- // Just be noisy without messaging the player.
- noisy(noise_level, monster->pos(), monster->mindex());
- }
- }
- }
-
- if (monster->confused())
- {
- if (!mmov.origin() || one_chance_in(15))
- {
- const coord_def newpos = monster->pos() + mmov;
- if (in_bounds(newpos)
- && (habitat == HT_LAND
- || monster_habitable_grid(monster, grd(newpos))))
- {
- return _do_move_monster(monster, mmov);
- }
- }
- return (false);
- }
-
- // If a water monster is currently flopping around on land, it cannot
- // really control where it wants to move, though there's a 50% chance
- // of flopping into an adjacent water grid.
- if (monster->has_ench(ENCH_AQUATIC_LAND))
- {
- std::vector<coord_def> adj_water;
- std::vector<coord_def> adj_move;
- for (adjacent_iterator ai(monster->pos()); ai; ++ai)
- {
- if (!cell_is_solid(*ai))
- {
- adj_move.push_back(*ai);
- if (feat_is_watery(grd(*ai)))
- adj_water.push_back(*ai);
- }
- }
- if (adj_move.empty())
- {
- simple_monster_message(monster, " flops around on dry land!");
- return (false);
- }
-
- std::vector<coord_def> moves = adj_water;
- if (adj_water.empty() || coinflip())
- moves = adj_move;
-
- coord_def newpos = monster->pos();
- int count = 0;
- for (unsigned int i = 0; i < moves.size(); ++i)
- if (one_chance_in(++count))
- newpos = moves[i];
-
- const monsters *mon2 = monster_at(newpos);
- if (newpos == you.pos() && mons_wont_attack(monster)
- || (mon2 && mons_wont_attack(monster) == mons_wont_attack(mon2)))
- {
-
- simple_monster_message(monster, " flops around on dry land!");
- return (false);
- }
-
- return _do_move_monster(monster, newpos - monster->pos());
- }
-
- // Let's not even bother with this if mmov is zero.
- if (mmov.origin())
- return (false);
-
- for (int count_x = 0; count_x < 3; count_x++)
- for (int count_y = 0; count_y < 3; count_y++)
- {
- const int targ_x = monster->pos().x + count_x - 1;
- const int targ_y = monster->pos().y + count_y - 1;
-
- // Bounds check: don't consider moving out of grid!
- if (!in_bounds(targ_x, targ_y))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
- dungeon_feature_type target_grid = grd[targ_x][targ_y];
-
- if (target_grid == DNGN_DEEP_WATER)
- deep_water_available = true;
-
- good_move[count_x][count_y] =
- _mon_can_move_to_pos(monster, coord_def(count_x-1, count_y-1));
- }
-
- // Now we know where we _can_ move.
-
- const coord_def newpos = monster->pos() + mmov;
- // Normal/smart monsters know about secret doors, since they live in
- // the dungeon.
- if (grd(newpos) == DNGN_CLOSED_DOOR
- || feat_is_secret_door(grd(newpos)) && mons_intel(monster) >= I_NORMAL)
- {
- if (mons_is_zombified(monster))
- {
- // For zombies, monster type is kept in mon->base_monster.
- if (mons_class_itemuse(monster->base_monster) >= MONUSE_OPEN_DOORS)
- {
- _mons_open_door(monster, newpos);
- return (true);
- }
- }
- else if (mons_itemuse(monster) >= MONUSE_OPEN_DOORS)
- {
- _mons_open_door(monster, newpos);
- return (true);
- }
- } // endif - secret/closed doors
-
- // Monsters that eat items (currently only jellies) also eat doors.
- // However, they don't realise that secret doors make good eating.
- if ((grd(newpos) == DNGN_CLOSED_DOOR || grd(newpos) == DNGN_OPEN_DOOR)
- && mons_itemeat(monster) == MONEAT_ITEMS
- // Doors with permarock marker cannot be eaten.
- && !feature_marker_at(newpos, DNGN_PERMAROCK_WALL))
- {
- grd(newpos) = DNGN_FLOOR;
-
- _jelly_grows(monster);
-
- if (see_cell(newpos))
- {
- viewwindow(true, false);
-
- if (!you.can_see(monster))
- {
- mpr("The door mysteriously vanishes.");
- interrupt_activity( AI_FORCE_INTERRUPT );
- }
- }
- } // done door-eating jellies
-
- // Water creatures have a preference for water they can hide in -- bwr
- // [ds] Weakened the powerful attraction to deep water if the monster
- // is in good health.
- if (habitat == HT_WATER
- && deep_water_available
- && grd(monster->pos()) != DNGN_DEEP_WATER
- && grd(newpos) != DNGN_DEEP_WATER
- && newpos != you.pos()
- && (one_chance_in(3)
- || monster->hit_points <= (monster->max_hit_points * 3) / 4))
- {
- int count = 0;
-
- for (int cx = 0; cx < 3; cx++)
- for (int cy = 0; cy < 3; cy++)
- {
- if (good_move[cx][cy]
- && grd[monster->pos().x + cx - 1][monster->pos().y + cy - 1]
- == DNGN_DEEP_WATER)
- {
- if (one_chance_in(++count))
- {
- mmov.x = cx - 1;
- mmov.y = cy - 1;
- }
- }
- }
- }
-
- // Now, if a monster can't move in its intended direction, try
- // either side. If they're both good, move in whichever dir
- // gets it closer (farther for fleeing monsters) to its target.
- // If neither does, do nothing.
- if (good_move[mmov.x + 1][mmov.y + 1] == false)
- _find_good_alternate_move(monster, good_move);
-
- // ------------------------------------------------------------------
- // If we haven't found a good move by this point, we're not going to.
- // ------------------------------------------------------------------
-
- // Take care of beetle burrowing.
- if (mons_class_flag(monster->type, M_BURROWS))
- {
- const dungeon_feature_type feat = grd(monster->pos() + mmov);
- if ((feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL)
- && good_move[mmov.x + 1][mmov.y + 1] == true)
- {
- grd(monster->pos() + mmov) = DNGN_FLOOR;
- set_terrain_changed(monster->pos() + mmov);
-
- if (player_can_hear(monster->pos() + mmov))
- {
- // Message depends on whether caused by boring beetle or
- // acid (Dissolution).
- mpr((monster->type == MONS_BORING_BEETLE) ?
- "You hear a grinding noise." :
- "You hear a sizzling sound.", MSGCH_SOUND);
- }
- }
- }
-
- bool ret = false;
- if (good_move[mmov.x + 1][mmov.y + 1] && !mmov.origin())
- {
- // Check for attacking player.
- if (monster->pos() + mmov == you.pos())
- {
- ret = monster_attack(monster);
- mmov.reset();
- }
-
- // If we're following the player through stairs, the only valid
- // movement is towards the player. -- bwr
- if (testbits(monster->flags, MF_TAKING_STAIRS))
- {
- const delay_type delay = current_delay_action();
- if (delay != DELAY_ASCENDING_STAIRS
- && delay != DELAY_DESCENDING_STAIRS)
- {
- monster->flags &= ~MF_TAKING_STAIRS;
-
-#ifdef DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS,
- "BUG: %s was marked as follower when not following!",
- monster->name(DESC_PLAIN).c_str(), true);
-#endif
- }
- else
- {
- ret = true;
- mmov.reset();
-
-#if DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS,
- "%s is skipping movement in order to follow.",
- monster->name(DESC_CAP_THE).c_str(), true );
-#endif
- }
- }
-
- // Check for attacking another monster.
- if (monsters* targ = monster_at(monster->pos() + mmov))
- {
- if (mons_aligned(monster->mindex(), targ->mindex()))
- ret = _monster_swaps_places(monster, mmov);
- else
- {
- monsters_fight(monster, targ);
- ret = true;
- }
-
- // If the monster swapped places, the work's already done.
- mmov.reset();
- }
-
- if (monster->type == MONS_EFREET
- || monster->type == MONS_FIRE_ELEMENTAL)
- {
- place_cloud( CLOUD_FIRE, monster->pos(),
- 2 + random2(4), monster->kill_alignment() );
- }
-
- if (monster->type == MONS_ROTTING_DEVIL
- || monster->type == MONS_CURSE_TOE)
- {
- place_cloud( CLOUD_MIASMA, monster->pos(),
- 2 + random2(3), monster->kill_alignment() );
- }
- }
- else
- {
- mmov.reset();
-
- // Fleeing monsters that can't move will panic and possibly
- // turn to face their attacker.
- _make_mons_stop_fleeing(monster);
- }
-
- if (mmov.x || mmov.y || (monster->confused() && one_chance_in(6)))
- return (_do_move_monster(monster, mmov));
-
- return (ret);
-}
-
-static void _mons_in_cloud(monsters *monster)
-{
- int wc = env.cgrid(monster->pos());
- int hurted = 0;
- bolt beam;
-
- const int speed = ((monster->speed > 0) ? monster->speed : 10);
- bool wake = false;
-
- if (mons_is_mimic( monster->type ))
- {
- mimic_alert(monster);
- return;
- }
-
- const cloud_struct &cloud(env.cloud[wc]);
- switch (cloud.type)
- {
- case CLOUD_DEBUGGING:
- mprf(MSGCH_ERROR,
- "Monster %s stepped on a nonexistent cloud at (%d,%d)",
- monster->name(DESC_PLAIN, true).c_str(),
- monster->pos().x, monster->pos().y);
- return;
-
- case CLOUD_FIRE:
- case CLOUD_FOREST_FIRE:
- if (monster->type == MONS_FIRE_VORTEX
- || monster->type == MONS_EFREET
- || monster->type == MONS_FIRE_ELEMENTAL)
- {
- return;
- }
-
- simple_monster_message(monster, " is engulfed in flames!");
-
- hurted +=
- resist_adjust_damage( monster,
- BEAM_FIRE,
- monster->res_fire(),
- ((random2avg(16, 3) + 6) * 10) / speed );
-
- hurted -= random2(1 + monster->ac);
- break;
-
- case CLOUD_STINK:
- simple_monster_message(monster, " is engulfed in noxious gasses!");
-
- if (monster->res_poison() > 0)
- return;
-
- beam.flavour = BEAM_CONFUSION;
- beam.thrower = cloud.killer;
-
- if (cloud.whose == KC_FRIENDLY)
- beam.beam_source = ANON_FRIENDLY_MONSTER;
-
- if (mons_class_is_confusable(monster->type)
- && 1 + random2(27) >= monster->hit_dice)
- {
- beam.apply_enchantment_to_monster(monster);
- }
-
- hurted += (random2(3) * 10) / speed;
- break;
-
- case CLOUD_COLD:
- simple_monster_message(monster, " is engulfed in freezing vapours!");
-
- hurted +=
- resist_adjust_damage( monster,
- BEAM_COLD,
- monster->res_cold(),
- ((6 + random2avg(16, 3)) * 10) / speed );
-
- hurted -= random2(1 + monster->ac);
- break;
-
- case CLOUD_POISON:
- simple_monster_message(monster, " is engulfed in a cloud of poison!");
-
- if (monster->res_poison() > 0)
- return;
-
- poison_monster(monster, cloud.whose);
- // If the monster got poisoned, wake it up.
- wake = true;
-
- hurted += (random2(8) * 10) / speed;
-
- if (monster->res_poison() < 0)
- hurted += (random2(4) * 10) / speed;
- break;
-
- case CLOUD_STEAM:
- {
- // FIXME: couldn't be bothered coding for armour of res fire
-
- simple_monster_message(monster, " is engulfed in steam!");
-
- const int steam_base_damage = steam_cloud_damage(cloud);
- hurted +=
- resist_adjust_damage(
- monster,
- BEAM_STEAM,
- monster->res_steam(),
- (random2avg(steam_base_damage, 2) * 10) / speed);
-
- hurted -= random2(1 + monster->ac);
- break;
- }
-
- case CLOUD_MIASMA:
- simple_monster_message(monster, " is engulfed in a dark miasma!");
-
- if (monster->res_rotting())
- return;
-
- miasma_monster(monster, cloud.whose);
-
- hurted += (10 * random2avg(12, 3)) / speed; // 3
- break;
-
- case CLOUD_RAIN:
- if (monster->is_fiery())
- {
- if (!silenced(monster->pos()))
- simple_monster_message(monster, " sizzles in the rain!");
- else
- simple_monster_message(monster, " steams in the rain!");
-
- hurted += ((4 * random2(3)) - random2(monster->ac));
- wake = true;
- }
- break;
-
- case CLOUD_MUTAGENIC:
- simple_monster_message(monster, " is engulfed in a mutagenic fog!");
-
- // Will only polymorph a monster if they're not magic immune, can
- // mutate, aren't res asphyx, and pass the same check as meph cloud.
- if (monster->can_mutate() && !mons_immune_magic(monster)
- && 1 + random2(27) >= monster->hit_dice
- && !monster->res_asphyx())
- {
- if (monster->mutate())
- wake = true;
- }
- break;
-
- default: // 'harmless' clouds -- colored smoke, etc {dlb}.
- return;
- }
-
- // A sleeping monster that sustains damage will wake up.
- if ((wake || hurted > 0) && monster->asleep())
- {
- // We have no good coords to give the monster as the source of the
- // disturbance other than the cloud itself.
- behaviour_event(monster, ME_DISTURB, MHITNOT, monster->pos());
- }
-
- if (hurted > 0)
- {
-#ifdef DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS, "%s takes %d damage from cloud.",
- monster->name(DESC_CAP_THE).c_str(), hurted);
-#endif
- monster->hurt(NULL, hurted, BEAM_MISSILE, false);
-
- if (monster->hit_points < 1)
- {
- mon_enchant death_ench(ENCH_NONE, 0, cloud.whose);
- monster_die(monster, cloud.killer, death_ench.kill_agent());
- }
- }
-}
-
bool monster_descriptor(int which_class, mon_desc_type which_descriptor)
{
if (which_descriptor == MDSC_LEAVES_HIDE)
@@ -9698,30 +3415,6 @@ bool heal_monster(monsters * patient, int health_boost,
return (success);
}
-static spell_type _map_wand_to_mspell(int wand_type)
-{
- switch (wand_type)
- {
- case WAND_FLAME: return SPELL_THROW_FLAME;
- case WAND_FROST: return SPELL_THROW_FROST;
- case WAND_SLOWING: return SPELL_SLOW;
- case WAND_HASTING: return SPELL_HASTE;
- case WAND_MAGIC_DARTS: return SPELL_MAGIC_DART;
- case WAND_HEALING: return SPELL_MINOR_HEALING;
- case WAND_PARALYSIS: return SPELL_PARALYSE;
- case WAND_FIRE: return SPELL_BOLT_OF_FIRE;
- case WAND_COLD: return SPELL_BOLT_OF_COLD;
- case WAND_CONFUSION: return SPELL_CONFUSE;
- case WAND_INVISIBILITY: return SPELL_INVISIBILITY;
- case WAND_TELEPORTATION: return SPELL_TELEPORT_OTHER;
- case WAND_LIGHTNING: return SPELL_LIGHTNING_BOLT;
- case WAND_DRAINING: return SPELL_BOLT_OF_DRAINING;
- case WAND_DISINTEGRATION: return SPELL_DISINTEGRATE;
- case WAND_POLYMORPH_OTHER: return SPELL_POLYMORPH_OTHER;
- default: return SPELL_NO_SPELL;
- }
-}
-
void seen_monster(monsters *monster)
{
// If the monster is in the auto_exclude list, automatically
@@ -9839,3 +3532,369 @@ int dismiss_monsters(std::string pattern) {
}
return (ndismissed);
}
+
+bool is_item_jelly_edible(const item_def &item)
+{
+ // Don't eat artefacts.
+ if (is_artefact(item))
+ return (false);
+
+ // Shouldn't eat stone things
+ // - but what about wands and rings?
+ if (item.base_type == OBJ_MISSILES
+ && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
+ {
+ return (false);
+ }
+
+ // Don't eat special game items.
+ if (item.base_type == OBJ_ORBS
+ || (item.base_type == OBJ_MISCELLANY
+ && (item.sub_type == MISC_RUNE_OF_ZOT
+ || item.sub_type == MISC_HORN_OF_GERYON)))
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+bool monster_random_space(const monsters *monster, coord_def& target,
+ bool forbid_sanctuary)
+{
+ int tries = 0;
+ while (tries++ < 1000)
+ {
+ target = random_in_bounds();
+
+ // Don't land on top of another monster.
+ if (actor_at(target))
+ continue;
+
+ if (is_sanctuary(target) && forbid_sanctuary)
+ continue;
+
+ if (monster_habitable_grid(monster, grd(target)))
+ return (true);
+ }
+
+ return (false);
+}
+
+bool monster_random_space(monster_type mon, coord_def& target,
+ bool forbid_sanctuary)
+{
+ monsters dummy;
+ dummy.type = mon;
+
+ return monster_random_space(&dummy, target, forbid_sanctuary);
+}
+
+void monster_teleport(monsters *monster, bool instan, bool silent)
+{
+ if (!instan)
+ {
+ if (monster->del_ench(ENCH_TP))
+ {
+ if (!silent)
+ simple_monster_message(monster, " seems more stable.");
+ }
+ else
+ {
+ if (!silent)
+ simple_monster_message(monster, " looks slightly unstable.");
+
+ monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER,
+ random_range(20, 30)) );
+ }
+
+ return;
+ }
+
+ bool was_seen = you.can_see(monster) && !mons_is_lurking(monster);
+
+ if (!silent)
+ simple_monster_message(monster, " disappears!");
+
+ const coord_def oldplace = monster->pos();
+
+ // Pick the monster up.
+ mgrd(oldplace) = NON_MONSTER;
+
+ coord_def newpos;
+ if (monster_random_space(monster, newpos, !mons_wont_attack(monster)))
+ monster->moveto(newpos);
+
+ mgrd(monster->pos()) = monster_index(monster);
+
+ // Mimics change form/colour when teleported.
+ if (mons_is_mimic(monster->type))
+ {
+ monster_type old_type = monster->type;
+ monster->type = static_cast<monster_type>(
+ MONS_GOLD_MIMIC + random2(5));
+ monster->colour = get_mimic_colour(monster);
+
+ // If it's changed form, you won't recognise it.
+ // This assumes that a non-gold mimic turning into another item of
+ // the same description is really, really unlikely.
+ if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC)
+ was_seen = false;
+ }
+
+ const bool now_visible = mons_near(monster);
+ if (!silent && now_visible)
+ {
+ if (was_seen)
+ simple_monster_message(monster, " reappears nearby!");
+ else
+ {
+ // Even if it doesn't interrupt an activity (the player isn't
+ // delayed, the monster isn't hostile) we still want to give
+ // a message.
+ activity_interrupt_data ai(monster, "thin air");
+ if (!interrupt_activity(AI_SEE_MONSTER, ai))
+ simple_monster_message(monster, " appears out of thin air!");
+ }
+ }
+
+ if (monster->visible_to(&you) && now_visible)
+ handle_seen_interrupt(monster);
+
+ // Leave a purple cloud.
+ place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3),
+ monster->kill_alignment());
+
+ monster->check_redraw(oldplace);
+ monster->apply_location_effects(oldplace);
+
+ mons_relocated(monster);
+
+ // Teleporting mimics change form - if they reappear out of LOS, they are
+ // no longer known.
+ if (mons_is_mimic(monster->type))
+ {
+ if (now_visible)
+ monster->flags |= MF_KNOWN_MIMIC;
+ else
+ monster->flags &= ~MF_KNOWN_MIMIC;
+ }
+}
+
+void mons_clear_trapping_net(monsters *mon)
+{
+ if (!mons_is_caught(mon))
+ return;
+
+ const int net = get_trapping_net(mon->pos());
+ if (net != NON_ITEM)
+ remove_item_stationary(mitm[net]);
+
+ mon->del_ench(ENCH_HELD, true);
+}
+
+bool mons_clonable(const monsters* mon, bool needs_adjacent)
+{
+ // No uniques or ghost demon monsters. Also, figuring out the name
+ // for the clone of a named monster isn't worth it.
+ if (mons_is_unique(mon->type)
+ || mons_is_ghost_demon(mon->type)
+ || mon->is_named())
+ {
+ return (false);
+ }
+
+ if (needs_adjacent)
+ {
+ // Is there space for the clone?
+ bool square_found = false;
+ for (int i = 0; i < 8; i++)
+ {
+ const coord_def p = mon->pos() + Compass[i];
+
+ if (in_bounds(p)
+ && !actor_at(p)
+ && monster_habitable_grid(mon, grd(p)))
+ {
+ square_found = true;
+ break;
+ }
+ }
+ if (!square_found)
+ return (false);
+ }
+
+ // Is the monster carrying an artefact?
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int index = mon->inv[i];
+
+ if (index == NON_ITEM)
+ continue;
+
+ if (is_artefact(mitm[index]))
+ return (false);
+ }
+
+ return (true);
+}
+
+int clone_mons(const monsters* orig, bool quiet, bool* obvious,
+ coord_def pos)
+{
+ // Is there an open slot in menv?
+ int midx = NON_MONSTER;
+ for (int i = 0; i < MAX_MONSTERS; i++)
+ if (menv[i].type == MONS_NO_MONSTER)
+ {
+ midx = i;
+ break;
+ }
+
+ if (midx == NON_MONSTER)
+ return (NON_MONSTER);
+
+ if (!in_bounds(pos))
+ {
+ // Find an adjacent square.
+ int squares = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ const coord_def p = orig->pos() + Compass[i];
+
+ if (in_bounds(p)
+ && !actor_at(p)
+ && monster_habitable_grid(orig, grd(p)))
+ {
+ if (one_chance_in(++squares))
+ pos = p;
+ }
+ }
+
+ if (squares == 0)
+ return (NON_MONSTER);
+ }
+
+ ASSERT( !actor_at(pos) );
+
+ monsters &mon(menv[midx]);
+
+ mon = *orig;
+ mon.position = pos;
+ mgrd(pos) = midx;
+
+ // Duplicate objects, or unequip them if they can't be duplicated.
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int old_index = orig->inv[i];
+
+ if (old_index == NON_ITEM)
+ continue;
+
+ const int new_index = get_item_slot(0);
+ if (new_index == NON_ITEM)
+ {
+ mon.unequip(mitm[old_index], i, 0, true);
+ mon.inv[i] = NON_ITEM;
+ continue;
+ }
+
+ mon.inv[i] = new_index;
+ mitm[new_index] = mitm[old_index];
+ mitm[new_index].set_holding_monster(midx);
+ }
+
+ bool _obvious;
+ if (obvious == NULL)
+ obvious = &_obvious;
+ *obvious = false;
+
+ if (you.can_see(orig) && you.can_see(&mon))
+ {
+ if (!quiet)
+ simple_monster_message(orig, " is duplicated!");
+ *obvious = true;
+ }
+
+ mark_interesting_monst(&mon, mon.behaviour);
+ if (you.can_see(&mon))
+ {
+ handle_seen_interrupt(&mon);
+ viewwindow(true, false);
+ }
+
+ if (crawl_state.arena)
+ arena_placed_monster(&mon);
+
+ return (midx);
+}
+
+std::string summoned_poof_msg(const monsters* monster, bool plural)
+{
+ int summon_type = 0;
+ bool valid_mon = false;
+ if (monster != NULL && !invalid_monster(monster))
+ {
+ (void) monster->is_summoned(NULL, &summon_type);
+ valid_mon = true;
+ }
+
+ std::string msg = "disappear%s in a puff of smoke";
+ bool no_chaos = false;
+
+ switch (summon_type)
+ {
+ case SPELL_SHADOW_CREATURES:
+ msg = "dissolve%s into shadows";
+ no_chaos = true;
+ break;
+
+ case MON_SUMM_CHAOS:
+ msg = "degenerate%s into a cloud of primal chaos";
+ break;
+
+ case MON_SUMM_WRATH:
+ case MON_SUMM_AID:
+ if (valid_mon && is_good_god(monster->god))
+ {
+ msg = "dissolve%s into sparkling lights";
+ no_chaos = true;
+ }
+ break;
+ }
+
+ if (valid_mon)
+ {
+ if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10)
+ || monster->type == MONS_CHAOS_SPAWN)
+ {
+ msg = "degenerate%s into a cloud of primal chaos";
+ }
+
+ if (mons_is_holy(monster) && summon_type != SPELL_SHADOW_CREATURES
+ && summon_type != MON_SUMM_CHAOS)
+ {
+ msg = "dissolve%s into sparkling lights";
+ }
+ }
+
+ // Conjugate.
+ msg = make_stringf(msg.c_str(), plural ? "" : "s");
+
+ return (msg);
+}
+
+std::string summoned_poof_msg(const int midx, const item_def &item)
+{
+ if (midx == NON_MONSTER)
+ return summoned_poof_msg(static_cast<const monsters*>(NULL), item);
+ else
+ return summoned_poof_msg(&menv[midx], item);
+}
+
+std::string summoned_poof_msg(const monsters* monster, const item_def &item)
+{
+ ASSERT(item.flags & ISFLAG_SUMMONED);
+
+ return summoned_poof_msg(monster, item.quantity > 1);
+}
diff --git a/crawl-ref/source/monstuff.h b/crawl-ref/source/monstuff.h
index 5d6f3925cb..1b808fed6d 100644
--- a/crawl-ref/source/monstuff.h
+++ b/crawl-ref/source/monstuff.h
@@ -1,5 +1,5 @@
/*
- * File: monstuff.cc
+ * File: monstuff.h
* Summary: Misc monster related functions.
* Written by: Linley Henzell
*/
@@ -111,10 +111,6 @@ void monster_cleanup(monsters *monster);
int dismiss_monsters(std::string pattern);
-void behaviour_event(monsters *mon, mon_event_type event_type,
- int src = MHITNOT, coord_def src_pos = coord_def(),
- bool allow_shout = true);
-
bool curse_an_item(bool decay_potions, bool quiet = false);
@@ -147,8 +143,6 @@ bool simple_monster_message(const monsters *monster, const char *event,
int param = 0,
description_level_type descrip = DESC_CAP_THE);
-void make_mons_leave_level(monsters *mon);
-
bool choose_any_monster(const monsters* mon);
monsters *choose_random_nearby_monster(
int weight,
@@ -171,14 +165,10 @@ bool swap_check(monsters *monster, coord_def &loc, bool quiet = false);
std::string get_wounds_description(const monsters *monster);
void print_wounds(const monsters *monster);
-void handle_monsters(void);
bool monster_descriptor(int which_class, mon_desc_type which_descriptor);
-bool message_current_target(void);
unsigned int monster_index(const monsters *monster);
-bool monster_can_hit_monster(monsters *monster, const monsters *targ);
-
void mons_get_damage_level(const monsters*, std::string& desc,
mon_dam_level_type&);
@@ -197,17 +187,25 @@ int mons_thrown_weapon_damage(const item_def *weap);
int mons_natural_regen_rate(monsters *monster);
-bool mons_avoids_cloud(const monsters *monster, cloud_type cl_type,
- bool placement = false);
-
-// Like the above, but allow a monster to move from one damaging cloud
-// to another.
-bool mons_avoids_cloud(const monsters *monster, int cloud_num,
- cloud_type *cl_type = NULL, bool placement = false);
-
void mons_relocated(monsters *mons);
bool can_go_straight(const coord_def& p1, const coord_def& p2,
dungeon_feature_type allowed);
+bool is_item_jelly_edible(const item_def &item);
+
+bool monster_random_space(const monsters *monster, coord_def& target,
+ bool forbid_sanctuary = false);
+bool monster_random_space(monster_type mon, coord_def& target,
+ bool forbid_sanctuary = false);
+void monster_teleport(monsters *monster, bool instan, bool silent = false);
+void mons_clear_trapping_net(monsters *mon);
+
+bool mons_clonable(const monsters* orig, bool needs_adjacent = true);
+int clone_mons(const monsters* orig, bool quiet = false,
+ bool* obvious = NULL, coord_def pos = coord_def(0, 0) );
+
+std::string summoned_poof_msg(const monsters* monster, bool plural = false);
+std::string summoned_poof_msg(const int midx, const item_def &item);
+std::string summoned_poof_msg(const monsters* monster, const item_def &item);
#endif
diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc
index a86faa226f..acaa231a67 100644
--- a/crawl-ref/source/mstuff2.cc
+++ b/crawl-ref/source/mstuff2.cc
@@ -48,3229 +48,4 @@
#include "traps.h"
#include "view.h"
-static int _monster_abjuration(const monsters *caster, bool actual);
-static bool _mons_abjured(monsters *monster, bool nearby)
-{
- if (nearby && _monster_abjuration(monster, false) > 0
- && coinflip())
- {
- _monster_abjuration(monster, true);
- return (true);
- }
-
- return (false);
-}
-
-static monster_type _pick_random_wraith()
-{
- static monster_type wraiths[] =
- {
- MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
- MONS_SPECTRAL_WARRIOR, MONS_PHANTOM, MONS_HUNGRY_GHOST,
- MONS_FLAYED_GHOST
- };
-
- return (RANDOM_ELEMENT(wraiths));
-}
-
-static monster_type _pick_horrible_thing()
-{
- return (one_chance_in(4) ? MONS_TENTACLED_MONSTROSITY
- : MONS_ABOMINATION_LARGE);
-}
-
-static monster_type _pick_undead_summon()
-{
- static monster_type undead[] =
- {
- MONS_NECROPHAGE, MONS_GHOUL, MONS_HUNGRY_GHOST, MONS_FLAYED_GHOST,
- MONS_ZOMBIE_SMALL, MONS_SKELETON_SMALL, MONS_SIMULACRUM_SMALL,
- MONS_FLYING_SKULL, MONS_FLAMING_CORPSE, MONS_MUMMY, MONS_VAMPIRE,
- MONS_WIGHT, MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
- MONS_SPECTRAL_WARRIOR, MONS_ZOMBIE_LARGE, MONS_SKELETON_LARGE,
- MONS_SIMULACRUM_LARGE, MONS_SHADOW
- };
-
- return (RANDOM_ELEMENT(undead));
-}
-
-static void _do_high_level_summon(monsters *monster, bool monsterNearby,
- spell_type spell_cast,
- monster_type (*mpicker)(), int nsummons,
- god_type god, coord_def *target = NULL)
-{
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- const int duration = std::min(2 + monster->hit_dice / 5, 6);
-
- for (int i = 0; i < nsummons; ++i)
- {
- monster_type which_mons = mpicker();
-
- if (which_mons == MONS_NO_MONSTER)
- continue;
-
- create_monster(
- mgen_data(which_mons, SAME_ATTITUDE(monster),
- duration, spell_cast, target ? *target : monster->pos(),
- monster->foe, 0, god));
- }
-}
-
-static bool _los_free_spell(spell_type spell_cast)
-{
- return (spell_cast == SPELL_HELLFIRE_BURST
- || spell_cast == SPELL_BRAIN_FEED
- || spell_cast == SPELL_SMITING
- || spell_cast == SPELL_HAUNT
- || spell_cast == SPELL_FIRE_STORM
- || spell_cast == SPELL_AIRSTRIKE);
-}
-
-// Returns true if a message referring to the player's legs makes sense.
-static bool _legs_msg_applicable()
-{
- return (you.species != SP_NAGA
- && (you.species != SP_MERFOLK || !player_is_swimming()));
-}
-
-void mons_cast_haunt(monsters *monster)
-{
- coord_def fpos;
-
- switch (monster->foe)
- {
- case MHITNOT:
- return;
-
- case MHITYOU:
- fpos = you.pos();
- break;
-
- default:
- fpos = menv[monster->foe].pos();
- }
-
- _do_high_level_summon(monster, mons_near(monster), SPELL_HAUNT,
- _pick_random_wraith, random_range(3, 6), GOD_NO_GOD, &fpos);
-}
-
-void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
- bool do_noise)
-{
- // Always do setup. It might be done already, but it doesn't hurt
- // to do it again (cheap).
- setup_mons_cast(monster, pbolt, spell_cast);
-
- // single calculation permissible {dlb}
- bool monsterNearby = mons_near(monster);
-
- int sumcount = 0;
- int sumcount2;
- int duration = 0;
-
-#if DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS, "Mon #%d casts %s (#%d)",
- monster_index(monster), spell_title(spell_cast), spell_cast);
-#endif
-
- if (spell_cast == SPELL_CANTRIP)
- do_noise = false; // Spell itself does the messaging.
-
- if (_los_free_spell(spell_cast) && !spell_is_direct_explosion(spell_cast))
- {
- if (monster->foe == MHITYOU || monster->foe == MHITNOT)
- {
- if (monsterNearby)
- {
- if (do_noise)
- mons_cast_noise(monster, pbolt, spell_cast);
- direct_effect(monster, spell_cast, pbolt, &you);
- }
- return;
- }
-
- if (do_noise)
- mons_cast_noise(monster, pbolt, spell_cast);
- direct_effect(monster, spell_cast, pbolt, monster->get_foe());
- return;
- }
-
-#ifdef DEBUG
- const unsigned int flags = get_spell_flags(spell_cast);
-
- ASSERT(!(flags & (SPFLAG_TESTING | SPFLAG_MAPPING)));
-
- // Targeted spells need a valid target.
- ASSERT(!(flags & SPFLAG_TARGETTING_MASK) || in_bounds(pbolt.target));
-#endif
-
- if (do_noise)
- mons_cast_noise(monster, pbolt, spell_cast);
-
- // If the monster's a priest, assume summons come from priestly
- // abilities, in which case they'll have the same god. If the
- // monster is neither a priest nor a wizard, assume summons come
- // from intrinsic abilities, in which case they'll also have the
- // same god.
- const bool priest = mons_class_flag(monster->type, M_PRIEST);
- const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
- god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD;
-
- switch (spell_cast)
- {
- default:
- break;
-
- case SPELL_MAJOR_HEALING:
- if (heal_monster(monster, 50 + random2avg(monster->hit_dice * 10, 2),
- false))
- {
- simple_monster_message(monster, " is healed.");
- }
- return;
-
- case SPELL_BERSERKER_RAGE:
- monster->go_berserk(true);
- return;
-
- case SPELL_SUMMON_SMALL_MAMMALS:
- case SPELL_VAMPIRE_SUMMON:
- if (spell_cast == SPELL_SUMMON_SMALL_MAMMALS)
- sumcount2 = 1 + random2(4);
- else
- sumcount2 = 3 + random2(3) + monster->hit_dice / 5;
-
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- const monster_type rats[] = { MONS_ORANGE_RAT, MONS_GREEN_RAT,
- MONS_GREY_RAT, MONS_RAT };
- const monster_type mon = (one_chance_in(3) ? MONS_GIANT_BAT
- : RANDOM_ELEMENT(rats));
- create_monster(
- mgen_data(mon, SAME_ATTITUDE(monster),
- 5, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_SHADOW_CREATURES: // summon anything appropriate for level
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1);
-
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(RANDOM_MONSTER, SAME_ATTITUDE(monster),
- 5, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_WATER_ELEMENTALS:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1);
-
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- create_monster(
- mgen_data(MONS_WATER_ELEMENTAL, SAME_ATTITUDE(monster),
- 3, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_KRAKEN_TENTACLES:
- {
- int kraken_index = monster_index(monster);
- if (invalid_monster_index(duration))
- {
- mpr("Error! Kraken is not a part of the current environment!",
- MSGCH_ERROR);
- return;
- }
- sumcount2 = std::max(random2(9), random2(9)); // up to eight tentacles
- if (sumcount2 == 0)
- return;
-
- for (sumcount = 0; sumcount < MAX_MONSTERS; ++sumcount)
- if (menv[sumcount].type == MONS_KRAKEN_TENTACLE
- && (int)menv[sumcount].number == kraken_index)
- {
- // Reduce by tentacles already placed.
- sumcount2--;
- }
-
- for (sumcount = sumcount2; sumcount > 0; --sumcount)
- {
- // Tentacles aren't really summoned (controlled by spell_cast
- // being passed to summon_type), so I'm not sure what the
- // abjuration value (3) is doing there. (jpeg)
- if (create_monster(
- mgen_data(MONS_KRAKEN_TENTACLE, SAME_ATTITUDE(monster),
- 3, spell_cast, monster->pos(), monster->foe, 0, god,
- MONS_NO_MONSTER, kraken_index, monster->colour,
- you.your_level, PROX_CLOSE_TO_PLAYER,
- you.level_type)) == -1)
- {
- sumcount2--;
- }
- }
- if (sumcount2 == 1)
- mpr("A tentacle rises from the water!");
- else if (sumcount2 > 1)
- mpr("Tentacles burst out of the water!");
- return;
- }
- case SPELL_FAKE_RAKSHASA_SUMMON:
- sumcount2 = (coinflip() ? 2 : 3);
-
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- create_monster(
- mgen_data(MONS_RAKSHASA_FAKE, SAME_ATTITUDE(monster),
- 3, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_SUMMON_DEMON: // class 2-4 demons
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- create_monster(
- mgen_data(summon_any_demon(DEMON_COMMON),
- SAME_ATTITUDE(monster), duration, spell_cast,
- monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_SUMMON_UGLY_THING:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- const int chance = std::max(6 - (monster->hit_dice / 6), 1);
- monster_type mon = (one_chance_in(chance) ? MONS_VERY_UGLY_THING
- : MONS_UGLY_THING);
-
- create_monster(
- mgen_data(mon, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_ANIMATE_DEAD:
- // see special handling in monstuff::handle_spell() {dlb}
- animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster),
- monster->foe, god);
- return;
-
- case SPELL_CALL_IMP: // class 5 demons
- sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(summon_any_demon(DEMON_LESSER),
- SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_SCORPIONS:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(MONS_SCORPION, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_UFETUBUS:
- sumcount2 = 2 + random2(2) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
-
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(MONS_UFETUBUS, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_BEAST: // Geryon
- create_monster(
- mgen_data(MONS_BEAST, SAME_ATTITUDE(monster),
- 4, spell_cast, monster->pos(), monster->foe, 0, god));
- return;
-
- case SPELL_SUMMON_ICE_BEAST:
- create_monster(
- mgen_data(MONS_ICE_BEAST, SAME_ATTITUDE(monster),
- 5, spell_cast, monster->pos(), monster->foe, 0, god));
- return;
-
- case SPELL_SUMMON_MUSHROOMS: // Summon swarms of icky crawling fungi.
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 4 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
- for (int i = 0; i < sumcount2; ++i)
- {
- create_monster(
- mgen_data(MONS_WANDERING_MUSHROOM, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_HORRIBLE_THINGS:
- _do_high_level_summon(monster, monsterNearby, spell_cast,
- _pick_horrible_thing, random_range(3, 5), god);
- return;
-
- case SPELL_CONJURE_BALL_LIGHTNING:
- {
- const int n = 2 + random2(monster->hit_dice / 4);
- for (int i = 0; i < n; ++i)
- {
- create_monster(
- mgen_data(MONS_BALL_LIGHTNING, SAME_ATTITUDE(monster),
- 2, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
- }
-
- case SPELL_SUMMON_UNDEAD: // Summon undead around player.
- _do_high_level_summon(monster, monsterNearby, spell_cast,
- _pick_undead_summon,
- 2 + random2(2)
- + random2(monster->hit_dice / 4 + 1), god);
- return;
-
- case SPELL_SYMBOL_OF_TORMENT:
- if (!monsterNearby || mons_friendly(monster))
- return;
-
- torment(monster_index(monster), monster->pos());
- return;
-
- case SPELL_SUMMON_GREATER_DEMON:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(monster->hit_dice / 10 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(summon_any_demon(DEMON_GREATER),
- SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe,
- 0, god));
- }
- return;
-
- // Journey -- Added in Summon Lizards and Draconian
- case SPELL_SUMMON_DRAKES:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
-
- {
- std::vector<monster_type> monsters;
-
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- monster_type mon = summon_any_dragon(DRAGON_LIZARD);
-
- if (mon == MONS_DRAGON)
- {
- monsters.clear();
- monsters.push_back(summon_any_dragon(DRAGON_DRAGON));
- break;
- }
-
- monsters.push_back(mon);
- }
-
- for (int i = 0, size = monsters.size(); i < size; ++i)
- {
- create_monster(
- mgen_data(monsters[i], SAME_ATTITUDE(monster),
- duration, spell_cast,
- monster->pos(), monster->foe, 0, god));
- }
- }
- return;
-
- // TODO: Outsource the cantrip messages and allow specification of
- // special cantrip spells per monster, like for speech, both as
- // "self buffs" and "player enchantments".
- case SPELL_CANTRIP:
- {
- // Monster spell of uselessness, just prints a message.
- // This spell exists so that some monsters with really strong
- // spells (ie orc priest) can be toned down a bit. -- bwr
- //
- // XXX: Needs expansion, and perhaps different priest/mage flavours.
-
- // Don't give any message if the monster isn't nearby.
- // (Otherwise you could get them from halfway across the level.)
- if (!mons_near(monster))
- return;
-
- const bool friendly = mons_friendly(monster);
- const bool buff_only = !friendly && is_sanctuary(you.pos());
- const msg_channel_type channel = (friendly) ? MSGCH_FRIEND_ENCHANT
- : MSGCH_MONSTER_ENCHANT;
-
- if (monster->type == MONS_GASTRONOK)
- {
- bool has_mon_foe = !invalid_monster_index(monster->foe);
- std::string slugform = "";
- if (buff_only || crawl_state.arena && !has_mon_foe
- || friendly && !has_mon_foe || coinflip())
- {
- slugform = getSpeakString("gastronok_self_buff");
- if (!slugform.empty())
- {
- slugform = replace_all(slugform, "@The_monster@",
- monster->name(DESC_CAP_THE));
- mpr(slugform.c_str(), channel);
- }
- }
- else if (!friendly && !has_mon_foe)
- {
- mons_cast_noise(monster, pbolt, spell_cast);
-
- // "Enchant" the player.
- slugform = getSpeakString("gastronok_debuff");
- if (!slugform.empty()
- && (slugform.find("legs") == std::string::npos
- || _legs_msg_applicable()))
- {
- mpr(slugform.c_str());
- }
- }
- else
- {
- // "Enchant" another monster.
- const monsters* foe
- = dynamic_cast<const monsters*>(monster->get_foe());
- slugform = getSpeakString("gastronok_other_buff");
- if (!slugform.empty())
- {
- slugform = replace_all(slugform, "@The_monster@",
- foe->name(DESC_CAP_THE));
- mpr(slugform.c_str(), MSGCH_MONSTER_ENCHANT);
- }
- }
- }
- else
- {
- // Messages about the monster influencing itself.
- const char* buff_msgs[] = { " glows brightly for a moment.",
- " looks stronger.",
- " becomes somewhat translucent.",
- "'s eyes start to glow." };
-
- // Messages about the monster influencing you.
- const char* other_msgs[] = {
- "You feel troubled.",
- "You feel a wave of unholy energy pass over you."
- };
-
- if (buff_only || crawl_state.arena || x_chance_in_y(2,3))
- {
- simple_monster_message(monster, RANDOM_ELEMENT(buff_msgs),
- channel);
- }
- else if (friendly)
- {
- simple_monster_message(monster, " shimmers for a moment.",
- channel);
- }
- else // "Enchant" the player.
- {
- mons_cast_noise(monster, pbolt, spell_cast);
- mpr(RANDOM_ELEMENT(other_msgs));
- }
- }
- return;
- }
- case SPELL_BLINK_OTHER:
- {
- // Allow the caster to comment on moving the foe.
- std::string msg = getSpeakString(monster->name(DESC_PLAIN)
- + " blink_other");
- if (!msg.empty() && msg != "__NONE")
- {
- mons_speaks_msg(monster, msg, MSGCH_TALK,
- silenced(you.pos()) || silenced(monster->pos()));
- }
- break;
- }
- case SPELL_TOMB_OF_DOROKLOHE:
- {
- sumcount = 0;
- for (adjacent_iterator ai(monster->pos()); ai; ++ai)
- {
- // we can blink away the crowd, but only our allies
- if (mgrd(*ai) != NON_MONSTER
- && monster_at(*ai)->attitude != monster->attitude)
- sumcount++;
- if (grd(*ai) != DNGN_FLOOR && grd(*ai) > DNGN_MAX_NONREACH
- && !feat_is_trap(grd(*ai)))
- sumcount++;
- }
- if (abs(you.pos().x-monster->pos().x)<=1 &&
- abs(you.pos().y-monster->pos().y)<=1)
- sumcount++;
- if (sumcount)
- {
- monster->blink();
- return;
- }
-
- sumcount = 0;
- for (adjacent_iterator ai(monster->pos()); ai; ++ai)
- {
- if (mgrd(*ai) != NON_MONSTER && monster_at(*ai) != monster)
- {
- monster_at(*ai)->blink();
- if (mgrd(*ai) != NON_MONSTER)
- {
- monster_at(*ai)->teleport(true);
- if (mgrd(*ai) != NON_MONSTER)
- continue;
- }
- }
- if (grd(*ai) == DNGN_FLOOR || feat_is_trap(grd(*ai)))
- {
- grd(*ai) = DNGN_ROCK_WALL;
- sumcount++;
- }
- }
- if (sumcount)
- mpr("Walls emerge from the floor!");
- monster->number = 1; // mark Khufu as entombed
- return;
- }
- }
-
- // If a monster just came into view and immediately cast a spell,
- // we need to refresh the screen before drawing the beam.
- viewwindow(true, false);
- if (spell_is_direct_explosion(spell_cast))
- {
- const actor *foe = monster->get_foe();
- const bool need_more = foe && (foe == &you || see_cell(foe->pos()));
- pbolt.in_explosion_phase = false;
- pbolt.explode(need_more);
- }
- else
- pbolt.fire();
-}
-
-void mons_cast_noise(monsters *monster, bolt &pbolt, spell_type spell_cast)
-{
- bool force_silent = false;
-
- spell_type real_spell = spell_cast;
-
- if (spell_cast == SPELL_DRACONIAN_BREATH)
- {
- int type = monster->type;
- if (mons_genus(type) == MONS_DRACONIAN)
- type = draco_subspecies(monster);
-
- switch (type)
- {
- case MONS_MOTTLED_DRACONIAN:
- real_spell = SPELL_STICKY_FLAME_SPLASH;
- break;
-
- case MONS_YELLOW_DRACONIAN:
- real_spell = SPELL_ACID_SPLASH;
- break;
-
- case MONS_PLAYER_GHOST:
- // Draining breath is silent.
- force_silent = true;
- break;
-
- default:
- break;
- }
- }
- else if (monster->type == MONS_SHADOW_DRAGON)
- // Draining breath is silent.
- force_silent = true;
-
- const bool unseen = !you.can_see(monster);
- const bool silent = silenced(monster->pos()) || force_silent;
- const bool no_silent = mons_class_flag(monster->type, M_SPELL_NO_SILENT);
-
- if (unseen && silent)
- return;
-
- const unsigned int flags = get_spell_flags(real_spell);
-
- const bool priest = mons_class_flag(monster->type, M_PRIEST);
- const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
- const bool innate = !(priest || wizard || no_silent)
- || (flags & SPFLAG_INNATE);
-
- int noise;
- if (silent
- || (innate
- && !mons_class_flag(monster->type, M_NOISY_SPELLS)
- && !(flags & SPFLAG_NOISY)
- && mons_genus(monster->type) != MONS_DRAGON))
- {
- noise = 0;
- }
- else
- {
- if (mons_genus(monster->type) == MONS_DRAGON)
- noise = get_shout_noise_level(S_ROAR);
- else
- noise = spell_noise(real_spell);
- }
-
- const std::string cast_str = " cast";
-
- const std::string spell_name = spell_title(real_spell);
- const mon_body_shape shape = get_mon_shape(monster);
-
- std::vector<std::string> key_list;
-
- // First try the spells name.
- if (shape <= MON_SHAPE_NAGA)
- {
- if (!innate && (priest || wizard))
- key_list.push_back(spell_name + cast_str + " real");
- if (mons_intel(monster) >= I_NORMAL)
- key_list.push_back(spell_name + cast_str + " gestures");
- }
- key_list.push_back(spell_name + cast_str);
-
- const unsigned int num_spell_keys = key_list.size();
-
- // Next the monster type name, then species name, then genus name.
- key_list.push_back(mons_type_name(monster->type, DESC_PLAIN) + cast_str);
- key_list.push_back(mons_type_name(mons_species(monster->type), DESC_PLAIN)
- + cast_str);
- key_list.push_back(mons_type_name(mons_genus(monster->type), DESC_PLAIN)
- + cast_str);
-
- // Last, generic wizard, priest or demon.
- if (wizard)
- key_list.push_back("wizard" + cast_str);
- else if (priest)
- key_list.push_back("priest" + cast_str);
- else if (mons_is_demon(monster->type))
- key_list.push_back("demon" + cast_str);
-
- const bool visible_beam = pbolt.type != 0 && pbolt.type != ' '
- && pbolt.name[0] != '0'
- && !pbolt.is_enchantment();
-
- const bool targeted = (flags & SPFLAG_TARGETTING_MASK)
- && (pbolt.target != monster->pos() || visible_beam);
-
- if (targeted)
- {
- // For targeted spells, try with the targeted suffix first.
- for (unsigned int i = key_list.size() - 1; i >= num_spell_keys; i--)
- {
- std::string str = key_list[i] + " targeted";
- key_list.insert(key_list.begin() + i, str);
- }
-
- // Generic beam messages.
- if (visible_beam)
- {
- key_list.push_back(pbolt.get_short_name() + " beam " + cast_str);
- key_list.push_back("beam catchall cast");
- }
- }
-
- std::string prefix;
- if (silent)
- prefix = "silent ";
- else if (unseen)
- prefix = "unseen ";
-
- std::string msg;
- for (unsigned int i = 0; i < key_list.size(); i++)
- {
- const std::string key = key_list[i];
-
- msg = getSpeakString(prefix + key);
- if (msg == "__NONE")
- {
- msg = "";
- break;
- }
- else if (msg == "__NEXT")
- {
- msg = "";
- if (i < num_spell_keys)
- i = num_spell_keys - 1;
- else if (ends_with(key, " targeted"))
- i++;
- continue;
- }
- else if (!msg.empty())
- break;
-
- // If we got no message and we're using the silent prefix, then
- // try again without the prefix.
- if (prefix != "silent")
- continue;
-
- msg = getSpeakString(key);
- if (msg == "__NONE")
- {
- msg = "";
- break;
- }
- else if (msg == "__NEXT")
- {
- msg = "";
- if (i < num_spell_keys)
- i = num_spell_keys - 1;
- else if (ends_with(key, " targeted"))
- i++;
- continue;
- }
- else if (!msg.empty())
- break;
- }
-
- if (msg.empty())
- {
- if (silent)
- return;
-
- noisy(noise, monster->pos(), monster->mindex());
- return;
- }
-
- /////////////////////
- // We have a message.
- /////////////////////
-
- const bool gestured = msg.find("Gesture") != std::string::npos
- || msg.find(" gesture") != std::string::npos
- || msg.find("Point") != std::string::npos
- || msg.find(" point") != std::string::npos;
-
- bolt tracer = pbolt;
- if (targeted)
- {
- // For a targeted but rangeless spell make the range positive so that
- // fire_tracer() will fill out path_taken.
- if (pbolt.range == 0 && pbolt.target != monster->pos())
- tracer.range = ENV_SHOW_DIAMETER;
-
- fire_tracer(monster, tracer);
- }
-
- std::string targ_prep = "at";
- std::string target = "nothing";
-
- if (!targeted)
- target = "NO TARGET";
- else if (pbolt.target == you.pos())
- target = "you";
- else if (pbolt.target == monster->pos())
- target = monster->pronoun(PRONOUN_REFLEXIVE);
- // Monsters should only use targeted spells while foe == MHITNOT
- // if they're targetting themselves.
- else if (monster->foe == MHITNOT && !monster->confused())
- target = "NONEXISTENT FOE";
- else if (!invalid_monster_index(monster->foe)
- && menv[monster->foe].type == MONS_NO_MONSTER)
- {
- target = "DEAD FOE";
- }
- else if (in_bounds(pbolt.target) && see_cell(pbolt.target))
- {
- if (const monsters* mtarg = monster_at(pbolt.target))
- {
- if (you.can_see(mtarg))
- target = mtarg->name(DESC_NOCAP_THE);
- }
- }
-
- // Monster might be aiming past the real target, or maybe some fuzz has
- // been applied because the target is invisible.
- if (target == "nothing" && targeted)
- {
- if (pbolt.aimed_at_spot)
- {
- int count = 0;
- for (adjacent_iterator ai(pbolt.target); ai; ++ai)
- {
- const actor* act = actor_at(*ai);
- if (act && act != monster && you.can_see(act))
- {
- targ_prep = "next to";
-
- if (act->atype() == ACT_PLAYER || one_chance_in(++count))
- target = act->name(DESC_NOCAP_THE);
-
- if (act->atype() == ACT_PLAYER)
- break;
- }
- }
- }
-
- const bool visible_path = visible_beam || gestured;
- bool mons_targ_aligned = false;
-
- const std::vector<coord_def> &path = tracer.path_taken;
- for (unsigned int i = 0; i < path.size(); i++)
- {
- const coord_def pos = path[i];
-
- if (pos == monster->pos())
- continue;
-
- const monsters *m = monster_at(pos);
- if (pos == you.pos())
- {
- // Be egotistical and assume that the monster is aiming at
- // the player, rather than the player being in the path of
- // a beam aimed at an ally.
- if (!mons_wont_attack(monster))
- {
- targ_prep = "at";
- target = "you";
- break;
- }
- // If the ally is confused or aiming at an invisible enemy,
- // with the player in the path, act like it's targeted at
- // the player if there isn't any visible target earlier
- // in the path.
- else if (target == "nothing")
- {
- targ_prep = "at";
- target = "you";
- mons_targ_aligned = true;
- }
- }
- else if (visible_path && m && you.can_see(m))
- {
- bool is_aligned = mons_aligned(m->mindex(), monster->mindex());
- std::string name = m->name(DESC_NOCAP_THE);
-
- if (target == "nothing")
- {
- mons_targ_aligned = is_aligned;
- target = name;
- }
- // If the first target was aligned with the beam source then
- // the first subsequent non-aligned monster in the path will
- // take it's place.
- else if (mons_targ_aligned && !is_aligned)
- {
- mons_targ_aligned = false;
- target = name;
- }
- targ_prep = "at";
- }
- else if (visible_path && target == "nothing")
- {
- int count = 0;
- for (adjacent_iterator ai(pbolt.target); ai; ++ai)
- {
- const actor* act = monster_at(*ai);
- if (act && act != monster && you.can_see(act))
- {
- targ_prep = "past";
- if (act->atype() == ACT_PLAYER
- || one_chance_in(++count))
- {
- target = act->name(DESC_NOCAP_THE);
- }
-
- if (act->atype() == ACT_PLAYER)
- break;
- }
- }
- }
- } // for (unsigned int i = 0; i < path.size(); i++)
- } // if (target == "nothing" && targeted)
-
- const actor* foe = monster->get_foe();
-
- // If we still can't find what appears to be the target, and the
- // monster isn't just throwing the spell in a random direction,
- // we should be able to tell what the monster was aiming for if
- // we can see the monster's foe and the beam (or the beam path
- // implied by gesturing). But only if the beam didn't actually hit
- // anything (but if it did hit something, why didn't that monster
- // show up in the beam's path?)
- if (targeted
- && target == "nothing"
- && (tracer.foe_info.count + tracer.friend_info.count) == 0
- && foe != NULL
- && you.can_see(foe)
- && !monster->confused()
- && (visible_beam || gestured))
- {
- target = foe->name(DESC_NOCAP_THE);
- targ_prep = (pbolt.aimed_at_spot ? "next to" : "past");
- }
-
- // If the monster gestures to create an invisible beam then
- // assume that anything close to the beam is the intended target.
- // Also, if the monster gestures to create a visible beam but it
- // misses still say that the monster gestured "at" the target,
- // rather than "past".
- if (gestured || target == "nothing")
- targ_prep = "at";
-
- msg = replace_all(msg, "@at@", targ_prep);
- msg = replace_all(msg, "@target@", target);
-
- std::string beam_name;
- if (!targeted)
- beam_name = "NON TARGETED BEAM";
- else if (pbolt.name.empty())
- beam_name = "INVALID BEAM";
- else if (!tracer.seen)
- beam_name = "UNSEEN BEAM";
- else
- beam_name = pbolt.get_short_name();
-
- msg = replace_all(msg, "@beam@", beam_name);
-
- const msg_channel_type chan =
- (unseen ? MSGCH_SOUND :
- mons_friendly_real(monster) ? MSGCH_FRIEND_SPELL
- : MSGCH_MONSTER_SPELL);
-
- if (silent)
- mons_speaks_msg(monster, msg, chan, true);
- else if (noisy(noise, monster->pos(), monster->mindex()) || !unseen)
- {
- // noisy() returns true if the player heard the noise.
- mons_speaks_msg(monster, msg, chan);
- }
-}
-
-// Set up bolt structure for monster spell casting.
-void setup_mons_cast(monsters *monster, bolt &pbolt,
- spell_type spell_cast)
-{
- // always set these -- used by things other than fire_beam()
-
- // [ds] Used to be 12 * MHD and later buggily forced to -1 downstairs.
- // Setting this to a more realistic number now that that bug is
- // squashed.
- pbolt.ench_power = 4 * monster->hit_dice;
-
- if (spell_cast == SPELL_TELEPORT_SELF)
- pbolt.ench_power = 2000;
-
- pbolt.beam_source = monster_index(monster);
-
- // Convenience for the hapless innocent who assumes that this
- // damn function does all possible setup. [ds]
- if (pbolt.target.origin())
- pbolt.target = monster->target;
-
- // Set bolt type and range.
- if (_los_free_spell(spell_cast))
- {
- pbolt.range = 0;
- switch (spell_cast)
- {
- case SPELL_BRAIN_FEED:
- pbolt.type = DMNBM_BRAIN_FEED;
- return;
- case SPELL_SMITING:
- case SPELL_AIRSTRIKE:
- pbolt.type = DMNBM_SMITING;
- return;
- default:
- // Other spells get normal setup:
- break;
- }
- }
-
- // The below are no-ops since they don't involve direct_effect,
- // fire_tracer, or beam.
- switch (spell_cast)
- {
- case SPELL_SUMMON_SMALL_MAMMALS:
- case SPELL_MAJOR_HEALING:
- case SPELL_VAMPIRE_SUMMON:
- case SPELL_SHADOW_CREATURES: // summon anything appropriate for level
- case SPELL_FAKE_RAKSHASA_SUMMON:
- case SPELL_SUMMON_DEMON:
- case SPELL_SUMMON_UGLY_THING:
- case SPELL_ANIMATE_DEAD:
- case SPELL_CALL_IMP:
- case SPELL_SUMMON_SCORPIONS:
- case SPELL_SUMMON_UFETUBUS:
- case SPELL_SUMMON_BEAST: // Geryon
- case SPELL_SUMMON_UNDEAD: // summon undead around player
- case SPELL_SUMMON_ICE_BEAST:
- case SPELL_SUMMON_MUSHROOMS:
- case SPELL_CONJURE_BALL_LIGHTNING:
- case SPELL_SUMMON_DRAKES:
- case SPELL_SUMMON_HORRIBLE_THINGS:
- case SPELL_HAUNT:
- case SPELL_SYMBOL_OF_TORMENT:
- case SPELL_SUMMON_GREATER_DEMON:
- case SPELL_CANTRIP:
- case SPELL_BERSERKER_RAGE:
- case SPELL_WATER_ELEMENTALS:
- case SPELL_KRAKEN_TENTACLES:
- case SPELL_BLINK:
- case SPELL_CONTROLLED_BLINK:
- case SPELL_TOMB_OF_DOROKLOHE:
- return;
- default:
- break;
- }
-
- // Need to correct this for power of spellcaster
- int power = 12 * monster->hit_dice;
-
- bolt theBeam = mons_spells(monster, spell_cast, power);
-
- pbolt.colour = theBeam.colour;
- pbolt.range = theBeam.range;
- pbolt.hit = theBeam.hit;
- pbolt.damage = theBeam.damage;
-
- if (theBeam.ench_power != -1)
- pbolt.ench_power = theBeam.ench_power;
-
- pbolt.type = theBeam.type;
- pbolt.flavour = theBeam.flavour;
- pbolt.thrower = theBeam.thrower;
- pbolt.name = theBeam.name;
- pbolt.short_name = theBeam.short_name;
- pbolt.is_beam = theBeam.is_beam;
- pbolt.source = monster->pos();
- pbolt.is_tracer = false;
- pbolt.is_explosion = theBeam.is_explosion;
- pbolt.ex_size = theBeam.ex_size;
-
- pbolt.foe_ratio = theBeam.foe_ratio;
-
- if (!pbolt.is_enchantment())
- pbolt.aux_source = pbolt.name;
- else
- pbolt.aux_source.clear();
-
- if (spell_cast == SPELL_HASTE
- || spell_cast == SPELL_INVISIBILITY
- || spell_cast == SPELL_MINOR_HEALING
- || spell_cast == SPELL_TELEPORT_SELF)
- {
- pbolt.target = monster->pos();
- }
- else if (spell_cast == SPELL_PORKALATOR && one_chance_in(3))
- {
- int target = -1;
- int count = 0;
- monster_type hog_type = MONS_HOG;
- for (int i = 0; i < MAX_MONSTERS; i++)
- {
- monsters *targ = &menv[i];
-
- if (!monster->can_see(targ))
- continue;
-
- hog_type = MONS_HOG;
- if (targ->holiness() == MH_DEMONIC)
- hog_type = MONS_HELL_HOG;
- else if (targ->holiness() != MH_NATURAL)
- continue;
-
- if (targ->type != hog_type
- && mons_atts_aligned(monster->attitude, targ->attitude)
- && mons_power(hog_type) + random2(4) >= mons_power(targ->type)
- && (!mons_class_flag(targ->type, M_SPELLCASTER) || coinflip())
- && one_chance_in(++count))
- {
- target = i;
- }
- }
-
- if (target != -1)
- {
- monsters *targ = &menv[target];
- pbolt.target = targ->pos();
-#if DEBUG_DIAGNOSTICS
- mprf("Porkalator: targetting %s instead",
- targ->name(DESC_PLAIN).c_str());
-#endif
- monster_polymorph(targ, hog_type);
- }
- // else target remains as specified
- }
-}
-
-bool monster_random_space(const monsters *monster, coord_def& target,
- bool forbid_sanctuary)
-{
- int tries = 0;
- while (tries++ < 1000)
- {
- target = random_in_bounds();
-
- // Don't land on top of another monster.
- if (actor_at(target))
- continue;
-
- if (is_sanctuary(target) && forbid_sanctuary)
- continue;
-
- if (monster_habitable_grid(monster, grd(target)))
- return (true);
- }
-
- return (false);
-}
-
-bool monster_random_space(monster_type mon, coord_def& target,
- bool forbid_sanctuary)
-{
- monsters dummy;
- dummy.type = mon;
-
- return monster_random_space(&dummy, target, forbid_sanctuary);
-}
-
-void monster_teleport(monsters *monster, bool instan, bool silent)
-{
- if (!instan)
- {
- if (monster->del_ench(ENCH_TP))
- {
- if (!silent)
- simple_monster_message(monster, " seems more stable.");
- }
- else
- {
- if (!silent)
- simple_monster_message(monster, " looks slightly unstable.");
-
- monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER,
- random_range(20, 30)) );
- }
-
- return;
- }
-
- bool was_seen = you.can_see(monster) && !mons_is_lurking(monster);
-
- if (!silent)
- simple_monster_message(monster, " disappears!");
-
- const coord_def oldplace = monster->pos();
-
- // Pick the monster up.
- mgrd(oldplace) = NON_MONSTER;
-
- coord_def newpos;
- if (monster_random_space(monster, newpos, !mons_wont_attack(monster)))
- monster->moveto(newpos);
-
- mgrd(monster->pos()) = monster_index(monster);
-
- // Mimics change form/colour when teleported.
- if (mons_is_mimic(monster->type))
- {
- monster_type old_type = monster->type;
- monster->type = static_cast<monster_type>(
- MONS_GOLD_MIMIC + random2(5));
- monster->colour = get_mimic_colour(monster);
-
- // If it's changed form, you won't recognise it.
- // This assumes that a non-gold mimic turning into another item of
- // the same description is really, really unlikely.
- if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC)
- was_seen = false;
- }
-
- const bool now_visible = mons_near(monster);
- if (!silent && now_visible)
- {
- if (was_seen)
- simple_monster_message(monster, " reappears nearby!");
- else
- {
- // Even if it doesn't interrupt an activity (the player isn't
- // delayed, the monster isn't hostile) we still want to give
- // a message.
- activity_interrupt_data ai(monster, "thin air");
- if (!interrupt_activity(AI_SEE_MONSTER, ai))
- simple_monster_message(monster, " appears out of thin air!");
- }
- }
-
- if (monster->visible_to(&you) && now_visible)
- handle_seen_interrupt(monster);
-
- // Leave a purple cloud.
- place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3),
- monster->kill_alignment());
-
- monster->check_redraw(oldplace);
- monster->apply_location_effects(oldplace);
-
- mons_relocated(monster);
-
- // Teleporting mimics change form - if they reappear out of LOS, they are
- // no longer known.
- if (mons_is_mimic(monster->type))
- {
- if (now_visible)
- monster->flags |= MF_KNOWN_MIMIC;
- else
- monster->flags &= ~MF_KNOWN_MIMIC;
- }
-}
-
-void 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;
-}
-
-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);
-}
-
-static void _scale_draconian_breath(bolt& beam, int drac_type)
-{
- int scaling = 100;
- switch (drac_type)
- {
- case MONS_RED_DRACONIAN:
- beam.name = "searing blast";
- beam.aux_source = "blast of searing breath";
- scaling = 65;
- break;
-
- case MONS_WHITE_DRACONIAN:
- beam.name = "chilling blast";
- beam.aux_source = "blast of chilling breath";
- beam.short_name = "frost";
- scaling = 65;
- break;
-
- case MONS_PLAYER_GHOST: // draconians only
- beam.name = "blast of negative energy";
- beam.aux_source = "blast of draining breath";
- beam.flavour = BEAM_NEG;
- beam.colour = DARKGREY;
- scaling = 65;
- break;
- }
- beam.damage.size = scaling * beam.damage.size / 100;
-}
-
-static spell_type _draco_type_to_breath(int drac_type)
-{
- switch (drac_type)
- {
- case MONS_BLACK_DRACONIAN: return SPELL_LIGHTNING_BOLT;
- case MONS_MOTTLED_DRACONIAN: return SPELL_STICKY_FLAME_SPLASH;
- case MONS_YELLOW_DRACONIAN: return SPELL_ACID_SPLASH;
- case MONS_GREEN_DRACONIAN: return SPELL_POISONOUS_CLOUD;
- case MONS_PURPLE_DRACONIAN: return SPELL_ISKENDERUNS_MYSTIC_BLAST;
- case MONS_RED_DRACONIAN: return SPELL_FIRE_BREATH;
- case MONS_WHITE_DRACONIAN: return SPELL_COLD_BREATH;
- case MONS_PALE_DRACONIAN: return SPELL_STEAM_BALL;
-
- // Handled later.
- case MONS_PLAYER_GHOST: return SPELL_DRACONIAN_BREATH;
-
- default:
- DEBUGSTR("Invalid monster using draconian breath spell");
- break;
- }
-
- return (SPELL_DRACONIAN_BREATH);
-}
-
-
-bolt mons_spells( monsters *mons, spell_type spell_cast, int power )
-{
- ASSERT(power > 0);
-
- bolt beam;
-
- // Initialise to some bogus values so we can catch problems.
- beam.name = "****";
- beam.colour = 1000;
- beam.hit = -1;
- beam.damage = dice_def( 1, 0 );
- beam.ench_power = -1;
- beam.type = 0;
- beam.flavour = BEAM_NONE;
- beam.thrower = KILL_MISC;
- beam.is_beam = false;
- beam.is_explosion = false;
-
- // Sandblast is different, and gets range updated later
- if (spell_cast != SPELL_SANDBLAST)
- beam.range = spell_range(spell_cast, power, true, false);
-
- const int drac_type = (mons_genus(mons->type) == MONS_DRACONIAN)
- ? draco_subspecies(mons) : mons->type;
-
- spell_type real_spell = spell_cast;
-
- if (spell_cast == SPELL_DRACONIAN_BREATH)
- real_spell = _draco_type_to_breath(drac_type);
-
- beam.type = dchar_glyph(DCHAR_FIRED_ZAP); // default
- beam.thrower = KILL_MON_MISSILE;
-
- // FIXME: this should use the zap_data[] struct from beam.cc!
- switch (real_spell)
- {
- case SPELL_MAGIC_DART:
- beam.colour = LIGHTMAGENTA;
- beam.name = "magic dart";
- beam.damage = dice_def( 3, 4 + (power / 100) );
- beam.hit = AUTOMATIC_HIT;
- beam.flavour = BEAM_MMISSILE;
- break;
-
- case SPELL_THROW_FLAME:
- beam.colour = RED;
- beam.name = "puff of flame";
- beam.damage = dice_def( 3, 5 + (power / 40) );
- beam.hit = 25 + power / 40;
- beam.flavour = BEAM_FIRE;
- break;
-
- case SPELL_THROW_FROST:
- beam.colour = WHITE;
- beam.name = "puff of frost";
- beam.damage = dice_def( 3, 5 + (power / 40) );
- beam.hit = 25 + power / 40;
- beam.flavour = BEAM_COLD;
- break;
-
- case SPELL_SANDBLAST:
- beam.colour = BROWN;
- beam.name = "rocky blast";
- beam.damage = dice_def( 3, 5 + (power / 40) );
- beam.hit = 20 + power / 40;
- beam.flavour = BEAM_FRAG;
- beam.range = 2; // spell_range() is wrong here
- break;
-
- case SPELL_DISPEL_UNDEAD:
- beam.flavour = BEAM_DISPEL_UNDEAD;
- beam.damage = dice_def( 3, std::min(6 + power / 10, 40) );
- beam.is_beam = true;
- break;
-
- case SPELL_PARALYSE:
- beam.flavour = BEAM_PARALYSIS;
- beam.is_beam = true;
- break;
-
- case SPELL_SLOW:
- beam.flavour = BEAM_SLOW;
- beam.is_beam = true;
- break;
-
- case SPELL_HASTE: // (self)
- beam.flavour = BEAM_HASTE;
- break;
-
- case SPELL_BACKLIGHT:
- beam.flavour = BEAM_BACKLIGHT;
- beam.is_beam = true;
- break;
-
- case SPELL_CONFUSE:
- beam.flavour = BEAM_CONFUSION;
- beam.is_beam = true;
- break;
-
- case SPELL_SLEEP:
- beam.flavour = BEAM_SLEEP;
- beam.is_beam = true;
- break;
-
- case SPELL_POLYMORPH_OTHER:
- beam.flavour = BEAM_POLYMORPH;
- beam.is_beam = true;
- // Be careful with this one.
- // Having allies mutate you is infuriating.
- beam.foe_ratio = 1000;
- break;
-
- case SPELL_VENOM_BOLT:
- beam.name = "bolt of poison";
- beam.damage = dice_def( 3, 6 + power / 13 );
- beam.colour = LIGHTGREEN;
- beam.flavour = BEAM_POISON;
- beam.hit = 19 + power / 20;
- beam.is_beam = true;
- break;
-
- case SPELL_POISON_ARROW:
- beam.name = "poison arrow";
- beam.damage = dice_def( 3, 7 + power / 12 );
- beam.colour = LIGHTGREEN;
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_POISON_ARROW;
- beam.hit = 20 + power / 25;
- break;
-
- case SPELL_BOLT_OF_MAGMA:
- beam.name = "bolt of magma";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = RED;
- beam.flavour = BEAM_LAVA;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_BOLT_OF_FIRE:
- beam.name = "bolt of fire";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = RED;
- beam.flavour = BEAM_FIRE;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_FLING_ICICLE:
- beam.name = "shard of ice";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = WHITE;
- beam.flavour = BEAM_ICE;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_BOLT_OF_COLD:
- beam.name = "bolt of cold";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = WHITE;
- beam.flavour = BEAM_COLD;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_FREEZING_CLOUD:
- beam.name = "freezing blast";
- beam.damage = dice_def( 2, 9 + power / 11 );
- beam.colour = WHITE;
- beam.flavour = BEAM_COLD;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_SHOCK:
- beam.name = "zap";
- beam.damage = dice_def( 1, 8 + (power / 20) );
- beam.colour = LIGHTCYAN;
- beam.flavour = BEAM_ELECTRICITY;
- beam.hit = 17 + power / 20;
- beam.is_beam = true;
- break;
-
- case SPELL_LIGHTNING_BOLT:
- beam.name = "bolt of lightning";
- beam.damage = dice_def( 3, 10 + power / 17 );
- beam.colour = LIGHTCYAN;
- beam.flavour = BEAM_ELECTRICITY;
- beam.hit = 16 + power / 40;
- beam.is_beam = true;
- break;
-
- case SPELL_INVISIBILITY:
- beam.flavour = BEAM_INVISIBILITY;
- break;
-
- case SPELL_FIREBALL:
- beam.colour = RED;
- beam.name = "fireball";
- beam.damage = dice_def( 3, 7 + power / 10 );
- beam.hit = 40;
- beam.flavour = BEAM_FIRE;
- beam.foe_ratio = 60;
- beam.is_explosion = true;
- break;
-
- case SPELL_FIRE_STORM:
- setup_fire_storm(mons, power / 2, beam);
- beam.foe_ratio = random_range(40, 55);
- break;
-
- case SPELL_ICE_STORM:
- beam.name = "great blast of cold";
- beam.colour = BLUE;
- beam.damage = calc_dice( 10, 18 + power / 2 );
- beam.hit = 20 + power / 10; // 50: 25 100: 30
- beam.ench_power = power; // used for radius
- beam.flavour = BEAM_ICE; // half resisted
- beam.is_explosion = true;
- beam.foe_ratio = random_range(40, 55);
- break;
-
- case SPELL_HELLFIRE_BURST:
- beam.aux_source = "burst of hellfire";
- beam.name = "burst of hellfire";
- beam.ex_size = 1;
- beam.flavour = BEAM_HELLFIRE;
- beam.is_explosion = true;
- beam.colour = RED;
- beam.aux_source.clear();
- beam.is_tracer = false;
- beam.hit = 20;
- beam.damage = mons_foe_is_mons(mons) ? dice_def(5, 7)
- : dice_def(3, 20);
- break;
-
- case SPELL_MINOR_HEALING:
- beam.flavour = BEAM_HEALING;
- beam.hit = 25 + (power / 5);
- break;
-
- case SPELL_TELEPORT_SELF:
- beam.flavour = BEAM_TELEPORT;
- break;
-
- case SPELL_TELEPORT_OTHER:
- beam.flavour = BEAM_TELEPORT;
- beam.is_beam = true;
- break;
-
- case SPELL_LEHUDIBS_CRYSTAL_SPEAR: // was splinters
- beam.name = "crystal spear";
- beam.damage = dice_def( 3, 16 + power / 10 );
- beam.colour = WHITE;
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_MMISSILE;
- beam.hit = 22 + power / 20;
- break;
-
- case SPELL_DIG:
- beam.flavour = BEAM_DIGGING;
- beam.is_beam = true;
- break;
-
- case SPELL_BOLT_OF_DRAINING: // negative energy
- beam.name = "bolt of negative energy";
- beam.damage = dice_def( 3, 6 + power / 13 );
- beam.colour = DARKGREY;
- beam.flavour = BEAM_NEG;
- beam.hit = 16 + power / 35;
- beam.is_beam = true;
- break;
-
- case SPELL_ISKENDERUNS_MYSTIC_BLAST: // mystic blast
- beam.colour = LIGHTMAGENTA;
- beam.name = "orb of energy";
- beam.short_name = "energy";
- beam.damage = dice_def( 3, 7 + (power / 14) );
- beam.hit = 20 + (power / 20);
- beam.flavour = BEAM_MMISSILE;
- break;
-
- case SPELL_STEAM_BALL:
- beam.colour = LIGHTGREY;
- beam.name = "ball of steam";
- beam.damage = dice_def( 3, 7 + (power / 15) );
- beam.hit = 20 + power / 20;
- beam.flavour = BEAM_STEAM;
- break;
-
- case SPELL_PAIN:
- beam.flavour = BEAM_PAIN;
- beam.damage = dice_def( 1, 7 + (power / 20) );
- beam.ench_power = std::max(50, 8 * mons->hit_dice);
- beam.is_beam = true;
- break;
-
- case SPELL_STICKY_FLAME_SPLASH:
- case SPELL_STICKY_FLAME:
- beam.colour = RED;
- beam.name = "sticky flame";
- beam.damage = dice_def( 3, 3 + power / 50 );
- beam.hit = 18 + power / 15;
- beam.flavour = BEAM_FIRE;
- break;
-
- case SPELL_POISONOUS_CLOUD:
- beam.name = "blast of poison";
- beam.damage = dice_def( 3, 3 + power / 25 );
- beam.colour = LIGHTGREEN;
- beam.flavour = BEAM_POISON;
- beam.hit = 18 + power / 25;
- beam.is_beam = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_ENERGY_BOLT: // eye of devastation
- beam.colour = YELLOW;
- beam.name = "bolt of energy";
- beam.short_name = "energy";
- beam.damage = dice_def( 3, 20 );
- beam.hit = 15 + power / 30;
- beam.flavour = BEAM_NUKE; // a magical missile which destroys walls
- beam.is_beam = true;
- break;
-
- case SPELL_STING: // sting
- beam.colour = GREEN;
- beam.name = "sting";
- beam.damage = dice_def( 1, 6 + power / 25 );
- beam.hit = 60;
- beam.flavour = BEAM_POISON;
- break;
-
- case SPELL_IRON_SHOT:
- beam.colour = LIGHTCYAN;
- beam.name = "iron shot";
- beam.damage = dice_def( 3, 8 + (power / 9) );
- beam.hit = 20 + (power / 25);
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_MMISSILE; // similarly unresisted thing
- break;
-
- case SPELL_STONE_ARROW:
- beam.colour = LIGHTGREY;
- beam.name = "stone arrow";
- beam.damage = dice_def( 3, 5 + (power / 10) );
- beam.hit = 14 + power / 35;
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_MMISSILE; // similarly unresisted thing
- break;
-
- case SPELL_POISON_SPLASH:
- beam.colour = GREEN;
- beam.name = "splash of poison";
- beam.damage = dice_def( 1, 4 + power / 10 );
- beam.hit = 16 + power / 20;
- beam.flavour = BEAM_POISON;
- break;
-
- case SPELL_ACID_SPLASH:
- beam.colour = YELLOW;
- beam.name = "splash of acid";
- beam.damage = dice_def( 3, 7 );
- beam.hit = 20 + (3 * mons->hit_dice);
- beam.flavour = BEAM_ACID;
- break;
-
- case SPELL_DISINTEGRATE:
- beam.flavour = BEAM_DISINTEGRATION;
- beam.ench_power = 50;
- beam.damage = dice_def( 1, 30 + (power / 10) );
- beam.is_beam = true;
- break;
-
- case SPELL_MEPHITIC_CLOUD: // swamp drake, player ghost
- beam.name = "foul vapour";
- beam.damage = dice_def(1,0);
- beam.colour = GREEN;
- // Well, it works, even if the name isn't quite intuitive.
- beam.flavour = BEAM_POTION_STINKING_CLOUD;
- beam.hit = 14 + power / 30;
- beam.ench_power = power; // probably meaningless
- beam.is_explosion = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_MIASMA: // death drake
- beam.name = "foul vapour";
- beam.damage = dice_def( 3, 5 + power / 24 );
- beam.colour = DARKGREY;
- beam.flavour = BEAM_MIASMA;
- beam.hit = 17 + power / 20;
- beam.is_beam = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_QUICKSILVER_BOLT: // Quicksilver dragon
- beam.colour = random_colour();
- beam.name = "bolt of energy";
- beam.short_name = "energy";
- beam.damage = dice_def( 3, 25 );
- beam.hit = 16 + power / 25;
- beam.flavour = BEAM_MMISSILE;
- break;
-
- case SPELL_HELLFIRE: // fiend's hellfire
- beam.name = "blast of hellfire";
- beam.aux_source = "blast of hellfire";
- beam.colour = RED;
- beam.damage = dice_def( 3, 25 );
- beam.hit = 24;
- beam.flavour = BEAM_HELLFIRE;
- beam.is_beam = true;
- beam.is_explosion = true;
- break;
-
- case SPELL_METAL_SPLINTERS:
- beam.name = "spray of metal splinters";
- beam.short_name = "metal splinters";
- beam.damage = dice_def( 3, 20 + power / 20 );
- beam.colour = CYAN;
- beam.flavour = BEAM_FRAG;
- beam.hit = 19 + power / 30;
- beam.is_beam = true;
- break;
-
- case SPELL_BANISHMENT:
- beam.flavour = BEAM_BANISH;
- beam.is_beam = true;
- break;
-
- case SPELL_BLINK_OTHER:
- beam.flavour = BEAM_BLINK;
- beam.is_beam = true;
- break;
-
- case SPELL_FIRE_BREATH:
- beam.name = "blast of flame";
- beam.aux_source = "blast of fiery breath";
- beam.damage = dice_def( 3, (mons->hit_dice * 2) );
- beam.colour = RED;
- beam.hit = 30;
- beam.flavour = BEAM_FIRE;
- beam.is_beam = true;
- break;
-
- case SPELL_COLD_BREATH:
- beam.name = "blast of cold";
- beam.aux_source = "blast of icy breath";
- beam.short_name = "frost";
- beam.damage = dice_def( 3, (mons->hit_dice * 2) );
- beam.colour = WHITE;
- beam.hit = 30;
- beam.flavour = BEAM_COLD;
- beam.is_beam = true;
- break;
-
- case SPELL_DRACONIAN_BREATH:
- beam.damage = dice_def( 3, (mons->hit_dice * 2) );
- beam.hit = 30;
- beam.is_beam = true;
- break;
-
- case SPELL_PORKALATOR:
- beam.name = "porkalator";
- beam.type = 0;
- beam.flavour = BEAM_PORKALATOR;
- beam.thrower = KILL_MON_MISSILE;
- beam.is_beam = true;
- break;
-
- default:
- if (!is_valid_spell(real_spell))
- DEBUGSTR("Invalid spell #%d cast by %s", (int) real_spell,
- mons->name(DESC_PLAIN, true).c_str());
-
- DEBUGSTR("Unknown monster spell '%s' cast by %s",
- spell_title(real_spell),
- mons->name(DESC_PLAIN, true).c_str());
-
- return (beam);
- }
-
- if (beam.is_enchantment())
- {
- beam.type = dchar_glyph(DCHAR_SPACE);
- beam.name = "0";
- }
-
- if (spell_cast == SPELL_DRACONIAN_BREATH)
- _scale_draconian_breath(beam, drac_type);
-
- // Accuracy is lowered by one quarter if the dragon is attacking
- // a target that is wielding a weapon of dragon slaying (which
- // makes the dragon/draconian avoid looking at the foe).
- // FIXME: This effect is not yet implemented for player draconians
- // or characters in dragon form breathing at monsters wielding a
- // weapon with this brand.
- if (is_dragonkind(mons))
- {
- if (actor *foe = mons->get_foe())
- {
- if (const item_def *weapon = foe->weapon())
- {
- if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING)
- {
- beam.hit *= 3;
- beam.hit /= 4;
- }
- }
- }
- }
-
- return (beam);
-}
-
-static int _monster_abjure_square(const coord_def &pos,
- int pow, int actual,
- int wont_attack)
-{
- monsters *target = monster_at(pos);
- if (target == NULL)
- return (0);
-
- if (!target->alive()
- || ((bool)wont_attack == mons_wont_attack_real(target)))
- {
- return (0);
- }
-
- int duration;
-
- if (!target->is_summoned(&duration))
- return (0);
-
- pow = std::max(20, fuzz_value(pow, 40, 25));
-
- if (!actual)
- return (pow > 40 || pow >= duration);
-
- // TSO and Trog's abjuration protection.
- bool shielded = false;
- if (you.religion == GOD_SHINING_ONE)
- {
- pow = pow * (30 - target->hit_dice) / 30;
- if (pow < duration)
- {
- simple_god_message(" protects your fellow warrior from evil "
- "magic!");
- shielded = true;
- }
- }
- else if (you.religion == GOD_TROG)
- {
- pow = pow * 4 / 5;
- if (pow < duration)
- {
- simple_god_message(" shields your ally from puny magic!");
- shielded = true;
- }
- }
- else if (is_sanctuary(target->pos()))
- {
- pow = 0;
- mpr("Zin's power protects your fellow warrior from evil magic!",
- MSGCH_GOD);
- shielded = true;
- }
-
-#ifdef DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS, "Abj: dur: %d, pow: %d, ndur: %d",
- duration, pow, duration - pow);
-#endif
-
- mon_enchant abj = target->get_ench(ENCH_ABJ);
- if (!target->lose_ench_duration(abj, pow))
- {
- if (!shielded)
- simple_monster_message(target, " shudders.");
- return (1);
- }
-
- return (0);
-}
-
-static int _apply_radius_around_square( const coord_def &c, int radius,
- int (*fn)(const coord_def &, int, int, int),
- int pow, int par1, int par2)
-{
- int res = 0;
- for (int yi = -radius; yi <= radius; ++yi)
- {
- const coord_def c1(c.x - radius, c.y + yi);
- const coord_def c2(c.x + radius, c.y + yi);
- if (in_bounds(c1))
- res += fn(c1, pow, par1, par2);
- if (in_bounds(c2))
- res += fn(c2, pow, par1, par2);
- }
-
- for (int xi = -radius + 1; xi < radius; ++xi)
- {
- const coord_def c1(c.x + xi, c.y - radius);
- const coord_def c2(c.x + xi, c.y + radius);
- if (in_bounds(c1))
- res += fn(c1, pow, par1, par2);
- if (in_bounds(c2))
- res += fn(c2, pow, par1, par2);
- }
- return (res);
-}
-
-static int _monster_abjuration(const monsters *caster, bool actual)
-{
- const bool wont_attack = mons_wont_attack_real(caster);
- int maffected = 0;
-
- if (actual)
- mpr("Send 'em back where they came from!");
-
- int pow = std::min(caster->hit_dice * 90, 2500);
-
- // Abjure radius.
- for (int rad = 1; rad < 5 && pow >= 30; ++rad)
- {
- int number_hit =
- _apply_radius_around_square(caster->pos(), rad,
- _monster_abjure_square,
- pow, actual, wont_attack);
-
- maffected += number_hit;
-
- // Each affected monster drops power.
- //
- // We could further tune this by the actual amount of abjuration
- // damage done to each summon, but the player will probably never
- // notice. :-)
- while (number_hit-- > 0)
- pow = pow * 90 / 100;
-
- pow /= 2;
- }
- return (maffected);
-}
-
-bool silver_statue_effects(monsters *mons)
-{
- actor *foe = mons->get_foe();
- if (foe && mons->can_see(foe) && !one_chance_in(3))
- {
- const std::string msg =
- "'s eyes glow " + weird_glowing_colour() + '.';
- simple_monster_message(mons, msg.c_str(), MSGCH_WARN);
-
- create_monster(
- mgen_data(
- summon_any_demon((coinflip() ? DEMON_COMMON
- : DEMON_LESSER)),
- SAME_ATTITUDE(mons), 5, 0, foe->pos(), mons->foe));
- return (true);
- }
- return (false);
-}
-
-bool orange_statue_effects(monsters *mons)
-{
- actor *foe = mons->get_foe();
- if (foe && mons->can_see(foe) && !one_chance_in(3))
- {
- if (you.can_see(foe))
- {
- if (foe == &you)
- mprf(MSGCH_WARN, "A hostile presence attacks your mind!");
- else if (you.can_see(mons))
- mprf(MSGCH_WARN, "%s fixes %s piercing gaze on %s.",
- mons->name(DESC_CAP_THE).c_str(),
- mons->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
- foe->name(DESC_NOCAP_THE).c_str());
- }
-
- MiscastEffect(foe, monster_index(mons), SPTYP_DIVINATION,
- random2(15), random2(150),
- "an orange crystal statue");
- return (true);
- }
-
- return (false);
-}
-
-bool ugly_thing_mutate(monsters *ugly, bool proximity)
-{
- bool success = false;
-
- std::string src = "";
-
- unsigned char mon_colour = BLACK;
-
- if (!proximity)
- success = true;
- else if (one_chance_in(8))
- {
- int you_mutate_chance = 0;
- int mon_mutate_chance = 0;
-
- for (adjacent_iterator ri(ugly->pos()); ri; ++ri)
- {
- if (you.pos() == *ri)
- you_mutate_chance = get_contamination_level();
- else
- {
- monsters *ugly_near = monster_at(*ri);
-
- if (!ugly_near
- || ugly_near->type != MONS_UGLY_THING
- && ugly_near->type != MONS_VERY_UGLY_THING)
- {
- continue;
- }
-
- for (int i = 0; i < 2; ++i)
- {
- if (coinflip())
- {
- mon_mutate_chance++;
-
- if (coinflip())
- {
- const int ugly_colour =
- make_low_colour(ugly->colour);
- const int ugly_near_colour =
- make_low_colour(ugly_near->colour);
-
- if (ugly_colour != ugly_near_colour)
- mon_colour = ugly_near_colour;
- }
- }
-
- if (ugly_near->type != MONS_VERY_UGLY_THING)
- break;
- }
- }
- }
-
- you_mutate_chance = std::min(16, you_mutate_chance);
- mon_mutate_chance = std::min(16, mon_mutate_chance);
-
- if (!one_chance_in(you_mutate_chance + mon_mutate_chance + 1))
- {
- const bool proximity_you =
- (you_mutate_chance > mon_mutate_chance) ? true :
- (you_mutate_chance == mon_mutate_chance) ? coinflip()
- : false;
-
- src = proximity_you ? " from you" : " from its kin";
-
- success = true;
- }
- }
-
- if (success)
- {
- simple_monster_message(ugly,
- make_stringf(" basks in the mutagenic energy%s and changes!",
- src.c_str()).c_str());
-
- ugly->uglything_mutate(mon_colour);
-
- return (true);
- }
-
- return (false);
-}
-
-// Inflict any enchantments the parent slime has on its offspring,
-// leaving durations unchanged, I guess. -cao
-static void _split_ench_durations(monsters *initial_slime, monsters *split_off)
-{
- mon_enchant_list::iterator i;
-
- for (i = initial_slime->enchantments.begin();
- i != initial_slime->enchantments.end(); ++i)
- {
- split_off->add_ench(i->second);
- }
-
-}
-
-// What to do about any enchantments these two slimes may have? For
-// now, we are averaging the durations. -cao
-static void _merge_ench_durations(monsters *initial_slime, monsters *merge_to)
-{
- mon_enchant_list::iterator i;
-
- int initial_count = initial_slime->number;
- int merge_to_count = merge_to->number;
- int total_count = initial_count + merge_to_count;
-
- for (i = initial_slime->enchantments.begin();
- i != initial_slime->enchantments.end(); ++i)
- {
- // Does the other slime have this enchantment as well?
- mon_enchant temp = merge_to->get_ench(i->first);
- // If not, use duration 0 for their part of the average.
- int duration = temp.ench == ENCH_NONE ? 0 : temp.duration;
-
- i->second.duration = (i->second.duration * initial_count
- + duration * merge_to_count)/total_count;
-
- if (!i->second.duration)
- i->second.duration = 1;
-
- merge_to->add_ench(i->second);
- }
-
- for (i = merge_to->enchantments.begin();
- i != merge_to->enchantments.end(); ++i)
- {
- if (initial_slime->enchantments.find(i->first)
- != initial_slime->enchantments.end()
- && i->second.duration > 1)
- {
- i->second.duration = (merge_to_count * i->second.duration)
- / total_count;
-
- merge_to->update_ench(i->second);
- }
- }
-}
-
-// Calculate slime creature hp based on how many are merged.
-static void _stats_from_blob_count(monsters *slime, float hp_per_blob)
-{
- slime->max_hit_points = (int)(slime->number * hp_per_blob);
- slime->hit_points = slime->max_hit_points;
-}
-
-// Create a new slime creature at 'target', and split 'thing''s hp and
-// merge count with the new monster.
-static bool _do_split(monsters *thing, coord_def & target)
-{
- // Create a new slime.
- int slime_idx = create_monster(mgen_data(MONS_SLIME_CREATURE,
- thing->behaviour,
- 0,
- 0,
- target,
- thing->foe,
- MG_FORCE_PLACE));
-
- if (slime_idx == -1)
- return (false);
-
- monsters *new_slime = &env.mons[slime_idx];
-
- // Inflict the new slime with any enchantments on the parent.
- _split_ench_durations(thing, new_slime);
- new_slime->attitude = thing->attitude;
- new_slime->flags = thing->flags;
-
- if (!new_slime)
- return (false);
-
- if (you.can_see(thing))
- mprf("%s splits.", thing->name(DESC_CAP_A).c_str());
-
- int split_off = thing->number / 2;
- float hp_per_blob = thing->max_hit_points / float(thing->number);
-
- thing->number -= split_off;
- new_slime->number = split_off;
-
- new_slime->hit_dice = thing->hit_dice;
-
- _stats_from_blob_count(thing, hp_per_blob);
- _stats_from_blob_count(new_slime, hp_per_blob);
-
- return (true);
-}
-
-// Actually merge two slime creature, pooling their hp, etc.
-// initial_slime is the one that gets killed off by this process.
-static bool _do_merge(monsters *initial_slime, monsters *merge_to)
-{
- // Combine enchantment durations.
- _merge_ench_durations(initial_slime, merge_to);
-
- merge_to->number += initial_slime->number;
- merge_to->max_hit_points += initial_slime->max_hit_points;
- merge_to->hit_points += initial_slime->max_hit_points;
-
- // Merge monster flags (mostly so that MF_CREATED_NEUTRAL, etc. are
- // passed on if the merged slime subsequently splits. Hopefully
- // this won't do anything weird.
- merge_to->flags |= initial_slime->flags;
-
- // Merging costs the combined slime some energy.
- monsterentry* entry = get_monster_data(merge_to->type);
- merge_to->speed_increment -= entry->energy_usage.move;
-
- // This is dumb. With that said, the idea is that if 2 slimes merge
- // you can gain a space by moving away the turn after (maybe this is
- // too nice but there will probably be a lot of complaints about the
- // damage on higher level slimes). So we subtracted some energy
- // above, but if merge_to hasn't moved yet this turn, that will just
- // cancel its turn in this round of world_reacts(). So we are going
- // to see if merge_to has gone already by checking its mindex (this
- // works because handle_monsters just iterates over env.mons in
- // ascending order).
- if (initial_slime->mindex() < merge_to->mindex())
- merge_to->speed_increment -= entry->energy_usage.move;
-
- // Overwrite the state of the slime getting merged into, because it
- // might have been resting or something.
- merge_to->behaviour = initial_slime->behaviour;
- merge_to->foe = initial_slime->foe;
-
- behaviour_event(merge_to, ME_EVAL);
-
- // Messaging.
- if (you.can_see(merge_to))
- {
- if (you.can_see(initial_slime))
- {
- mprf("Two slime creatures merge to form %s.",
- merge_to->name(DESC_NOCAP_A).c_str());
- }
- else
- {
- mprf("A slime creature suddenly becomes %s.",
- merge_to->name(DESC_NOCAP_A).c_str());
- }
-
- you.flash_colour = LIGHTGREEN;
- viewwindow(true, false);
-
- int flash_delay = 150;
- // Scale delay to match change in arena_delay.
- if (crawl_state.arena)
- {
- flash_delay *= Options.arena_delay;
- flash_delay /= 600;
- }
-
- delay(flash_delay);
- }
- else if (you.can_see(initial_slime))
- mpr("A slime creature suddenly disappears!");
-
- // Have to 'kill' the slime doing the merging.
- monster_die(initial_slime, KILL_MISC, NON_MONSTER, true);
-
- return (true);
-}
-
-// Slime creatures can split but not merge under these conditions.
-static bool _unoccupied_slime(monsters *thing)
-{
- return (thing->asleep()
- || mons_is_wandering(thing)
- || thing->foe == MHITNOT);
-}
-
-// Slime creatures cannot split or merge under these conditions.
-static bool _disabled_slime(monsters *thing)
-{
- return (!thing
- || mons_is_fleeing(thing)
- || mons_is_confused(thing)
- || mons_is_paralysed(thing));
-}
-
-// See if there are any appropriate adjacent slime creatures for 'thing'
-// to merge with. If so, carry out the merge.
-static bool _slime_merge(monsters *thing)
-{
- if (!thing || _disabled_slime(thing) || _unoccupied_slime(thing))
- return (false);
-
- int max_slime_merge = 5;
- int compass_idx[8] = {0, 1, 2, 3, 4, 5, 6, 7};
- std::random_shuffle(compass_idx, compass_idx + 8);
- coord_def origin = thing->pos();
-
- // Check for adjacent slime creatures.
- for (int i = 0; i < 8; ++i)
- {
- coord_def target = origin + Compass[compass_idx[i]];
- monsters *other_thing = monster_at(target);
-
- // We found an adjacent monster. Is it another slime creature
- // we can consider merging with?
- if (other_thing
- && other_thing->mons_species() == MONS_SLIME_CREATURE
- && other_thing->attitude == thing->attitude
- && other_thing->is_summoned() == thing->is_summoned()
- && !mons_is_shapeshifter(other_thing)
- && !_disabled_slime(other_thing))
- {
- // We can actually merge if doing so won't take us over the
- // merge cap and the 'movement' would bring us closer to our
- // target.
- int new_blob_count = other_thing->number + thing->number;
- if (new_blob_count <= max_slime_merge
- && grid_distance(thing->target, thing->pos()) >
- grid_distance(thing->target, target))
- {
- return (_do_merge(thing, other_thing));
- }
- }
- }
-
- // No adjacent slime creatures we could merge with.
- return (false);
-}
-
-// See if slime creature 'thing' can split, and carry out the split if
-// we can find a square to place the new slime creature on.
-static bool _slime_split(monsters *thing)
-{
- if (!thing
- || !_unoccupied_slime(thing)
- || _disabled_slime(thing)
- || thing->number <= 1)
- {
- return (false);
- }
-
- int compass_idx[] = {0, 1, 2, 3, 4, 5, 6, 7};
- std::random_shuffle(compass_idx, compass_idx + 8);
- coord_def origin = thing->pos();
-
- // Anywhere we can place an offspring?
- for (int i = 0; i < 8; ++i)
- {
- coord_def target = origin + Compass[compass_idx[i]];
-
- if (mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target))
- && !actor_at(target))
- {
- // This can fail if placing a new monster fails. That
- // probably means we have too many monsters on the level,
- // so just return in that case.
- return (_do_split(thing, target));
- }
- }
-
- // No free squares.
- return (false);
-}
-
-// See if a given slime creature can split or merge.
-bool slime_split_merge(monsters *thing)
-{
- // No merging/splitting shapeshifters.
- if (!thing
- || mons_is_shapeshifter(thing)
- || thing->mons_species() != MONS_SLIME_CREATURE)
- {
- return (false);
- }
-
- if (_slime_split(thing))
- return (true);
-
- return (_slime_merge(thing));
-}
-
-bool orc_battle_cry(monsters *chief)
-{
- const actor *foe = chief->get_foe();
- int affected = 0;
-
- if (foe
- && (foe != &you || !mons_friendly(chief))
- && !silenced(chief->pos())
- && chief->can_see(foe)
- && coinflip())
- {
- const int boss_index = monster_index(chief);
- const int level = chief->hit_dice > 12? 2 : 1;
- std::vector<monsters*> seen_affected;
- for (int i = 0; i < MAX_MONSTERS; ++i)
- {
- monsters *mon = &menv[i];
- if (mon != chief
- && mon->alive()
- && mons_species(mon->type) == MONS_ORC
- && mons_aligned(boss_index, i)
- && mon->hit_dice < chief->hit_dice
- && !mon->has_ench(ENCH_BERSERK)
- && !mon->has_ench(ENCH_MIGHT)
- && !mon->cannot_move()
- && !mon->confused()
- && chief->can_see(mon))
- {
- mon_enchant ench = mon->get_ench(ENCH_BATTLE_FRENZY);
- if (ench.ench == ENCH_NONE || ench.degree < level)
- {
- const int dur =
- random_range(12, 20) * speed_to_duration(mon->speed);
-
- if (ench.ench != ENCH_NONE)
- {
- ench.degree = level;
- ench.duration = std::max(ench.duration, dur);
- mon->update_ench(ench);
- }
- else
- {
- mon->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, level,
- KC_OTHER, dur));
- }
-
- affected++;
- if (you.can_see(mon))
- seen_affected.push_back(mon);
-
- if (mon->asleep())
- behaviour_event(mon, ME_DISTURB, MHITNOT, chief->pos());
- }
- }
- }
-
- if (affected)
- {
- if (you.can_see(chief) && player_can_hear(chief->pos()))
- {
- mprf(MSGCH_SOUND, "%s roars a battle-cry!",
- chief->name(DESC_CAP_THE).c_str());
- }
-
- // The yell happens whether you happen to see it or not.
- noisy(15, chief->pos(), chief->mindex());
-
- // Disabling detailed frenzy announcement because it's so spammy.
- const msg_channel_type channel =
- mons_friendly_real(chief) ? MSGCH_MONSTER_ENCHANT
- : MSGCH_FRIEND_ENCHANT;
-
- if (!seen_affected.empty())
- {
- std::string who;
- if (seen_affected.size() == 1)
- {
- who = seen_affected[0]->name(DESC_CAP_THE);
- mprf(channel, "%s goes into a battle-frenzy!", who.c_str());
- }
- else
- {
- int type = seen_affected[0]->type;
- for (unsigned int i = 0; i < seen_affected.size(); i++)
- {
- if (seen_affected[i]->type != type)
- {
- // just mention plain orcs
- type = MONS_ORC;
- break;
- }
- }
- who = get_monster_data(type)->name;
-
- mprf(channel, "%s %s go into a battle-frenzy!",
- mons_friendly(chief) ? "Your" : "The",
- pluralise(who).c_str());
- }
- }
- }
- }
- // Orc battle cry doesn't cost the monster an action.
- return (false);
-}
-
-static bool _make_monster_angry(const monsters *mon, monsters *targ)
-{
- if (mons_friendly_real(mon) != mons_friendly_real(targ))
- return (false);
-
- // targ is guaranteed to have a foe (needs_berserk checks this).
- // Now targ needs to be closer to *its* foe than mon is (otherwise
- // mon might be in the way).
-
- coord_def victim;
- if (targ->foe == MHITYOU)
- victim = you.pos();
- else if (targ->foe != MHITNOT)
- {
- const monsters *vmons = &menv[targ->foe];
- if (!vmons->alive())
- return (false);
- victim = vmons->pos();
- }
- else
- {
- // Should be impossible. needs_berserk should find this case.
- ASSERT(false);
- return (false);
- }
-
- // If mon may be blocking targ from its victim, don't try.
- if (victim.distance_from(targ->pos()) > victim.distance_from(mon->pos()))
- return (false);
-
- if (you.can_see(mon))
- {
- mprf("%s goads %s on!", mon->name(DESC_CAP_THE).c_str(),
- targ->name(DESC_NOCAP_THE).c_str());
- }
-
- targ->go_berserk(false);
-
- return (true);
-}
-
-bool moth_incite_monsters(const monsters *mon)
-{
- if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos()))
- return false;
-
- int goaded = 0;
- for (int i = 0; i < MAX_MONSTERS; ++i)
- {
- monsters *targ = &menv[i];
- if (targ == mon || !targ->alive() || !targ->needs_berserk())
- continue;
-
- if (mon->pos().distance_from(targ->pos()) > 3)
- continue;
-
- if (is_sanctuary(targ->pos()))
- continue;
-
- // Cannot goad other moths of wrath!
- if (targ->type == MONS_MOTH_OF_WRATH)
- continue;
-
- if (_make_monster_angry(mon, targ) && !one_chance_in(3 * ++goaded))
- return (true);
- }
-
- return (false);
-}
-
-void mons_clear_trapping_net(monsters *mon)
-{
- if (!mons_is_caught(mon))
- return;
-
- const int net = get_trapping_net(mon->pos());
- if (net != NON_ITEM)
- remove_item_stationary(mitm[net]);
-
- mon->del_ench(ENCH_HELD, true);
-}
-
-bool mons_clonable(const monsters* mon, bool needs_adjacent)
-{
- // No uniques or ghost demon monsters. Also, figuring out the name
- // for the clone of a named monster isn't worth it.
- if (mons_is_unique(mon->type)
- || mons_is_ghost_demon(mon->type)
- || mon->is_named())
- {
- return (false);
- }
-
- if (needs_adjacent)
- {
- // Is there space for the clone?
- bool square_found = false;
- for (int i = 0; i < 8; i++)
- {
- const coord_def p = mon->pos() + Compass[i];
-
- if (in_bounds(p)
- && !actor_at(p)
- && monster_habitable_grid(mon, grd(p)))
- {
- square_found = true;
- break;
- }
- }
- if (!square_found)
- return (false);
- }
-
- // Is the monster carrying an artefact?
- for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
- {
- const int index = mon->inv[i];
-
- if (index == NON_ITEM)
- continue;
-
- if (is_artefact(mitm[index]))
- return (false);
- }
-
- return (true);
-}
-
-int clone_mons(const monsters* orig, bool quiet, bool* obvious,
- coord_def pos)
-{
- // Is there an open slot in menv?
- int midx = NON_MONSTER;
- for (int i = 0; i < MAX_MONSTERS; i++)
- if (menv[i].type == MONS_NO_MONSTER)
- {
- midx = i;
- break;
- }
-
- if (midx == NON_MONSTER)
- return (NON_MONSTER);
-
- if (!in_bounds(pos))
- {
- // Find an adjacent square.
- int squares = 0;
- for (int i = 0; i < 8; i++)
- {
- const coord_def p = orig->pos() + Compass[i];
-
- if (in_bounds(p)
- && !actor_at(p)
- && monster_habitable_grid(orig, grd(p)))
- {
- if (one_chance_in(++squares))
- pos = p;
- }
- }
-
- if (squares == 0)
- return (NON_MONSTER);
- }
-
- ASSERT( !actor_at(pos) );
-
- monsters &mon(menv[midx]);
-
- mon = *orig;
- mon.position = pos;
- mgrd(pos) = midx;
-
- // Duplicate objects, or unequip them if they can't be duplicated.
- for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
- {
- const int old_index = orig->inv[i];
-
- if (old_index == NON_ITEM)
- continue;
-
- const int new_index = get_item_slot(0);
- if (new_index == NON_ITEM)
- {
- mon.unequip(mitm[old_index], i, 0, true);
- mon.inv[i] = NON_ITEM;
- continue;
- }
-
- mon.inv[i] = new_index;
- mitm[new_index] = mitm[old_index];
- mitm[new_index].set_holding_monster(midx);
- }
-
- bool _obvious;
- if (obvious == NULL)
- obvious = &_obvious;
- *obvious = false;
-
- if (you.can_see(orig) && you.can_see(&mon))
- {
- if (!quiet)
- simple_monster_message(orig, " is duplicated!");
- *obvious = true;
- }
-
- mark_interesting_monst(&mon, mon.behaviour);
- if (you.can_see(&mon))
- {
- handle_seen_interrupt(&mon);
- viewwindow(true, false);
- }
-
- if (crawl_state.arena)
- arena_placed_monster(&mon);
-
- return (midx);
-}
-
-std::string summoned_poof_msg(const monsters* monster, bool plural)
-{
- int summon_type = 0;
- bool valid_mon = false;
- if (monster != NULL && !invalid_monster(monster))
- {
- (void) monster->is_summoned(NULL, &summon_type);
- valid_mon = true;
- }
-
- std::string msg = "disappear%s in a puff of smoke";
- bool no_chaos = false;
-
- switch (summon_type)
- {
- case SPELL_SHADOW_CREATURES:
- msg = "dissolve%s into shadows";
- no_chaos = true;
- break;
-
- case MON_SUMM_CHAOS:
- msg = "degenerate%s into a cloud of primal chaos";
- break;
-
- case MON_SUMM_WRATH:
- case MON_SUMM_AID:
- if (valid_mon && is_good_god(monster->god))
- {
- msg = "dissolve%s into sparkling lights";
- no_chaos = true;
- }
- break;
- }
-
- if (valid_mon)
- {
- if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10)
- || monster->type == MONS_CHAOS_SPAWN)
- {
- msg = "degenerate%s into a cloud of primal chaos";
- }
-
- if (mons_is_holy(monster) && summon_type != SPELL_SHADOW_CREATURES
- && summon_type != MON_SUMM_CHAOS)
- {
- msg = "dissolve%s into sparkling lights";
- }
- }
-
- // Conjugate.
- msg = make_stringf(msg.c_str(), plural ? "" : "s");
-
- return (msg);
-}
-
-std::string summoned_poof_msg(const int midx, const item_def &item)
-{
- if (midx == NON_MONSTER)
- return summoned_poof_msg(static_cast<const monsters*>(NULL), item);
- else
- return summoned_poof_msg(&menv[midx], item);
-}
-
-std::string summoned_poof_msg(const monsters* monster, const item_def &item)
-{
- ASSERT(item.flags & ISFLAG_SUMMONED);
-
- return summoned_poof_msg(monster, item.quantity > 1);
-}
diff --git a/crawl-ref/source/mstuff2.h b/crawl-ref/source/mstuff2.h
index 4f0a5ba3c8..848dacbf7c 100644
--- a/crawl-ref/source/mstuff2.h
+++ b/crawl-ref/source/mstuff2.h
@@ -14,33 +14,4 @@
struct bolt;
-bolt mons_spells(monsters *mons, spell_type spell_cast, int power);
-void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
- bool do_noise = true);
-void mons_cast_noise(monsters *monster, bolt &pbolt, spell_type spell_cast);
-void setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast);
-bool mons_throw(monsters *monster, bolt &pbolt, int hand_used);
-void setup_generic_throw(monsters *monster, bolt &pbolt);
-void mons_trap(monsters *monster);
-bool monster_random_space(const monsters *monster, coord_def& target,
- bool forbid_sanctuary = false);
-bool monster_random_space(monster_type mon, coord_def& target,
- bool forbid_sanctuary = false);
-void monster_teleport(monsters *monster, bool instan, bool silent = false);
-bool ugly_thing_mutate(monsters *ugly, bool proximity = false);
-bool slime_split_merge(monsters *thing);
-bool orc_battle_cry(monsters *chief);
-bool orange_statue_effects(monsters *mons);
-bool silver_statue_effects(monsters *mons);
-bool moth_incite_monsters(const monsters *mon);
-void mons_clear_trapping_net(monsters *mon);
-void mons_cast_haunt(monsters *monster);
-
-bool mons_clonable(const monsters* orig, bool needs_adjacent = true);
-int clone_mons(const monsters* orig, bool quiet = false,
- bool* obvious = NULL, coord_def pos = coord_def(0, 0) );
-
-std::string summoned_poof_msg(const monsters* monster, bool plural = false);
-std::string summoned_poof_msg(const int midx, const item_def &item);
-std::string summoned_poof_msg(const monsters* monster, const item_def &item);
#endif
diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc
index f19fe4203b..a1a87c7508 100644
--- a/crawl-ref/source/religion.cc
+++ b/crawl-ref/source/religion.cc
@@ -50,10 +50,10 @@
#include "makeitem.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "mon-util.h"
#include "monplace.h"
#include "monstuff.h"
-#include "mstuff2.h"
#include "mutation.h"
#include "newgame.h"
#include "notes.h"
diff --git a/crawl-ref/source/spells2.cc b/crawl-ref/source/spells2.cc
index bc5ce39e56..3a9741b49f 100644
--- a/crawl-ref/source/spells2.cc
+++ b/crawl-ref/source/spells2.cc
@@ -33,6 +33,7 @@
#include "los.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
diff --git a/crawl-ref/source/spells3.cc b/crawl-ref/source/spells3.cc
index 15397a1f77..3dc8033cd2 100644
--- a/crawl-ref/source/spells3.cc
+++ b/crawl-ref/source/spells3.cc
@@ -33,6 +33,7 @@
#include "los.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
diff --git a/crawl-ref/source/spells4.cc b/crawl-ref/source/spells4.cc
index 160fee75b1..dd452c1fbd 100644
--- a/crawl-ref/source/spells4.cc
+++ b/crawl-ref/source/spells4.cc
@@ -32,10 +32,10 @@
#include "makeitem.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "ouch.h"
#include "player.h"
#include "quiver.h"
diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc
index fd6b110c00..79fefc24f6 100644
--- a/crawl-ref/source/spl-cast.cc
+++ b/crawl-ref/source/spl-cast.cc
@@ -32,8 +32,8 @@
#include "menu.h"
#include "misc.h"
#include "message.h"
+#include "mon-cast.h"
#include "monstuff.h"
-#include "mstuff2.h"
#include "mutation.h"
#include "ouch.h"
#include "player.h"
diff --git a/crawl-ref/source/spl-util.cc b/crawl-ref/source/spl-util.cc
index d588fb62e7..df0e76626e 100644
--- a/crawl-ref/source/spl-util.cc
+++ b/crawl-ref/source/spl-util.cc
@@ -26,6 +26,7 @@
#include "stuff.h"
#include "los.h"
#include "macro.h"
+#include "mon-behv.h"
#include "monstuff.h"
#include "notes.h"
#include "player.h"
diff --git a/crawl-ref/source/terrain.cc b/crawl-ref/source/terrain.cc
index 86b4e9cf14..a3b1e48ff3 100644
--- a/crawl-ref/source/terrain.cc
+++ b/crawl-ref/source/terrain.cc
@@ -22,8 +22,8 @@
#include "message.h"
#include "misc.h"
#include "monplace.h"
+#include "monstuff.h"
#include "mon-util.h"
-#include "mstuff2.h"
#include "ouch.h"
#include "overmap.h"
#include "player.h"
diff --git a/crawl-ref/source/tutorial.cc b/crawl-ref/source/tutorial.cc
index 3d9a177e72..32010e4dfa 100644
--- a/crawl-ref/source/tutorial.cc
+++ b/crawl-ref/source/tutorial.cc
@@ -35,9 +35,9 @@
#include "menu.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "mon-pick.h"
#include "mon-util.h"
-#include "monstuff.h"
#include "mutation.h"
#include "newgame.h"
#include "jobs.h"
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index 42b6254e90..245aaee4e0 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -42,6 +42,7 @@
#include "macro.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc
index 34e879f41e..a79672a555 100644
--- a/crawl-ref/source/xom.cc
+++ b/crawl-ref/source/xom.cc
@@ -23,6 +23,7 @@
#include "makeitem.h"
#include "message.h"
#include "misc.h"
+#include "mon-behv.h"
#include "mon-util.h"
#include "monplace.h"
#include "monstuff.h"