From 204607ad3e8aecb32e01f303b1e86545d3d57f62 Mon Sep 17 00:00:00 2001 From: Matthew Cline Date: Sun, 1 Nov 2009 01:55:04 -0700 Subject: 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. --- crawl-ref/source/mon-behv.cc | 1928 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1928 insertions(+) create mode 100644 crawl-ref/source/mon-behv.cc (limited to 'crawl-ref/source/mon-behv.cc') 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 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 &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 &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 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 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 , 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 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); +} -- cgit v1.2.3-54-g00ecf