diff options
Diffstat (limited to 'crawl-ref/source/monstuff.cc')
-rw-r--r-- | crawl-ref/source/monstuff.cc | 6685 |
1 files changed, 372 insertions, 6313 deletions
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index 3f4e706d27..0f01faf0e4 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -7,55 +7,36 @@ #include "AppHdr.h" #include "monstuff.h" -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <algorithm> +//#include <stdlib.h> +//#include <string.h> +//#include <stdio.h> +//#include <algorithm> #ifdef TARGET_OS_DOS #include <conio.h> #endif -#include "externs.h" - #include "arena.h" #include "artefact.h" -#include "beam.h" #include "cloud.h" -#include "colour.h" -#include "database.h" -#include "debug.h" #include "delay.h" -#include "describe.h" #include "dgnevent.h" #include "directn.h" -#include "exclude.h" -#include "fight.h" #include "files.h" -#include "ghost.h" #include "godabil.h" -#include "hiscores.h" -#include "it_use2.h" -#include "itemname.h" #include "items.h" -#include "itemprop.h" #include "kills.h" -#include "los.h" -#include "makeitem.h" -#include "mapmark.h" #include "message.h" #include "misc.h" +#include "mon-behv.h" #include "monplace.h" #include "monspeak.h" -#include "mon-util.h" -#include "mutation.h" -#include "mstuff2.h" #include "notes.h" #include "player.h" +#include "random.h" #include "religion.h" #include "spl-mis.h" #include "spl-util.h" -#include "spells3.h" #include "state.h" #include "stuff.h" #include "terrain.h" @@ -67,38 +48,6 @@ #include "xom.h" static bool _wounded_damaged(monster_type mon_type); -static bool _handle_special_ability(monsters *monster, bolt & beem); -static bool _handle_pickup(monsters *monster); -static void _handle_behaviour(monsters *monster); -static void _set_nearest_monster_foe(monsters *monster); -static void _mons_in_cloud(monsters *monster); -static bool _mon_can_move_to_pos(const monsters *monster, - const coord_def& delta, - bool just_check = false); -static bool _is_trap_safe(const monsters *monster, const coord_def& where, - bool just_check = false); -static bool _monster_move(monsters *monster); -static spell_type _map_wand_to_mspell(int wand_type); -static bool _is_item_jelly_edible(const item_def &item); - -static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move, - bool potentially_blocking); - -// [dshaligram] Doesn't need to be extern. -static coord_def mmov; - -static const coord_def mon_compass[8] = { - coord_def(-1,-1), coord_def(0,-1), coord_def(1,-1), coord_def(1,0), - coord_def( 1, 1), coord_def(0, 1), coord_def(-1,1), coord_def(-1,0) -}; - -static bool immobile_monster[MAX_MONSTERS]; - -// A probably needless optimization: convert the C string "just seen" to -// a C++ string just once, instead of twice every time a monster moves. -static const std::string _just_seen("just seen"); - -#define ENERGY_SUBMERGE(entry) (std::max(entry->energy_usage.swim / 2, 1)) // This function creates an artificial item to represent a mimic's appearance. // Eventually, mimics could be redone to be more like dancing weapons... @@ -2012,70 +1961,6 @@ void monster_cleanup(monsters *monster) you.pet_target = MHITNOT; } -static bool _jelly_divide(monsters *parent) -{ - if (!mons_class_flag(parent->type, M_SPLITS)) - return (false); - - const int reqd = std::max(parent->hit_dice * 8, 50); - if (parent->hit_points < reqd) - return (false); - - monsters *child = NULL; - coord_def child_spot; - int num_spots = 0; - - // First, find a suitable spot for the child {dlb}: - for (adjacent_iterator ai(parent->pos()); ai; ++ai) - if (actor_at(*ai) == NULL && parent->can_pass_through(*ai)) - if ( one_chance_in(++num_spots) ) - child_spot = *ai; - - if ( num_spots == 0 ) - return (false); - - int k = 0; - - // Now that we have a spot, find a monster slot {dlb}: - for (k = 0; k < MAX_MONSTERS; k++) - { - child = &menv[k]; - - if (child->type == -1) - break; - else if (k == MAX_MONSTERS - 1) - return (false); - } - - // Handle impact of split on parent {dlb}: - parent->max_hit_points /= 2; - - if (parent->hit_points > parent->max_hit_points) - parent->hit_points = parent->max_hit_points; - - parent->init_experience(); - parent->experience = parent->experience * 3 / 5 + 1; - - // Create child {dlb}: - // This is terribly partial and really requires - // more thought as to generation ... {dlb} - *child = *parent; - child->max_hit_points = child->hit_points; - child->speed_increment = 70 + random2(5); - child->moveto(child_spot); - - mgrd(child->pos()) = k; - - if (!simple_monster_message(parent, " splits in two!")) - if (player_can_hear(parent->pos()) || player_can_hear(child->pos())) - mpr("You hear a squelching noise.", MSGCH_SOUND); - - if (crawl_state.arena) - arena_placed_monster(child); - - return (true); -} - // If you're invis and throw/zap whatever, alerts menv to your position. void alert_nearby_monsters(void) { @@ -2593,52 +2478,6 @@ void slimify_monster(monsters *mon, bool hostile) mons_make_god_gift(mon, GOD_JIYVA); } -static void _set_random_target(monsters* mon) -{ - mon->target = random_in_bounds(); // If we don't find anything better. - for (int tries = 0; tries < 150; ++tries) - { - coord_def delta = coord_def(random2(13), random2(13)) - coord_def(6, 6); - if (delta.origin()) - continue; - - const coord_def newtarget = delta + mon->pos(); - if (!in_bounds(newtarget)) - continue; - - mon->target = newtarget; - break; - } -} - -static void _set_random_slime_target(monsters* mon) -{ - // Strictly neutral slimes will go for the nearest item. - int item_idx; - coord_def orig_target = mon->target; - - for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false); ri; ++ri) - { - item_idx = igrd(*ri); - if (item_idx != NON_ITEM) - { - for (stack_iterator si(*ri); si; ++si) - { - item_def& item(*si); - - if (_is_item_jelly_edible(item)) - { - mon->target = *ri; - break; - } - } - } - } - - if (mon->target == mon->pos() || mon->target == you.pos()) - _set_random_target(mon); -} - // allow_adjacent: allow target to be adjacent to origin. // restrict_LOS: restrict target to be within PLAYER line of sight. bool random_near_space(const coord_def& origin, coord_def& target, @@ -2958,552 +2797,6 @@ static bool _wounded_damaged(monster_type mon_type) return (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT); } -//--------------------------------------------------------------- -// -// behaviour_event -// -// 1. Change any of: monster state, foe, and attitude -// 2. Call handle_behaviour to re-evaluate AI state and target x,y -// -//--------------------------------------------------------------- -void behaviour_event(monsters *mon, mon_event_type event, int src, - coord_def src_pos, bool allow_shout) -{ - ASSERT(src >= 0 && src <= MHITYOU); - ASSERT(!crawl_state.arena || src != MHITYOU); - ASSERT(in_bounds(src_pos) || src_pos.origin()); - - const beh_type old_behaviour = mon->behaviour; - - bool isSmart = (mons_intel(mon) > I_ANIMAL); - bool wontAttack = mons_wont_attack_real(mon); - bool sourceWontAttack = false; - bool setTarget = false; - bool breakCharm = false; - bool was_sleeping = mon->asleep(); - - if (src == MHITYOU) - sourceWontAttack = true; - else if (src != MHITNOT) - sourceWontAttack = mons_wont_attack_real( &menv[src] ); - - if (is_sanctuary(mon->pos()) && mons_is_fleeing_sanctuary(mon)) - { - mon->behaviour = BEH_FLEE; - mon->foe = MHITYOU; - mon->target = env.sanctuary_pos; - return; - } - - switch (event) - { - case ME_DISTURB: - // Assumes disturbed by noise... - if (mon->asleep()) - { - mon->behaviour = BEH_WANDER; - - if (mons_near(mon)) - remove_auto_exclude(mon, true); - } - - // A bit of code to make Projected Noise actually do - // something again. Basically, dumb monsters and - // monsters who aren't otherwise occupied will at - // least consider the (apparent) source of the noise - // interesting for a moment. -- bwr - if (!isSmart || mon->foe == MHITNOT || mons_is_wandering(mon)) - { - if (mon->is_patrolling()) - break; - - ASSERT(!src_pos.origin()); - mon->target = src_pos; - } - break; - - case ME_WHACK: - case ME_ANNOY: - // Will turn monster against <src>, unless they - // are BOTH friendly or good neutral AND stupid, - // or else fleeing anyway. Hitting someone over - // the head, of course, always triggers this code. - if (event == ME_WHACK - || ((wontAttack != sourceWontAttack || isSmart) - && !mons_is_fleeing(mon) && !mons_is_panicking(mon))) - { - // Monster types that you can't gain experience from cannot - // fight back, so don't bother having them do so. If you - // worship Feawn, create a ring of friendly plants, and try - // to break out of the ring by killing a plant, you'll get - // a warning prompt and penance only once. Without the - // hostility check, the plant will remain friendly until it - // dies, and you'll get a warning prompt and penance once - // *per hit*. This may not be the best way to address the - // issue, though. -cao - if (mons_class_flag(mon->type, M_NO_EXP_GAIN) - && mon->attitude != ATT_FRIENDLY - && mon->attitude != ATT_GOOD_NEUTRAL) - { - return; - } - - mon->foe = src; - - if (mon->asleep() && mons_near(mon)) - remove_auto_exclude(mon, true); - - if (!mons_is_cornered(mon)) - mon->behaviour = BEH_SEEK; - - if (src == MHITYOU) - { - mon->attitude = ATT_HOSTILE; - breakCharm = true; - } - } - - // Now set target so that monster can whack back (once) at an - // invisible foe. - if (event == ME_WHACK) - setTarget = true; - break; - - case ME_ALERT: - // Allow monsters falling asleep while patrolling (can happen if - // they're left alone for a long time) to be woken by this event. - if (mons_friendly(mon) && mon->is_patrolling() - && !mon->asleep()) - { - break; - } - - if (mon->asleep() && mons_near(mon)) - remove_auto_exclude(mon, true); - - // Will alert monster to <src> and turn them - // against them, unless they have a current foe. - // It won't turn friends hostile either. - if (!mons_is_fleeing(mon) && !mons_is_panicking(mon) - && !mons_is_cornered(mon)) - { - mon->behaviour = BEH_SEEK; - } - - if (mon->foe == MHITNOT) - mon->foe = src; - - if (!src_pos.origin() - && (mon->foe == MHITNOT || mon->foe == src - || mons_is_wandering(mon))) - { - if (mon->is_patrolling()) - break; - - mon->target = src_pos; - - // XXX: Should this be done in _handle_behaviour()? - if (src == MHITYOU && src_pos == you.pos() - && !see_cell(mon->pos())) - { - const dungeon_feature_type can_move = - (mons_amphibious(mon)) ? DNGN_DEEP_WATER - : DNGN_SHALLOW_WATER; - - _try_pathfind(mon, can_move, true); - } - } - break; - - case ME_SCARE: - // Stationary monsters can't flee, and berserking monsters - // are too enraged. - if (mons_is_stationary(mon) || mon->has_ench(ENCH_BERSERK)) - { - mon->del_ench(ENCH_FEAR, true, true); - break; - } - - // Neither do plants or nonliving beings. - if (mon->holiness() == MH_PLANT - || mon->holiness() == MH_NONLIVING) - { - mon->del_ench(ENCH_FEAR, true, true); - break; - } - - // Assume monsters know where to run from, even if player is - // invisible. - mon->behaviour = BEH_FLEE; - mon->foe = src; - mon->target = src_pos; - if (src == MHITYOU) - { - // Friendly monsters don't become hostile if you read a - // scroll of fear, but enslaved ones will. - // Send friendlies off to a random target so they don't cling - // to you in fear. - if (mons_friendly(mon)) - { - breakCharm = true; - mon->foe = MHITNOT; - _set_random_target(mon); - } - else - setTarget = true; - } - else if (mons_friendly(mon) && !crawl_state.arena) - mon->foe = MHITYOU; - - if (see_cell(mon->pos())) - learned_something_new(TUT_FLEEING_MONSTER); - break; - - case ME_CORNERED: - // Some monsters can't flee. - if (mon->behaviour != BEH_FLEE && !mon->has_ench(ENCH_FEAR)) - break; - - // Pacified monsters shouldn't change their behaviour. - if (mons_is_pacified(mon)) - break; - - // Just set behaviour... foe doesn't change. - if (!mons_is_cornered(mon)) - { - if (mons_friendly(mon) && !crawl_state.arena) - { - mon->foe = MHITYOU; - simple_monster_message(mon, " returns to your side!"); - } - else - simple_monster_message(mon, " turns to fight!"); - } - - mon->behaviour = BEH_CORNERED; - break; - - case ME_EVAL: - break; - } - - if (setTarget) - { - if (src == MHITYOU) - { - mon->target = you.pos(); - mon->attitude = ATT_HOSTILE; - } - else if (src != MHITNOT) - mon->target = menv[src].pos(); - } - - // Now, break charms if appropriate. - if (breakCharm) - mon->del_ench(ENCH_CHARM); - - // Do any resultant foe or state changes. - _handle_behaviour(mon); - ASSERT(in_bounds(mon->target) || mon->target.origin()); - - // If it woke up and you're its new foe, it might shout. - if (was_sleeping && !mon->asleep() && allow_shout - && mon->foe == MHITYOU && !mons_wont_attack(mon)) - { - handle_monster_shouts(mon); - } - - const bool wasLurking = - (old_behaviour == BEH_LURK && !mons_is_lurking(mon)); - const bool isPacified = mons_is_pacified(mon); - - if ((wasLurking || isPacified) - && (event == ME_DISTURB || event == ME_ALERT || event == ME_EVAL)) - { - // Lurking monsters or pacified monsters leaving the level won't - // stop doing so just because they noticed something. - mon->behaviour = old_behaviour; - } - else if (wasLurking && mon->has_ench(ENCH_SUBMERGED) - && !mon->del_ench(ENCH_SUBMERGED)) - { - // The same goes for lurking submerged monsters, if they can't - // unsubmerge. - mon->behaviour = BEH_LURK; - } - - ASSERT(!crawl_state.arena - || mon->foe != MHITYOU && mon->target != you.pos()); -} - -static bool _choose_random_patrol_target_grid(monsters *mon) -{ - const int intel = mons_intel(mon); - - // Zombies will occasionally just stand around. - // This does not mean that they don't move every second turn. Rather, - // once they reach their chosen target, there's a 50% chance they'll - // just remain there until next turn when this function is called - // again. - if (intel == I_PLANT && coinflip()) - return (true); - - // If there's no chance we'll find the patrol point, quit right away. - if (grid_distance(mon->pos(), mon->patrol_point) > 2 * LOS_RADIUS) - return (false); - - // Can the monster see the patrol point from its current position? - const bool patrol_seen = mon->mon_see_cell(mon->patrol_point, - habitat2grid(mons_primary_habitat(mon))); - - if (intel == I_PLANT && !patrol_seen) - { - // Really stupid monsters won't even try to get back into the - // patrol zone. - return (false); - } - - // While the patrol point is in easy reach, monsters of insect/plant - // intelligence will only use a range of 5 (distance from the patrol point). - // Otherwise, try to get back using the full LOS. - const int rad = (intel >= I_ANIMAL || !patrol_seen) ? LOS_RADIUS : 5; - const bool is_smart = (intel >= I_NORMAL); - - los_def patrol(mon->patrol_point, opacity_monmove(*mon), bounds_radius(rad)); - patrol.update(); - los_def lm(mon->pos(), opacity_monmove(*mon)); - if (is_smart || !patrol_seen) - { - // For stupid monsters, don't bother if the patrol point is in sight. - lm.update(); - } - - int count_grids = 0; - for (radius_iterator ri(mon->patrol_point, LOS_RADIUS, true, false); - ri; ++ri) - { - // Don't bother for the current position. If everything fails, - // we'll stay here anyway. - if (*ri == mon->pos()) - continue; - - if (!mon->can_pass_through_feat(grd(*ri))) - continue; - - // Don't bother moving to squares (currently) occupied by a - // monster. We'll usually be able to find other target squares - // (and if we're not, we couldn't move anyway), and this avoids - // monsters trying to move onto a grid occupied by a plant or - // sleeping monster. - if (monster_at(*ri)) - continue; - - if (patrol_seen) - { - // If the patrol point can be easily (within LOS) reached - // from the current position, it suffices if the target is - // within reach of the patrol point OR the current position: - // we can easily get there. - // Only smart monsters will even attempt to move out of the - // patrol area. - // NOTE: Either of these can take us into a position where the - // target cannot be easily reached (e.g. blocked by a wall) - // and the patrol point is out of sight, too. Such a case - // will be handled below, though it might take a while until - // a monster gets out of a deadlock. (5% chance per turn.) - if (!patrol.see_cell(*ri) && - (!is_smart || !lm.see_cell(*ri))) - { - continue; - } - } - else - { - // If, however, the patrol point is out of reach, we have to - // make sure the new target brings us into reach of it. - // This means that the target must be reachable BOTH from - // the patrol point AND the current position. - if (!patrol.see_cell(*ri) || - !lm.see_cell(*ri)) - { - continue; - } - - // If this fails for all surrounding squares (probably because - // we're too far away), we fall back to heading directly for - // the patrol point. - } - - bool set_target = false; - if (intel == I_PLANT && *ri == mon->patrol_point) - { - // Slightly greater chance to simply head for the centre. - count_grids += 3; - if (x_chance_in_y(3, count_grids)) - set_target = true; - } - else if (one_chance_in(++count_grids)) - set_target = true; - - if (set_target) - mon->target = *ri; - } - - return (count_grids); -} - -//#define DEBUG_PATHFIND - -// Check all grids in LoS and mark lava and/or water as seen if the -// appropriate grids are encountered, so we later only need to do the -// visibility check for monsters that can't pass a feature potentially in -// the way. We don't care about shallow water as most monsters can safely -// cross that, and fire elementals alone aren't really worth the extra -// hassle. :) -static void _check_lava_water_in_sight() -{ - you.lava_in_sight = you.water_in_sight = 0; - for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) - { - // XXX: remove explicit coordinate translation. - const coord_def ep = *ri - you.pos() + coord_def(ENV_SHOW_OFFSET, - ENV_SHOW_OFFSET); - if (env.show(ep)) - { - const dungeon_feature_type feat = grd(*ri); - if (feat == DNGN_LAVA) - { - you.lava_in_sight = 1; - if (you.water_in_sight > 0) - break; - } - else if (feat == DNGN_DEEP_WATER) - { - you.water_in_sight = 1; - if (you.lava_in_sight > 0) - break; - } - } - } -} - -// If a monster can see but not directly reach the target, and then fails to -// find a path to get there, mark all surrounding (in a radius of 2) monsters -// of the same (or greater) movement restrictions as also being unable to -// find a path, so we won't need to calculate again. -// Should there be a direct path to the target for a monster thus marked, it -// will still be able to come nearer (and the mark will then be cleared). -static void _mark_neighbours_target_unreachable(monsters *mon) -{ - // Highly intelligent monsters are perfectly capable of pathfinding - // and don't need their neighbour's advice. - const mon_intel_type intel = mons_intel(mon); - if (intel > I_NORMAL) - return; - - const bool flies = mons_flies(mon); - const bool amphibious = mons_amphibious(mon); - const habitat_type habit = mons_primary_habitat(mon); - - for (radius_iterator ri(mon->pos(), 2, true, false); ri; ++ri) - { - if (*ri == mon->pos()) - continue; - - // Don't alert monsters out of sight (e.g. on the other side of - // a wall). - if (!mon->mon_see_cell(*ri)) - continue; - - monsters* const m = monster_at(*ri); - if (m == NULL) - continue; - - // Don't restrict smarter monsters as they might find a path - // a dumber monster wouldn't. - if (mons_intel(m) > intel) - continue; - - // Monsters of differing habitats might prefer different routes. - if (mons_primary_habitat(m) != habit) - continue; - - // A flying monster has an advantage over a non-flying one. - if (!flies && mons_flies(m)) - continue; - - // Same for a swimming one, around water. - if (you.water_in_sight > 0 && !amphibious && mons_amphibious(m)) - continue; - - if (m->travel_target == MTRAV_NONE) - m->travel_target = MTRAV_UNREACHABLE; - } -} - -static bool _is_level_exit(const coord_def& pos) -{ - // All types of stairs. - if (feat_is_stair(grd(pos))) - return (true); - - // Teleportation and shaft traps. - const trap_type tt = get_trap_type(pos); - if (tt == TRAP_TELEPORT || tt == TRAP_SHAFT) - return (true); - - return (false); -} - -static void _find_all_level_exits(std::vector<level_exit> &e) -{ - e.clear(); - - for (rectangle_iterator ri(1); ri; ++ri) - { - if (!in_bounds(*ri)) - continue; - - if (_is_level_exit(*ri)) - e.push_back(level_exit(*ri, false)); - } -} - -static int _mons_find_nearest_level_exit(const monsters *mon, - std::vector<level_exit> &e, - bool reset = false) -{ - if (e.empty() || reset) - _find_all_level_exits(e); - - int retval = -1; - int old_dist = -1; - - for (unsigned int i = 0; i < e.size(); ++i) - { - if (e[i].unreachable) - continue; - - int dist = grid_distance(mon->pos(), e[i].target); - - if (old_dist == -1 || old_dist >= dist) - { - // Ignore teleportation and shaft traps that the monster - // shouldn't know about. - if (!mons_is_native_in_branch(mon) - && grd(e[i].target) == DNGN_UNDISCOVERED_TRAP) - { - continue; - } - - retval = i; - old_dist = dist; - } - } - - return (retval); -} - // If _mons_find_level_exits() is ever expanded to handle more grid // types, this should be expanded along with it. static void _mons_indicate_level_exit(const monsters *mon) @@ -3547,23 +2840,6 @@ void make_mons_leave_level(monsters *mon) } } -static void _set_no_path_found(monsters *mon) -{ -#ifdef DEBUG_PATHFIND - mpr("No path found!"); -#endif - - mon->travel_target = MTRAV_UNREACHABLE; - // Pass information on to nearby monsters. - _mark_neighbours_target_unreachable(mon); -} - -static bool _target_is_unreachable(monsters *mon) -{ - return (mon->travel_target == MTRAV_UNREACHABLE - || mon->travel_target == MTRAV_KNOWN_UNREACHABLE); -} - // Checks whether there is a straight path from p1 to p2 that passes // through features >= allowed. // If it exists, such a path may be missed; on the other hand, it @@ -3583,1295 +2859,6 @@ bool can_go_straight(const coord_def& p1, const coord_def& p2, true, true)); } -// The monster is trying to get to the player (MHITYOU). -// Check whether there's an unobstructed path to the player (in sight!), -// either by using an existing travel_path or calculating a new one. -// Returns true if no further handling necessary, else false. -static bool _try_pathfind(monsters *mon, const dungeon_feature_type can_move, - bool potentially_blocking) -{ - // Just because we can *see* the player, that doesn't mean - // we can actually get there. To find about that, we first - // check for transparent walls. If there are transparent - // walls in the way we'll need pathfinding, no matter what. - // (Though monsters with a los attack don't need to get any - // closer to hurt the player.) - // If no walls are detected, there could still be a river - // or a pool of lava in the way. So we check whether there - // is water or lava in LoS (boolean) and if so, try to find - // a way around it. It's possible that the player can see - // lava but it actually has no influence on the monster's - // movement (because it's lying in the opposite direction) - // but if so, we'll find that out during path finding. - // In another attempt of optimization, don't bother with - // path finding if the monster in question has no trouble - // travelling through water or flying across lava. - // Also, if no path is found (too far away, perhaps) set a - // flag, so we don't directly calculate the whole thing again - // next turn, and even extend that flag to neighbouring - // monsters of similar movement restrictions. - - // Smart monsters that can fire through walls won't use - // pathfinding, and it's also not necessary if the monster - // is already adjacent to you. - if (potentially_blocking && mons_intel(mon) >= I_NORMAL - && !mons_friendly(mon) && mons_has_los_ability(mon->type) - || grid_distance(mon->pos(), you.pos()) == 1) - { - potentially_blocking = false; - } - else - { - // If we don't already know whether there's water or lava - // in LoS of the player, find out now. - if (you.lava_in_sight == -1 || you.water_in_sight == -1) - _check_lava_water_in_sight(); - - // Flying monsters don't see water/lava as obstacle. - // Also don't use pathfinding if the monster can shoot - // across the blocking terrain, and is smart enough to - // realise that. - if (!potentially_blocking && !mons_flies(mon) - && (mons_intel(mon) < I_NORMAL - || mons_friendly(mon) - || (!mons_has_ranged_spell(mon, true) - && !mons_has_ranged_attack(mon)))) - { - const habitat_type habit = mons_primary_habitat(mon); - if (you.lava_in_sight > 0 && habit != HT_LAVA - || you.water_in_sight > 0 && habit != HT_WATER - && can_move != DNGN_DEEP_WATER) - { - potentially_blocking = true; - } - } - } - - if (!potentially_blocking - || can_go_straight(mon->pos(), you.pos(), can_move)) - { - // The player is easily reachable. - // Clear travel path and target, if necessary. - if (mon->travel_target != MTRAV_PATROL - && mon->travel_target != MTRAV_NONE) - { - if (mon->is_travelling()) - mon->travel_path.clear(); - mon->travel_target = MTRAV_NONE; - } - return (false); - } - - // Even if the target has been to "unreachable" (the monster already tried, - // and failed, to find a path) there's a chance of trying again. - if (!_target_is_unreachable(mon) || one_chance_in(12)) - { -#ifdef DEBUG_PATHFIND - mprf("%s: Player out of reach! What now?", - mon->name(DESC_PLAIN).c_str()); -#endif - // If we're already on our way, do nothing. - if (mon->is_travelling() && mon->travel_target == MTRAV_PLAYER) - { - const int len = mon->travel_path.size(); - const coord_def targ = mon->travel_path[len - 1]; - - // Current target still valid? - if (can_go_straight(targ, you.pos(), can_move)) - { - // Did we reach the target? - if (mon->pos() == mon->travel_path[0]) - { - // Get next waypoint. - mon->travel_path.erase( mon->travel_path.begin() ); - - if (!mon->travel_path.empty()) - { - mon->target = mon->travel_path[0]; - return (true); - } - } - else if (can_go_straight(mon->pos(), mon->travel_path[0], - can_move)) - { - mon->target = mon->travel_path[0]; - return (true); - } - } - } - - // Use pathfinding to find a (new) path to the player. - const int dist = grid_distance(mon->pos(), you.pos()); - -#ifdef DEBUG_PATHFIND - mprf("Need to calculate a path... (dist = %d)", dist); -#endif - const int range = mons_tracking_range(mon); - if (range > 0 && dist > range) - { - mon->travel_target = MTRAV_UNREACHABLE; -#ifdef DEBUG_PATHFIND - mprf("Distance too great, don't attempt pathfinding! (%s)", - mon->name(DESC_PLAIN).c_str()); -#endif - return (false); - } - -#ifdef DEBUG_PATHFIND - mprf("Need a path for %s from (%d, %d) to (%d, %d), max. dist = %d", - mon->name(DESC_PLAIN).c_str(), mon->pos(), you.pos(), range); -#endif - monster_pathfind mp; - if (range > 0) - mp.set_range(range); - - if (mp.init_pathfind(mon, you.pos())) - { - mon->travel_path = mp.calc_waypoints(); - if (!mon->travel_path.empty()) - { - // Okay then, we found a path. Let's use it! - mon->target = mon->travel_path[0]; - mon->travel_target = MTRAV_PLAYER; - return (true); - } - else - _set_no_path_found(mon); - } - else - _set_no_path_found(mon); - } - - // We didn't find a path. - return (false); -} - -// Returns true if a monster left the level. -static bool _pacified_leave_level(monsters *mon, std::vector<level_exit> e, - int e_index) -{ - // If a pacified monster is leaving the level, and has reached an - // exit (whether that exit was its target or not), handle it here. - // Likewise, if a pacified monster is far enough away from the - // player, make it leave the level. - if (_is_level_exit(mon->pos()) - || (e_index != -1 && mon->pos() == e[e_index].target) - || grid_distance(mon->pos(), you.pos()) >= LOS_RADIUS * 4) - { - make_mons_leave_level(mon); - return (true); - } - - return (false); -} - -// Counts deep water twice. -static int _count_water_neighbours(coord_def p) -{ - int water_count = 0; - for (adjacent_iterator ai(p); ai; ++ai) - { - if (grd(*ai) == DNGN_SHALLOW_WATER) - water_count++; - else if (grd(*ai) == DNGN_DEEP_WATER) - water_count += 2; - } - return (water_count); -} - -// Pick the nearest water grid that is surrounded by the most -// water squares within LoS. -static bool _find_siren_water_target(monsters *mon) -{ - ASSERT(mon->type == MONS_SIREN); - - // Moving away could break the entrancement, so don't do this. - if ((mon->pos() - you.pos()).rdist() >= 6) - return (false); - - // Already completely surrounded by deep water. - if (_count_water_neighbours(mon->pos()) >= 16) - return (true); - - if (mon->travel_target == MTRAV_SIREN) - { - coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]); -#ifdef DEBUG_PATHFIND - mprf("siren target is (%d, %d), dist = %d", - targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist()); -#endif - if ((mon->pos() - targ_pos).rdist() > 2) - return (true); - } - - int best_water_count = 0; - coord_def best_target; - bool first = true; - - while (true) - { - int best_num = 0; - for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false); - ri; ++ri) - { - if (!feat_is_water(grd(*ri))) - continue; - - // In the first iteration only count water grids that are - // not closer to the player than to the siren. - if (first && (mon->pos() - *ri).rdist() > (you.pos() - *ri).rdist()) - continue; - - // Counts deep water twice. - const int water_count = _count_water_neighbours(*ri); - if (water_count < best_water_count) - continue; - - if (water_count > best_water_count) - { - best_water_count = water_count; - best_target = *ri; - best_num = 1; - } - else // water_count == best_water_count - { - const int old_dist = (mon->pos() - best_target).rdist(); - const int new_dist = (mon->pos() - *ri).rdist(); - if (new_dist > old_dist) - continue; - - if (new_dist < old_dist) - { - best_target = *ri; - best_num = 1; - } - else if (one_chance_in(++best_num)) - best_target = *ri; - } - } - - if (!first || best_water_count > 0) - break; - - // Else start the second iteration. - first = false; - } - - if (!best_water_count) - return (false); - - // We're already optimally placed. - if (best_target == mon->pos()) - return (true); - - monster_pathfind mp; -#ifdef WIZARD - // Remove old highlighted areas to make place for the new ones. - for (rectangle_iterator ri(1); ri; ++ri) - env.map(*ri).property &= ~(FPROP_HIGHLIGHT); -#endif - - if (mp.init_pathfind(mon, best_target)) - { - mon->travel_path = mp.calc_waypoints(); - - if (!mon->travel_path.empty()) - { -#ifdef WIZARD - for (unsigned int i = 0; i < mon->travel_path.size(); i++) - env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT; -#endif -#ifdef DEBUG_PATHFIND - mprf("Found a path to (%d, %d) with %d surrounding water squares", - best_target.x, best_target.y, best_water_count); -#endif - // Okay then, we found a path. Let's use it! - mon->target = mon->travel_path[0]; - mon->travel_target = MTRAV_SIREN; - return (true); - } - } - - return (false); -} - -static bool _find_wall_target(monsters *mon) -{ - ASSERT(mons_wall_shielded(mon)); - - if (mon->travel_target == MTRAV_WALL) - { - coord_def targ_pos(mon->travel_path[mon->travel_path.size() - 1]); - - // Target grid might have changed since we started, like if the - // player destroys the wall the monster wants to hide in. - if (cell_is_solid(targ_pos) - && monster_habitable_grid(mon, grd(targ_pos))) - { - // Wall is still good. -#ifdef DEBUG_PATHFIND - mprf("%s target is (%d, %d), dist = %d", - mon->name(DESC_PLAIN, true).c_str(), - targ_pos.x, targ_pos.y, (int) (mon->pos() - targ_pos).rdist()); -#endif - return (true); - } - - mon->travel_path.clear(); - mon->travel_target = MTRAV_NONE; - } - - int best_dist = INT_MAX; - bool best_closer_to_player = false; - coord_def best_target; - - for (radius_iterator ri(mon->pos(), LOS_RADIUS, true, false); - ri; ++ri) - { - if (!cell_is_solid(*ri) - || !monster_habitable_grid(mon, grd(*ri))) - { - continue; - } - - int dist = (mon->pos() - *ri).rdist(); - bool closer_to_player = false; - if (dist > (you.pos() - *ri).rdist()) - closer_to_player = true; - - if (dist < best_dist) - { - best_dist = dist; - best_closer_to_player = closer_to_player; - best_target = *ri; - } - else if (best_closer_to_player && !closer_to_player - && dist == best_dist) - { - best_closer_to_player = false; - best_target = *ri; - } - } - - if (best_dist == INT_MAX || !in_bounds(best_target)) - return (false); - - monster_pathfind mp; -#ifdef WIZARD - // Remove old highlighted areas to make place for the new ones. - for (rectangle_iterator ri(1); ri; ++ri) - env.map(*ri).property &= ~(FPROP_HIGHLIGHT); -#endif - - if (mp.init_pathfind(mon, best_target)) - { - mon->travel_path = mp.calc_waypoints(); - - if (!mon->travel_path.empty()) - { -#ifdef WIZARD - for (unsigned int i = 0; i < mon->travel_path.size(); i++) - env.map(mon->travel_path[i]).property |= FPROP_HIGHLIGHT; -#endif -#ifdef DEBUG_PATHFIND - mprf("Found a path to (%d, %d)", best_target.x, best_target.y); -#endif - // Okay then, we found a path. Let's use it! - mon->target = mon->travel_path[0]; - mon->travel_target = MTRAV_WALL; - return (true); - } - } - return (false); -} - -// Returns true if further handling neeeded. -static bool _handle_monster_travelling(monsters *mon, - const dungeon_feature_type can_move) -{ -#ifdef DEBUG_PATHFIND - mprf("Monster %s reached target (%d, %d)", - mon->name(DESC_PLAIN).c_str(), mon->target.x, mon->target.y); -#endif - - // Hey, we reached our first waypoint! - if (mon->pos() == mon->travel_path[0]) - { -#ifdef DEBUG_PATHFIND - mpr("Arrived at first waypoint."); -#endif - mon->travel_path.erase( mon->travel_path.begin() ); - if (mon->travel_path.empty()) - { -#ifdef DEBUG_PATHFIND - mpr("We reached the end of our path: stop travelling."); -#endif - mon->travel_target = MTRAV_NONE; - return (true); - } - else - { - mon->target = mon->travel_path[0]; -#ifdef DEBUG_PATHFIND - mprf("Next waypoint: (%d, %d)", mon->target.x, mon->target.y); -#endif - return (false); - } - } - - // Can we still see our next waypoint? - if (!can_go_straight(mon->pos(), mon->travel_path[0], can_move)) - { -#ifdef DEBUG_PATHFIND - mpr("Can't see waypoint grid."); -#endif - // Apparently we got sidetracked a bit. - // Check the waypoints vector backwards and pick the first waypoint - // we can see. - - // XXX: Note that this might still not be the best thing to do - // since another path might be even *closer* to our actual target now. - // Not by much, though, since the original path was optimal (A*) and - // the distance between the waypoints is rather small. - - int erase = -1; // Erase how many waypoints? - const int size = mon->travel_path.size(); - for (int i = size - 1; i >= 0; --i) - { - if (can_go_straight(mon->pos(), mon->travel_path[i], can_move)) - { - mon->target = mon->travel_path[i]; - erase = i; - break; - } - } - - if (erase > 0) - { -#ifdef DEBUG_PATHFIND - mprf("Need to erase %d of %d waypoints.", - erase, size); -#endif - // Erase all waypoints that came earlier: - // we don't need them anymore. - while (0 < erase--) - mon->travel_path.erase( mon->travel_path.begin() ); - } - else - { - // We can't reach our old path from our current - // position, so calculate a new path instead. - monster_pathfind mp; - - // The last coordinate in the path vector is our destination. - const int len = mon->travel_path.size(); - if (mp.init_pathfind(mon, mon->travel_path[len-1])) - { - mon->travel_path = mp.calc_waypoints(); - if (!mon->travel_path.empty()) - { - mon->target = mon->travel_path[0]; -#ifdef DEBUG_PATHFIND - mprf("Next waypoint: (%d, %d)", - mon->target.x, mon->target.y); -#endif - } - else - { - mon->travel_target = MTRAV_NONE; - return (true); - } - } - else - { - // Or just forget about the whole thing. - mon->travel_path.clear(); - mon->travel_target = MTRAV_NONE; - return (true); - } - } - } - - // Else, we can see the next waypoint and are making good progress. - // Carry on, then! - return (false); -} - -// Returns true if further handling neeeded. -static bool _handle_monster_patrolling(monsters *mon) -{ - if (!_choose_random_patrol_target_grid(mon)) - { - // If we couldn't find a target that is within easy reach - // of the monster and close to the patrol point, depending - // on monster intelligence, do one of the following: - // * set current position as new patrol point - // * forget about patrolling - // * head back to patrol point - - if (mons_intel(mon) == I_PLANT) - { - // Really stupid monsters forget where they're supposed to be. - if (mons_friendly(mon)) - { - // Your ally was told to wait, and wait it will! - // (Though possibly not where you told it to.) - mon->patrol_point = mon->pos(); - } - else - { - // Stop patrolling. - mon->patrol_point.reset(); - mon->travel_target = MTRAV_NONE; - return (true); - } - } - else - { - // It's time to head back! - // Other than for tracking the player, there's currently - // no distinction between smart and stupid monsters when - // it comes to travelling back to the patrol point. This - // is in part due to the flavour of e.g. bees finding - // their way back to the Hive (and patrolling should - // really be restricted to cases like this), and for the - // other part it's not all that important because we - // calculate the path once and then follow it home, and - // the player won't ever see the orderly fashion the - // bees will trudge along. - // What he will see is them swarming back to the Hive - // entrance after some time, and that is what matters. - monster_pathfind mp; - if (mp.init_pathfind(mon, mon->patrol_point)) - { - mon->travel_path = mp.calc_waypoints(); - if (!mon->travel_path.empty()) - { - mon->target = mon->travel_path[0]; - mon->travel_target = MTRAV_PATROL; - } - else - { - // We're so close we don't even need a path. - mon->target = mon->patrol_point; - } - } - else - { - // Stop patrolling. - mon->patrol_point.reset(); - mon->travel_target = MTRAV_NONE; - return (true); - } - } - } - else - { -#ifdef DEBUG_PATHFIND - mprf("Monster %s (pp: %d, %d) is now patrolling to (%d, %d)", - mon->name(DESC_PLAIN).c_str(), - mon->patrol_point.x, mon->patrol_point.y, - mon->target.x, mon->target.y); -#endif - } - - return (false); -} - -static void _check_wander_target(monsters *mon, bool isPacified = false, - dungeon_feature_type can_move = DNGN_UNSEEN) -{ - // default wander behaviour - if (mon->pos() == mon->target - || mons_is_batty(mon) || !isPacified && one_chance_in(20)) - { - bool need_target = true; - - if (!can_move) - { - can_move = (mons_amphibious(mon) ? DNGN_DEEP_WATER - : DNGN_SHALLOW_WATER); - } - - if (mon->is_travelling()) - need_target = _handle_monster_travelling(mon, can_move); - - // If we still need a target because we're not travelling - // (any more), check for patrol routes instead. - if (need_target && mon->is_patrolling()) - need_target = _handle_monster_patrolling(mon); - - // XXX: This is really dumb wander behaviour... instead of - // changing the goal square every turn, better would be to - // have the monster store a direction and have the monster - // head in that direction for a while, then shift the - // direction to the left or right. We're changing this so - // wandering monsters at least appear to have some sort of - // attention span. -- bwr - if (need_target) - _set_random_target(mon); - } -} - -static void _arena_set_foe(monsters *mons) -{ - const int mind = monster_index(mons); - - int nearest = -1; - int best_distance = -1; - - int nearest_unseen = -1; - int best_unseen_distance = -1; - for (int i = 0; i < MAX_MONSTERS; ++i) - { - if (mind == i) - continue; - - const monsters *other(&menv[i]); - if (!other->alive() || mons_aligned(mind, i)) - continue; - - // Don't fight test spawners, since they're only pseudo-monsters - // placed to spawn real monsters, plus they're impossible to - // kill. But test spawners can fight each other, to give them a - // target to spawn against. - if (other->type == MONS_TEST_SPAWNER - && mons->type != MONS_TEST_SPAWNER) - { - continue; - } - - const int distance = grid_distance(mons->pos(), other->pos()); - const bool seen = mons->can_see(other); - - if (seen) - { - if (best_distance == -1 || distance < best_distance) - { - best_distance = distance; - nearest = i; - } - } - else - { - if (best_unseen_distance == -1 || distance < best_unseen_distance) - { - best_unseen_distance = distance; - nearest_unseen = i; - } - } - - if ((best_distance == -1 || distance < best_distance) - && mons->can_see(other)) - - { - best_distance = distance; - nearest = i; - } - } - - if (nearest != -1) - { - mons->foe = nearest; - mons->target = menv[nearest].pos(); - mons->behaviour = BEH_SEEK; - } - else if (nearest_unseen != -1) - { - mons->target = menv[nearest_unseen].pos(); - if (mons->type == MONS_TEST_SPAWNER) - { - mons->foe = nearest_unseen; - mons->behaviour = BEH_SEEK; - } - else - mons->behaviour = BEH_WANDER; - } - else - { - mons->foe = MHITNOT; - mons->behaviour = BEH_WANDER; - } - if (mons->behaviour == BEH_WANDER) - _check_wander_target(mons); - - ASSERT(mons->foe == MHITNOT || !mons->target.origin()); -} - -//--------------------------------------------------------------- -// -// handle_behaviour -// -// 1. Evaluates current AI state -// 2. Sets monster target x,y based on current foe -// -// XXX: Monsters of I_NORMAL or above should select a new target -// if their current target is another monster which is sitting in -// a wall and is immune to most attacks while in a wall, unless -// the monster has a spell or special/nearby ability which isn't -// affected by the wall. -//--------------------------------------------------------------- -static void _handle_behaviour(monsters *mon) -{ - bool changed = true; - bool isFriendly = mons_friendly(mon); - bool isNeutral = mons_neutral(mon); - bool wontAttack = mons_wont_attack_real(mon); - - // Whether the player is in LOS of the monster and can see - // or has guessed the player's location. - bool proxPlayer = mons_near(mon) && !crawl_state.arena; - - bool trans_wall_block = trans_wall_blocking(mon->pos()); - -#ifdef WIZARD - // If stealth is greater than actually possible (wizmode level) - // pretend the player isn't there, but only for hostile monsters. - if (proxPlayer && you.skills[SK_STEALTH] > 27 && !mons_wont_attack(mon)) - proxPlayer = false; -#endif - bool proxFoe; - bool isHurt = (mon->hit_points <= mon->max_hit_points / 4 - 1); - bool isHealthy = (mon->hit_points > mon->max_hit_points / 2); - bool isSmart = (mons_intel(mon) > I_ANIMAL); - bool isScared = mon->has_ench(ENCH_FEAR); - bool isMobile = !mons_is_stationary(mon); - bool isPacified = mons_is_pacified(mon); - bool patrolling = mon->is_patrolling(); - static std::vector<level_exit> e; - static int e_index = -1; - - // Check for confusion -- early out. - if (mon->has_ench(ENCH_CONFUSION)) - { - _set_random_target(mon); - return; - } - - if (mons_is_fleeing_sanctuary(mon) - && mons_is_fleeing(mon) - && is_sanctuary(you.pos())) - { - return; - } - - if (crawl_state.arena) - { - if (Options.arena_force_ai) - { - if (!mon->get_foe() || mon->target.origin() || one_chance_in(3)) - mon->foe = MHITNOT; - if (mon->foe == MHITNOT || mon->foe == MHITYOU) - _arena_set_foe(mon); - return; - } - // If we're not forcing monsters to attack, just make sure they're - // not targetting the player in arena mode. - else if (mon->foe == MHITYOU) - mon->foe = MHITNOT; - } - - if (mons_wall_shielded(mon) && cell_is_solid(mon->pos())) - { - // Monster is safe, so its behaviour can be simplified to fleeing. - if (mon->behaviour == BEH_CORNERED || mon->behaviour == BEH_PANIC - || isScared) - { - mon->behaviour = BEH_FLEE; - } - } - - const dungeon_feature_type can_move = - (mons_amphibious(mon)) ? DNGN_DEEP_WATER : DNGN_SHALLOW_WATER; - - // Validate current target exists. - if (mon->foe != MHITNOT && mon->foe != MHITYOU) - { - const monsters& foe_monster = menv[mon->foe]; - if (!foe_monster.alive()) - mon->foe = MHITNOT; - if (mons_friendly(&foe_monster) == isFriendly) - mon->foe = MHITNOT; - } - - // Change proxPlayer depending on invisibility and standing - // in shallow water. - if (proxPlayer && !you.visible_to(mon)) - { - proxPlayer = false; - - const int intel = mons_intel(mon); - // Sometimes, if a player is right next to a monster, they will 'see'. - if (grid_distance(you.pos(), mon->pos()) == 1 - && one_chance_in(3)) - { - proxPlayer = true; - } - - // [dshaligram] Very smart monsters have a chance of clueing in to - // invisible players in various ways. - if (intel == I_NORMAL && one_chance_in(13) - || intel == I_HIGH && one_chance_in(6)) - { - proxPlayer = true; - } - } - - // Set friendly target, if they don't already have one. - // Berserking allies ignore your commands! - if (isFriendly - && you.pet_target != MHITNOT - && (mon->foe == MHITNOT || mon->foe == MHITYOU) - && !mon->has_ench(ENCH_BERSERK) - && mon->mons_species() != MONS_GIANT_SPORE ) - { - mon->foe = you.pet_target; - } - - // Instead, berserkers attack nearest monsters. - if ((mon->has_ench(ENCH_BERSERK) || mon->mons_species() == MONS_GIANT_SPORE) - && (mon->foe == MHITNOT || isFriendly && mon->foe == MHITYOU)) - { - // Intelligent monsters prefer to attack the player, - // even when berserking. - if (!isFriendly && proxPlayer && mons_intel(mon) >= I_NORMAL) - mon->foe = MHITYOU; - else - _set_nearest_monster_foe(mon); - } - - // Pacified monsters leaving the level prefer not to attack. - // Others choose the nearest foe. - if (!isPacified && mon->foe == MHITNOT) - _set_nearest_monster_foe(mon); - - // Monsters do not attack themselves. {dlb} - if (mon->foe == monster_index(mon)) - mon->foe = MHITNOT; - - // Friendly and good neutral monsters do not attack other friendly - // and good neutral monsters. - if (mon->foe != MHITNOT && mon->foe != MHITYOU - && wontAttack && mons_wont_attack_real(&menv[mon->foe])) - { - mon->foe = MHITNOT; - } - - // Neutral monsters prefer not to attack players, or other neutrals. - if (isNeutral && mon->foe != MHITNOT - && (mon->foe == MHITYOU || mons_neutral(&menv[mon->foe]))) - { - mon->foe = MHITNOT; - } - - // Unfriendly monsters fighting other monsters will usually - // target the player, if they're healthy. - if (!isFriendly && !isNeutral - && mon->foe != MHITYOU && mon->foe != MHITNOT - && proxPlayer && !(mon->has_ench(ENCH_BERSERK)) && isHealthy - && !one_chance_in(3)) - { - mon->foe = MHITYOU; - } - - // Validate current target again. - if (mon->foe != MHITNOT && mon->foe != MHITYOU) - { - const monsters& foe_monster = menv[mon->foe]; - if (!foe_monster.alive()) - mon->foe = MHITNOT; - if (mons_friendly(&foe_monster) == isFriendly) - mon->foe = MHITNOT; - } - - while (changed) - { - actor* afoe = mon->get_foe(); - proxFoe = afoe && mon->can_see(afoe); - - coord_def foepos = coord_def(0,0); - if (afoe) - foepos = afoe->pos(); - - if (mon->foe == MHITYOU) - proxFoe = proxPlayer; // Take invis into account. - - // Track changes to state; attitude never changes here. - beh_type new_beh = mon->behaviour; - unsigned short new_foe = mon->foe; - - // Take care of monster state changes. - switch (mon->behaviour) - { - case BEH_SLEEP: - // default sleep state - mon->target = mon->pos(); - new_foe = MHITNOT; - break; - - case BEH_LURK: - case BEH_SEEK: - // No foe? Then wander or seek the player. - if (mon->foe == MHITNOT) - { - if (crawl_state.arena || !proxPlayer || isNeutral || patrolling) - new_beh = BEH_WANDER; - else - { - new_foe = MHITYOU; - mon->target = you.pos(); - } - break; - } - - // Foe gone out of LOS? - if (!proxFoe) - { - if (mon->travel_target == MTRAV_SIREN) - mon->travel_target = MTRAV_NONE; - - if (mon->foe == MHITYOU && mon->is_travelling() - && mon->travel_target == MTRAV_PLAYER) - { - // We've got a target, so we'll continue on our way. -#ifdef DEBUG_PATHFIND - mpr("Player out of LoS... start wandering."); -#endif - new_beh = BEH_WANDER; - break; - } - - if (isFriendly) - { - if (patrolling || crawl_state.arena) - { - new_foe = MHITNOT; - new_beh = BEH_WANDER; - } - else - { - new_foe = MHITYOU; - mon->target = foepos; - } - break; - } - - ASSERT(mon->foe != MHITNOT); - if (mon->foe_memory > 0) - { - // If we've arrived at our target x,y - // do a stealth check. If the foe - // fails, monster will then start - // tracking foe's CURRENT position, - // but only for a few moves (smell and - // intuition only go so far). - - if (mon->pos() == mon->target) - { - if (mon->foe == MHITYOU) - { - if (one_chance_in(you.skills[SK_STEALTH]/3)) - mon->target = you.pos(); - else - mon->foe_memory = 0; - } - else - { - if (coinflip()) // XXX: cheesy! - mon->target = menv[mon->foe].pos(); - else - mon->foe_memory = 0; - } - } - - // Either keep chasing, or start wandering. - if (mon->foe_memory < 2) - { - mon->foe_memory = 0; - new_beh = BEH_WANDER; - } - break; - } - - ASSERT(mon->foe_memory == 0); - // Hack: smarter monsters will tend to pursue the player longer. - switch (mons_intel(mon)) - { - case I_HIGH: - mon->foe_memory = 100 + random2(200); - break; - case I_NORMAL: - mon->foe_memory = 50 + random2(100); - break; - case I_ANIMAL: - case I_INSECT: - mon->foe_memory = 25 + random2(75); - break; - case I_PLANT: - mon->foe_memory = 10 + random2(50); - break; - } - break; // switch/case BEH_SEEK - } - - ASSERT(proxFoe && mon->foe != MHITNOT); - // Monster can see foe: continue 'tracking' - // by updating target x,y. - if (mon->foe == MHITYOU) - { - // The foe is the player. - if (mon->type == MONS_SIREN - && player_mesmerised_by(mon) - && _find_siren_water_target(mon)) - { - break; - } - - if (_try_pathfind(mon, can_move, trans_wall_block)) - break; - - // Whew. If we arrived here, path finding didn't yield anything - // (or wasn't even attempted) and we need to set our target - // the traditional way. - - // Sometimes, your friends will wander a bit. - if (isFriendly && one_chance_in(8)) - { - _set_random_target(mon); - mon->foe = MHITNOT; - new_beh = BEH_WANDER; - } - else - { - mon->target = you.pos(); - } - } - else - { - // We have a foe but it's not the player. - mon->target = menv[mon->foe].pos(); - } - - // Smart monsters, zombified monsters other than spectral - // things, plants, and nonliving monsters cannot flee. - if (isHurt && !isSmart && isMobile - && (!mons_is_zombified(mon) || mon->type == MONS_SPECTRAL_THING) - && mon->holiness() != MH_PLANT - && mon->holiness() != MH_NONLIVING) - { - new_beh = BEH_FLEE; - } - break; - - case BEH_WANDER: - if (isPacified) - { - // If a pacified monster isn't travelling toward - // someplace from which it can leave the level, make it - // start doing so. If there's no such place, either - // search the level for such a place again, or travel - // randomly. - if (mon->travel_target != MTRAV_PATROL) - { - new_foe = MHITNOT; - mon->travel_path.clear(); - - e_index = _mons_find_nearest_level_exit(mon, e); - - if (e_index == -1 || one_chance_in(20)) - e_index = _mons_find_nearest_level_exit(mon, e, true); - - if (e_index != -1) - { - mon->travel_target = MTRAV_PATROL; - patrolling = true; - mon->patrol_point = e[e_index].target; - mon->target = e[e_index].target; - } - else - { - mon->travel_target = MTRAV_NONE; - patrolling = false; - mon->patrol_point.reset(); - _set_random_target(mon); - } - } - - if (_pacified_leave_level(mon, e, e_index)) - return; - } - - if (mons_strict_neutral(mon) && mons_is_slime(mon) - && you.religion == GOD_JIYVA) - { - _set_random_slime_target(mon); - } - - // Is our foe in LOS? - // Batty monsters don't automatically reseek so that - // they'll flitter away, we'll reset them just before - // they get movement in handle_monsters() instead. -- bwr - if (proxFoe && !mons_is_batty(mon)) - { - new_beh = BEH_SEEK; - break; - } - - _check_wander_target(mon, isPacified, can_move); - - // During their wanderings, monsters will eventually relax - // their guard (stupid ones will do so faster, smart - // monsters have longer memories). Pacified monsters will - // also eventually switch the place from which they want to - // leave the level, in case their current choice is blocked. - if (!proxFoe && mon->foe != MHITNOT - && one_chance_in(isSmart ? 60 : 20) - || isPacified && one_chance_in(isSmart ? 40 : 120)) - { - new_foe = MHITNOT; - if (mon->is_travelling() && mon->travel_target != MTRAV_PATROL - || isPacified) - { -#ifdef DEBUG_PATHFIND - mpr("It's been too long! Stop travelling."); -#endif - mon->travel_path.clear(); - mon->travel_target = MTRAV_NONE; - - if (isPacified && e_index != -1) - e[e_index].unreachable = true; - } - } - break; - - case BEH_FLEE: - // Check for healed. - if (isHealthy && !isScared) - new_beh = BEH_SEEK; - - // Smart monsters flee until they can flee no more... - // possible to get a 'CORNERED' event, at which point - // we can jump back to WANDER if the foe isn't present. - - if (isFriendly) - { - // Special-cased below so that it will flee *towards* you. - if (mon->foe == MHITYOU) - mon->target = you.pos(); - } - else if (mons_wall_shielded(mon) && _find_wall_target(mon)) - ; // Wall target found. - else if (proxFoe) - { - // Special-cased below so that it will flee *from* the - // correct position. - mon->target = foepos; - } - break; - - case BEH_CORNERED: - // Plants and nonliving monsters cannot fight back. - if (mon->holiness() == MH_PLANT - || mon->holiness() == MH_NONLIVING) - { - break; - } - - if (isHealthy) - new_beh = BEH_SEEK; - - // Foe gone out of LOS? - if (!proxFoe) - { - if ((isFriendly || proxPlayer) && !isNeutral && !patrolling) - new_foe = MHITYOU; - else - new_beh = BEH_WANDER; - } - else - { - mon->target = foepos; - } - break; - - default: - return; // uh oh - } - - changed = (new_beh != mon->behaviour || new_foe != mon->foe); - mon->behaviour = new_beh; - - if (mon->foe != new_foe) - mon->foe_memory = 0; - - mon->foe = new_foe; - } - - if (mon->travel_target == MTRAV_WALL && cell_is_solid(mon->pos())) - { - if (mon->behaviour == BEH_FLEE) - { - // Monster is safe, so stay put. - mon->target = mon->pos(); - mon->foe = MHITNOT; - } - } -} - -static bool _mons_check_foe(monsters *mon, const coord_def& p, - bool friendly, bool neutral) -{ - if (!inside_level_bounds(p)) - return (false); - - if (!friendly && !neutral && p == you.pos() - && you.visible_to(mon) && !is_sanctuary(p)) - { - return (true); - } - - if (monsters *foe = monster_at(p)) - { - if (foe != mon - && mon->can_see(foe) - && (friendly || !is_sanctuary(p)) - && (mons_friendly(foe) != friendly - || (neutral && !mons_neutral(foe)))) - { - return (true); - } - } - return (false); -} - -// Choose random nearest monster as a foe. -void _set_nearest_monster_foe(monsters *mon) -{ - const bool friendly = mons_friendly_real(mon); - const bool neutral = mons_neutral(mon); - - for (int k = 1; k <= LOS_RADIUS; ++k) - { - std::vector<coord_def> monster_pos; - for (int i = -k; i <= k; ++i) - for (int j = -k; j <= k; (abs(i) == k ? j++ : j += 2*k)) - { - const coord_def p = mon->pos() + coord_def(i, j); - if (_mons_check_foe(mon, p, friendly, neutral)) - monster_pos.push_back(p); - } - if (monster_pos.empty()) - continue; - - const coord_def mpos = monster_pos[random2(monster_pos.size())]; - if (mpos == you.pos()) - mon->foe = MHITYOU; - else - mon->foe = env.mgrid(mpos); - return; - } -} - // The default suitable() function for choose_random_nearby_monster(). bool choose_any_monster(const monsters* mon) { @@ -4969,512 +2956,6 @@ bool simple_monster_message(const monsters *monster, const char *event, return (false); } -// Altars as well as branch entrances are considered interesting for -// some monster types. -static bool _mon_on_interesting_grid(monsters *mon) -{ - // Patrolling shouldn't happen all the time. - if (one_chance_in(4)) - return (false); - - const dungeon_feature_type feat = grd(mon->pos()); - - switch (feat) - { - // Holy beings will tend to patrol around altars to the good gods. - case DNGN_ALTAR_ELYVILON: - if (!one_chance_in(3)) - return (false); - // else fall through - case DNGN_ALTAR_ZIN: - case DNGN_ALTAR_SHINING_ONE: - return (mons_is_holy(mon)); - - // Orcs will tend to patrol around altars to Beogh, and guard the - // stairway from and to the Orcish Mines. - case DNGN_ALTAR_BEOGH: - case DNGN_ENTER_ORCISH_MINES: - case DNGN_RETURN_FROM_ORCISH_MINES: - return (mons_is_native_in_branch(mon, BRANCH_ORCISH_MINES)); - - // Same for elves and the Elven Halls. - case DNGN_ENTER_ELVEN_HALLS: - case DNGN_RETURN_FROM_ELVEN_HALLS: - return (mons_is_native_in_branch(mon, BRANCH_ELVEN_HALLS)); - - // Killer bees always return to their hive. - case DNGN_ENTER_HIVE: - return (mons_is_native_in_branch(mon, BRANCH_HIVE)); - - default: - return (false); - } -} - -// If a hostile monster finds itself on a grid of an "interesting" feature, -// while unoccupied, it will remain in that area, and try to return to it -// if it left it for fighting, seeking etc. -static void _maybe_set_patrol_route(monsters *monster) -{ - if (mons_is_wandering(monster) - && !mons_friendly(monster) - && !monster->is_patrolling() - && _mon_on_interesting_grid(monster)) - { - monster->patrol_point = monster->pos(); - } -} - -// Check whether there's a monster of the same type and alignment adjacent -// to the given monster in at least one of three given directions (relative to -// the monster position). -static bool _allied_monster_at(monsters *mon, coord_def a, coord_def b, - coord_def c) -{ - std::vector<coord_def> pos; - pos.push_back(mon->pos() + a); - pos.push_back(mon->pos() + b); - pos.push_back(mon->pos() + c); - - for (unsigned int i = 0; i < pos.size(); i++) - { - if (!in_bounds(pos[i])) - continue; - - const monsters *ally = monster_at(pos[i]); - if (ally == NULL) - continue; - - if (mons_is_stationary(ally)) - continue; - - // Hostile monsters of normal intelligence only move aside for - // monsters of the same genus. - if (mons_intel(mon) <= I_NORMAL && !mons_wont_attack_real(mon) - && mons_genus(mon->type) != mons_genus(ally->type)) - { - continue; - } - - if (mons_aligned(mon->mindex(), ally->mindex())) - return (true); - } - - return (false); -} - -// Check up to eight grids in the given direction for whether there's a -// monster of the same alignment as the given monster that happens to -// have a ranged attack. If this is true for the first monster encountered, -// returns true. Otherwise returns false. -static bool _ranged_allied_monster_in_dir(monsters *mon, coord_def p) -{ - coord_def pos = mon->pos(); - - for (int i = 1; i <= LOS_RADIUS; i++) - { - pos += p; - if (!in_bounds(pos)) - break; - - const monsters* ally = monster_at(pos); - if (ally == NULL) - continue; - - if (mons_aligned(mon->mindex(), ally->mindex())) - { - // Hostile monsters of normal intelligence only move aside for - // monsters of the same type. - if (mons_intel(mon) <= I_NORMAL && !mons_wont_attack(mon) - && mons_genus(mon->type) != mons_genus(ally->type)) - { - return (false); - } - - if (mons_has_ranged_attack(ally) - || mons_has_ranged_spell(ally, true)) - { - return (true); - } - } - break; - } - return (false); -} - -//--------------------------------------------------------------- -// -// handle_movement -// -// Move the monster closer to its target square. -// -//--------------------------------------------------------------- -static void _handle_movement(monsters *monster) -{ - coord_def delta; - - _maybe_set_patrol_route(monster); - - // Monsters will try to flee out of a sanctuary. - if (is_sanctuary(monster->pos()) - && mons_is_influenced_by_sanctuary(monster) - && !mons_is_fleeing_sanctuary(monster)) - { - mons_start_fleeing_from_sanctuary(monster); - } - else if (mons_is_fleeing_sanctuary(monster) - && !is_sanctuary(monster->pos())) - { - // Once outside there's a chance they'll regain their courage. - // Nonliving and berserking monsters always stop immediately, - // since they're only being forced out rather than actually - // scared. - if (monster->holiness() == MH_NONLIVING - || monster->has_ench(ENCH_BERSERK) - || x_chance_in_y(2, 5)) - { - mons_stop_fleeing_from_sanctuary(monster); - } - } - - // Some calculations. - if (mons_class_flag(monster->type, M_BURROWS) && monster->foe == MHITYOU) - { - // Boring beetles always move in a straight line in your - // direction. - delta = you.pos() - monster->pos(); - } - else - { - delta = monster->target - monster->pos(); - - if (crawl_state.arena && Options.arena_force_ai - && !mons_is_stationary(monster)) - { - const bool ranged = (mons_has_ranged_attack(monster) - || mons_has_ranged_spell(monster)); - - // Smiters are happy if they have clear visibility through - // glass, but other monsters must go around. - const bool glass_ok = mons_has_smite_attack(monster); - - // Monsters in the arena are smarter than the norm and - // always pathfind to their targets. - if (delta.abs() > 2 - && (!ranged - || !monster->mon_see_cell(monster->target, !glass_ok))) - { - monster_pathfind mp; - if (mp.init_pathfind(monster, monster->target)) - delta = mp.next_pos(monster->pos()) - monster->pos(); - } - } - } - - // Move the monster. - mmov.x = (delta.x > 0) ? 1 : ((delta.x < 0) ? -1 : 0); - mmov.y = (delta.y > 0) ? 1 : ((delta.y < 0) ? -1 : 0); - - if (mons_is_fleeing(monster) && monster->travel_target != MTRAV_WALL - && (!mons_friendly(monster) - || monster->target != you.pos())) - { - mmov *= -1; - } - - // Don't allow monsters to enter a sanctuary or attack you inside a - // sanctuary, even if you're right next to them. - if (is_sanctuary(monster->pos() + mmov) - && (!is_sanctuary(monster->pos()) - || monster->pos() + mmov == you.pos())) - { - mmov.reset(); - } - - // Bounds check: don't let fleeing monsters try to run off the grid. - const coord_def s = monster->pos() + mmov; - if (!in_bounds_x(s.x)) - mmov.x = 0; - if (!in_bounds_y(s.y)) - mmov.y = 0; - - // Now quit if we can't move. - if (mmov.origin()) - return; - - if (delta.rdist() > 3) - { - // Reproduced here is some semi-legacy code that makes monsters - // move somewhat randomly along oblique paths. It is an - // exceedingly good idea, given crawl's unique line of sight - // properties. - // - // Added a check so that oblique movement paths aren't used when - // close to the target square. -- bwr - - // Sometimes we'll just move parallel the x axis. - if (abs(delta.x) > abs(delta.y) && coinflip()) - mmov.y = 0; - - // Sometimes we'll just move parallel the y axis. - if (abs(delta.y) > abs(delta.x) && coinflip()) - mmov.x = 0; - } - - const coord_def newpos(monster->pos() + mmov); - FixedArray < bool, 3, 3 > good_move; - - for (int count_x = 0; count_x < 3; count_x++) - for (int count_y = 0; count_y < 3; count_y++) - { - const int targ_x = monster->pos().x + count_x - 1; - const int targ_y = monster->pos().y + count_y - 1; - - // Bounds check: don't consider moving out of grid! - if (!in_bounds(targ_x, targ_y)) - { - good_move[count_x][count_y] = false; - continue; - } - - good_move[count_x][count_y] = - _mon_can_move_to_pos(monster, coord_def(count_x-1, count_y-1)); - } - - if (mons_wall_shielded(monster)) - { - // The rock worm will try to move along through rock for as long as - // possible. If the player is walking through a corridor, for example, - // moving along in the wall beside him is much preferable to actually - // leaving the wall. - // This might cause the rock worm to take detours but it still - // comes off as smarter than otherwise. - if (mmov.x != 0 && mmov.y != 0) // diagonal movement - { - bool updown = false; - bool leftright = false; - - coord_def t = monster->pos() + coord_def(mmov.x, 0); - if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t))) - updown = true; - - t = monster->pos() + coord_def(0, mmov.y); - if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t))) - leftright = true; - - if (updown && (!leftright || coinflip())) - mmov.y = 0; - else if (leftright) - mmov.x = 0; - } - else if (mmov.x == 0 && monster->target.x == monster->pos().x) - { - bool left = false; - bool right = false; - coord_def t = monster->pos() + coord_def(-1, mmov.y); - if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t))) - left = true; - - t = monster->pos() + coord_def(1, mmov.y); - if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t))) - right = true; - - if (left && (!right || coinflip())) - mmov.x = -1; - else if (right) - mmov.x = 1; - } - else if (mmov.y == 0 && monster->target.y == monster->pos().y) - { - bool up = false; - bool down = false; - coord_def t = monster->pos() + coord_def(mmov.x, -1); - if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t))) - up = true; - - t = monster->pos() + coord_def(mmov.x, 1); - if (in_bounds(t) && feat_is_rock(grd(t)) && !feat_is_permarock(grd(t))) - down = true; - - if (up && (!down || coinflip())) - mmov.y = -1; - else if (down) - mmov.y = 1; - } - } - - // If the monster is moving in your direction, whether to attack or - // protect you, or towards a monster it intends to attack, check - // whether we first need to take a step to the side to make sure the - // reinforcement can follow through. Only do this with 50% chance, - // though, so it's not completely predictable. - - // First, check whether the monster is smart enough to even consider - // this. - if ((newpos == you.pos() - || mgrd(newpos) != NON_MONSTER && monster->foe == mgrd(newpos)) - && mons_intel(monster) >= I_ANIMAL - && coinflip() - && !mons_is_confused(monster) && !mons_is_caught(monster) - && !monster->has_ench(ENCH_BERSERK)) - { - // If the monster is moving parallel to the x or y axis, check - // whether - // - // a) the neighbouring grids are blocked - // b) there are other unblocked grids adjacent to the target - // c) there's at least one allied monster waiting behind us. - // - // (For really smart monsters, also check whether there's a - // monster farther back in the corridor that has some kind of - // ranged attack.) - if (mmov.y == 0) - { - if (!good_move[1][0] && !good_move[1][2] - && (good_move[mmov.x+1][0] || good_move[mmov.x+1][2]) - && (_allied_monster_at(monster, coord_def(-mmov.x, -1), - coord_def(-mmov.x, 0), - coord_def(-mmov.x, 1)) - || mons_intel(monster) >= I_NORMAL - && !mons_wont_attack_real(monster) - && _ranged_allied_monster_in_dir(monster, - coord_def(-mmov.x, 0)))) - { - if (good_move[mmov.x+1][0]) - mmov.y = -1; - if (good_move[mmov.x+1][2] && (mmov.y == 0 || coinflip())) - mmov.y = 1; - } - } - else if (mmov.x == 0) - { - if (!good_move[0][1] && !good_move[2][1] - && (good_move[0][mmov.y+1] || good_move[2][mmov.y+1]) - && (_allied_monster_at(monster, coord_def(-1, -mmov.y), - coord_def(0, -mmov.y), - coord_def(1, -mmov.y)) - || mons_intel(monster) >= I_NORMAL - && !mons_wont_attack_real(monster) - && _ranged_allied_monster_in_dir(monster, - coord_def(0, -mmov.y)))) - { - if (good_move[0][mmov.y+1]) - mmov.x = -1; - if (good_move[2][mmov.y+1] && (mmov.x == 0 || coinflip())) - mmov.x = 1; - } - } - else // We're moving diagonally. - { - if (good_move[mmov.x+1][1]) - { - if (!good_move[1][mmov.y+1] - && (_allied_monster_at(monster, coord_def(-mmov.x, -1), - coord_def(-mmov.x, 0), - coord_def(-mmov.x, 1)) - || mons_intel(monster) >= I_NORMAL - && !mons_wont_attack_real(monster) - && _ranged_allied_monster_in_dir(monster, - coord_def(-mmov.x, -mmov.y)))) - { - mmov.y = 0; - } - } - else if (good_move[1][mmov.y+1] - && (_allied_monster_at(monster, coord_def(-1, -mmov.y), - coord_def(0, -mmov.y), - coord_def(1, -mmov.y)) - || mons_intel(monster) >= I_NORMAL - && !mons_wont_attack_real(monster) - && _ranged_allied_monster_in_dir(monster, - coord_def(-mmov.x, -mmov.y)))) - { - mmov.x = 0; - } - } - } - - // Now quit if we can't move. - if (mmov.origin()) - return; - - // Try to stay in sight of the player if we're moving towards - // him/her, in order to avoid the monster coming into view, - // shouting, and then taking a step in a path to the player which - // temporarily takes it out of view, which can lead to the player - // getting "comes into view" and shout messages with no monster in - // view. - - // Doesn't matter for arena mode. - if (crawl_state.arena) - return; - - // Did we just come into view? - if (monster->seen_context != _just_seen) - return; - - monster->seen_context.clear(); - - // If the player can't see us, it doesn't matter. - if (!(monster->flags & MF_WAS_IN_VIEW)) - return; - - const coord_def old_pos = monster->pos(); - const int old_dist = grid_distance(you.pos(), old_pos); - - // We're not moving towards the player. - if (grid_distance(you.pos(), old_pos + mmov) >= old_dist) - { - // Give a message if we move back out of view. - monster->seen_context = _just_seen; - return; - } - - // We're already staying in the player's LOS. - if (see_cell(old_pos + mmov)) - return; - - // Try to find a move that brings us closer to the player while - // keeping us in view. - int matches = 0; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - { - if (i == 0 && j == 0) - continue; - - if (!good_move[i][j]) - continue; - - delta.set(i - 1, j - 1); - coord_def tmp = old_pos + delta; - - if (grid_distance(you.pos(), tmp) < old_dist && see_cell(tmp)) - { - if (one_chance_in(++matches)) - mmov = delta; - break; - } - } - - // The only way to get closer to the player is to step out of view; - // give a message so they player isn't confused about its being - // announced as coming into view but not being seen. - monster->seen_context = _just_seen; -} - -static void _make_mons_stop_fleeing(monsters *mon) -{ - if (mons_is_fleeing(mon)) - behaviour_event(mon, ME_CORNERED); -} - -static bool _is_player_or_mon_sanct(const monsters* monster) -{ - return (is_sanctuary(you.pos()) - || is_sanctuary(monster->pos())); -} - bool mons_avoids_cloud(const monsters *monster, cloud_struct cloud, bool placement) { @@ -5639,1544 +3120,6 @@ bool mons_avoids_cloud(const monsters *monster, int cloud_num, return (!mons_avoids_cloud(monster, our_cloud, true)); } -//--------------------------------------------------------------- -// -// handle_nearby_ability -// -// Gives monsters a chance to use a special ability when they're -// next to the player. -// -//--------------------------------------------------------------- -static void _handle_nearby_ability(monsters *monster) -{ - actor *foe = monster->get_foe(); - if (!foe - || !monster->can_see(foe) - || monster->asleep() - || monster->submerged()) - { - return; - } - -#define MON_SPEAK_CHANCE 21 - - if (monster->is_patrolling() || mons_is_wandering(monster) - || monster->attitude == ATT_NEUTRAL) - { - // Very fast wandering/patrolling monsters might, in one monster turn, - // move into the player's LOS and then back out (or the player - // might move into their LOS and the monster move back out before - // the player's view has a chance to update) so prevent them - // from speaking. - ; - } - else if ((mons_class_flag(monster->type, M_SPEAKS) - || !monster->mname.empty()) - && one_chance_in(MON_SPEAK_CHANCE)) - { - mons_speaks(monster); - } - else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED) - { - // Non-humanoid-ish monsters have a low chance of speaking - // without the M_SPEAKS flag, to give the dungeon some - // atmosphere/flavour. - int chance = MON_SPEAK_CHANCE * 4; - - // Band members are a lot less likely to speak, since there's - // a lot of them. - if (testbits(monster->flags, MF_BAND_MEMBER)) - chance *= 10; - - // However, confused and fleeing monsters are more interesting. - if (mons_is_fleeing(monster)) - chance /= 2; - if (monster->has_ench(ENCH_CONFUSION)) - chance /= 2; - - if (one_chance_in(chance)) - mons_speaks(monster); - } - // Okay then, don't speak. - - if (monster_can_submerge(monster, grd(monster->pos())) - && !monster->caught() // No submerging while caught. - && !player_mesmerised_by(monster) // No submerging if player entranced. - && !mons_is_lurking(monster) // Handled elsewhere. - && monster->wants_submerge()) - { - monsterentry* entry = get_monster_data(monster->type); - - monster->add_ench(ENCH_SUBMERGED); - monster->speed_increment -= ENERGY_SUBMERGE(entry); - update_beholders(monster); - return; - } - - switch (monster->type) - { - case MONS_SPATIAL_VORTEX: - case MONS_KILLER_KLOWN: - // Choose random colour. - monster->colour = random_colour(); - break; - - case MONS_GIANT_EYEBALL: - if (coinflip() - && !mons_is_wandering(monster) - && !mons_is_fleeing(monster) - && !mons_is_pacified(monster) - && !_is_player_or_mon_sanct(monster)) - { - if (you.can_see(monster) && you.can_see(foe)) - mprf("%s stares at %s.", - monster->name(DESC_CAP_THE).c_str(), - foe->name(DESC_NOCAP_THE).c_str()); - - // Subtly different from old paralysis behaviour, but - // it'll do. - foe->paralyse(monster, 2 + random2(3)); - } - break; - - case MONS_EYE_OF_DRAINING: - if (coinflip() - && foe->atype() == ACT_PLAYER - && !mons_is_wandering(monster) - && !mons_is_fleeing(monster) - && !mons_is_pacified(monster) - && !_is_player_or_mon_sanct(monster)) - { - simple_monster_message(monster, " stares at you."); - - dec_mp(5 + random2avg(13, 3)); - - heal_monster(monster, 10, true); // heh heh {dlb} - } - break; - - case MONS_AIR_ELEMENTAL: - if (one_chance_in(5)) - monster->add_ench(ENCH_SUBMERGED); - break; - - case MONS_PANDEMONIUM_DEMON: - if (monster->ghost->cycle_colours) - monster->colour = random_colour(); - break; - - default: - break; - } -} - -// Returns true if you resist the siren's call. -static bool _siren_movement_effect(const monsters *monster) -{ - bool do_resist = (you.attribute[ATTR_HELD] || you_resist_magic(70)); - - if (!do_resist) - { - coord_def dir(coord_def(0,0)); - if (monster->pos().x < you.pos().x) - dir.x = -1; - else if (monster->pos().x > you.pos().x) - dir.x = 1; - if (monster->pos().y < you.pos().y) - dir.y = -1; - else if (monster->pos().y > you.pos().y) - dir.y = 1; - - const coord_def newpos = you.pos() + dir; - - if (!in_bounds(newpos) || is_feat_dangerous(grd(newpos)) - || !you.can_pass_through_feat(grd(newpos))) - { - do_resist = true; - } - else - { - bool swapping = false; - monsters *mon = monster_at(newpos); - if (mon) - { - if (mons_wont_attack(mon) - && !mons_is_stationary(mon) - && !mons_cannot_act(mon) - && !mon->asleep() - && swap_check(mon, you.pos(), true)) - { - swapping = true; - } - else if (!mon->submerged()) - do_resist = true; - } - - if (!do_resist) - { - const coord_def oldpos = you.pos(); - mprf("The pull of her song draws you forwards."); - - if (swapping) - { - if (mgrd(oldpos) != NON_MONSTER) - { - mprf("Something prevents you from swapping places " - "with %s.", - mon->name(DESC_NOCAP_THE).c_str()); - return (do_resist); - } - - int swap_mon = mgrd(newpos); - // Pick the monster up. - mgrd(newpos) = NON_MONSTER; - mon->moveto(oldpos); - - // Plunk it down. - mgrd(mon->pos()) = swap_mon; - - mprf("You swap places with %s.", - mon->name(DESC_NOCAP_THE).c_str()); - } - move_player_to_grid(newpos, true, true, true); - - if (swapping) - mon->apply_location_effects(newpos); - } - } - } - - return (do_resist); -} - -//--------------------------------------------------------------- -// -// handle_special_ability -// -//--------------------------------------------------------------- -static bool _handle_special_ability(monsters *monster, bolt & beem) -{ - bool used = false; - - const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN) - ? draco_subspecies( monster ) - : static_cast<monster_type>( monster->type ); - - // Slime creatures can split while out of sight. - if ((!mons_near(monster) - || monster->asleep() - || monster->submerged()) - && monster->mons_species() != MONS_SLIME_CREATURE) - { - return (false); - } - - const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL - : MSGCH_MONSTER_SPELL); - - spell_type spell = SPELL_NO_SPELL; - - switch (mclass) - { - case MONS_UGLY_THING: - case MONS_VERY_UGLY_THING: - // A (very) ugly thing's proximity to you if you're glowing, or - // to others of its kind, can mutate it into a different (very) - // ugly thing. - used = ugly_thing_mutate(monster, true); - break; - - case MONS_SLIME_CREATURE: - // Slime creatures may split or merge depending on the - // situation. - used = slime_split_merge(monster); - if (!monster->alive()) - return (true); - break; - - case MONS_ORC_KNIGHT: - case MONS_ORC_WARLORD: - case MONS_SAINT_ROKA: - if (is_sanctuary(monster->pos())) - break; - - used = orc_battle_cry(monster); - break; - - case MONS_ORANGE_STATUE: - if (_is_player_or_mon_sanct(monster)) - break; - - used = orange_statue_effects(monster); - break; - - case MONS_SILVER_STATUE: - if (_is_player_or_mon_sanct(monster)) - break; - - used = silver_statue_effects(monster); - break; - - case MONS_BALL_LIGHTNING: - if (is_sanctuary(monster->pos())) - break; - - if (monster->attitude == ATT_HOSTILE - && distance(you.pos(), monster->pos()) <= 5) - { - monster->hit_points = -1; - used = true; - break; - } - - for (int i = 0; i < MAX_MONSTERS; i++) - { - monsters *targ = &menv[i]; - - if (targ->type == MONS_NO_MONSTER) - continue; - - if (distance(monster->pos(), targ->pos()) >= 5) - continue; - - if (mons_atts_aligned(monster->attitude, targ->attitude)) - continue; - - // Faking LOS by checking the neighbouring square. - coord_def diff = targ->pos() - monster->pos(); - coord_def sg(sgn(diff.x), sgn(diff.y)); - coord_def t = monster->pos() + sg; - - if (!inside_level_bounds(t)) - continue; - - if (!feat_is_solid(grd(t))) - { - monster->hit_points = -1; - used = true; - break; - } - } - break; - - case MONS_LAVA_SNAKE: - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (!you.visible_to(monster)) - break; - - if (coinflip()) - break; - - // Setup tracer. - beem.name = "glob of lava"; - beem.aux_source = "glob of lava"; - beem.range = 6; - beem.damage = dice_def(3, 10); - beem.hit = 20; - beem.colour = RED; - beem.type = dchar_glyph(DCHAR_FIRED_ZAP); - beem.flavour = BEAM_LAVA; - beem.beam_source = monster_index(monster); - beem.thrower = KILL_MON; - - // Fire tracer. - fire_tracer(monster, beem); - - // Good idea? - if (mons_should_fire(beem)) - { - _make_mons_stop_fleeing(monster); - simple_monster_message(monster, " spits lava!"); - beem.fire(); - used = true; - } - break; - - case MONS_ELECTRIC_EEL: - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (!you.visible_to(monster)) - break; - - if (coinflip()) - break; - - // Setup tracer. - beem.name = "bolt of electricity"; - beem.aux_source = "bolt of electricity"; - beem.range = 8; - beem.damage = dice_def( 3, 6 ); - beem.hit = 50; - beem.colour = LIGHTCYAN; - beem.type = dchar_glyph(DCHAR_FIRED_ZAP); - beem.flavour = BEAM_ELECTRICITY; - beem.beam_source = monster_index(monster); - beem.thrower = KILL_MON; - beem.is_beam = true; - - // Fire tracer. - fire_tracer(monster, beem); - - // Good idea? - if (mons_should_fire(beem)) - { - _make_mons_stop_fleeing(monster); - simple_monster_message(monster, - " shoots out a bolt of electricity!"); - beem.fire(); - used = true; - } - break; - - case MONS_ACID_BLOB: - case MONS_OKLOB_PLANT: - case MONS_YELLOW_DRACONIAN: - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (_is_player_or_mon_sanct(monster)) - break; - - if (one_chance_in(3)) - { - spell = SPELL_ACID_SPLASH; - setup_mons_cast(monster, beem, spell); - - // Fire tracer. - fire_tracer(monster, beem); - - // Good idea? - if (mons_should_fire(beem)) - { - _make_mons_stop_fleeing(monster); - mons_cast(monster, beem, spell); - mmov.reset(); - used = true; - } - } - break; - - case MONS_MOTH_OF_WRATH: - if (one_chance_in(3)) - used = moth_incite_monsters(monster); - break; - - case MONS_SNORG: - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (monster->foe == MHITNOT - || monster->foe == MHITYOU && mons_friendly(monster)) - { - break; - } - - // There's a 5% chance of Snorg spontaneously going berserk that - // increases to 20% once he is wounded. - if (monster->hit_points == monster->max_hit_points && !one_chance_in(4)) - break; - - if (one_chance_in(5)) - monster->go_berserk(true); - break; - - case MONS_PIT_FIEND: - if (one_chance_in(3)) - break; - // deliberate fall through - case MONS_FIEND: - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (_is_player_or_mon_sanct(monster)) - break; - - // Friendly fiends won't use torment, preferring hellfire - // (right now there is no way a monster can predict how - // badly they'll damage the player with torment) -- GDL - - // Well, I guess you could allow it if the player is torment - // resistant, but there's a very good reason torment resistant - // players can't cast Torment themselves, and allowing your - // allies to cast it would just introduce harmless Torment - // through the backdoor. Thus, shouldn't happen. (jpeg) - if (one_chance_in(4)) - { - spell_type spell_cast = SPELL_NO_SPELL; - - switch (random2(4)) - { - case 0: - if (!mons_friendly(monster)) - { - _make_mons_stop_fleeing(monster); - spell_cast = SPELL_SYMBOL_OF_TORMENT; - mons_cast(monster, beem, spell_cast); - used = true; - break; - } - // deliberate fallthrough -- see above - case 1: - case 2: - case 3: - spell_cast = SPELL_HELLFIRE; - setup_mons_cast(monster, beem, spell_cast); - - // Fire tracer. - fire_tracer(monster, beem); - - // Good idea? - if (mons_should_fire(beem)) - { - _make_mons_stop_fleeing(monster); - - mons_cast(monster, beem, spell_cast); - used = true; - } - break; - } - - mmov.reset(); - } - break; - - case MONS_IMP: - case MONS_PHANTOM: - case MONS_INSUBSTANTIAL_WISP: - case MONS_BLINK_FROG: - case MONS_KILLER_KLOWN: - case MONS_PRINCE_RIBBIT: - if (one_chance_in(7) || mons_is_caught(monster) && one_chance_in(3)) - used = monster_blink(monster); - break; - - case MONS_MANTICORE: - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (!you.visible_to(monster)) - break; - - // The fewer spikes the manticore has left, the less - // likely it will use them. - if (random2(16) >= static_cast<int>(monster->number)) - break; - - // Do the throwing right here, since the beam is so - // easy to set up and doesn't involve inventory. - - // Set up the beam. - beem.name = "volley of spikes"; - beem.aux_source = "volley of spikes"; - beem.range = 6; - beem.hit = 14; - beem.damage = dice_def( 2, 10 ); - beem.beam_source = monster_index(monster); - beem.type = dchar_glyph(DCHAR_FIRED_MISSILE); - beem.colour = LIGHTGREY; - beem.flavour = BEAM_MISSILE; - beem.thrower = KILL_MON; - beem.is_beam = false; - - // Fire tracer. - fire_tracer(monster, beem); - - // Good idea? - if (mons_should_fire(beem)) - { - _make_mons_stop_fleeing(monster); - simple_monster_message(monster, " flicks its tail!"); - beem.fire(); - used = true; - // Decrement # of volleys left. - monster->number--; - } - break; - - case MONS_PLAYER_GHOST: - { - const ghost_demon &ghost = *(monster->ghost); - - if (ghost.species < SP_RED_DRACONIAN - || ghost.species == SP_GREY_DRACONIAN - || ghost.species >= SP_BASE_DRACONIAN - || ghost.xl < 7 - || one_chance_in(ghost.xl - 5)) - { - break; - } - } - // Intentional fallthrough - - case MONS_WHITE_DRACONIAN: - case MONS_RED_DRACONIAN: - spell = SPELL_DRACONIAN_BREATH; - // Intentional fallthrough - - case MONS_ICE_DRAGON: - if (spell == SPELL_NO_SPELL) - spell = SPELL_COLD_BREATH; - // Intentional fallthrough - - // Dragon breath weapons: - case MONS_DRAGON: - case MONS_HELL_HOUND: - case MONS_LINDWURM: - case MONS_FIREDRAKE: - case MONS_XTAHUA: - if (spell == SPELL_NO_SPELL) - spell = SPELL_FIRE_BREATH; - - if (monster->has_ench(ENCH_CONFUSION)) - break; - - if (!you.visible_to(monster)) - break; - - if (monster->type != MONS_HELL_HOUND && x_chance_in_y(3, 13) - || one_chance_in(10)) - { - setup_mons_cast(monster, beem, spell); - - // Fire tracer. - fire_tracer(monster, beem); - - // Good idea? - if (mons_should_fire(beem)) - { - _make_mons_stop_fleeing(monster); - mons_cast(monster, beem, spell); - mmov.reset(); - used = true; - } - } - break; - - case MONS_MERMAID: - case MONS_SIREN: - { - // Don't behold observer in the arena. - if (crawl_state.arena) - break; - - // Don't behold player already half down or up the stairs. - if (!you.delay_queue.empty()) - { - delay_queue_item delay = you.delay_queue.front(); - - if (delay.type == DELAY_ASCENDING_STAIRS - || delay.type == DELAY_DESCENDING_STAIRS) - { -#ifdef DEBUG_DIAGNOSTICS - mpr("Taking stairs, don't mesmerise.", MSGCH_DIAGNOSTICS); -#endif - break; - } - } - - // Won't sing if either of you silenced, or it's friendly, - // confused, fleeing, or leaving the level. - if (monster->has_ench(ENCH_CONFUSION) - || mons_is_fleeing(monster) - || mons_is_pacified(monster) - || mons_friendly(monster) - || !player_can_hear(monster->pos())) - { - break; - } - - // Don't even try on berserkers. Mermaids know their limits. - if (you.duration[DUR_BERSERKER]) - break; - - // Reduce probability because of spamminess. - if (you.species == SP_MERFOLK && !one_chance_in(4)) - break; - - // A wounded invisible mermaid is less likely to give away her position. - if (monster->invisible() - && monster->hit_points <= monster->max_hit_points / 2 - && !one_chance_in(3)) - { - break; - } - - bool already_mesmerised = player_mesmerised_by(monster); - - if (one_chance_in(5) - || monster->foe == MHITYOU && !already_mesmerised && coinflip()) - { - noisy(12, monster->pos(), monster->mindex(), true); - - bool did_resist = false; - if (you.can_see(monster)) - { - simple_monster_message(monster, - make_stringf(" chants %s song.", - already_mesmerised ? "her luring" : "a haunting").c_str(), - spl); - - if (monster->type == MONS_SIREN) - { - if (_siren_movement_effect(monster)) - { - canned_msg(MSG_YOU_RESIST); // flavour only - did_resist = true; - } - } - } - else - { - // If you're already mesmerised by an invisible mermaid she - // can still prolong the enchantment; otherwise you "resist". - if (already_mesmerised) - mpr("You hear a luring song.", MSGCH_SOUND); - else - { - if (one_chance_in(4)) // reduce spamminess - { - if (coinflip()) - mpr("You hear a haunting song.", MSGCH_SOUND); - else - mpr("You hear an eerie melody.", MSGCH_SOUND); - - canned_msg(MSG_YOU_RESIST); // flavour only - } - break; - } - } - - // Once mesmerised by a particular monster, you cannot resist - // anymore. - if (!already_mesmerised - && (you.species == SP_MERFOLK || you_resist_magic(100))) - { - if (!did_resist) - canned_msg(MSG_YOU_RESIST); - break; - } - - if (!you.duration[DUR_MESMERISED]) - { - you.duration[DUR_MESMERISED] = 7; - you.mesmerised_by.push_back(monster_index(monster)); - mprf(MSGCH_WARN, "You are mesmerised by %s!", - monster->name(DESC_NOCAP_THE).c_str()); - } - else - { - you.duration[DUR_MESMERISED] += 5; - if (!already_mesmerised) - you.mesmerised_by.push_back(monster_index(monster)); - } - used = true; - - if (you.duration[DUR_MESMERISED] > 12) - you.duration[DUR_MESMERISED] = 12; - } - break; - } - - default: - break; - } - - if (used) - monster->lose_energy(EUT_SPECIAL); - - return (used); -} - -//--------------------------------------------------------------- -// -// _handle_potion -// -// Give the monster a chance to quaff a potion. Returns true if -// the monster imbibed. -// -//--------------------------------------------------------------- -static bool _handle_potion(monsters *monster, bolt & beem) -{ - if (monster->asleep() - || monster->inv[MSLOT_POTION] == NON_ITEM - || !one_chance_in(3)) - { - return (false); - } - - bool rc = false; - - const int potion_idx = monster->inv[MSLOT_POTION]; - item_def& potion = mitm[potion_idx]; - const potion_type ptype = static_cast<potion_type>(potion.sub_type); - - if (monster->can_drink_potion(ptype) && monster->should_drink_potion(ptype)) - { - const bool was_visible = you.can_see(monster); - - // Drink the potion. - const item_type_id_state_type id = monster->drink_potion_effect(ptype); - - // Give ID if necessary. - if (was_visible && id != ID_UNKNOWN_TYPE) - set_ident_type(OBJ_POTIONS, ptype, id); - - // Remove it from inventory. - if (dec_mitm_item_quantity(potion_idx, 1)) - monster->inv[MSLOT_POTION] = NON_ITEM; - else if (is_blood_potion(potion)) - remove_oldest_blood_potion(potion); - - monster->lose_energy(EUT_ITEM); - rc = true; - } - - return (rc); -} - -static bool _handle_reaching(monsters *monster) -{ - bool ret = false; - const int wpn = monster->inv[MSLOT_WEAPON]; - - if (monster->submerged()) - return (false); - - if (mons_aligned(monster_index(monster), monster->foe)) - return (false); - - if (wpn != NON_ITEM && get_weapon_brand(mitm[wpn]) == SPWPN_REACHING) - { - if (monster->foe == MHITYOU) - { - const coord_def delta = monster->pos() - you.pos(); - const int x_middle = std::max(monster->pos().x, you.pos().x) - - (abs(delta.x) / 2); - const int y_middle = std::max(monster->pos().y, you.pos().y) - - (abs(delta.y) / 2); - const coord_def middle(x_middle, y_middle); - - // This check isn't redundant -- player may be invisible. - if (monster->target == you.pos() - && grid_distance(monster->pos(), you.pos()) == 2 - && (see_cell_no_trans(monster->pos()) - || grd(middle) > DNGN_MAX_NONREACH)) - { - ret = true; - monster_attack(monster, false); - } - } - else if (monster->foe != MHITNOT) - { - monsters& mfoe = menv[monster->foe]; - coord_def foepos = mfoe.pos(); - // Same comments as to invisibility as above. - if (monster->target == foepos - && monster->mon_see_cell(foepos, true) - && grid_distance(monster->pos(), foepos) == 2) - { - ret = true; - monsters_fight(monster, &mfoe, false); - } - } - } - - // Player saw the item reach. - if (ret && !is_artefact(mitm[wpn]) && you.can_see(monster)) - set_ident_flags(mitm[wpn], ISFLAG_KNOW_TYPE); - - return (ret); -} - -//--------------------------------------------------------------- -// -// handle_scroll -// -// Give the monster a chance to read a scroll. Returns true if -// the monster read something. -// -//--------------------------------------------------------------- -static bool _handle_scroll(monsters *monster) -{ - // Yes, there is a logic to this ordering {dlb}: - if (monster->asleep() - || mons_is_confused(monster) - || monster->submerged() - || monster->inv[MSLOT_SCROLL] == NON_ITEM - || !one_chance_in(3)) - { - return (false); - } - - // Make sure the item actually is a scroll. - if (mitm[monster->inv[MSLOT_SCROLL]].base_type != OBJ_SCROLLS) - return (false); - - bool read = false; - item_type_id_state_type ident = ID_UNKNOWN_TYPE; - bool was_visible = you.can_see(monster); - - // Notice how few cases are actually accounted for here {dlb}: - const int scroll_type = mitm[monster->inv[MSLOT_SCROLL]].sub_type; - switch (scroll_type) - { - case SCR_TELEPORTATION: - if (!monster->has_ench(ENCH_TP)) - { - if (mons_is_caught(monster) || mons_is_fleeing(monster) - || mons_is_pacified(monster)) - { - simple_monster_message(monster, " reads a scroll."); - monster_teleport(monster, false); - read = true; - ident = ID_KNOWN_TYPE; - } - } - break; - - case SCR_BLINKING: - if (mons_is_caught(monster) || mons_is_fleeing(monster) - || mons_is_pacified(monster)) - { - if (mons_near(monster)) - { - simple_monster_message(monster, " reads a scroll."); - monster_blink(monster); - read = true; - ident = ID_KNOWN_TYPE; - } - } - break; - - case SCR_SUMMONING: - if (mons_near(monster)) - { - simple_monster_message(monster, " reads a scroll."); - const int mon = create_monster( - mgen_data(MONS_ABOMINATION_SMALL, SAME_ATTITUDE(monster), - 0, 0, monster->pos(), monster->foe, MG_FORCE_BEH)); - - read = true; - if (mon != -1) - { - if (you.can_see(&menv[mon])) - { - mprf("%s appears!", menv[mon].name(DESC_CAP_A).c_str()); - ident = ID_KNOWN_TYPE; - } - player_angers_monster(&menv[mon]); - } - else if (you.can_see(monster)) - canned_msg(MSG_NOTHING_HAPPENS); - } - break; - } - - if (read) - { - if (dec_mitm_item_quantity(monster->inv[MSLOT_SCROLL], 1)) - monster->inv[MSLOT_SCROLL] = NON_ITEM; - - if (ident != ID_UNKNOWN_TYPE && was_visible) - set_ident_type(OBJ_SCROLLS, scroll_type, ident); - - monster->lose_energy(EUT_ITEM); - } - - return read; -} - -//--------------------------------------------------------------- -// -// handle_wand -// -// Give the monster a chance to zap a wand. Returns true if the -// monster zapped. -// -//--------------------------------------------------------------- -static bool _handle_wand(monsters *monster, bolt &beem) -{ - // Yes, there is a logic to this ordering {dlb}: - if (!mons_near(monster) - || monster->asleep() - || monster->has_ench(ENCH_SUBMERGED) - || monster->inv[MSLOT_WAND] == NON_ITEM - || mitm[monster->inv[MSLOT_WAND]].plus <= 0 - || coinflip()) - { - return (false); - } - - bool niceWand = false; - bool zap = false; - bool was_visible = you.can_see(monster); - - item_def &wand(mitm[monster->inv[MSLOT_WAND]]); - - // map wand type to monster spell type - const spell_type mzap = _map_wand_to_mspell(wand.sub_type); - if (mzap == SPELL_NO_SPELL) - return (false); - - // set up the beam - int power = 30 + monster->hit_dice; - bolt theBeam = mons_spells(monster, mzap, power); - - beem.name = theBeam.name; - beem.beam_source = monster_index(monster); - beem.source = monster->pos(); - beem.colour = theBeam.colour; - beem.range = theBeam.range; - beem.damage = theBeam.damage; - beem.ench_power = theBeam.ench_power; - beem.hit = theBeam.hit; - beem.type = theBeam.type; - beem.flavour = theBeam.flavour; - beem.thrower = theBeam.thrower; - beem.is_beam = theBeam.is_beam; - beem.is_explosion = theBeam.is_explosion; - -#if HISCORE_WEAPON_DETAIL - beem.aux_source = - wand.name(DESC_QUALNAME, false, true, false, false); -#else - beem.aux_source = - wand.name(DESC_QUALNAME, false, true, false, false, - ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES); -#endif - - const int wand_type = wand.sub_type; - switch (wand_type) - { - case WAND_DISINTEGRATION: - // Dial down damage from wands of disintegration, since - // disintegration beams can do large amounts of damage. - beem.damage.size = beem.damage.size * 2 / 3; - break; - - case WAND_ENSLAVEMENT: - case WAND_DIGGING: - case WAND_RANDOM_EFFECTS: - // These have been deemed "too tricky" at this time {dlb}: - return (false); - - case WAND_POLYMORPH_OTHER: - // Monsters can be very trigger happy with wands, reduce this - // for polymorph. - if (!one_chance_in(5)) - return (false); - break; - - // These are wands that monsters will aim at themselves {dlb}: - case WAND_HASTING: - if (!monster->has_ench(ENCH_HASTE)) - { - beem.target = monster->pos(); - niceWand = true; - break; - } - return (false); - - case WAND_HEALING: - if (monster->hit_points <= monster->max_hit_points / 2) - { - beem.target = monster->pos(); - niceWand = true; - break; - } - return (false); - - case WAND_INVISIBILITY: - if (!monster->has_ench(ENCH_INVIS) - && !monster->has_ench(ENCH_SUBMERGED) - && (!mons_friendly(monster) || you.can_see_invisible(false))) - { - beem.target = monster->pos(); - niceWand = true; - break; - } - return (false); - - case WAND_TELEPORTATION: - if (monster->hit_points <= monster->max_hit_points / 2 - || mons_is_caught(monster)) - { - if (!monster->has_ench(ENCH_TP) - && !one_chance_in(20)) - { - beem.target = monster->pos(); - niceWand = true; - break; - } - // This break causes the wand to be tried on the player. - break; - } - return (false); - } - - // Fire tracer, if necessary. - if (!niceWand) - { - fire_tracer( monster, beem ); - - // Good idea? - zap = mons_should_fire(beem); - } - - if (niceWand || zap) - { - if (!niceWand) - _make_mons_stop_fleeing(monster); - - if (!simple_monster_message(monster, " zaps a wand.")) - { - if (!silenced(you.pos())) - mpr("You hear a zap.", MSGCH_SOUND); - } - - // charge expenditure {dlb} - wand.plus--; - beem.is_tracer = false; - beem.fire(); - - if (was_visible) - { - if (niceWand || !beem.is_enchantment() || beem.obvious_effect) - set_ident_type(OBJ_WANDS, wand_type, ID_KNOWN_TYPE); - else - set_ident_type(OBJ_WANDS, wand_type, ID_MON_TRIED_TYPE); - - // Increment zap count. - if (wand.plus2 >= 0) - wand.plus2++; - } - - monster->lose_energy(EUT_ITEM); - - return (true); - } - - return (false); -} - -// Returns a suitable breath weapon for the draconian; does not handle all -// draconians, does fire a tracer. -static spell_type _get_draconian_breath_spell( monsters *monster ) -{ - spell_type draco_breath = SPELL_NO_SPELL; - - if (mons_genus( monster->type ) == MONS_DRACONIAN) - { - switch (draco_subspecies( monster )) - { - case MONS_DRACONIAN: - case MONS_YELLOW_DRACONIAN: // already handled as ability - break; - default: - draco_breath = SPELL_DRACONIAN_BREATH; - break; - } - } - - - if (draco_breath != SPELL_NO_SPELL) - { - // [ds] Check line-of-fire here. It won't happen elsewhere. - bolt beem; - setup_mons_cast(monster, beem, draco_breath); - - fire_tracer(monster, beem); - - if (!mons_should_fire(beem)) - draco_breath = SPELL_NO_SPELL; - } - - return (draco_breath); -} - -static bool _is_emergency_spell(const monster_spells &msp, int spell) -{ - // If the emergency spell appears early, it's probably not a dedicated - // escape spell. - for (int i = 0; i < 5; ++i) - if (msp[i] == spell) - return (false); - - return (msp[5] == spell); -} - -static bool _mon_has_spells(monsters *monster) -{ - for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) - if (monster->spells[i] != SPELL_NO_SPELL) - return (true); - - return (false); -} - -//--------------------------------------------------------------- -// -// handle_spell -// -// Give the monster a chance to cast a spell. Returns true if -// a spell was cast. -// -//--------------------------------------------------------------- -static bool _handle_spell(monsters *monster, bolt &beem) -{ - bool monsterNearby = mons_near(monster); - bool finalAnswer = false; // as in: "Is that your...?" {dlb} - const spell_type draco_breath = _get_draconian_breath_spell(monster); - - // A polymorphed unique will retain his or her spells even in another - // form. If the new form has the SPELLCASTER flag, casting happens as - // normally, otherwise we need to enforce it, but it only happens with - // a 50% chance. - const bool spellcasting_poly - = !mons_class_flag(monster->type, M_SPELLCASTER) - && mons_class_flag(monster->type, M_SPEAKS) - && _mon_has_spells(monster); - - if (is_sanctuary(monster->pos()) && !mons_wont_attack(monster)) - return (false); - - // Yes, there is a logic to this ordering {dlb}: - if (monster->asleep() - || monster->submerged() - || !mons_class_flag(monster->type, M_SPELLCASTER) - && !spellcasting_poly - && draco_breath == SPELL_NO_SPELL) - { - return (false); - } - - // If the monster's a priest, assume summons come from priestly - // abilities, in which case they'll have the same god. If the - // monster is neither a priest nor a wizard, assume summons come - // from intrinsic abilities, in which case they'll also have the - // same god. - const bool priest = mons_class_flag(monster->type, M_PRIEST); - const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS); - god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD; - - if (silenced(monster->pos()) - && (priest || wizard || spellcasting_poly - || mons_class_flag(monster->type, M_SPELL_NO_SILENT))) - { - return (false); - } - - // Shapeshifters don't get spells. - if (mons_is_shapeshifter(monster) && (priest || wizard)) - return (false); - else if (monster->has_ench(ENCH_CONFUSION) - && !mons_class_flag(monster->type, M_CONFUSED)) - { - return (false); - } - else if (monster->type == MONS_PANDEMONIUM_DEMON - && !monster->ghost->spellcaster) - { - return (false); - } - else if (random2(200) > monster->hit_dice + 50 - || monster->type == MONS_BALL_LIGHTNING && coinflip()) - { - return (false); - } - else if (spellcasting_poly && coinflip()) // 50% chance of not casting - return (false); - else - { - spell_type spell_cast = SPELL_NO_SPELL; - monster_spells hspell_pass(monster->spells); - - // 1KB: the following code is never used for unfriendlies! - if (!mon_enemies_around(monster)) - { - // Force the casting of dig when the player is not visible - - // this is EVIL! - if (monster->has_spell(SPELL_DIG) - && mons_is_seeking(monster)) - { - spell_cast = SPELL_DIG; - finalAnswer = true; - } - else if ((monster->has_spell(SPELL_MINOR_HEALING) - || monster->has_spell(SPELL_MAJOR_HEALING)) - && monster->hit_points < monster->max_hit_points) - { - // The player's out of sight! - // Quick, let's take a turn to heal ourselves. -- bwr - spell_cast = monster->has_spell(SPELL_MAJOR_HEALING) ? - SPELL_MAJOR_HEALING : SPELL_MINOR_HEALING; - finalAnswer = true; - } - else if (mons_is_fleeing(monster) || mons_is_pacified(monster)) - { - // Since the player isn't around, we'll extend the monster's - // normal choices to include the self-enchant slot. - int foundcount = 0; - for (int i = NUM_MONSTER_SPELL_SLOTS - 1; i >= 0; --i) - { - if (ms_useful_fleeing_out_of_sight(monster, hspell_pass[i]) - && one_chance_in(++foundcount)) - { - spell_cast = hspell_pass[i]; - finalAnswer = true; - } - } - } - else if (monster->foe == MHITYOU && !monsterNearby) - return (false); - } - - // Monsters caught in a net try to get away. - // This is only urgent if enemies are around. - if (!finalAnswer && mon_enemies_around(monster) - && mons_is_caught(monster) && one_chance_in(4)) - { - for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) - { - if (ms_quick_get_away(monster, hspell_pass[i])) - { - spell_cast = hspell_pass[i]; - finalAnswer = true; - break; - } - } - } - - // Promote the casting of useful spells for low-HP monsters. - if (!finalAnswer - && monster->hit_points < monster->max_hit_points / 4 - && !one_chance_in(4)) - { - // Note: There should always be at least some chance we don't - // get here... even if the monster is on its last HP. That - // way we don't have to worry about monsters infinitely casting - // Healing on themselves (e.g. orc high priests). - if ((mons_is_fleeing(monster) || mons_is_pacified(monster)) - && ms_low_hitpoint_cast(monster, hspell_pass[5])) - { - spell_cast = hspell_pass[5]; - finalAnswer = true; - } - - if (!finalAnswer) - { - int found_spell = 0; - for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) - { - if (ms_low_hitpoint_cast(monster, hspell_pass[i]) - && one_chance_in(++found_spell)) - { - spell_cast = hspell_pass[i]; - finalAnswer = true; - } - } - } - } - - if (!finalAnswer) - { - // If nothing found by now, safe friendlies and good - // neutrals will rarely cast. - if (mons_wont_attack(monster) && !mon_enemies_around(monster) - && !one_chance_in(10)) - { - return (false); - } - - // Remove healing/invis/haste if we don't need them. - int num_no_spell = 0; - - for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) - { - if (hspell_pass[i] == SPELL_NO_SPELL) - num_no_spell++; - else if (ms_waste_of_time(monster, hspell_pass[i]) - || hspell_pass[i] == SPELL_DIG) - { - // Should monster not have selected dig by now, - // it never will. - hspell_pass[i] = SPELL_NO_SPELL; - num_no_spell++; - } - } - - // If no useful spells... cast no spell. - if (num_no_spell == NUM_MONSTER_SPELL_SLOTS - && draco_breath == SPELL_NO_SPELL) - { - return (false); - } - - const bolt orig_beem = beem; - // Up to four tries to pick a spell. - for (int loopy = 0; loopy < 4; ++loopy) - { - beem = orig_beem; - - bool spellOK = false; - - // Setup spell - monsters that are fleeing or pacified - // and leaving the level will always try to choose their - // emergency spell. - if (mons_is_fleeing(monster) || mons_is_pacified(monster)) - { - spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL - : hspell_pass[5]); - - // Pacified monsters leaving the level won't choose - // emergency spells harmful to the area. - if (spell_cast != SPELL_NO_SPELL - && mons_is_pacified(monster) - && spell_harms_area(spell_cast)) - { - spell_cast = SPELL_NO_SPELL; - } - } - else - { - // Randomly picking one of the non-emergency spells: - spell_cast = hspell_pass[random2(5)]; - } - - if (spell_cast == SPELL_NO_SPELL) - continue; - - // Setup the spell. - setup_mons_cast(monster, beem, spell_cast); - - // beam-type spells requiring tracers - if (spell_needs_tracer(spell_cast)) - { - const bool explode = - spell_is_direct_explosion(spell_cast); - fire_tracer(monster, beem, explode); - // Good idea? - if (mons_should_fire(beem)) - spellOK = true; - } - else - { - // All direct-effect/summoning/self-enchantments/etc. - spellOK = true; - - if (ms_direct_nasty(spell_cast) - && mons_aligned(monster_index(monster), - monster->foe)) - { - spellOK = false; - } - else if (monster->foe == MHITYOU || monster->foe == MHITNOT) - { - // XXX: Note the crude hack so that monsters can - // use ME_ALERT to target (we should really have - // a measure of time instead of peeking to see - // if the player is still there). -- bwr - if (!you.visible_to(monster) - && (monster->target != you.pos() || coinflip())) - { - spellOK = false; - } - } - else if (!monster->can_see(&menv[monster->foe])) - { - spellOK = false; - } - else if (monster->type == MONS_DAEVA - && monster->god == GOD_SHINING_ONE) - { - const monsters *mon = &menv[monster->foe]; - - // Don't allow TSO-worshipping daevas to make - // unchivalric magic attacks, except against - // appropriate monsters. - if (is_unchivalric_attack(monster, mon) - && !tso_unchivalric_attack_safe_monster(mon)) - { - spellOK = false; - } - } - } - - // If not okay, then maybe we'll cast a defensive spell. - if (!spellOK) - { - spell_cast = (coinflip() ? hspell_pass[2] - : SPELL_NO_SPELL); - } - - if (spell_cast != SPELL_NO_SPELL) - break; - } - } - - // If there's otherwise no ranged attack use the breath weapon. - // The breath weapon is also occasionally used. - if (draco_breath != SPELL_NO_SPELL - && (spell_cast == SPELL_NO_SPELL - || !_is_emergency_spell(hspell_pass, spell_cast) - && one_chance_in(4)) - && !_is_player_or_mon_sanct(monster)) - { - spell_cast = draco_breath; - finalAnswer = true; - } - - // Should the monster *still* not have a spell, well, too bad {dlb}: - if (spell_cast == SPELL_NO_SPELL) - return (false); - - // Friendly monsters don't use polymorph other, for fear of harming - // the player. - if (spell_cast == SPELL_POLYMORPH_OTHER && mons_friendly(monster)) - return (false); - - // Try to animate dead: if nothing rises, pretend we didn't cast it. - if (spell_cast == SPELL_ANIMATE_DEAD - && !animate_dead(monster, 100, SAME_ATTITUDE(monster), - monster->foe, god, false)) - { - return (false); - } - - if (monster->type == MONS_BALL_LIGHTNING) - monster->hit_points = -1; - - // FINALLY! determine primary spell effects {dlb}: - if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK) - { - // Why only cast blink if nearby? {dlb} - if (monsterNearby) - { - mons_cast_noise(monster, beem, spell_cast); - monster_blink(monster); - - monster->lose_energy(EUT_SPELL); - } - else - return (false); - } - else - { - if (spell_needs_foe(spell_cast)) - _make_mons_stop_fleeing(monster); - - mons_cast(monster, beem, spell_cast); - mmov.reset(); - monster->lose_energy(EUT_SPELL); - } - } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ... - - return (true); -} - // Returns a rough estimate of damage from throwing the wielded weapon. int mons_thrown_weapon_damage(const item_def *weap) { @@ -7244,135 +3187,6 @@ int mons_pick_best_missile(monsters *mons, item_def **launcher, } } -//--------------------------------------------------------------- -// -// handle_throw -// -// Give the monster a chance to throw something. Returns true if -// the monster hurled. -// -//--------------------------------------------------------------- -static bool _handle_throw(monsters *monster, bolt & beem) -{ - // Yes, there is a logic to this ordering {dlb}: - if (monster->incapacitated() - || monster->asleep() - || monster->submerged()) - { - return (false); - } - - if (mons_itemuse(monster) < MONUSE_STARTING_EQUIPMENT) - return (false); - - const bool archer = mons_class_flag(monster->type, M_ARCHER); - // Highly-specialised archers are more likely to shoot than talk. - if (one_chance_in(archer? 9 : 5)) - return (false); - - // Don't allow offscreen throwing for now. - if (monster->foe == MHITYOU && !mons_near(monster)) - return (false); - - // Monsters won't shoot in melee range, largely for balance reasons. - // Specialist archers are an exception to this rule. - if (!archer && adjacent(beem.target, monster->pos())) - return (false); - - // Greatly lowered chances if the monster is fleeing or pacified and - // leaving the level. - if ((mons_is_fleeing(monster) || mons_is_pacified(monster)) - && !one_chance_in(8)) - { - return (false); - } - - item_def *launcher = NULL; - const item_def *weapon = NULL; - const int mon_item = mons_pick_best_missile(monster, &launcher); - - if (mon_item == NON_ITEM || !is_valid_item(mitm[mon_item])) - return (false); - - if (_is_player_or_mon_sanct(monster)) - return (false); - - item_def *missile = &mitm[mon_item]; - - // Throwing a net at a target that is already caught would be - // completely useless, so bail out. - const actor *act = actor_at(beem.target); - if (missile->base_type == OBJ_MISSILES - && missile->sub_type == MI_THROWING_NET - && act && act->caught()) - { - return (false); - } - - // If the attack needs a launcher that we can't wield, bail out. - if (launcher) - { - weapon = monster->mslot_item(MSLOT_WEAPON); - if (weapon && weapon != launcher && weapon->cursed()) - return (false); - } - - // Ok, we'll try it. - setup_generic_throw( monster, beem ); - - // Set fake damage for the tracer. - beem.damage = dice_def(10, 10); - - // Set item for tracer, even though it probably won't be used - beem.item = missile; - - // Fire tracer. - fire_tracer( monster, beem ); - - // Clear fake damage (will be set correctly in mons_throw). - beem.damage = 0; - - // Good idea? - if (mons_should_fire( beem )) - { - // Monsters shouldn't shoot if fleeing, so let them "turn to attack". - _make_mons_stop_fleeing(monster); - - if (launcher && launcher != weapon) - monster->swap_weapons(); - - beem.name.clear(); - return (mons_throw( monster, beem, mon_item )); - } - - return (false); -} - -static bool _handle_monster_spell(monsters *monster, bolt &beem) -{ - // Shapeshifters don't get spells. - if (!mons_is_shapeshifter(monster) - || !mons_class_flag(monster->type, M_ACTUAL_SPELLS)) - { - if (_handle_spell(monster, beem)) - return (true); - } - - return (false); -} - -// Give the monster its action energy (aka speed_increment). -static void _monster_add_energy(monsters *monster) -{ - if (monster->speed > 0) - { - // Randomise to make counting off monster moves harder: - const int energy_gained = - std::max(1, div_rand_round(monster->speed * you.time_taken, 10)); - monster->speed_increment += energy_gained; - } -} - int mons_natural_regen_rate(monsters *monster) { // A HD divider ranging from 3 (at 1 HD) to 1 (at 8 HD). @@ -7398,1165 +3212,6 @@ int mons_natural_regen_rate(monsters *monster) return (std::max(div_rand_round(monster->hit_dice, divider), 1)); } -static inline bool _mons_natural_regen_roll(monsters *monster) -{ - const int regen_rate = mons_natural_regen_rate(monster); - return (x_chance_in_y(regen_rate, 25)); -} - -// Do natural regeneration for monster. -static void _monster_regenerate(monsters *monster) -{ - if (monster->has_ench(ENCH_SICK) || !mons_can_regenerate(monster)) - return; - - // Non-land creatures out of their element cannot regenerate. - if (mons_primary_habitat(monster) != HT_LAND - && !monster_habitable_grid(monster, grd(monster->pos()))) - { - return; - } - - if (monster_descriptor(monster->type, MDSC_REGENERATES) - || (monster->type == MONS_FIRE_ELEMENTAL - && (grd(monster->pos()) == DNGN_LAVA - || cloud_type_at(monster->pos()) == CLOUD_FIRE)) - - || (monster->type == MONS_WATER_ELEMENTAL - && feat_is_watery(grd(monster->pos()))) - - || (monster->type == MONS_AIR_ELEMENTAL - && env.cgrid(monster->pos()) == EMPTY_CLOUD - && one_chance_in(3)) - - || _mons_natural_regen_roll(monster)) - { - heal_monster(monster, 1, false); - } -} - -static bool _swap_monsters(monsters* mover, monsters* moved) -{ - // Can't swap with a stationary monster. - if (mons_is_stationary(moved)) - return (false); - - // Swapping is a purposeful action. - if (mover->confused()) - return (false); - - // Right now just happens in sanctuary. - if (!is_sanctuary(mover->pos()) || !is_sanctuary(moved->pos())) - return (false); - - // A friendly or good-neutral monster moving past a fleeing hostile - // or neutral monster, or vice versa. - if (mons_wont_attack_real(mover) == mons_wont_attack_real(moved) - || mons_is_fleeing(mover) == mons_is_fleeing(moved)) - { - return (false); - } - - // Don't swap places if the player explicitly ordered their pet to - // attack monsters. - if ((mons_friendly(mover) || mons_friendly(moved)) - && you.pet_target != MHITYOU && you.pet_target != MHITNOT) - { - return (false); - } - - if (!mover->can_pass_through(moved->pos()) - || !moved->can_pass_through(mover->pos())) - { - return (false); - } - - if (!monster_habitable_grid(mover, grd(moved->pos())) - || !monster_habitable_grid(moved, grd(mover->pos()))) - { - return (false); - } - - // Okay, we can do the swap. - const coord_def mover_pos = mover->pos(); - const coord_def moved_pos = moved->pos(); - - mover->pos() = moved_pos; - moved->pos() = mover_pos; - - mgrd(mover->pos()) = mover->mindex(); - mgrd(moved->pos()) = moved->mindex(); - - if (you.can_see(mover) && you.can_see(moved)) - { - mprf("%s and %s swap places.", mover->name(DESC_CAP_THE).c_str(), - moved->name(DESC_NOCAP_THE).c_str()); - } - - return (true); -} - -static void _swim_or_move_energy(monsters *mon) -{ - const dungeon_feature_type feat = grd(mon->pos()); - - // FIXME: Replace check with mons_is_swimming()? - mon->lose_energy( (feat >= DNGN_LAVA && feat <= DNGN_SHALLOW_WATER - && !mon->airborne()) ? EUT_SWIM - : EUT_MOVE ); -} - -static void _khufu_drop_tomb(monsters *monster) -{ - int count = 0; - - monster->behaviour = BEH_SEEK; // don't wander on duty! - for (adjacent_iterator ai(monster->pos()); ai; ++ai) - { - if (grd(*ai) == DNGN_ROCK_WALL) - { - grd(*ai) = DNGN_FLOOR; - count++; - } - } - if (count) - if (mons_near(monster)) - mpr("The walls disappear!"); - else - mpr("You hear a deep rumble."); - monster->number = 0; - monster->lose_energy(EUT_SPELL); -} - -#ifdef DEBUG -# define DEBUG_ENERGY_USE(problem) \ - if (monster->speed_increment == old_energy && monster->alive()) \ - mprf(MSGCH_DIAGNOSTICS, \ - problem " for monster '%s' consumed no energy", \ - monster->name(DESC_PLAIN).c_str(), true); -#else -# define DEBUG_ENERGY_USE(problem) ((void) 0) -#endif - -static void _handle_monster_move(monsters *monster) -{ - monster->hit_points = std::min(monster->max_hit_points, - monster->hit_points); - - // Monster just summoned (or just took stairs), skip this action. - if (testbits( monster->flags, MF_JUST_SUMMONED )) - { - monster->flags &= ~MF_JUST_SUMMONED; - return; - } - - mon_acting mact(monster); - - _monster_add_energy(monster); - - // Handle clouds on nonmoving monsters. - if (monster->speed == 0 - && env.cgrid(monster->pos()) != EMPTY_CLOUD - && !monster->submerged()) - { - _mons_in_cloud( monster ); - } - - // Apply monster enchantments once for every normal-speed - // player turn. - monster->ench_countdown -= you.time_taken; - while (monster->ench_countdown < 0) - { - monster->ench_countdown += 10; - monster->apply_enchantments(); - - // If the monster *merely* died just break from the loop - // rather than quit altogether, since we have to deal with - // giant spores and ball lightning exploding at the end of the - // function, but do return if the monster's data has been - // reset, since then the monster type is invalid. - if (monster->type == MONS_NO_MONSTER) - return; - else if (monster->hit_points < 1) - break; - } - - // Memory is decremented here for a reason -- we only want it - // decrementing once per monster "move". - if (monster->foe_memory > 0) - monster->foe_memory--; - - // Otherwise there are potential problems with summonings. - if (monster->type == MONS_GLOWING_SHAPESHIFTER) - monster->add_ench(ENCH_GLOWING_SHAPESHIFTER); - - if (monster->type == MONS_SHAPESHIFTER) - monster->add_ench(ENCH_SHAPESHIFTER); - - // We reset batty monsters from wander to seek here, instead - // of in handle_behaviour() since that will be called with - // every single movement, and we want these monsters to - // hit and run. -- bwr - if (monster->foe != MHITNOT && mons_is_wandering(monster) - && mons_is_batty(monster)) - { - monster->behaviour = BEH_SEEK; - } - - monster->check_speed(); - - monsterentry* entry = get_monster_data(monster->type); - if (!entry) - return; - - int old_energy = INT_MAX; - int non_move_energy = std::min(entry->energy_usage.move, - entry->energy_usage.swim); - -#if DEBUG_MONS_SCAN - bool monster_was_floating = mgrd(monster->pos()) != monster->mindex(); -#endif - - while (monster->has_action_energy()) - { - // The continues & breaks are WRT this. - if (!monster->alive()) - break; - - const coord_def old_pos = monster->pos(); - -#if DEBUG_MONS_SCAN - if (!monster_was_floating - && mgrd(monster->pos()) != monster->mindex()) - { - mprf(MSGCH_ERROR, "Monster %s became detached from mgrd " - "in _handle_monster_move() loop", - monster->name(DESC_PLAIN, true).c_str()); - mpr("[[[[[[[[[[[[[[[[[[", MSGCH_WARN); - debug_mons_scan(); - mpr("]]]]]]]]]]]]]]]]]]", MSGCH_WARN); - monster_was_floating = true; - } - else if (monster_was_floating - && mgrd(monster->pos()) == monster->mindex()) - { - mprf(MSGCH_DIAGNOSTICS, "Monster %s re-attached itself to mgrd " - "in _handle_monster_move() loop", - monster->name(DESC_PLAIN, true).c_str()); - monster_was_floating = false; - } -#endif - - if (monster->speed_increment >= old_energy) - { -#ifdef DEBUG - if (monster->speed_increment == old_energy) - { - mprf(MSGCH_DIAGNOSTICS, "'%s' has same energy as last loop", - monster->name(DESC_PLAIN, true).c_str()); - } - else - { - mprf(MSGCH_DIAGNOSTICS, "'%s' has MORE energy than last loop", - monster->name(DESC_PLAIN, true).c_str()); - } -#endif - monster->speed_increment = old_energy - 10; - old_energy = monster->speed_increment; - continue; - } - old_energy = monster->speed_increment; - - monster->shield_blocks = 0; - - cloud_type cl_type; - const int cloud_num = env.cgrid(monster->pos()); - const bool avoid_cloud = mons_avoids_cloud(monster, cloud_num, - &cl_type); - if (cl_type != CLOUD_NONE) - { - if (avoid_cloud) - { - if (monster->submerged()) - { - monster->speed_increment -= entry->energy_usage.swim; - break; - } - - if (monster->type == MONS_NO_MONSTER) - { - monster->speed_increment -= entry->energy_usage.move; - break; // problem with vortices - } - } - - _mons_in_cloud(monster); - - if (monster->type == MONS_NO_MONSTER) - { - monster->speed_increment = 1; - break; - } - } - - if (monster->type == MONS_TIAMAT && one_chance_in(3)) - { - const int cols[] = { RED, WHITE, DARKGREY, GREEN, MAGENTA }; - monster->colour = RANDOM_ELEMENT(cols); - } - - _monster_regenerate(monster); - - if (mons_cannot_act(monster)) - { - monster->speed_increment -= non_move_energy; - continue; - } - - _handle_behaviour(monster); - - // _handle_behaviour() could make the monster leave the level. - if (!monster->alive()) - break; - - ASSERT(!crawl_state.arena || monster->foe != MHITYOU); - ASSERT(in_bounds(monster->target) || monster->target.origin()); - - // Submerging monsters will hide from clouds. - if (avoid_cloud - && monster_can_submerge(monster, grd(monster->pos())) - && !monster->caught() - && !monster->submerged()) - { - monster->add_ench(ENCH_SUBMERGED); - monster->speed_increment -= ENERGY_SUBMERGE(entry); - continue; - } - - if (monster->speed >= 100) - { - monster->speed_increment -= non_move_energy; - continue; - } - - if (igrd(monster->pos()) != NON_ITEM - && (mons_itemuse(monster) >= MONUSE_WEAPONS_ARMOUR - || mons_itemeat(monster) != MONEAT_NOTHING)) - { - // Keep neutral and charmed monsters from picking up stuff. - // Same for friendlies if friendly_pickup is set to "none". - if (!mons_neutral(monster) && !monster->has_ench(ENCH_CHARM) - || (you.religion == GOD_JIYVA && mons_is_slime(monster)) - && (!mons_friendly(monster) - || you.friendly_pickup != FRIENDLY_PICKUP_NONE)) - { - if (_handle_pickup(monster)) - { - DEBUG_ENERGY_USE("handle_pickup()"); - continue; - } - } - } - - // Lurking monsters only stop lurking if their target is right - // next to them, otherwise they just sit there. - // However, if the monster is involuntarily submerged but - // still alive (e.g., nonbreathing which had water poured - // on top of it), this doesn't apply. - if (mons_is_lurking(monster) || monster->has_ench(ENCH_SUBMERGED)) - { - if (monster->foe != MHITNOT - && grid_distance(monster->target, monster->pos()) <= 1) - { - if (monster->submerged()) - { - // Don't unsubmerge if the monster is too damaged or - // if the monster is afraid, or if it's avoiding the - // cloud on top of the water. - if (monster->hit_points <= monster->max_hit_points / 2 - || monster->has_ench(ENCH_FEAR) - || avoid_cloud) - { - monster->speed_increment -= non_move_energy; - continue; - } - - if (!monster->del_ench(ENCH_SUBMERGED)) - { - // Couldn't unsubmerge. - monster->speed_increment -= non_move_energy; - continue; - } - } - monster->behaviour = BEH_SEEK; - } - else - { - monster->speed_increment -= non_move_energy; - continue; - } - } - - if (mons_is_caught(monster)) - { - // Struggling against the net takes time. - _swim_or_move_energy(monster); - } - else if (!mons_is_petrified(monster)) - { - // Calculates mmov based on monster target. - _handle_movement(monster); - - if (mons_is_confused(monster) - || monster->type == MONS_AIR_ELEMENTAL - && monster->submerged()) - { - mmov.reset(); - int pfound = 0; - for (adjacent_iterator ai(monster->pos(), false); ai; ++ai) - if (monster->can_pass_through(*ai)) - if (one_chance_in(++pfound)) - mmov = *ai - monster->pos(); - - // OK, mmov determined. - const coord_def newcell = mmov + monster->pos(); - monsters* enemy = monster_at(newcell); - if (enemy - && newcell != monster->pos() - && !is_sanctuary(monster->pos())) - { - if (monsters_fight(monster, enemy)) - { - mmov.reset(); - DEBUG_ENERGY_USE("monsters_fight()"); - continue; - } - else - { - // FIXME: None of these work! - // Instead run away! - if (monster->add_ench(mon_enchant(ENCH_FEAR))) - { - behaviour_event(monster, ME_SCARE, - MHITNOT, newcell); - } - break; - } - } - } - } - _handle_nearby_ability(monster); - - if (monster->type == MONS_KHUFU && monster->number - && monster->hit_points==monster->max_hit_points) - _khufu_drop_tomb(monster); - - if (!monster->asleep() && !mons_is_wandering(monster) - // Berserking monsters are limited to running up and - // hitting their foes. - && !monster->has_ench(ENCH_BERSERK) - // Slime creatures can split while wandering or resting. - || monster->type == MONS_SLIME_CREATURE) - { - bolt beem; - - beem.source = monster->pos(); - beem.target = monster->target; - beem.beam_source = monster->mindex(); - - // Prevents unfriendlies from nuking you from offscreen. - // How nice! - const bool friendly_or_near = - mons_friendly(monster) || monster->near_foe(); - if (friendly_or_near - || monster->type == MONS_TEST_SPAWNER - // Slime creatures can split when offscreen. - || monster->type == MONS_SLIME_CREATURE) - { - // [ds] Special abilities shouldn't overwhelm - // spellcasting in monsters that have both. This aims - // to give them both roughly the same weight. - if (coinflip() ? _handle_special_ability(monster, beem) - || _handle_monster_spell(monster, beem) - : _handle_monster_spell(monster, beem) - || _handle_special_ability(monster, beem)) - { - DEBUG_ENERGY_USE("spell or special"); - continue; - } - } - - if (friendly_or_near) - { - if (_handle_potion(monster, beem)) - { - DEBUG_ENERGY_USE("_handle_potion()"); - continue; - } - - if (_handle_scroll(monster)) - { - DEBUG_ENERGY_USE("_handle_scroll()"); - continue; - } - - if (_handle_wand(monster, beem)) - { - DEBUG_ENERGY_USE("_handle_wand()"); - continue; - } - - if (_handle_reaching(monster)) - { - DEBUG_ENERGY_USE("_handle_reaching()"); - continue; - } - } - - if (_handle_throw(monster, beem)) - { - DEBUG_ENERGY_USE("_handle_throw()"); - continue; - } - } - - if (!mons_is_caught(monster)) - { - if (monster->pos() + mmov == you.pos()) - { - ASSERT(!crawl_state.arena); - - if (!mons_friendly(monster)) - { - // If it steps into you, cancel other targets. - monster->foe = MHITYOU; - monster->target = you.pos(); - - monster_attack(monster); - - if (mons_is_batty(monster)) - { - monster->behaviour = BEH_WANDER; - _set_random_target(monster); - } - DEBUG_ENERGY_USE("monster_attack()"); - mmov.reset(); - continue; - } - } - - // See if we move into (and fight) an unfriendly monster. - monsters* targ = monster_at(monster->pos() + mmov); - if (targ - && targ != monster - && !mons_aligned(monster->mindex(), targ->mindex()) - && monster_can_hit_monster(monster, targ)) - { - // Maybe they can swap places? - if (_swap_monsters(monster, targ)) - { - _swim_or_move_energy(monster); - continue; - } - // Figure out if they fight. - else if (monsters_fight(monster, targ)) - { - if (mons_is_batty(monster)) - { - monster->behaviour = BEH_WANDER; - _set_random_target(monster); - // monster->speed_increment -= monster->speed; - } - - mmov.reset(); - DEBUG_ENERGY_USE("monsters_fight()"); - continue; - } - } - - if (invalid_monster(monster) || mons_is_stationary(monster)) - { - if (monster->speed_increment == old_energy) - monster->speed_increment -= non_move_energy; - continue; - } - - if (mons_cannot_move(monster) || !_monster_move(monster)) - monster->speed_increment -= non_move_energy; - } - update_beholders(monster); - - // Reevaluate behaviour, since the monster's surroundings have - // changed (it may have moved, or died for that matter). Don't - // bother for dead monsters. :) - if (monster->alive()) - { - _handle_behaviour(monster); - ASSERT(in_bounds(monster->target) || monster->target.origin()); - } - } - - if (monster->type != MONS_NO_MONSTER && monster->hit_points < 1) - monster_die(monster, KILL_MISC, NON_MONSTER); -} - -//--------------------------------------------------------------- -// -// handle_monsters -// -// This is the routine that controls monster AI. -// -//--------------------------------------------------------------- -void handle_monsters() -{ - // Keep track of monsters that have already moved and don't allow - // them to move again. - memset(immobile_monster, 0, sizeof immobile_monster); - - for (int i = 0; i < MAX_MONSTERS; ++i) - { - monsters *monster = &menv[i]; - - if (!monster->alive() || immobile_monster[i]) - continue; - - const coord_def oldpos = monster->pos(); - - _handle_monster_move(monster); - - if (!invalid_monster(monster) && monster->pos() != oldpos) - immobile_monster[i] = true; - - // If the player got banished, discard pending monster actions. - if (you.banished) - { - // Clear list of mesmerising monsters. - if (you.duration[DUR_MESMERISED]) - { - you.mesmerised_by.clear(); - you.duration[DUR_MESMERISED] = 0; - } - break; - } - } - - // Clear any summoning flags so that lower indiced - // monsters get their actions in the next round. - for (int i = 0; i < MAX_MONSTERS; i++) - menv[i].flags &= ~MF_JUST_SUMMONED; -} - -static bool _is_item_jelly_edible(const item_def &item) -{ - // Don't eat artefacts. - if (is_artefact(item)) - return (false); - - // Shouldn't eat stone things - // - but what about wands and rings? - if (item.base_type == OBJ_MISSILES - && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK)) - { - return (false); - } - - // Don't eat special game items. - if (item.base_type == OBJ_ORBS - || (item.base_type == OBJ_MISCELLANY - && (item.sub_type == MISC_RUNE_OF_ZOT - || item.sub_type == MISC_HORN_OF_GERYON))) - { - return (false); - } - - return (true); -} - -// XXX: This function assumes that only jellies eat items. -static bool _monster_eat_item(monsters *monster, bool nearby) -{ - if (!mons_eats_items(monster)) - return (false); - - // Friendly jellies won't eat (unless worshipping Jiyva). - if (mons_friendly(monster) && you.religion != GOD_JIYVA) - return (false); - - int hps_gained = 0; - int max_eat = roll_dice(1, 10); - int eaten = 0; - bool eaten_net = false; - - for (stack_iterator si(monster->pos()); - si && eaten < max_eat && hps_gained < 50; ++si) - { - if (!_is_item_jelly_edible(*si)) - continue; - -#if DEBUG_DIAGNOSTICS || DEBUG_EATERS - mprf(MSGCH_DIAGNOSTICS, - "%s eating %s", monster->name(DESC_PLAIN, true).c_str(), - si->name(DESC_PLAIN).c_str()); -#endif - - int quant = si->quantity; - - if (si->base_type != OBJ_GOLD) - { - quant = std::min(quant, max_eat - eaten); - - hps_gained += (quant * item_mass(*si)) / 20 + quant; - eaten += quant; - - if (mons_is_caught(monster) - && si->base_type == OBJ_MISSILES - && si->sub_type == MI_THROWING_NET - && item_is_stationary(*si)) - { - monster->del_ench(ENCH_HELD, true); - eaten_net = true; - } - } - else - { - // Shouldn't be much trouble to digest a huge pile of gold! - if (quant > 500) - quant = 500 + roll_dice(2, (quant - 500) / 2); - - hps_gained += quant / 10 + 1; - eaten++; - } - - if (you.religion == GOD_JIYVA) - { - const int quantity = si->quantity; - const int value = item_value(*si) / quantity; - int pg = 0; - int timeout = 0; - - for (int m = 0; m < quantity; ++m) - { - if (x_chance_in_y(value / 2 + 1, 30 + you.piety / 4)) - { - if (timeout <= 0) - pg += random2(item_value(*si) / 6); - else - timeout -= value / 5; - } - } - - if (pg > 0) - { - simple_god_message(" appreciates your sacrifice."); - gain_piety(pg); - } - - if (you.piety > 80 && random2(you.piety) > 50 && one_chance_in(4)) - { - if (you.can_safely_mutate()) - { - simple_god_message(" alters your body."); - - bool success = false; - const int rand = random2(100); - - if (rand < 40) - success = mutate(RANDOM_MUTATION, true, false, true); - else if (rand < 60) - { - success = delete_mutation(RANDOM_MUTATION, true, false, - true); - } - else - { - success = mutate(RANDOM_GOOD_MUTATION, true, false, - true); - } - - if (success) - { - timeout = (100 + roll_dice(2, 4)); - you.num_gifts[you.religion]++; - take_note(Note(NOTE_GOD_GIFT, you.religion)); - } - else - mpr("You feel as though nothing has changed."); - } - } - } - - if (quant >= si->quantity) - item_was_destroyed(*si, monster->mindex()); - - dec_mitm_item_quantity(si.link(), quant); - } - - if (eaten > 0) - { - hps_gained = std::max(hps_gained, 1); - hps_gained = std::min(hps_gained, 50); - - // This is done manually instead of using heal_monster(), - // because that function doesn't work quite this way. -- bwr - monster->hit_points += hps_gained; - monster->max_hit_points = std::max(monster->hit_points, - monster->max_hit_points); - - if (player_can_hear(monster->pos())) - { - mprf(MSGCH_SOUND, "You hear a%s slurping noise.", - nearby ? "" : " distant"); - } - - if (eaten_net) - simple_monster_message(monster, " devours the net!"); - - _jelly_divide(monster); - } - - return (eaten > 0); -} - -static bool _monster_eat_single_corpse(monsters *monster, item_def& item, - bool do_heal, bool nearby) -{ - if (item.base_type != OBJ_CORPSES || item.sub_type != CORPSE_BODY) - return (false); - - monster_type mt = static_cast<monster_type>(item.plus); - if (do_heal) - { - monster->hit_points += 1 + random2(mons_weight(mt)) / 100; - - // Limited growth factor here - should 77 really be the cap? {dlb}: - monster->hit_points = std::min(100, monster->hit_points); - monster->max_hit_points = std::max(monster->hit_points, - monster->max_hit_points); - } - - if (nearby) - { - mprf("%s eats %s.", monster->name(DESC_CAP_THE).c_str(), - item.name(DESC_NOCAP_THE).c_str()); - } - - // Assume that eating a corpse requires butchering it. Use logic - // from misc.cc:turn_corpse_into_chunks() and the butchery-related - // delays in delay.cc:stop_delay(). - - const int max_chunks = mons_weight(mt) / 150; - - // Only fresh corpses bleed enough to colour the ground. - if (!food_is_rotten(item)) - bleed_onto_floor(monster->pos(), mt, max_chunks, true); - - if (mons_skeleton(mt) && one_chance_in(3)) - turn_corpse_into_skeleton(item); - else - destroy_item(item.index()); - - return (true); -} - -static bool _monster_eat_corpse(monsters *monster, bool do_heal, bool nearby) -{ - if (!mons_eats_corpses(monster)) - return (false); - - int eaten = 0; - - for (stack_iterator si(monster->pos()); si; ++si) - { - if (_monster_eat_single_corpse(monster, *si, do_heal, nearby)) - { - eaten++; - break; - } - } - - return (eaten > 0); -} - -static bool _monster_eat_food(monsters *monster, bool nearby) -{ - if (!mons_eats_food(monster)) - return (false); - - if (mons_is_fleeing(monster)) - return (false); - - int eaten = 0; - - for (stack_iterator si(monster->pos()); si; ++si) - { - const bool is_food = (si->base_type == OBJ_FOOD); - const bool is_corpse = (si->base_type == OBJ_CORPSES - && si->sub_type == CORPSE_BODY); - - if (!is_food && !is_corpse) - continue; - - if ((mons_wont_attack(monster) - || grid_distance(monster->pos(), you.pos()) > 1) - && coinflip()) - { - if (is_food) - { - if (nearby) - { - mprf("%s eats %s.", monster->name(DESC_CAP_THE).c_str(), - quant_name(*si, 1, DESC_NOCAP_THE).c_str()); - } - - dec_mitm_item_quantity(si.link(), 1); - - eaten++; - break; - } - else - { - // Assume that only undead can heal from eating corpses. - if (_monster_eat_single_corpse(monster, *si, - monster->holiness() == MH_UNDEAD, - nearby)) - { - eaten++; - break; - } - } - } - } - - return (eaten > 0); -} - -//--------------------------------------------------------------- -// -// handle_pickup -// -// Returns false if monster doesn't spend any time picking something up. -// -//--------------------------------------------------------------- -static bool _handle_pickup(monsters *monster) -{ - if (monster->asleep() || monster->submerged()) - return (false); - - const bool nearby = mons_near(monster); - int count_pickup = 0; - - if (mons_itemeat(monster) != MONEAT_NOTHING) - { - if (mons_eats_items(monster)) - { - if (_monster_eat_item(monster, nearby)) - return (false); - } - else if (mons_eats_corpses(monster)) - { - // Assume that only undead can heal from eating corpses. - if (_monster_eat_corpse(monster, monster->holiness() == MH_UNDEAD, - nearby)) - { - return (false); - } - } - else if (mons_eats_food(monster)) - { - if (_monster_eat_food(monster, nearby)) - return (false); - } - } - - if (mons_itemuse(monster) >= MONUSE_WEAPONS_ARMOUR) - { - // Note: Monsters only look at stuff near the top of stacks. - // - // XXX: Need to put in something so that monster picks up - // multiple items (e.g. ammunition) identical to those it's - // carrying. - // - // Monsters may now pick up up to two items in the same turn. - // (jpeg) - for (stack_iterator si(monster->pos()); si; ++si) - { - if (monster->pickup_item(*si, nearby)) - count_pickup++; - - if (count_pickup > 1 || coinflip()) - break; - } - } - - return (count_pickup > 0); -} - -static void _jelly_grows(monsters *monster) -{ - if (player_can_hear(monster->pos())) - { - mprf(MSGCH_SOUND, "You hear a%s slurping noise.", - mons_near(monster) ? "" : " distant"); - } - - monster->hit_points += 5; - - // note here, that this makes jellies "grow" {dlb}: - if (monster->hit_points > monster->max_hit_points) - monster->max_hit_points = monster->hit_points; - - _jelly_divide(monster); -} - -static bool _mons_can_displace(const monsters *mpusher, const monsters *mpushee) -{ - if (invalid_monster(mpusher) || invalid_monster(mpushee)) - return (false); - - const int ipushee = monster_index(mpushee); - if (invalid_monster_index(ipushee)) - return (false); - - if (immobile_monster[ipushee]) - return (false); - - // Confused monsters can't be pushed past, sleeping monsters - // can't push. Note that sleeping monsters can't be pushed - // past, either, but they may be woken up by a crowd trying to - // elbow past them, and the wake-up check happens downstream. - if (mons_is_confused(mpusher) || mons_is_confused(mpushee) - || mons_cannot_move(mpusher) || mons_cannot_move(mpushee) - || mons_is_stationary(mpusher) || mons_is_stationary(mpushee) - || mpusher->asleep()) - { - return (false); - } - - // Batty monsters are unpushable. - if (mons_is_batty(mpusher) || mons_is_batty(mpushee)) - return (false); - - if (!monster_shover(mpusher)) - return (false); - - // Fleeing monsters of the same type may push past higher ranking ones. - if (!monster_senior(mpusher, mpushee, mons_is_fleeing(mpusher))) - return (false); - - return (true); -} - -static bool _monster_swaps_places( monsters *mon, const coord_def& delta ) -{ - if (delta.origin()) - return (false); - - monsters* const m2 = monster_at(mon->pos() + delta); - - if (!m2) - return (false); - - if (!_mons_can_displace(mon, m2)) - return (false); - - if (m2->asleep()) - { - if (coinflip()) - { -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, - "Alerting monster %s at (%d,%d)", - m2->name(DESC_PLAIN).c_str(), m2->pos().x, m2->pos().y); -#endif - behaviour_event(m2, ME_ALERT, MHITNOT); - } - return (false); - } - - // Check that both monsters will be happy at their proposed new locations. - const coord_def c = mon->pos(); - const coord_def n = mon->pos() + delta; - - if (!_habitat_okay(mon, grd(n)) || !_habitat_okay(m2, grd(c))) - return (false); - - // Okay, do the swap! - _swim_or_move_energy(mon); - - mon->pos() = n; - mgrd(n) = monster_index(mon); - m2->pos() = c; - const int m2i = monster_index(m2); - ASSERT(m2i >= 0 && m2i < MAX_MONSTERS); - mgrd(c) = m2i; - immobile_monster[m2i] = true; - - mon->check_redraw(c); - mon->apply_location_effects(c); - m2->check_redraw(c); - m2->apply_location_effects(n); - - // The seen context no longer applies if the monster is moving normally. - mon->seen_context.clear(); - m2->seen_context.clear(); - - return (false); -} - -static bool _do_move_monster(monsters *monster, const coord_def& delta) -{ - const coord_def f = monster->pos() + delta; - - if (!in_bounds(f)) - return (false); - - if (f == you.pos()) - { - monster_attack(monster); - return (true); - } - - // This includes the case where the monster attacks itself. - if (monsters* def = monster_at(f)) - { - monsters_fight(monster, def); - return (true); - } - - // The monster gave a "comes into view" message and then immediately - // moved back out of view, leaing the player nothing to see, so give - // this message to avoid confusion. - if (monster->seen_context == _just_seen && !see_cell(f)) - simple_monster_message(monster, " moves out of view."); - else if (Options.tutorial_left && (monster->flags & MF_WAS_IN_VIEW) - && !see_cell(f)) - { - learned_something_new(TUT_MONSTER_LEFT_LOS, monster->pos()); - } - - // The seen context no longer applies if the monster is moving normally. - monster->seen_context.clear(); - - // This appears to be the real one, ie where the movement occurs: - _swim_or_move_energy(monster); - - if (grd(monster->pos()) == DNGN_DEEP_WATER && grd(f) != DNGN_DEEP_WATER - && !monster_habitable_grid(monster, DNGN_DEEP_WATER)) - { - monster->seen_context = "emerges from the water"; - } - mgrd(monster->pos()) = NON_MONSTER; - - monster->pos() = f; - - mgrd(monster->pos()) = monster_index(monster); - - monster->check_redraw(monster->pos() - delta); - monster->apply_location_effects(monster->pos() - delta); - - return (true); -} - void mons_check_pool(monsters *monster, const coord_def &oldpos, killer_type killer, int killnum) { @@ -8620,944 +3275,6 @@ void mons_check_pool(monsters *monster, const coord_def &oldpos, } } -// Randomise potential damage. -static int _estimated_trap_damage(trap_type trap) -{ - switch (trap) - { - case TRAP_BLADE: return (10 + random2(30)); - case TRAP_DART: return (random2(4)); - case TRAP_ARROW: return (random2(7)); - case TRAP_SPEAR: return (random2(10)); - case TRAP_BOLT: return (random2(13)); - case TRAP_AXE: return (random2(15)); - default: return (0); - } -} - -// Check whether a given trap (described by trap position) can be -// regarded as safe. Takes into account monster intelligence and -// allegiance. -// (just_check is used for intelligent monsters trying to avoid traps.) -static bool _is_trap_safe(const monsters *monster, const coord_def& where, - bool just_check) -{ - const int intel = mons_intel(monster); - - const trap_def *ptrap = find_trap(where); - if (!ptrap) - return (true); - const trap_def& trap = *ptrap; - - const bool player_knows_trap = (trap.is_known(&you)); - - // No friendly monsters will ever enter a Zot trap you know. - if (player_knows_trap && mons_friendly(monster) && trap.type == TRAP_ZOT) - return (false); - - // Dumb monsters don't care at all. - if (intel == I_PLANT) - return (true); - - if (trap.type == TRAP_SHAFT && monster->will_trigger_shaft()) - { - if (mons_is_fleeing(monster) && intel >= I_NORMAL - || mons_is_pacified(monster)) - { - return (true); - } - return (false); - } - - // Hostile monsters are not afraid of non-mechanical traps. - // Allies will try to avoid teleportation and zot traps. - const bool mechanical = (trap.category() == DNGN_TRAP_MECHANICAL); - - if (trap.is_known(monster)) - { - if (just_check) - return (false); // Square is blocked. - else - { - // Test for corridor-like environment. - const int x = where.x - monster->pos().x; - const int y = where.y - monster->pos().y; - - // The question is whether the monster (m) can easily reach its - // presumable destination (x) without stepping on the trap. Traps - // in corridors do not allow this. See e.g - // #x# ## - // #^# or m^x - // m ## - // - // The same problem occurs if paths are blocked by monsters, - // hostile terrain or other traps rather than walls. - // What we do is check whether the squares with the relative - // positions (-1,0)/(+1,0) or (0,-1)/(0,+1) form a "corridor" - // (relative to the _trap_ position rather than the monster one). - // If they don't, the trap square is marked as "unsafe" (because - // there's a good alternative move for the monster to take), - // otherwise the decision will be made according to later tests - // (monster hp, trap type, ...) - // If a monster still gets stuck in a corridor it will usually be - // because it has less than half its maximum hp. - - if ((_mon_can_move_to_pos(monster, coord_def(x-1, y), true) - || _mon_can_move_to_pos(monster, coord_def(x+1,y), true)) - && (_mon_can_move_to_pos(monster, coord_def(x,y-1), true) - || _mon_can_move_to_pos(monster, coord_def(x,y+1), true))) - { - return (false); - } - } - } - - // Friendlies will try not to be parted from you. - if (intelligent_ally(monster) && trap.type == TRAP_TELEPORT - && player_knows_trap && mons_near(monster)) - { - return (false); - } - - // Healthy monsters don't mind a little pain. - if (mechanical && monster->hit_points >= monster->max_hit_points / 2 - && (intel == I_ANIMAL - || monster->hit_points > _estimated_trap_damage(trap.type))) - { - return (true); - } - - // Friendly and good neutral monsters don't enjoy Zot trap perks; - // handle accordingly. In the arena Zot traps affect all monsters. - if (mons_wont_attack(monster) || crawl_state.arena) - { - return (mechanical ? mons_flies(monster) - : !trap.is_known(monster) || trap.type != TRAP_ZOT); - } - else - return (!mechanical || mons_flies(monster)); -} - -static void _mons_open_door(monsters* monster, const coord_def &pos) -{ - dungeon_feature_type grid = grd(pos); - const char *adj = "", *noun = "door"; - - bool was_secret = false; - bool was_seen = false; - - std::set<coord_def> all_door; - find_connected_range(pos, DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR, all_door); - get_door_description(all_door.size(), &adj, &noun); - - for (std::set<coord_def>::iterator i = all_door.begin(); - i != all_door.end(); ++i) - { - const coord_def& dc = *i; - if (grd(dc) == DNGN_SECRET_DOOR && see_cell(dc)) - { - grid = grid_secret_door_appearance(dc); - was_secret = true; - } - - if (see_cell(dc)) - was_seen = true; - else - set_terrain_changed(dc); - - grd[dc.x][dc.y] = DNGN_OPEN_DOOR; - } - - if (was_seen) - { - viewwindow(true, false); - - if (was_secret) - { - mprf("%s was actually a secret door!", - feature_description(grid, NUM_TRAPS, false, - DESC_CAP_THE, false).c_str()); - learned_something_new(TUT_SEEN_SECRET_DOOR, pos); - } - - std::string open_str = "opens the "; - open_str += adj; - open_str += noun; - open_str += "."; - - monster->seen_context = open_str; - - if (!you.can_see(monster)) - { - mprf("Something unseen %s", open_str.c_str()); - interrupt_activity(AI_FORCE_INTERRUPT); - } - else if (!you_are_delayed()) - { - mprf("%s %s", monster->name(DESC_CAP_A).c_str(), - open_str.c_str()); - } - } - - monster->lose_energy(EUT_MOVE); -} - -static bool _no_habitable_adjacent_grids(const monsters *mon) -{ - for (adjacent_iterator ai(mon->pos()); ai; ++ai) - if (_habitat_okay(mon, grd(*ai))) - return (false); - - return (true); -} - -// Check whether a monster can move to given square (described by its relative -// coordinates to the current monster position). just_check is true only for -// calls from is_trap_safe when checking the surrounding squares of a trap. -static bool _mon_can_move_to_pos(const monsters *monster, - const coord_def& delta, bool just_check) -{ - const coord_def targ = monster->pos() + delta; - - // Bounds check: don't consider moving out of grid! - if (!in_bounds(targ)) - return (false); - - // No monster may enter the open sea. - if (grd(targ) == DNGN_OPEN_SEA) - return (false); - - // Non-friendly and non-good neutral monsters won't enter - // sanctuaries. - if (!mons_wont_attack(monster) - && is_sanctuary(targ) - && !is_sanctuary(monster->pos())) - { - return (false); - } - - // Inside a sanctuary don't attack anything! - if (is_sanctuary(monster->pos()) && actor_at(targ)) - return (false); - - const dungeon_feature_type target_grid = grd(targ); - const habitat_type habitat = mons_primary_habitat(monster); - - // The kraken is so large it cannot enter shallow water. - // Its tentacles can, and will, though. - if (monster->type == MONS_KRAKEN && target_grid == DNGN_SHALLOW_WATER) - return (false); - - // Effectively slows down monster movement across water. - // Fire elementals can't cross at all. - bool no_water = false; - if (monster->type == MONS_FIRE_ELEMENTAL || one_chance_in(5)) - no_water = true; - - cloud_type targ_cloud_type; - const int targ_cloud_num = env.cgrid(targ); - - if (mons_avoids_cloud(monster, targ_cloud_num, &targ_cloud_type)) - return (false); - - if (mons_class_flag(monster->type, M_BURROWS) - && (target_grid == DNGN_ROCK_WALL - || target_grid == DNGN_CLEAR_ROCK_WALL)) - { - // Don't burrow out of bounds. - if (!in_bounds(targ)) - return (false); - - // Don't burrow at an angle (legacy behaviour). - if (delta.x != 0 && delta.y != 0) - return (false); - } - else if (!monster->can_pass_through_feat(target_grid) - || no_water && feat_is_water(target_grid)) - { - return (false); - } - else if (!_habitat_okay(monster, target_grid)) - { - // If the monster somehow ended up in this habitat (and is - // not dead by now), give it a chance to get out again. - if (grd(monster->pos()) == target_grid - && _no_habitable_adjacent_grids(monster)) - { - return (true); - } - - return (false); - } - - // Wandering mushrooms don't move while you are looking. - if (monster->type == MONS_WANDERING_MUSHROOM && see_cell(targ)) - return (false); - - // Water elementals avoid fire and heat. - if (monster->type == MONS_WATER_ELEMENTAL - && (target_grid == DNGN_LAVA - || targ_cloud_type == CLOUD_FIRE - || targ_cloud_type == CLOUD_FOREST_FIRE - || targ_cloud_type == CLOUD_STEAM)) - { - return (false); - } - - // Fire elementals avoid water and cold. - if (monster->type == MONS_FIRE_ELEMENTAL - && (feat_is_watery(target_grid) - || targ_cloud_type == CLOUD_COLD)) - { - return (false); - } - - // Submerged water creatures avoid the shallows where - // they would be forced to surface. -- bwr - // [dshaligram] Monsters now prefer to head for deep water only if - // they're low on hitpoints. No point in hiding if they want a - // fight. - if (habitat == HT_WATER - && targ != you.pos() - && target_grid != DNGN_DEEP_WATER - && grd(monster->pos()) == DNGN_DEEP_WATER - && monster->hit_points < (monster->max_hit_points * 3) / 4) - { - return (false); - } - - // Smacking the player is always a good move if we're - // hostile (even if we're heading somewhere else). - // Also friendlies want to keep close to the player - // so it's okay as well. - - // Smacking another monster is good, if the monsters - // are aligned differently. - if (monsters *targmonster = monster_at(targ)) - { - if (just_check) - { - if (targ == monster->pos()) - return (true); - - return (false); // blocks square - } - - if (mons_aligned(monster->mindex(), targmonster->mindex()) - && !_mons_can_displace(monster, targmonster)) - { - return (false); - } - } - - // Friendlies shouldn't try to move onto the player's - // location, if they are aiming for some other target. - if (mons_wont_attack(monster) - && monster->foe != MHITYOU - && (monster->foe != MHITNOT || monster->is_patrolling()) - && targ == you.pos()) - { - return (false); - } - - // Wandering through a trap is OK if we're pretty healthy, - // really stupid, or immune to the trap. - if (!_is_trap_safe(monster, targ, just_check)) - return (false); - - // If we end up here the monster can safely move. - return (true); -} - -// Uses, and updates the global variable mmov. -static void _find_good_alternate_move(monsters *monster, - const FixedArray<bool, 3, 3>& good_move) -{ - const int current_distance = distance(monster->pos(), monster->target); - - int dir = -1; - for (int i = 0; i < 8; i++) - { - if (mon_compass[i] == mmov) - { - dir = i; - break; - } - } - - // Only handle if the original move is to an adjacent square. - if (dir == -1) - return; - - int dist[2]; - - // First 1 away, then 2 (3 is silly). - for (int j = 1; j <= 2; j++) - { - const int FAR_AWAY = 1000000; - - // Try both directions (but randomise which one is first). - const int sdir = coinflip() ? j : -j; - const int inc = -2 * sdir; - - for (int mod = sdir, i = 0; i < 2; mod += inc, i++) - { - const int newdir = (dir + 8 + mod) % 8; - if (good_move[mon_compass[newdir].x+1][mon_compass[newdir].y+1]) - { - dist[i] = distance(monster->pos()+mon_compass[newdir], - monster->target); - } - else - { - dist[i] = (mons_is_fleeing(monster)) ? (-FAR_AWAY) : FAR_AWAY; - } - } - - const int dir0 = ((dir + 8 + sdir) % 8); - const int dir1 = ((dir + 8 - sdir) % 8); - - // Now choose. - if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY) - continue; - - // Which one was better? -- depends on FLEEING or not. - if (mons_is_fleeing(monster)) - { - if (dist[0] >= dist[1] && dist[0] >= current_distance) - { - mmov = mon_compass[dir0]; - break; - } - if (dist[1] >= dist[0] && dist[1] >= current_distance) - { - mmov = mon_compass[dir1]; - break; - } - } - else - { - if (dist[0] <= dist[1] && dist[0] <= current_distance) - { - mmov = mon_compass[dir0]; - break; - } - if (dist[1] <= dist[0] && dist[1] <= current_distance) - { - mmov = mon_compass[dir1]; - break; - } - } - } -} - -static bool _monster_move(monsters *monster) -{ - FixedArray<bool, 3, 3> good_move; - - const habitat_type habitat = mons_primary_habitat(monster); - bool deep_water_available = false; - - if (monster->type == MONS_TRAPDOOR_SPIDER) - { - if (monster->submerged()) - return (false); - - // Trapdoor spiders hide if they can't see their foe. - // (Note that friendly trapdoor spiders will thus hide even - // if they can see you.) - const actor *foe = monster->get_foe(); - const bool can_see = foe && monster->can_see(foe); - - if (monster_can_submerge(monster, grd(monster->pos())) - && !can_see && !mons_is_confused(monster) - && !monster->caught() - && !monster->has_ench(ENCH_BERSERK)) - { - monster->add_ench(ENCH_SUBMERGED); - monster->behaviour = BEH_LURK; - return (false); - } - } - - // Berserking monsters make a lot of racket. - if (monster->has_ench(ENCH_BERSERK)) - { - int noise_level = get_shout_noise_level(mons_shouts(monster->type)); - if (noise_level > 0) - { - if (you.can_see(monster)) - { - if (one_chance_in(10)) - { - mprf(MSGCH_TALK_VISUAL, "%s rages.", - monster->name(DESC_CAP_THE).c_str()); - } - noisy(noise_level, monster->pos(), monster->mindex()); - } - else if (one_chance_in(5)) - handle_monster_shouts(monster, true); - else - { - // Just be noisy without messaging the player. - noisy(noise_level, monster->pos(), monster->mindex()); - } - } - } - - if (monster->confused()) - { - if (!mmov.origin() || one_chance_in(15)) - { - const coord_def newpos = monster->pos() + mmov; - if (in_bounds(newpos) - && (habitat == HT_LAND - || monster_habitable_grid(monster, grd(newpos)))) - { - return _do_move_monster(monster, mmov); - } - } - return (false); - } - - // If a water monster is currently flopping around on land, it cannot - // really control where it wants to move, though there's a 50% chance - // of flopping into an adjacent water grid. - if (monster->has_ench(ENCH_AQUATIC_LAND)) - { - std::vector<coord_def> adj_water; - std::vector<coord_def> adj_move; - for (adjacent_iterator ai(monster->pos()); ai; ++ai) - { - if (!cell_is_solid(*ai)) - { - adj_move.push_back(*ai); - if (feat_is_watery(grd(*ai))) - adj_water.push_back(*ai); - } - } - if (adj_move.empty()) - { - simple_monster_message(monster, " flops around on dry land!"); - return (false); - } - - std::vector<coord_def> moves = adj_water; - if (adj_water.empty() || coinflip()) - moves = adj_move; - - coord_def newpos = monster->pos(); - int count = 0; - for (unsigned int i = 0; i < moves.size(); ++i) - if (one_chance_in(++count)) - newpos = moves[i]; - - const monsters *mon2 = monster_at(newpos); - if (newpos == you.pos() && mons_wont_attack(monster) - || (mon2 && mons_wont_attack(monster) == mons_wont_attack(mon2))) - { - - simple_monster_message(monster, " flops around on dry land!"); - return (false); - } - - return _do_move_monster(monster, newpos - monster->pos()); - } - - // Let's not even bother with this if mmov is zero. - if (mmov.origin()) - return (false); - - for (int count_x = 0; count_x < 3; count_x++) - for (int count_y = 0; count_y < 3; count_y++) - { - const int targ_x = monster->pos().x + count_x - 1; - const int targ_y = monster->pos().y + count_y - 1; - - // Bounds check: don't consider moving out of grid! - if (!in_bounds(targ_x, targ_y)) - { - good_move[count_x][count_y] = false; - continue; - } - dungeon_feature_type target_grid = grd[targ_x][targ_y]; - - if (target_grid == DNGN_DEEP_WATER) - deep_water_available = true; - - good_move[count_x][count_y] = - _mon_can_move_to_pos(monster, coord_def(count_x-1, count_y-1)); - } - - // Now we know where we _can_ move. - - const coord_def newpos = monster->pos() + mmov; - // Normal/smart monsters know about secret doors, since they live in - // the dungeon. - if (grd(newpos) == DNGN_CLOSED_DOOR - || feat_is_secret_door(grd(newpos)) && mons_intel(monster) >= I_NORMAL) - { - if (mons_is_zombified(monster)) - { - // For zombies, monster type is kept in mon->base_monster. - if (mons_class_itemuse(monster->base_monster) >= MONUSE_OPEN_DOORS) - { - _mons_open_door(monster, newpos); - return (true); - } - } - else if (mons_itemuse(monster) >= MONUSE_OPEN_DOORS) - { - _mons_open_door(monster, newpos); - return (true); - } - } // endif - secret/closed doors - - // Monsters that eat items (currently only jellies) also eat doors. - // However, they don't realise that secret doors make good eating. - if ((grd(newpos) == DNGN_CLOSED_DOOR || grd(newpos) == DNGN_OPEN_DOOR) - && mons_itemeat(monster) == MONEAT_ITEMS - // Doors with permarock marker cannot be eaten. - && !feature_marker_at(newpos, DNGN_PERMAROCK_WALL)) - { - grd(newpos) = DNGN_FLOOR; - - _jelly_grows(monster); - - if (see_cell(newpos)) - { - viewwindow(true, false); - - if (!you.can_see(monster)) - { - mpr("The door mysteriously vanishes."); - interrupt_activity( AI_FORCE_INTERRUPT ); - } - } - } // done door-eating jellies - - // Water creatures have a preference for water they can hide in -- bwr - // [ds] Weakened the powerful attraction to deep water if the monster - // is in good health. - if (habitat == HT_WATER - && deep_water_available - && grd(monster->pos()) != DNGN_DEEP_WATER - && grd(newpos) != DNGN_DEEP_WATER - && newpos != you.pos() - && (one_chance_in(3) - || monster->hit_points <= (monster->max_hit_points * 3) / 4)) - { - int count = 0; - - for (int cx = 0; cx < 3; cx++) - for (int cy = 0; cy < 3; cy++) - { - if (good_move[cx][cy] - && grd[monster->pos().x + cx - 1][monster->pos().y + cy - 1] - == DNGN_DEEP_WATER) - { - if (one_chance_in(++count)) - { - mmov.x = cx - 1; - mmov.y = cy - 1; - } - } - } - } - - // Now, if a monster can't move in its intended direction, try - // either side. If they're both good, move in whichever dir - // gets it closer (farther for fleeing monsters) to its target. - // If neither does, do nothing. - if (good_move[mmov.x + 1][mmov.y + 1] == false) - _find_good_alternate_move(monster, good_move); - - // ------------------------------------------------------------------ - // If we haven't found a good move by this point, we're not going to. - // ------------------------------------------------------------------ - - // Take care of beetle burrowing. - if (mons_class_flag(monster->type, M_BURROWS)) - { - const dungeon_feature_type feat = grd(monster->pos() + mmov); - if ((feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL) - && good_move[mmov.x + 1][mmov.y + 1] == true) - { - grd(monster->pos() + mmov) = DNGN_FLOOR; - set_terrain_changed(monster->pos() + mmov); - - if (player_can_hear(monster->pos() + mmov)) - { - // Message depends on whether caused by boring beetle or - // acid (Dissolution). - mpr((monster->type == MONS_BORING_BEETLE) ? - "You hear a grinding noise." : - "You hear a sizzling sound.", MSGCH_SOUND); - } - } - } - - bool ret = false; - if (good_move[mmov.x + 1][mmov.y + 1] && !mmov.origin()) - { - // Check for attacking player. - if (monster->pos() + mmov == you.pos()) - { - ret = monster_attack(monster); - mmov.reset(); - } - - // If we're following the player through stairs, the only valid - // movement is towards the player. -- bwr - if (testbits(monster->flags, MF_TAKING_STAIRS)) - { - const delay_type delay = current_delay_action(); - if (delay != DELAY_ASCENDING_STAIRS - && delay != DELAY_DESCENDING_STAIRS) - { - monster->flags &= ~MF_TAKING_STAIRS; - -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, - "BUG: %s was marked as follower when not following!", - monster->name(DESC_PLAIN).c_str(), true); -#endif - } - else - { - ret = true; - mmov.reset(); - -#if DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, - "%s is skipping movement in order to follow.", - monster->name(DESC_CAP_THE).c_str(), true ); -#endif - } - } - - // Check for attacking another monster. - if (monsters* targ = monster_at(monster->pos() + mmov)) - { - if (mons_aligned(monster->mindex(), targ->mindex())) - ret = _monster_swaps_places(monster, mmov); - else - { - monsters_fight(monster, targ); - ret = true; - } - - // If the monster swapped places, the work's already done. - mmov.reset(); - } - - if (monster->type == MONS_EFREET - || monster->type == MONS_FIRE_ELEMENTAL) - { - place_cloud( CLOUD_FIRE, monster->pos(), - 2 + random2(4), monster->kill_alignment() ); - } - - if (monster->type == MONS_ROTTING_DEVIL - || monster->type == MONS_CURSE_TOE) - { - place_cloud( CLOUD_MIASMA, monster->pos(), - 2 + random2(3), monster->kill_alignment() ); - } - } - else - { - mmov.reset(); - - // Fleeing monsters that can't move will panic and possibly - // turn to face their attacker. - _make_mons_stop_fleeing(monster); - } - - if (mmov.x || mmov.y || (monster->confused() && one_chance_in(6))) - return (_do_move_monster(monster, mmov)); - - return (ret); -} - -static void _mons_in_cloud(monsters *monster) -{ - int wc = env.cgrid(monster->pos()); - int hurted = 0; - bolt beam; - - const int speed = ((monster->speed > 0) ? monster->speed : 10); - bool wake = false; - - if (mons_is_mimic( monster->type )) - { - mimic_alert(monster); - return; - } - - const cloud_struct &cloud(env.cloud[wc]); - switch (cloud.type) - { - case CLOUD_DEBUGGING: - mprf(MSGCH_ERROR, - "Monster %s stepped on a nonexistent cloud at (%d,%d)", - monster->name(DESC_PLAIN, true).c_str(), - monster->pos().x, monster->pos().y); - return; - - case CLOUD_FIRE: - case CLOUD_FOREST_FIRE: - if (monster->type == MONS_FIRE_VORTEX - || monster->type == MONS_EFREET - || monster->type == MONS_FIRE_ELEMENTAL) - { - return; - } - - simple_monster_message(monster, " is engulfed in flames!"); - - hurted += - resist_adjust_damage( monster, - BEAM_FIRE, - monster->res_fire(), - ((random2avg(16, 3) + 6) * 10) / speed ); - - hurted -= random2(1 + monster->ac); - break; - - case CLOUD_STINK: - simple_monster_message(monster, " is engulfed in noxious gasses!"); - - if (monster->res_poison() > 0) - return; - - beam.flavour = BEAM_CONFUSION; - beam.thrower = cloud.killer; - - if (cloud.whose == KC_FRIENDLY) - beam.beam_source = ANON_FRIENDLY_MONSTER; - - if (mons_class_is_confusable(monster->type) - && 1 + random2(27) >= monster->hit_dice) - { - beam.apply_enchantment_to_monster(monster); - } - - hurted += (random2(3) * 10) / speed; - break; - - case CLOUD_COLD: - simple_monster_message(monster, " is engulfed in freezing vapours!"); - - hurted += - resist_adjust_damage( monster, - BEAM_COLD, - monster->res_cold(), - ((6 + random2avg(16, 3)) * 10) / speed ); - - hurted -= random2(1 + monster->ac); - break; - - case CLOUD_POISON: - simple_monster_message(monster, " is engulfed in a cloud of poison!"); - - if (monster->res_poison() > 0) - return; - - poison_monster(monster, cloud.whose); - // If the monster got poisoned, wake it up. - wake = true; - - hurted += (random2(8) * 10) / speed; - - if (monster->res_poison() < 0) - hurted += (random2(4) * 10) / speed; - break; - - case CLOUD_STEAM: - { - // FIXME: couldn't be bothered coding for armour of res fire - - simple_monster_message(monster, " is engulfed in steam!"); - - const int steam_base_damage = steam_cloud_damage(cloud); - hurted += - resist_adjust_damage( - monster, - BEAM_STEAM, - monster->res_steam(), - (random2avg(steam_base_damage, 2) * 10) / speed); - - hurted -= random2(1 + monster->ac); - break; - } - - case CLOUD_MIASMA: - simple_monster_message(monster, " is engulfed in a dark miasma!"); - - if (monster->res_rotting()) - return; - - miasma_monster(monster, cloud.whose); - - hurted += (10 * random2avg(12, 3)) / speed; // 3 - break; - - case CLOUD_RAIN: - if (monster->is_fiery()) - { - if (!silenced(monster->pos())) - simple_monster_message(monster, " sizzles in the rain!"); - else - simple_monster_message(monster, " steams in the rain!"); - - hurted += ((4 * random2(3)) - random2(monster->ac)); - wake = true; - } - break; - - case CLOUD_MUTAGENIC: - simple_monster_message(monster, " is engulfed in a mutagenic fog!"); - - // Will only polymorph a monster if they're not magic immune, can - // mutate, aren't res asphyx, and pass the same check as meph cloud. - if (monster->can_mutate() && !mons_immune_magic(monster) - && 1 + random2(27) >= monster->hit_dice - && !monster->res_asphyx()) - { - if (monster->mutate()) - wake = true; - } - break; - - default: // 'harmless' clouds -- colored smoke, etc {dlb}. - return; - } - - // A sleeping monster that sustains damage will wake up. - if ((wake || hurted > 0) && monster->asleep()) - { - // We have no good coords to give the monster as the source of the - // disturbance other than the cloud itself. - behaviour_event(monster, ME_DISTURB, MHITNOT, monster->pos()); - } - - if (hurted > 0) - { -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "%s takes %d damage from cloud.", - monster->name(DESC_CAP_THE).c_str(), hurted); -#endif - monster->hurt(NULL, hurted, BEAM_MISSILE, false); - - if (monster->hit_points < 1) - { - mon_enchant death_ench(ENCH_NONE, 0, cloud.whose); - monster_die(monster, cloud.killer, death_ench.kill_agent()); - } - } -} - bool monster_descriptor(int which_class, mon_desc_type which_descriptor) { if (which_descriptor == MDSC_LEAVES_HIDE) @@ -9698,30 +3415,6 @@ bool heal_monster(monsters * patient, int health_boost, return (success); } -static spell_type _map_wand_to_mspell(int wand_type) -{ - switch (wand_type) - { - case WAND_FLAME: return SPELL_THROW_FLAME; - case WAND_FROST: return SPELL_THROW_FROST; - case WAND_SLOWING: return SPELL_SLOW; - case WAND_HASTING: return SPELL_HASTE; - case WAND_MAGIC_DARTS: return SPELL_MAGIC_DART; - case WAND_HEALING: return SPELL_MINOR_HEALING; - case WAND_PARALYSIS: return SPELL_PARALYSE; - case WAND_FIRE: return SPELL_BOLT_OF_FIRE; - case WAND_COLD: return SPELL_BOLT_OF_COLD; - case WAND_CONFUSION: return SPELL_CONFUSE; - case WAND_INVISIBILITY: return SPELL_INVISIBILITY; - case WAND_TELEPORTATION: return SPELL_TELEPORT_OTHER; - case WAND_LIGHTNING: return SPELL_LIGHTNING_BOLT; - case WAND_DRAINING: return SPELL_BOLT_OF_DRAINING; - case WAND_DISINTEGRATION: return SPELL_DISINTEGRATE; - case WAND_POLYMORPH_OTHER: return SPELL_POLYMORPH_OTHER; - default: return SPELL_NO_SPELL; - } -} - void seen_monster(monsters *monster) { // If the monster is in the auto_exclude list, automatically @@ -9839,3 +3532,369 @@ int dismiss_monsters(std::string pattern) { } return (ndismissed); } + +bool is_item_jelly_edible(const item_def &item) +{ + // Don't eat artefacts. + if (is_artefact(item)) + return (false); + + // Shouldn't eat stone things + // - but what about wands and rings? + if (item.base_type == OBJ_MISSILES + && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK)) + { + return (false); + } + + // Don't eat special game items. + if (item.base_type == OBJ_ORBS + || (item.base_type == OBJ_MISCELLANY + && (item.sub_type == MISC_RUNE_OF_ZOT + || item.sub_type == MISC_HORN_OF_GERYON))) + { + return (false); + } + + return (true); +} + +bool monster_random_space(const monsters *monster, coord_def& target, + bool forbid_sanctuary) +{ + int tries = 0; + while (tries++ < 1000) + { + target = random_in_bounds(); + + // Don't land on top of another monster. + if (actor_at(target)) + continue; + + if (is_sanctuary(target) && forbid_sanctuary) + continue; + + if (monster_habitable_grid(monster, grd(target))) + return (true); + } + + return (false); +} + +bool monster_random_space(monster_type mon, coord_def& target, + bool forbid_sanctuary) +{ + monsters dummy; + dummy.type = mon; + + return monster_random_space(&dummy, target, forbid_sanctuary); +} + +void monster_teleport(monsters *monster, bool instan, bool silent) +{ + if (!instan) + { + if (monster->del_ench(ENCH_TP)) + { + if (!silent) + simple_monster_message(monster, " seems more stable."); + } + else + { + if (!silent) + simple_monster_message(monster, " looks slightly unstable."); + + monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER, + random_range(20, 30)) ); + } + + return; + } + + bool was_seen = you.can_see(monster) && !mons_is_lurking(monster); + + if (!silent) + simple_monster_message(monster, " disappears!"); + + const coord_def oldplace = monster->pos(); + + // Pick the monster up. + mgrd(oldplace) = NON_MONSTER; + + coord_def newpos; + if (monster_random_space(monster, newpos, !mons_wont_attack(monster))) + monster->moveto(newpos); + + mgrd(monster->pos()) = monster_index(monster); + + // Mimics change form/colour when teleported. + if (mons_is_mimic(monster->type)) + { + monster_type old_type = monster->type; + monster->type = static_cast<monster_type>( + MONS_GOLD_MIMIC + random2(5)); + monster->colour = get_mimic_colour(monster); + + // If it's changed form, you won't recognise it. + // This assumes that a non-gold mimic turning into another item of + // the same description is really, really unlikely. + if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC) + was_seen = false; + } + + const bool now_visible = mons_near(monster); + if (!silent && now_visible) + { + if (was_seen) + simple_monster_message(monster, " reappears nearby!"); + else + { + // Even if it doesn't interrupt an activity (the player isn't + // delayed, the monster isn't hostile) we still want to give + // a message. + activity_interrupt_data ai(monster, "thin air"); + if (!interrupt_activity(AI_SEE_MONSTER, ai)) + simple_monster_message(monster, " appears out of thin air!"); + } + } + + if (monster->visible_to(&you) && now_visible) + handle_seen_interrupt(monster); + + // Leave a purple cloud. + place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3), + monster->kill_alignment()); + + monster->check_redraw(oldplace); + monster->apply_location_effects(oldplace); + + mons_relocated(monster); + + // Teleporting mimics change form - if they reappear out of LOS, they are + // no longer known. + if (mons_is_mimic(monster->type)) + { + if (now_visible) + monster->flags |= MF_KNOWN_MIMIC; + else + monster->flags &= ~MF_KNOWN_MIMIC; + } +} + +void mons_clear_trapping_net(monsters *mon) +{ + if (!mons_is_caught(mon)) + return; + + const int net = get_trapping_net(mon->pos()); + if (net != NON_ITEM) + remove_item_stationary(mitm[net]); + + mon->del_ench(ENCH_HELD, true); +} + +bool mons_clonable(const monsters* mon, bool needs_adjacent) +{ + // No uniques or ghost demon monsters. Also, figuring out the name + // for the clone of a named monster isn't worth it. + if (mons_is_unique(mon->type) + || mons_is_ghost_demon(mon->type) + || mon->is_named()) + { + return (false); + } + + if (needs_adjacent) + { + // Is there space for the clone? + bool square_found = false; + for (int i = 0; i < 8; i++) + { + const coord_def p = mon->pos() + Compass[i]; + + if (in_bounds(p) + && !actor_at(p) + && monster_habitable_grid(mon, grd(p))) + { + square_found = true; + break; + } + } + if (!square_found) + return (false); + } + + // Is the monster carrying an artefact? + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) + { + const int index = mon->inv[i]; + + if (index == NON_ITEM) + continue; + + if (is_artefact(mitm[index])) + return (false); + } + + return (true); +} + +int clone_mons(const monsters* orig, bool quiet, bool* obvious, + coord_def pos) +{ + // Is there an open slot in menv? + int midx = NON_MONSTER; + for (int i = 0; i < MAX_MONSTERS; i++) + if (menv[i].type == MONS_NO_MONSTER) + { + midx = i; + break; + } + + if (midx == NON_MONSTER) + return (NON_MONSTER); + + if (!in_bounds(pos)) + { + // Find an adjacent square. + int squares = 0; + for (int i = 0; i < 8; i++) + { + const coord_def p = orig->pos() + Compass[i]; + + if (in_bounds(p) + && !actor_at(p) + && monster_habitable_grid(orig, grd(p))) + { + if (one_chance_in(++squares)) + pos = p; + } + } + + if (squares == 0) + return (NON_MONSTER); + } + + ASSERT( !actor_at(pos) ); + + monsters &mon(menv[midx]); + + mon = *orig; + mon.position = pos; + mgrd(pos) = midx; + + // Duplicate objects, or unequip them if they can't be duplicated. + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) + { + const int old_index = orig->inv[i]; + + if (old_index == NON_ITEM) + continue; + + const int new_index = get_item_slot(0); + if (new_index == NON_ITEM) + { + mon.unequip(mitm[old_index], i, 0, true); + mon.inv[i] = NON_ITEM; + continue; + } + + mon.inv[i] = new_index; + mitm[new_index] = mitm[old_index]; + mitm[new_index].set_holding_monster(midx); + } + + bool _obvious; + if (obvious == NULL) + obvious = &_obvious; + *obvious = false; + + if (you.can_see(orig) && you.can_see(&mon)) + { + if (!quiet) + simple_monster_message(orig, " is duplicated!"); + *obvious = true; + } + + mark_interesting_monst(&mon, mon.behaviour); + if (you.can_see(&mon)) + { + handle_seen_interrupt(&mon); + viewwindow(true, false); + } + + if (crawl_state.arena) + arena_placed_monster(&mon); + + return (midx); +} + +std::string summoned_poof_msg(const monsters* monster, bool plural) +{ + int summon_type = 0; + bool valid_mon = false; + if (monster != NULL && !invalid_monster(monster)) + { + (void) monster->is_summoned(NULL, &summon_type); + valid_mon = true; + } + + std::string msg = "disappear%s in a puff of smoke"; + bool no_chaos = false; + + switch (summon_type) + { + case SPELL_SHADOW_CREATURES: + msg = "dissolve%s into shadows"; + no_chaos = true; + break; + + case MON_SUMM_CHAOS: + msg = "degenerate%s into a cloud of primal chaos"; + break; + + case MON_SUMM_WRATH: + case MON_SUMM_AID: + if (valid_mon && is_good_god(monster->god)) + { + msg = "dissolve%s into sparkling lights"; + no_chaos = true; + } + break; + } + + if (valid_mon) + { + if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10) + || monster->type == MONS_CHAOS_SPAWN) + { + msg = "degenerate%s into a cloud of primal chaos"; + } + + if (mons_is_holy(monster) && summon_type != SPELL_SHADOW_CREATURES + && summon_type != MON_SUMM_CHAOS) + { + msg = "dissolve%s into sparkling lights"; + } + } + + // Conjugate. + msg = make_stringf(msg.c_str(), plural ? "" : "s"); + + return (msg); +} + +std::string summoned_poof_msg(const int midx, const item_def &item) +{ + if (midx == NON_MONSTER) + return summoned_poof_msg(static_cast<const monsters*>(NULL), item); + else + return summoned_poof_msg(&menv[midx], item); +} + +std::string summoned_poof_msg(const monsters* monster, const item_def &item) +{ + ASSERT(item.flags & ISFLAG_SUMMONED); + + return summoned_poof_msg(monster, item.quantity > 1); +} |