summaryrefslogtreecommitdiffstats
path: root/stone_soup/crawl-ref/source/monstuff.cc
diff options
context:
space:
mode:
Diffstat (limited to 'stone_soup/crawl-ref/source/monstuff.cc')
-rw-r--r--stone_soup/crawl-ref/source/monstuff.cc5235
1 files changed, 0 insertions, 5235 deletions
diff --git a/stone_soup/crawl-ref/source/monstuff.cc b/stone_soup/crawl-ref/source/monstuff.cc
deleted file mode 100644
index f8305f5e4d..0000000000
--- a/stone_soup/crawl-ref/source/monstuff.cc
+++ /dev/null
@@ -1,5235 +0,0 @@
-/*
- * File: monstuff.cc
- * Summary: Misc monster related functions.
- * Written by: Linley Henzell
- *
- * Change History (most recent first):
- *
- * <8> 7 Aug 2001 MV Inteligent monsters now pick up gold
- * <7> 26 Mar 2001 GDL Fixed monster reaching
- * <6> 13 Mar 2001 GDL Rewrite of monster AI
- * <5> 31 July 2000 GDL More Manticore fixes.
- * <4> 29 July 2000 JDJ Fixed a bunch of places in handle_pickup where MSLOT_WEAPON
- * was being erroneously used.
- * <3> 25 July 2000 GDL Fixed Manticores
- * <2> 11/23/99 LRH Upgraded monster AI
- * <1> -/--/-- LRH Created
- */
-
-#include "AppHdr.h"
-#include "monstuff.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#ifdef DOS
-#include <conio.h>
-#endif
-
-#include "externs.h"
-
-#include "beam.h"
-#include "cloud.h"
-#include "debug.h"
-#include "dungeon.h"
-#include "fight.h"
-#include "itemname.h"
-#include "items.h"
-#include "itemprop.h"
-#include "misc.h"
-#include "monplace.h"
-#include "monspeak.h"
-#include "mon-util.h"
-#include "mstuff2.h"
-#include "player.h"
-#include "randart.h"
-#include "religion.h"
-#include "spl-cast.h"
-#include "spells2.h"
-#include "spells4.h"
-#include "stuff.h"
-#include "view.h"
-
-static bool handle_special_ability(struct monsters *monster, bolt & beem);
-static bool handle_pickup(struct monsters *monster);
-static void handle_behaviour(struct monsters *monster);
-static void mons_in_cloud(struct monsters *monster);
-static void monster_move(struct monsters *monster);
-static bool plant_spit(struct monsters *monster, struct bolt &pbolt);
-static int map_wand_to_mspell(int wand_type);
-
-// [dshaligram] Doesn't need to be extern.
-static int mmov_x, mmov_y;
-
-static int compass_x[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };
-static int compass_y[8] = { -1, -1, -1, 0, 1, 1, 1, 0 };
-
-static bool immobile_monster[MAX_MONSTERS];
-
-#define FAR_AWAY 1000000 // used in monster_move()
-
-// This function creates an arteficial item to represent a mimic's appearance.
-// Eventually, mimics could be redone to be more like Dancing wepaons...
-// there'd only be one type and it would look like the item it carries. -- bwr
-void get_mimic_item( const struct monsters *mimic, item_def &item )
-{
- ASSERT( mimic != NULL && mons_is_mimic( mimic->type ) );
-
- item.base_type = OBJ_UNASSIGNED;
- item.sub_type = 0;
- item.special = 0;
- item.colour = 0;
- item.flags = 0;
- item.quantity = 1;
- item.plus = 0;
- item.plus2 = 0;
- item.x = mimic->x;
- item.y = mimic->y;
- item.link = NON_ITEM;
-
- int prop = 127 * mimic->x + 269 * mimic->y;
-
- switch (mimic->type)
- {
- case MONS_WEAPON_MIMIC:
- item.base_type = OBJ_WEAPONS;
- item.sub_type = (59 * mimic->x + 79 * mimic->y) % NUM_WEAPONS;
-
- prop %= 100;
-
- if (prop < 20)
- {
- item.flags |= ISFLAG_RANDART;
- item.special = ((mimic->x << 8 + mimic->y) & RANDART_SEED_MASK);
- }
- else if (prop < 50)
- set_equip_desc( item, ISFLAG_GLOWING );
- else if (prop < 80)
- set_equip_desc( item, ISFLAG_RUNED );
- else if (prop < 85)
- set_equip_race( item, ISFLAG_ORCISH );
- else if (prop < 90)
- set_equip_race( item, ISFLAG_DWARVEN );
- else if (prop < 95)
- set_equip_race( item, ISFLAG_ELVEN );
- break;
-
- case MONS_ARMOUR_MIMIC:
- item.base_type = OBJ_ARMOUR;
- item.sub_type = (59 * mimic->x + 79 * mimic->y) % NUM_ARMOURS;
-
- prop %= 100;
-
- if (prop < 20)
- {
- item.flags |= ISFLAG_RANDART;
- item.special = ((mimic->x << 8 + mimic->y) & RANDART_SEED_MASK);
- }
- else if (prop < 40)
- set_equip_desc( item, ISFLAG_GLOWING );
- else if (prop < 60)
- set_equip_desc( item, ISFLAG_RUNED );
- else if (prop < 80)
- set_equip_desc( item, ISFLAG_EMBROIDERED_SHINY );
- else if (prop < 85)
- set_equip_race( item, ISFLAG_ORCISH );
- else if (prop < 90)
- set_equip_race( item, ISFLAG_DWARVEN );
- else if (prop < 95)
- set_equip_race( item, ISFLAG_ELVEN );
- break;
-
- case MONS_SCROLL_MIMIC:
- item.base_type = OBJ_SCROLLS;
- item.sub_type = prop % NUM_SCROLLS;
- break;
-
- case MONS_POTION_MIMIC:
- item.base_type = OBJ_POTIONS;
- item.sub_type = prop % NUM_POTIONS;
- break;
-
- case MONS_GOLD_MIMIC:
- default:
- item.base_type = OBJ_GOLD;
- item.quantity = 5 + prop % 30;
- break;
- }
-
- item_colour( item ); // also sets special vals for scrolls/poitions
-}
-
-// Sets the colour of a mimic to match its description... should be called
-// whenever a mimic is created or teleported. -- bwr
-int get_mimic_colour( struct monsters *mimic )
-{
- ASSERT( mimic != NULL && mons_is_mimic( mimic->type ) );
-
- if (mimic->type == MONS_SCROLL_MIMIC)
- return (LIGHTGREY);
- else if (mimic->type == MONS_GOLD_MIMIC)
- return (YELLOW);
-
- item_def item;
- get_mimic_item( mimic, item );
-
- return (item.colour);
-}
-
-// monster curses a random player inventory item:
-bool curse_an_item( char which, char power )
-{
- UNUSED( power );
-
- /* use which later, if I want to curse weapon/gloves whatever
- which, for now: 0 = non-mummy, 1 = mummy (potions as well)
- don't change mitm.special of !odecay */
-
- int count = 0;
- int item = ENDOFPACK;
-
- for (int i = 0; i < ENDOFPACK; i++)
- {
- if (!is_valid_item( you.inv[i] ))
- continue;
-
- if (you.inv[i].base_type == OBJ_WEAPONS
- || you.inv[i].base_type == OBJ_ARMOUR
- || you.inv[i].base_type == OBJ_JEWELLERY
- || you.inv[i].base_type == OBJ_POTIONS)
- {
- if (item_cursed( you.inv[i] ))
- continue;
-
- if (you.inv[i].base_type == OBJ_POTIONS
- && (which != 1 || you.inv[i].sub_type == POT_DECAY))
- {
- continue;
- }
-
- // item is valid for cursing, so we'll give it a chance
- count++;
- if (one_chance_in( count ))
- item = i;
- }
- }
-
- // any item to curse?
- if (item == ENDOFPACK)
- return (false);
-
- // curse item:
-
- /* problem: changes large piles of potions */
- /* don't change you.inv_special (just for fun) */
- if (you.inv[item].base_type == OBJ_POTIONS)
- {
- you.inv[item].sub_type = POT_DECAY;
- unset_ident_flags( you.inv[item], ISFLAG_IDENT_MASK ); // all different
- }
- else
- do_curse_item( you.inv[item] );
-
- return (true);
-}
-
-static void monster_drop_ething(struct monsters *monster,
- bool mark_item_origins = false)
-{
- /* drop weapons & missiles last (ie on top) so others pick up */
- int i; // loop variable {dlb}
- bool destroyed = false;
- bool hostile_grid = false;
-
- if (grd[monster->x][monster->y] == DNGN_LAVA ||
- grd[monster->x][monster->y] == DNGN_DEEP_WATER)
- {
- hostile_grid = true;
- }
-
- for (i = MSLOT_GOLD; i >= MSLOT_WEAPON; i--)
- {
- int item = monster->inv[i];
-
- if (item != NON_ITEM)
- {
- if (hostile_grid)
- {
- destroyed = true;
- destroy_item( item );
- }
- else
- {
- move_item_to_grid( &item, monster->x, monster->y );
- if (mark_item_origins && is_valid_item(mitm[item]))
- {
- origin_set_monster(mitm[item], monster);
- }
- }
-
- monster->inv[i] = NON_ITEM;
- }
- }
-
- if (destroyed)
- {
- if (grd[monster->x][monster->y] == DNGN_LAVA)
- mpr("You hear a hissing sound.", MSGCH_SOUND);
- else
- mpr("You hear a splashing sound.", MSGCH_SOUND);
- }
-} // end monster_drop_ething()
-
-static void place_monster_corpse(struct monsters *monster)
-{
- int corpse_class = mons_species(monster->type);
-
- if (mons_has_ench(monster, ENCH_SHAPESHIFTER))
- corpse_class = MONS_SHAPESHIFTER;
- else if (mons_has_ench(monster, ENCH_GLOWING_SHAPESHIFTER))
- corpse_class = MONS_GLOWING_SHAPESHIFTER;
-
- if (mons_weight(corpse_class) == 0
- || grd[monster->x][monster->y] == DNGN_LAVA
- || grd[monster->x][monster->y] == DNGN_DEEP_WATER || coinflip())
- {
- return;
- }
-
- int o = get_item_slot();
- if (o == NON_ITEM)
- return;
-
- mitm[o].flags = 0;
- mitm[o].base_type = OBJ_CORPSES;
- mitm[o].plus = corpse_class;
- mitm[o].plus2 = 0; // butcher work done
- mitm[o].sub_type = CORPSE_BODY;
- mitm[o].special = 210; // rot time
- mitm[o].colour = mons_colour(corpse_class);
- mitm[o].quantity = 1;
-
- if (mitm[o].colour == BLACK)
- mitm[o].colour = monster->number;
-
- // Don't care if 'o' is changed, and it shouldn't be (corpses don't stack)
- move_item_to_grid( &o, monster->x, monster->y );
-} // end place_monster_corpse()
-
-void monster_die(struct monsters *monster, char killer, int i)
-{
- int dmi; // dead monster's inventory
- int monster_killed = monster_index(monster);
- bool death_message = mons_near(monster) && player_monster_visible(monster);
-
- // From time to time Trog gives you a little bonus
- if (killer == KILL_YOU && you.berserker)
- {
- if (you.religion == GOD_TROG
- && (!player_under_penance() && you.piety > random2(1000)))
- {
- int bonus = 3 + random2avg( 10, 2 );
-
- you.berserker += bonus;
- you.might += bonus;
- haste_player( bonus );
-
- mpr( "You feel the power of Trog in you as your rage grows.",
- MSGCH_GOD, GOD_TROG );
- }
- else if (wearing_amulet( AMU_RAGE ) && one_chance_in(30))
- {
- int bonus = 2 + random2(4);
-
- you.berserker += bonus;
- you.might += bonus;
- haste_player( bonus );
-
- mpr( "Your amulet glows a violent red." );
- }
- }
-
- if (you.prev_targ == monster_killed)
- you.prev_targ = MHITNOT;
-
- const bool pet_kill = (MON_KILL(killer) && ((i >= 0 && i < 200)
- && mons_friendly(&menv[i])));
-
- if (monster->type == MONS_GIANT_SPORE
- || monster->type == MONS_BALL_LIGHTNING)
- {
- if (monster->hit_points < 1 && monster->hit_points > -15)
- return;
- }
- else if (monster->type == MONS_FIRE_VORTEX
- || monster->type == MONS_SPATIAL_VORTEX)
- {
- simple_monster_message( monster, " dissipates!", MSGCH_MONSTER_DAMAGE,
- MDAM_DEAD );
-
- if (!testbits(monster->flags, MF_CREATED_FRIENDLY))
- {
- if (YOU_KILL(killer))
- gain_exp( exper_value( monster ) );
- else if (pet_kill)
- gain_exp( exper_value( monster ) / 2 + 1 );
- }
-
- if (monster->type == MONS_FIRE_VORTEX)
- place_cloud(CLOUD_FIRE_MON, monster->x, monster->y, 2 + random2(4));
- }
- else if (monster->type == MONS_SIMULACRUM_SMALL
- || monster->type == MONS_SIMULACRUM_LARGE)
- {
- simple_monster_message( monster, " vaporizes!", MSGCH_MONSTER_DAMAGE,
- MDAM_DEAD );
-
- if (!testbits(monster->flags, MF_CREATED_FRIENDLY))
- {
- if (YOU_KILL(killer))
- gain_exp( exper_value( monster ) );
- else if (pet_kill)
- gain_exp( exper_value( monster ) / 2 + 1 );
- }
-
- place_cloud(CLOUD_COLD_MON, monster->x, monster->y, 2 + random2(4));
- }
- else if (monster->type == MONS_DANCING_WEAPON)
- {
- simple_monster_message(monster, " falls from the air.",
- MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
-
- if (!testbits(monster->flags, MF_CREATED_FRIENDLY))
- {
- if (YOU_KILL(killer))
- gain_exp( exper_value( monster ) );
- else if (pet_kill)
- gain_exp( exper_value( monster ) / 2 + 1 );
- }
- }
- else
- {
- switch (killer)
- {
- case KILL_YOU: /* You kill in combat. */
- case KILL_YOU_MISSILE: /* You kill by missile or beam. */
- {
- bool created_friendly =
- testbits(monster->flags, MF_CREATED_FRIENDLY);
-
- strcpy(info, "You ");
- strcat(info, (wounded_damaged(monster->type)) ? "destroy" : "kill");
- strcat(info, " ");
- strcat(info, ptr_monam(monster, DESC_NOCAP_THE));
- strcat(info, "!");
-
- if (death_message)
- mpr(info, MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
-
- if (!created_friendly)
- {
- gain_exp(exper_value( monster ));
- }
- else
- {
- if (death_message)
- mpr("That felt strangely unrewarding.");
- }
-
- // Xom doesn't care who you killed:
- if (you.religion == GOD_XOM
- && random2(70) <= 10 + monster->hit_dice)
- {
- Xom_acts(true, 1 + random2(monster->hit_dice), false);
- }
-
- // Trying to prevent summoning abuse here, so we're trying to
- // prevent summoned creatures from being being done_good kills,
- // Only affects monsters friendly when created.
- if (!created_friendly)
- {
- if (you.duration[DUR_PRAYER])
- {
- if (mons_holiness(monster) == MH_NATURAL)
- did_god_conduct(DID_DEDICATED_KILL_LIVING,
- monster->hit_dice);
-
- if (mons_holiness(monster) == MH_UNDEAD)
- did_god_conduct(DID_DEDICATED_KILL_UNDEAD,
- monster->hit_dice);
-
- if (mons_holiness(monster) == MH_DEMONIC)
- did_god_conduct(DID_DEDICATED_KILL_DEMON,
- monster->hit_dice);
-
- //jmf: Trog hates wizards
- if (mons_class_flag(monster->type, M_ACTUAL_SPELLS))
- did_god_conduct(DID_DEDICATED_KILL_WIZARD,
- monster->hit_dice);
-
- //jmf: maybe someone hates priests?
- if (mons_class_flag(monster->type, M_PRIEST))
- did_god_conduct(DID_DEDICATED_KILL_PRIEST,
- monster->hit_dice);
- }
-
- if (mons_holiness(monster) == MH_HOLY)
- did_god_conduct(DID_KILL_ANGEL, monster->hit_dice);
- }
-
- // Divine health and mp restoration doesn't happen when killing
- // born-friendly monsters. The mutation still applies, however.
- if (you.mutation[MUT_DEATH_STRENGTH]
- || (!created_friendly &&
- you.religion == GOD_MAKHLEB && you.duration[DUR_PRAYER] &&
- (!player_under_penance() && random2(you.piety) >= 30)))
- {
- if (you.hp < you.hp_max)
- {
- mpr("You feel a little better.");
- inc_hp(monster->hit_dice + random2(monster->hit_dice),
- false);
- }
- }
-
- if (!created_friendly
- && (you.religion == GOD_MAKHLEB || you.religion == GOD_VEHUMET)
- && you.duration[DUR_PRAYER]
- && (!player_under_penance() && random2(you.piety) >= 30))
- {
- if (you.magic_points < you.max_magic_points)
- {
- mpr("You feel your power returning.");
- inc_mp( 1 + random2(monster->hit_dice / 2), false );
- }
- }
-
- if (you.duration[DUR_DEATH_CHANNEL]
- && mons_holiness(monster) == MH_NATURAL
- && mons_weight(mons_species(monster->type)))
- {
- if (create_monster( MONS_SPECTRAL_THING, 0, BEH_FRIENDLY,
- monster->x, monster->y, you.pet_target,
- mons_species(monster->type)) != -1)
- {
- if (death_message)
- mpr("A glowing mist starts to gather...");
- }
- }
- break;
- }
-
- case KILL_MON: /* Monster kills in combat */
- case KILL_MON_MISSILE: /* Monster kills by missile or beam */
- simple_monster_message(monster, " dies!", MSGCH_MONSTER_DAMAGE,
- MDAM_DEAD);
-
- // no piety loss if god gifts killed by other monsters
- if (mons_friendly(monster) && !testbits(monster->flags,MF_GOD_GIFT))
- did_god_conduct(DID_FRIEND_DIES, 1 + (monster->hit_dice / 2));
-
- // Trying to prevent summoning abuse here, so we're trying to
- // prevent summoned creatures from being being done_good kills.
- // Only affects creatures which were friendly when summoned.
- if (!testbits(monster->flags, MF_CREATED_FRIENDLY) && pet_kill)
- {
- bool notice = false;
-
- gain_exp(exper_value( monster ) / 2 + 1);
-
- int targ_holy = mons_holiness(monster),
- attacker_holy = mons_holiness(&menv[i]);
-
- if (attacker_holy == MH_UNDEAD)
- {
- if (targ_holy == MH_NATURAL)
- notice |=
- did_god_conduct(DID_LIVING_KILLED_BY_UNDEAD_SLAVE,
- monster->hit_dice);
- }
- else if (you.religion == GOD_VEHUMET
- || testbits( menv[i].flags, MF_GOD_GIFT ))
- {
- // Yes, we are splitting undead pets from the others
- // as a way to focus Necomancy vs Summoning (ignoring
- // Summon Wraith here)... at least we're being nice and
- // putting the natural creature Summons together with
- // the Demon ones. Note that Vehumet gets a free
- // pass here since those followers are assumed to
- // come from Summoning spells... the others are
- // from invocations (Zin, TSO, Makh, Kiku). -- bwr
-
- if (targ_holy == MH_NATURAL)
- {
- notice |= did_god_conduct( DID_LIVING_KILLED_BY_SERVANT,
- monster->hit_dice );
-
- if (mons_class_flag( monster->type, M_EVIL ))
- {
- notice |=
- did_god_conduct(
- DID_NATURAL_EVIL_KILLED_BY_SERVANT,
- monster->hit_dice );
- }
- }
- else if (targ_holy == MH_DEMONIC)
- {
- notice |= did_god_conduct( DID_DEMON_KILLED_BY_SERVANT,
- monster->hit_dice );
- }
- else if (targ_holy == MH_UNDEAD)
- {
- notice |= did_god_conduct( DID_UNDEAD_KILLED_BY_SERVANT,
- monster->hit_dice );
- }
- }
-
- // Angel kills are always noticed.
- if (targ_holy == MH_HOLY)
- {
- notice |= did_god_conduct( DID_ANGEL_KILLED_BY_SERVANT,
- monster->hit_dice );
- }
-
- if (you.religion == GOD_VEHUMET
- && notice
- && (!player_under_penance() && random2(you.piety) >= 30))
- {
- /* Vehumet - only for non-undead servants (coding
- convenience, no real reason except that Vehumet
- prefers demons) */
- if (you.magic_points < you.max_magic_points)
- {
- mpr("You feel your power returning.");
- inc_mp( 1 + random2(monster->hit_dice / 2), false );
- }
- }
- }
- break;
-
- /* Monster killed by trap/inanimate thing/itself/poison not from you */
- case KILL_MISC:
- simple_monster_message(monster, " dies!", MSGCH_MONSTER_DAMAGE,
- MDAM_DEAD);
- break;
-
- case KILL_RESET:
- /* Monster doesn't die, just goes back to wherever it came from
- This must only be called by monsters running out of time (or
- abjuration), because it uses the beam variables! Or does it??? */
- simple_monster_message( monster,
- " disappears in a puff of smoke!" );
-
- place_cloud( CLOUD_GREY_SMOKE_MON + random2(3), monster->x,
- monster->y, 1 + random2(3) );
-
- // fall-through
-
- case KILL_DISMISSED:
- for (dmi = MSLOT_GOLD; dmi >= MSLOT_WEAPON; dmi--)
- { /* takes whatever it's carrying back home */
- if (monster->inv[dmi] != NON_ITEM)
- destroy_item(monster->inv[dmi]);
-
- monster->inv[dmi] = NON_ITEM;
- }
- break;
- }
- }
-
- if (monster->type == MONS_MUMMY)
- {
- if (YOU_KILL(killer))
- {
- if (curse_an_item(1, 0))
- mpr("You feel nervous for a moment...", MSGCH_MONSTER_SPELL);
- }
- }
- else if (monster->type == MONS_GUARDIAN_MUMMY
- || monster->type == MONS_GREATER_MUMMY
- || monster->type == MONS_MUMMY_PRIEST)
- {
- if (YOU_KILL(killer))
- {
- mpr("You feel extremely nervous for a moment...",
- MSGCH_MONSTER_SPELL);
-
- miscast_effect( SPTYP_NECROMANCY,
- 3 + (monster->type == MONS_GREATER_MUMMY) * 8
- + (monster->type == MONS_MUMMY_PRIEST) * 5,
- random2avg(88, 3), 100, "a mummy death curse" );
- }
- }
- else if (monster->type == MONS_BORIS)
- {
- // XXX: actual blood curse effect for Boris? -- bwr
-
- if (one_chance_in(5))
- mons_speaks( monster );
- else
- {
- // Provide the player with an ingame clue to Boris' return. -- bwr
- const int tmp = random2(6);
- simple_monster_message( monster,
- (tmp == 0) ? " says, \"You haven't seen the last of me!\"" :
- (tmp == 1) ? " says, \"I'll get you next time!\"" :
- (tmp == 2) ? " says, \"This isn't over yet!\"" :
- (tmp == 3) ? " says, \"I'll be back!\"" :
- (tmp == 4) ? " says, \"This isn't the end, its only just beginning!\"" :
- (tmp == 5) ? " says, \"Kill me? I think not!\""
- : " says, \"You cannot defeat me so easily!\"",
- MSGCH_TALK );
- }
-
- // Now that Boris is dead, he's a valid target for monster
- // creation again. -- bwr
- you.unique_creatures[ monster->type - 280 ] = 0;
- }
-
- if (killer != KILL_RESET && killer != KILL_DISMISSED)
- {
- you.kills.record_kill(monster, killer, pet_kill);
-
- if (mons_has_ench(monster, ENCH_ABJ_I, ENCH_ABJ_VI))
- {
- if (mons_weight(mons_species(monster->type)))
- {
- if (monster->type == MONS_SIMULACRUM_SMALL
- || monster->type == MONS_SIMULACRUM_LARGE)
- {
- simple_monster_message( monster, " vaporizes!" );
-
- place_cloud( CLOUD_COLD_MON, monster->x, monster->y,
- 1 + random2(3) );
- }
- else
- {
- simple_monster_message(monster,
- "'s corpse disappears in a puff of smoke!");
-
- place_cloud( CLOUD_GREY_SMOKE_MON + random2(3),
- monster->x, monster->y, 1 + random2(3) );
- }
- }
- }
- else
- {
- // have to add case for disintegration effect here? {dlb}
- place_monster_corpse(monster);
- }
- }
-
- monster_drop_ething(monster,
- killer == KILL_YOU_MISSILE
- || killer == KILL_YOU
- || pet_kill);
- monster_cleanup(monster);
-} // end monster_die
-
-void monster_cleanup(struct monsters *monster)
-{
- unsigned int monster_killed = monster_index(monster);
- int dmi = 0;
-
- for (unsigned char j = 0; j < NUM_MON_ENCHANTS; j++)
- monster->enchantment[j] = ENCH_NONE;
-
- monster->flags = 0;
- monster->type = -1;
- monster->hit_points = 0;
- monster->max_hit_points = 0;
- monster->hit_dice = 0;
- monster->armour_class = 0;
- monster->evasion = 0;
- monster->speed_increment = 0;
- monster->attitude = ATT_HOSTILE;
- monster->behaviour = BEH_SLEEP;
- monster->foe = MHITNOT;
-
- mgrd[monster->x][monster->y] = NON_MONSTER;
-
- for (dmi = MSLOT_GOLD; dmi >= MSLOT_WEAPON; dmi--)
- {
- monster->inv[dmi] = NON_ITEM;
- }
-
- for (dmi = 0; dmi < MAX_MONSTERS; dmi++)
- {
- if (menv[dmi].foe == monster_killed)
- menv[dmi].foe = MHITNOT;
- }
-
- if (you.pet_target == monster_killed)
- you.pet_target = MHITNOT;
-
-} // end monster_cleanup()
-
-static bool jelly_divide(struct monsters * parent)
-{
- int jex = 0, jey = 0; // loop variables {dlb}
- bool foundSpot = false; // to rid code of hideous goto {dlb}
- struct monsters *child = 0; // NULL - value determined with loop {dlb}
-
- if (!mons_class_flag( parent->type, M_SPLITS ) || parent->hit_points == 1)
- return (false);
-
- // first, find a suitable spot for the child {dlb}:
- for (jex = -1; jex < 3; jex++)
- {
- // loop moves beyond those tiles contiguous to parent {dlb}:
- if (jex > 1)
- return (false);
-
- for (jey = -1; jey < 2; jey++)
- {
- // 10-50 for now - must take clouds into account:
- if (mgrd[parent->x + jex][parent->y + jey] == NON_MONSTER
- && !grid_is_solid(grd[parent->x + jex][parent->y + jey])
- && (parent->x + jex != you.x_pos || parent->y + jey != you.y_pos))
- {
- foundSpot = true;
- break;
- }
- }
-
- if (foundSpot)
- break;
- } /* end of for jex */
-
- int k = 0; // must remain outside loop that follows {dlb}
-
- // 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;
-
- // create child {dlb}:
- // this is terribly partial and really requires
- // more thought as to generation ... {dlb}
- child->type = parent->type;
- child->hit_dice = parent->hit_dice;
- child->hit_points = parent->hit_points;
- child->max_hit_points = child->hit_points;
- child->armour_class = parent->armour_class;
- child->evasion = parent->evasion;
- child->speed = parent->speed;
- child->speed_increment = 70 + random2(5);
- child->behaviour = parent->behaviour; /* Look at this! */
- child->foe = parent->foe;
- child->attitude = parent->attitude;
-
- child->x = parent->x + jex;
- child->y = parent->y + jey;
-
- mgrd[child->x][child->y] = k;
-
- if (!simple_monster_message(parent, " splits in two!"))
- {
- if (!silenced(parent->x, parent->y) || !silenced(child->x, child->y))
- mpr("You hear a squelching noise.", MSGCH_SOUND);
- }
-
- return (true);
-} // end jelly_divde()
-
-// if you're invis and throw/zap whatever, alerts menv to your position
-void alert_nearby_monsters(void)
-{
- struct monsters *monster = 0; // NULL {dlb}
-
- for (int it = 0; it < MAX_MONSTERS; it++)
- {
- monster = &menv[it];
-
- // Judging from the above comment, this function isn't
- // intended to wake up monsters, so we're only going to
- // alert monsters that aren't sleeping. For cases where an
- // event should wake up monsters and alert them, I'd suggest
- // calling noisy() before calling this function. -- bwr
- if (monster->type != -1
- && monster->behaviour != BEH_SLEEP
- && mons_near(monster))
- {
- behaviour_event( monster, ME_ALERT, MHITYOU );
- }
- }
-} // end alert_nearby_monsters()
-
-static bool valid_morph( struct monsters *monster, int new_mclass )
-{
- unsigned char current_tile = grd[monster->x][monster->y];
-
- // morph targets are _always_ "base" classes, not derived ones.
- new_mclass = mons_species(new_mclass);
-
- /* various inappropriate polymorph targets */
- if (mons_class_holiness( new_mclass ) != mons_holiness( monster )
- || mons_class_flag( new_mclass, M_NO_EXP_GAIN ) // not helpless
- || new_mclass == mons_species( monster->type ) // must be different
- || new_mclass == MONS_PROGRAM_BUG
- || new_mclass == MONS_SHAPESHIFTER
- || new_mclass == MONS_GLOWING_SHAPESHIFTER
-
- // These shouldn't happen anyways (demons unaffected + holiness check),
- // but if we ever do have polydemon, these will be needed:
- || new_mclass == MONS_PLAYER_GHOST
- || new_mclass == MONS_PANDEMONIUM_DEMON
- || (new_mclass >= MONS_GERYON && new_mclass <= MONS_ERESHKIGAL))
- {
- return (false);
- }
-
- /* Not fair to instakill a monster like this --
- order of evaluation of inner conditional important */
- if (current_tile == DNGN_LAVA || current_tile == DNGN_DEEP_WATER)
- {
- if (!mons_class_flies(new_mclass)
- || monster_habitat(new_mclass) != current_tile)
- {
- return (false);
- }
- }
-
- // not fair to strand a water monster on dry land, either. :)
- if (monster_habitat(new_mclass) == DNGN_DEEP_WATER
- && current_tile != DNGN_DEEP_WATER
- && current_tile != DNGN_SHALLOW_WATER)
- {
- return (false);
- }
-
- // and putting lava monsters on non-lava sqaures is a no-no, too
- if (monster_habitat(new_mclass) == DNGN_LAVA && current_tile != DNGN_LAVA)
- return (false);
-
- return (true);
-} // end valid_morph()
-
-// note that power is (as of yet) unused within this function -
-// may be worthy of consideration of later implementation, though,
-// so I'll still let the parameter exist for the time being {dlb}
-bool monster_polymorph( struct monsters *monster, int targetc, int power )
-{
- char str_polymon[INFO_SIZE] = ""; // cannot use info[] here {dlb}
- bool player_messaged = false;
- int source_power, target_power, relax;
- int tries = 1000;
-
- UNUSED( power );
-
- // Used to be mons_power, but that just returns hit_dice
- // for the monster class. By using the current hit dice
- // the player gets the opportunity to use draining more
- // effectively against shapeshifters. -- bwr
- source_power = monster->hit_dice;
- relax = 2;
-
- if (targetc == RANDOM_MONSTER)
- {
- do
- {
- targetc = random2( NUM_MONSTERS );
-
- // valid targets are always base classes
- targetc = mons_species( targetc );
-
- target_power = mons_power( targetc );
-
- if (one_chance_in(100))
- relax++;
-
- if (relax > 50)
- return (simple_monster_message( monster, " shudders." ));
- }
- while (tries-- && (!valid_morph( monster, targetc )
- || target_power < source_power - relax
- || target_power > source_power + (relax * 3) / 2));
- }
-
- if(!valid_morph( monster, targetc )) {
- strcat( str_polymon, " looks momentarily different.");
- player_messaged = simple_monster_message( monster, str_polymon );
- return (player_messaged);
- }
-
- // messaging: {dlb}
- bool invis = mons_class_flag( targetc, M_INVIS )
- || mons_has_ench( monster, ENCH_INVIS );
-
- if (mons_has_ench( monster, ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER ))
- strcat( str_polymon, " changes into " );
- else if (targetc == MONS_PULSATING_LUMP)
- strcat( str_polymon, " degenerates into " );
- else
- strcat( str_polymon, " evaporates and reforms as " );
-
- if (invis && !player_see_invis())
- strcat( str_polymon, "something you cannot see!" );
- else
- {
- strcat( str_polymon, monam( 250, targetc, !invis, DESC_NOCAP_A ) );
-
- if (targetc == MONS_PULSATING_LUMP)
- strcat( str_polymon, " of flesh" );
-
- strcat( str_polymon, "!" );
- }
-
- player_messaged = simple_monster_message( monster, str_polymon );
-
- // the actual polymorphing:
- int old_hp = monster->hit_points;
- int old_hp_max = monster->max_hit_points;
-
- /* deal with mons_sec */
- monster->type = targetc;
- monster->number = 250;
-
- int abj = mons_has_ench( monster, ENCH_ABJ_I, ENCH_ABJ_VI );
- int shifter = mons_has_ench( monster, ENCH_GLOWING_SHAPESHIFTER,
- ENCH_SHAPESHIFTER );
-
- // Note: define_monster() will clear out all enchantments! -- bwr
- define_monster( monster_index(monster) );
-
- // put back important enchantments:
- if (abj != ENCH_NONE)
- mons_add_ench( monster, abj );
-
- if (shifter != ENCH_NONE)
- mons_add_ench( monster, shifter );
-
- if (mons_class_flag( monster->type, M_INVIS ))
- mons_add_ench( monster, ENCH_INVIS );
-
- monster->hit_points = monster->max_hit_points
- * ((old_hp * 100) / old_hp_max) / 100
- + random2(monster->max_hit_points);
-
- if (monster->hit_points > monster->max_hit_points)
- monster->hit_points = monster->max_hit_points;
-
- monster->speed_increment = 67 + random2(6);
-
- monster_drop_ething(monster);
-
- return (player_messaged);
-} // end monster_polymorph()
-
-void monster_blink(struct monsters *monster)
-{
- int nx, ny;
-
- if (!random_near_space(monster->x, monster->y, nx, ny,
- false, false))
- return;
-
- mgrd[monster->x][monster->y] = NON_MONSTER;
-
- monster->x = nx;
- monster->y = ny;
-
- mgrd[nx][ny] = monster_index(monster);
-} // end monster_blink()
-
-// allow_adjacent: allow target to be adjacent to origin
-// restrict_LOS: restict target to be within PLAYER line of sight
-bool random_near_space(int ox, int oy, int &tx, int &ty, bool allow_adjacent,
- bool restrict_LOS)
-{
- int tries = 0;
-
- do
- {
- tx = ox - 6 + random2(14);
- ty = oy - 6 + random2(14);
-
- // origin is not 'near'
- if (tx == ox && ty == oy)
- continue;
-
- tries++;
-
- if (tries > 149)
- break;
- }
- while ((!see_grid(tx, ty) && restrict_LOS)
- || grd[tx][ty] < DNGN_SHALLOW_WATER
- || mgrd[tx][ty] != NON_MONSTER
- || (!allow_adjacent && distance(ox, oy, tx, ty) <= 2));
-
- return (tries < 150);
-} // end random_near_space()
-
-static bool habitat_okay( struct monsters *monster, int targ )
-{
- bool ret = false;
- const int habitat = monster_habitat( monster->type );
-
- if (mons_flies( monster ))
- {
- // flying monsters don't care
- ret = true;
- }
- else if (mons_class_flag( monster->type, M_AMPHIBIOUS )
- && (targ == DNGN_DEEP_WATER || targ == DNGN_SHALLOW_WATER))
- {
- // Amphibious creatures are "land" by default in mon-data,
- // we allow them to swim here. -- bwr
- ret = true;
- }
- else if (monster->type == MONS_WATER_ELEMENTAL && targ >= DNGN_DEEP_WATER)
- {
- // water elementals can crawl out over the land
- ret = true;
- }
- else if (habitat == DNGN_FLOOR
- && (targ >= DNGN_FLOOR || targ == DNGN_SHALLOW_WATER))
- {
- // FLOOR habitat monster going to a non-bad place
- ret = true;
- }
- else if (habitat == DNGN_DEEP_WATER
- && (targ == DNGN_DEEP_WATER || targ == DNGN_SHALLOW_WATER))
- {
- // Water monster to water
- ret = true;
- }
- else if (habitat == DNGN_LAVA && targ == DNGN_LAVA)
- {
- // Lava monster to lava
- ret = true;
- }
-
- return (ret);
-}
-
-// This doesn't really swap places, it just sets the monster's
-// position equal to the player (the player has to be moved afterwards).
-// It also has a slight problem with the fact the if the player is
-// levitating over an inhospitable habitat for the monster the monster
-// will be put in a place it normally couldn't go (this could be a
-// feature because it prevents insta-killing). In order to prevent
-// that little problem, we go looking for a square for the monster
-// to "scatter" to instead... and if we can't find one the monster
-// just refuses to be swapped (not a bug, this is intentionally
-// avoiding the insta-kill). Another option is to look a bit
-// wider for a vaild square (either by a last attempt blink, or
-// by looking at a wider radius)... insta-killing should be a
-// last resort in this function (especially since Tome, Dig, and
-// Summoning can be used to set up death traps). If worse comes
-// to worse, at least consider making the Swap spell not work
-// when the player is over lava or water (if the player want's to
-// swap pets to their death, we can let that go). -- bwr
-bool swap_places(struct monsters *monster)
-{
- bool swap;
-
- int loc_x = you.x_pos;
- int loc_y = you.y_pos;
-
- swap = habitat_okay( monster, grd[loc_x][loc_y] );
-
- // chose an appropiate habitat square at random around the target.
- if (!swap)
- {
- int num_found = 0;
- int temp_x, temp_y;
-
- for (int x = -1; x <= 1; x++)
- {
- temp_x = you.x_pos + x;
- if (temp_x < 0 || temp_x >= GXM)
- continue;
-
- for (int y = -1; y <= 1; y++)
- {
- if (x == 0 && y == 0)
- continue;
-
- temp_y = you.y_pos + y;
- if (temp_y < 0 || temp_y >= GYM)
- continue;
-
- if (mgrd[temp_x][temp_y] == NON_MONSTER
- && habitat_okay( monster, grd[temp_x][temp_y] ))
- {
- // Found an appropiate space... check if we
- // switch the current choice to this one.
- num_found++;
- if (one_chance_in(num_found))
- {
- loc_x = temp_x;
- loc_y = temp_y;
- }
- }
- }
- }
-
- if (num_found)
- swap = true;
- }
-
- if (swap)
- {
- mpr("You swap places.");
-
- mgrd[monster->x][monster->y] = NON_MONSTER;
-
- monster->x = loc_x;
- monster->y = loc_y;
-
- mgrd[monster->x][monster->y] = monster_index(monster);
- }
- else
- {
- // Might not be ideal, but it's better that insta-killing
- // the monster... maybe try for a short blinki instead? -- bwr
- simple_monster_message( monster, " resists." );
- }
-
- return (swap);
-} // end swap_places()
-
-void print_wounds(struct monsters *monster)
-{
- // prevents segfault -- cannot use info[] here {dlb}
- char str_wound[INFO_SIZE];
- int dam_level;
-
- if (monster->type == -1)
- return;
-
- if (monster->hit_points == monster->max_hit_points
- || monster->hit_points < 1)
- {
- return;
- }
-
- if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS))
- return;
-
- strcpy(str_wound, " is ");
-
- if (monster->hit_points <= monster->max_hit_points / 6)
- {
- strcat(str_wound, "almost ");
- strcat(str_wound, wounded_damaged(monster->type) ? "destroyed"
- : "dead");
- dam_level = MDAM_ALMOST_DEAD;
- }
- else
- {
- if (monster->hit_points <= monster->max_hit_points / 6)
- {
- strcat(str_wound, "horribly ");
- dam_level = MDAM_HORRIBLY_DAMAGED;
- }
- else if (monster->hit_points <= monster->max_hit_points / 3)
- {
- strcat(str_wound, "heavily " );
- dam_level = MDAM_HEAVILY_DAMAGED;
- }
- else if (monster->hit_points <= 3 * (monster-> max_hit_points / 4))
- {
- strcat(str_wound, "moderately ");
- dam_level = MDAM_MODERATELY_DAMAGED;
- }
- else
- {
- strcat(str_wound, "lightly ");
- dam_level = MDAM_LIGHTLY_DAMAGED;
- }
-
- strcat(str_wound, wounded_damaged(monster->type) ? "damaged"
- : "wounded");
- }
-
- strcat(str_wound, ".");
- simple_monster_message(monster, str_wound, MSGCH_MONSTER_DAMAGE, dam_level);
-} // end print_wounds()
-
-// (true == 'damaged') [constructs, undead, etc.]
-// and (false == 'wounded') [living creatures, etc.] {dlb}
-bool wounded_damaged(int wound_class)
-{
- // this schema needs to be abstracted into real categories {dlb}:
- const int holy = mons_class_holiness(wound_class);
- if (holy == MH_UNDEAD || holy == MH_NONLIVING || holy == MH_PLANT)
- return (true);
-
- return (false);
-} // end wounded_damaged()
-
-//---------------------------------------------------------------
-//
-// 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( struct monsters *mon, int event, int src,
- int src_x, int src_y )
-{
- bool isSmart = (mons_intel(mon->type) > I_ANIMAL);
- bool isFriendly = mons_friendly(mon);
- bool sourceFriendly = false;
- bool setTarget = false;
- bool breakCharm = false;
-
- if (src == MHITYOU)
- sourceFriendly = true;
- else if (src != MHITNOT)
- sourceFriendly = mons_friendly( &menv[src] );
-
- switch(event)
- {
- case ME_DISTURB:
- // assumes disturbed by noise...
- if (mon->behaviour == BEH_SLEEP)
- mon->behaviour = BEH_WANDER;
-
- // A bit of code to make Project Noise actually so
- // 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 || mon->behaviour == BEH_WANDER)
- {
- mon->target_x = src_x;
- mon->target_y = src_y;
- }
- break;
-
- case ME_WHACK:
- case ME_ANNOY:
- // will turn monster against <src>, unless they
- // are BOTH friendly and stupid. Hitting someone
- // over the head, of course, always triggers this code.
- if (isFriendly != sourceFriendly || isSmart || event == ME_WHACK)
- {
- mon->foe = src;
-
- if (mon->behaviour != BEH_CORNERED)
- mon->behaviour = BEH_SEEK;
-
- if (src == MHITYOU)
- {
- mon->attitude = ATT_HOSTILE;
- breakCharm = true;
- }
- }
-
- // now set target x,y so that monster can whack
- // back (once) at an invisible foe
- if (event == ME_WHACK)
- setTarget = true;
- break;
-
- case ME_ALERT:
- // will alert monster to <src> and turn them
- // against them, unless they have a current foe.
- // it won't turn friends hostile either.
- if (mon->behaviour != BEH_CORNERED)
- mon->behaviour = BEH_SEEK;
-
- if (mon->foe == MHITNOT)
- mon->foe = src;
- break;
-
- case ME_SCARE:
- mon->foe = src;
- mon->behaviour = BEH_FLEE;
- // assume monsters know where to run from, even
- // if player is invisible.
- setTarget = true;
- break;
-
- case ME_CORNERED:
- // just set behaviour.. foe doesn't change.
- if (mon->behaviour != BEH_CORNERED && !mons_has_ench(mon,ENCH_FEAR))
- simple_monster_message(mon, " turns to fight!");
-
- mon->behaviour = BEH_CORNERED;
- break;
-
- case ME_EVAL:
- default:
- break;
- }
-
- if (setTarget)
- {
- if (src == MHITYOU)
- {
- mon->target_x = you.x_pos;
- mon->target_y = you.y_pos;
- mon->attitude = ATT_HOSTILE;
- }
- else if (src != MHITNOT)
- {
- mon->target_x = menv[src].x;
- mon->target_y = menv[src].y;
- }
- }
-
- // now, break charms if appropriate
- if (breakCharm)
- mons_del_ench( mon, ENCH_CHARM );
-
- // do any resultant foe or state changes
- handle_behaviour( mon );
-}
-
-//---------------------------------------------------------------
-//
-// handle_behaviour
-//
-// 1. Evalutates current AI state
-// 2. Sets monster targetx,y based on current foe
-//
-//---------------------------------------------------------------
-static void handle_behaviour(struct monsters *mon)
-{
- bool changed = true;
- bool isFriendly = mons_friendly(mon);
- bool proxPlayer = mons_near(mon);
- 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->type) > I_ANIMAL);
- bool isScared = mons_has_ench(mon, ENCH_FEAR);
-
- // immobility logic stolen from later on in handle_monster().. argh! --gdl
- bool isMobile = !(mon->type == MONS_OKLOB_PLANT
- || mon->type == MONS_CURSE_SKULL
- || (mon->type >= MONS_CURSE_TOE
- && mon->type <= MONS_POTION_MIMIC));
-
- // check for confusion -- early out.
- if (mons_has_ench(mon, ENCH_CONFUSION))
- {
- mon->target_x = 10 + random2(GXM - 10);
- mon->target_y = 10 + random2(GYM - 10);
- return;
- }
-
- // validate current target exists
- if (mon->foe != MHITNOT && mon->foe != MHITYOU)
- {
- if (menv[mon->foe].type == -1)
- mon->foe = MHITNOT;
- }
-
- // change proxPlayer depending on invisibility and standing
- // in shallow water
- if (proxPlayer && you.invis)
- {
- if (!mons_player_visible( mon ))
- proxPlayer = false;
-
- // must be able to see each other
- if (!see_grid(mon->x, mon->y))
- proxPlayer = false;
-
- // now, the corollary to that is that sometimes, if a
- // player is right next to a monster, they will 'see'
- if (grid_distance( you.x_pos, you.y_pos, mon->x, mon->y ) == 1
- && one_chance_in(3))
- {
- proxPlayer = true;
- }
- }
-
- // set friendly target, if they don't already have one
- if (isFriendly
- && you.pet_target != MHITNOT
- && (mon->foe == MHITNOT || mon->foe == MHITYOU))
- {
- mon->foe = you.pet_target;
- }
-
- // monsters do not attack themselves {dlb}
- if (mon->foe == monster_index(mon))
- mon->foe = MHITNOT;
-
- // friendly monsters do not attack other friendly monsters
- if (mon->foe != MHITNOT && mon->foe != MHITYOU)
- {
- if (isFriendly && mons_friendly(&menv[mon->foe]))
- mon->foe = MHITNOT;
- }
-
- // unfriendly monsters fighting other monsters will usually
- // target the player, if they're healthy
- if (!isFriendly && mon->foe != MHITYOU && mon->foe != MHITNOT
- && proxPlayer && !one_chance_in(3) && isHealthy)
- {
- mon->foe = MHITYOU;
- }
-
- // validate target again
- if (mon->foe != MHITNOT && mon->foe != MHITYOU)
- {
- if (menv[mon->foe].type == -1)
- mon->foe = MHITNOT;
- }
-
- while (changed)
- {
- int foe_x = you.x_pos;
- int foe_y = you.y_pos;
-
- // evaluate these each time; they may change
- if (mon->foe == MHITNOT)
- proxFoe = false;
- else
- {
- if (mon->foe == MHITYOU)
- {
- foe_x = you.x_pos;
- foe_y = you.y_pos;
- proxFoe = proxPlayer; // take invis into account
- }
- else
- {
- proxFoe = mons_near(mon, mon->foe);
-
- if (!mons_monster_visible( mon, &menv[mon->foe] ))
- proxFoe = false;
-
- // XXX monsters will rely on player LOS -- GDL
- if (!see_grid(menv[mon->foe].x, menv[mon->foe].y))
- proxFoe = false;
-
- foe_x = menv[mon->foe].x;
- foe_y = menv[mon->foe].y;
- }
- }
-
- // track changes to state; attitude never changes here.
- unsigned int new_beh = mon->behaviour;
- unsigned int new_foe = mon->foe;
-
- // take care of monster state changes
- switch(mon->behaviour)
- {
- case BEH_SLEEP:
- // default sleep state
- mon->target_x = mon->x;
- mon->target_y = mon->y;
- new_foe = MHITNOT;
- break;
-
- case BEH_SEEK:
- // no foe? then wander or seek the player
- if (mon->foe == MHITNOT)
- {
- if (!proxPlayer)
- new_beh = BEH_WANDER;
- else
- {
- new_foe = MHITYOU;
- mon->target_x = you.x_pos;
- mon->target_y = you.y_pos;
- }
-
- break;
- }
-
- // foe gone out of LOS?
- if (!proxFoe)
- {
- if (isFriendly)
- {
- new_foe = MHITYOU;
- mon->target_x = foe_x;
- mon->target_y = foe_y;
- break;
- }
-
- if (mon->foe_memory > 0 && mon->foe != MHITNOT)
- {
- // 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->x == mon->target_x &&
- mon->y == mon->target_y)
- {
- if (mon->foe == MHITYOU)
- {
- if (check_awaken(monster_index(mon)))
- {
- mon->target_x = you.x_pos;
- mon->target_y = you.y_pos;
- }
- else
- mon->foe_memory = 1;
- }
- else
- {
- if (coinflip()) // XXX: cheesy!
- {
- mon->target_x = menv[mon->foe].x;
- mon->target_y = menv[mon->foe].y;
- }
- else
- mon->foe_memory = 1;
- }
- }
-
- // either keep chasing, or start
- // wandering.
- if (mon->foe_memory < 2)
- {
- mon->foe_memory = 0;
- new_beh = BEH_WANDER;
- }
- break;
- }
-
- // hack: smarter monsters will
- // tend to pursue the player longer.
- int memory;
- switch(mons_intel(monster_index(mon)))
- {
- case I_HIGH:
- memory = 100 + random2(200);
- break;
- case I_NORMAL:
- memory = 50 + random2(100);
- break;
- case I_ANIMAL:
- default:
- memory = 25 + random2(75);
- break;
- case I_INSECT:
- memory = 10 + random2(50);
- break;
- }
-
- mon->foe_memory = memory;
- break; // from case
- }
-
- // monster can see foe: continue 'tracking'
- // by updating target x,y
- if (mon->foe == MHITYOU)
- {
- // sometimes, your friends will wander a bit.
- if (isFriendly && one_chance_in(8))
- {
- mon->target_x = 10 + random2(GXM - 10);
- mon->target_y = 10 + random2(GYM - 10);
- mon->foe = MHITNOT;
- new_beh = BEH_WANDER;
- }
- else
- {
- mon->target_x = you.x_pos;
- mon->target_y = you.y_pos;
- }
- }
- else
- {
- mon->target_x = menv[mon->foe].x;
- mon->target_y = menv[mon->foe].y;
- }
-
- if (isHurt && !isSmart && isMobile)
- new_beh = BEH_FLEE;
- break;
-
- case BEH_WANDER:
- // 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 && !testbits( mon->flags, MF_BATTY ))
- {
- new_beh = BEH_SEEK;
- break;
- }
-
- // default wander behaviour
- //
- // 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 ((mon->x == mon->target_x && mon->y == mon->target_y)
- || one_chance_in(20)
- || testbits( mon->flags, MF_BATTY ))
- {
- mon->target_x = 10 + random2(GXM - 10);
- mon->target_y = 10 + random2(GYM - 10);
- }
-
- // during their wanderings, monsters will
- // eventually relax their guard (stupid
- // ones will do so faster, smart monsters
- // have longer memories
- if (!proxFoe && mon->foe != MHITNOT)
- {
- if (one_chance_in( isSmart ? 60 : 20 ))
- new_foe = MHITNOT;
- }
- 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 (proxFoe)
- {
- // try to flee _from_ the correct position
- mon->target_x = foe_x;
- mon->target_y = foe_y;
- }
- break;
-
- case BEH_CORNERED:
- if (isHealthy)
- new_beh = BEH_SEEK;
-
- // foe gone out of LOS?
- if (!proxFoe)
- {
- if (isFriendly || proxPlayer)
- new_foe = MHITYOU;
- else
- new_beh = BEH_WANDER;
- }
- else
- {
- mon->target_x = foe_x;
- mon->target_y = foe_y;
- }
- 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;
- }
-} // end handle_behaviour()
-
-// note that this function *completely* blocks messaging for monsters
-// distant or invisible to the player ... look elsewhere for a function
-// permitting output of "It" messages for the invisible {dlb}
-// INtentionally avoids info and str_pass now. -- bwr
-bool simple_monster_message(struct monsters *monster, const char *event,
- int channel, int param)
-{
- char buff[INFO_SIZE];
-
- if (mons_near( monster )
- && (channel == MSGCH_MONSTER_SPELL || player_monster_visible(monster)))
- {
- snprintf( buff, sizeof(buff), "%s%s",
- ptr_monam(monster, DESC_CAP_THE), event );
-
- mpr( buff, channel, param );
- return (true);
- }
-
- return (false);
-} // end simple_monster_message()
-
-// used to adjust time durations in handle_enchantment() for monster speed
-static inline int mod_speed( int val, int speed )
-{
- return (speed ? (val * 10) / speed : val);
-}
-
-static bool handle_enchantment(struct monsters *monster)
-{
- const int habitat = monster_habitat( monster->type );
- bool died = false;
- int grid;
- int poisonval;
- int dam;
- int tmp;
-
- // Yes, this is the speed we want. This function will be called in
- // two curcumstances: (1) the monster can move and have enough energy,
- // and (2) the monster cannot move (speed == 0) and the monster loop
- // is running.
- //
- // In the first case we don't have to figure in the player's time,
- // since the rate of call to this function already does that (ie.
- // a bat would get here 6 times in 2 normal player turns, and if
- // the player was twice as fast it would be 6 times every four player
- // moves. So the only speed we care about is the monster vs the
- // absolute time frame.
- //
- // In the second case, we're hacking things so that plants can suffer
- // from sticky flame. The rate of call in this case is once every
- // player action... so the time_taken by the player is the ratio to
- // the absolute time frame.
- //
- // This will be used below for poison and sticky flame so that the
- // damage is apparently in the absolute time frame. This is done
- // by scaling the damage and the chance that the effect goes away.
- // The result is that poison on a regular monster will be doing
- // 1d3 damage every two rounds, and last eight rounds, and on
- // a bat the same poison will be doing 1/3 the damage each action
- // it gets (the mod fractions are randomized in), will have three
- // turns to the other monster's one, and the effect will survive
- // 3 times as many calls to this function (ie 8 rounds * 3 calls).
- //
- // -- bwr
- const int speed = (monster->speed == 0) ? you.time_taken : monster->speed;
-
- for (int p = 0; p < NUM_MON_ENCHANTS && !died; p++)
- {
- switch (monster->enchantment[p])
- {
- case ENCH_SLOW:
- if (random2(250) <= mod_speed( monster->hit_dice + 10, speed ))
- mons_del_ench(monster, ENCH_SLOW);
- break;
-
- case ENCH_HASTE:
- if (random2(1000) < mod_speed( 25, speed ))
- mons_del_ench(monster, ENCH_HASTE);
- break;
-
- case ENCH_FEAR:
- if (random2(150) <= mod_speed( monster->hit_dice + 5, speed ))
- mons_del_ench(monster, ENCH_FEAR);
- break;
-
- case ENCH_CONFUSION:
- if (random2(120) < mod_speed( monster->hit_dice + 5, speed ))
- {
- // don't delete perma-confusion
- if (!mons_class_flag(monster->type, M_CONFUSED))
- mons_del_ench(monster, ENCH_CONFUSION);
- }
- break;
-
- case ENCH_INVIS:
- if (random2(1000) < mod_speed( 25, speed ))
- {
- // don't delete perma-invis
- if (!mons_class_flag( monster->type, M_INVIS ))
- mons_del_ench(monster, ENCH_INVIS);
- }
- break;
-
- case ENCH_SUBMERGED:
- // not even air elementals unsubmerge into clouds
- if (env.cgrid[monster->x][monster->y] != EMPTY_CLOUD)
- break;
-
- // Air elementals are a special case, as their
- // submerging in air isn't up to choice. -- bwr
- if (monster->type == MONS_AIR_ELEMENTAL)
- {
- heal_monster( monster, 1, one_chance_in(5) );
-
- if (one_chance_in(5))
- mons_del_ench( monster, ENCH_SUBMERGED );
-
- break;
- }
-
- // Now we handle the others:
- grid = grd[monster->x][monster->y];
-
- // Badly injured monsters prefer to stay submerged...
- // electrical eels and lava snakes have ranged attacks
- // and are more likely to surface. -- bwr
- if (habitat == DNGN_FLOOR || habitat != grid)
- mons_del_ench( monster, ENCH_SUBMERGED ); // forced to surface
- else if (monster->hit_points <= monster->max_hit_points / 2)
- break;
- else if (((monster->type == MONS_ELECTRICAL_EEL
- || monster->type == MONS_LAVA_SNAKE)
- && (random2(1000) < mod_speed( 20, speed )
- || (mons_near(monster)
- && monster->hit_points == monster->max_hit_points
- && !one_chance_in(10))))
- || random2(5000) < mod_speed( 10, speed ))
- {
- mons_del_ench( monster, ENCH_SUBMERGED );
- }
- break;
-
- case ENCH_POISON_I:
- case ENCH_POISON_II:
- case ENCH_POISON_III:
- case ENCH_POISON_IV:
- case ENCH_YOUR_POISON_I:
- case ENCH_YOUR_POISON_II:
- case ENCH_YOUR_POISON_III:
- case ENCH_YOUR_POISON_IV:
- poisonval = monster->enchantment[p] - ENCH_POISON_I;
-
- if (poisonval < 0 || poisonval > 3)
- poisonval = monster->enchantment[p] - ENCH_YOUR_POISON_I;
-
- dam = (poisonval >= 3) ? 1 : 0;
-
- if (coinflip())
- dam += roll_dice( 1, poisonval + 2 );
-
- if (mons_res_poison(monster) < 0)
- dam += roll_dice( 2, poisonval ) - 1;
-
- // We adjust damage for monster speed (since this is applied
- // only when the monster moves), and we handle the factional
- // part as well (so that speed 30 creatures will take damage).
- dam *= 10;
- dam = (dam / speed) + ((random2(speed) < (dam % speed)) ? 1 : 0);
-
- if (dam > 0)
- {
- hurt_monster( monster, dam );
-
-#if DEBUG_DIAGNOSTICS
- // for debugging, we don't have this silent.
- simple_monster_message( monster, " takes poison damage.",
- MSGCH_DIAGNOSTICS );
- snprintf( info, INFO_SIZE, "poison damage: %d", dam );
- mpr( info, MSGCH_DIAGNOSTICS );
-#endif
-
- if (monster->hit_points < 1)
- {
- monster_die(monster,
- ((monster->enchantment[p] < ENCH_POISON_I)
- ? KILL_YOU : KILL_MISC), 0);
- died = true;
- }
- }
-
- // chance to get over poison (1 in 8, modified for speed)
- if (random2(1000) < mod_speed( 125, speed ))
- {
- if (monster->enchantment[p] == ENCH_POISON_I)
- mons_del_ench(monster, ENCH_POISON_I);
- else if (monster->enchantment[p] == ENCH_YOUR_POISON_I)
- mons_del_ench(monster, ENCH_YOUR_POISON_I);
- else
- monster->enchantment[p]--;
- }
- break;
-
- case ENCH_YOUR_ROT_I:
- if (random2(1000) < mod_speed( 250, speed ))
- mons_del_ench(monster, ENCH_YOUR_ROT_I);
- else if (monster->hit_points > 1
- && random2(1000) < mod_speed( 333, speed ))
- {
- hurt_monster(monster, 1);
- }
- break;
-
- //jmf: FIXME: if (undead) make_small_rot_cloud();
- case ENCH_YOUR_ROT_II:
- case ENCH_YOUR_ROT_III:
- case ENCH_YOUR_ROT_IV:
- if (monster->hit_points > 1
- && random2(1000) < mod_speed( 333, speed ))
- {
- hurt_monster(monster, 1);
- }
-
- if (random2(1000) < mod_speed( 250, speed ))
- monster->enchantment[p]--;
- break;
-
- case ENCH_BACKLIGHT_I:
- if (random2(1000) < mod_speed( 100, speed ))
- mons_del_ench( monster, ENCH_BACKLIGHT_I );
- break;
-
- case ENCH_BACKLIGHT_II:
- case ENCH_BACKLIGHT_III:
- case ENCH_BACKLIGHT_IV:
- if (random2(1000) < mod_speed( 200, speed ))
- monster->enchantment[p]--;
- break;
-
- // assumption: mons_res_fire has already been checked
- case ENCH_STICKY_FLAME_I:
- case ENCH_STICKY_FLAME_II:
- case ENCH_STICKY_FLAME_III:
- case ENCH_STICKY_FLAME_IV:
- case ENCH_YOUR_STICKY_FLAME_I:
- case ENCH_YOUR_STICKY_FLAME_II:
- case ENCH_YOUR_STICKY_FLAME_III:
- case ENCH_YOUR_STICKY_FLAME_IV:
- dam = roll_dice( 2, 4 ) - 1;
-
- if (mons_res_fire( monster ) < 0)
- dam += roll_dice( 2, 5 ) - 1;
-
- // We adjust damage for monster speed (since this is applied
- // only when the monster moves), and we handle the factional
- // part as well (so that speed 30 creatures will take damage).
- dam *= 10;
- dam = (dam / speed) + ((random2(speed) < (dam % speed)) ? 1 : 0);
-
- if (dam > 0)
- {
- hurt_monster( monster, dam );
- simple_monster_message(monster, " burns!");
-
-#if DEBUG_DIAGNOSTICS
- snprintf( info, INFO_SIZE, "sticky flame damage: %d", dam );
- mpr( info, MSGCH_DIAGNOSTICS );
-#endif
-
- if (monster->hit_points < 1)
- {
- monster_die(monster,
- ((monster->enchantment[p] < ENCH_STICKY_FLAME_I)
- ? KILL_YOU : KILL_MISC), 0);
- died = true;
- }
- }
-
- // chance to get over sticky flame (1 in 5, modified for speed)
- if (random2(1000) < mod_speed( 200, speed ))
- {
- if (monster->enchantment[p] == ENCH_STICKY_FLAME_I)
- mons_del_ench( monster, ENCH_STICKY_FLAME_I );
- else if (monster->enchantment[p] == ENCH_YOUR_STICKY_FLAME_I)
- mons_del_ench( monster, ENCH_YOUR_STICKY_FLAME_I );
- else
- monster->enchantment[p]--;
- }
- break;
-
- case ENCH_SHORT_LIVED:
- // This should only be used for ball lightning -- bwr
- if (random2(1000) < mod_speed( 200, speed ))
- monster->hit_points = -1;
- break;
-
- // 19 is taken by summoning:
- // If these are changed, must also change abjuration
- case ENCH_ABJ_I:
- case ENCH_ABJ_II:
- case ENCH_ABJ_III:
- case ENCH_ABJ_IV:
- if (random2(1000) < mod_speed( 100, speed ))
- monster->enchantment[p]--;
-
- if (monster->enchantment[p] < ENCH_ABJ_I)
- {
- monster_die(monster, KILL_RESET, 0);
- died = true;
- }
- break;
-
- case ENCH_ABJ_V:
- if (random2(1000) < mod_speed( 20, speed ))
- monster->enchantment[p] = ENCH_ABJ_IV;
- break;
-
- case ENCH_ABJ_VI:
- if (random2(1000) < mod_speed( 10, speed ))
- monster->enchantment[p] = ENCH_ABJ_V;
- break;
-
- case ENCH_CHARM:
- if (random2(500) <= mod_speed( monster->hit_dice + 10, speed ))
- mons_del_ench(monster, ENCH_CHARM);
- break;
-
- case ENCH_GLOWING_SHAPESHIFTER: // this ench never runs out
- // number of actions is fine for shapeshifters
- if (monster->type == MONS_GLOWING_SHAPESHIFTER
- || random2(1000) < mod_speed( 250, speed ))
- {
- monster_polymorph(monster, RANDOM_MONSTER, 0);
- }
- break;
-
- case ENCH_SHAPESHIFTER: // this ench never runs out
- if (monster->type == MONS_SHAPESHIFTER
- || random2(1000) < mod_speed( 1000 / ((15 * monster->hit_dice) / 5), speed ))
- {
- monster_polymorph(monster, RANDOM_MONSTER, 0);
- }
- break;
-
- case ENCH_TP_I:
- mons_del_ench( monster, ENCH_TP_I );
- monster_teleport( monster, true );
- break;
-
- case ENCH_TP_II:
- case ENCH_TP_III:
- case ENCH_TP_IV:
- tmp = mod_speed( 1000, speed );
-
- if (tmp < 1000 && random2(1000) < tmp)
- monster->enchantment[p]--;
- else if (monster->enchantment[p] - tmp / 1000 >= ENCH_TP_I)
- {
- monster->enchantment[p] -= tmp / 1000;
- tmp %= 1000;
-
- if (random2(1000) < tmp)
- {
- if (monster->enchantment[p] > ENCH_TP_I)
- monster->enchantment[p]--;
- else
- {
- mons_del_ench( monster, ENCH_TP_I, ENCH_TP_IV );
- monster_teleport( monster, true );
- }
- }
- }
- else
- {
- mons_del_ench( monster, ENCH_TP_I, ENCH_TP_IV );
- monster_teleport( monster, true );
- }
- break;
-
- case ENCH_SLEEP_WARY:
- if (random2(1000) < mod_speed( 50, speed ))
- mons_del_ench(monster, ENCH_SLEEP_WARY);
- break;
- }
- }
-
- return (died);
-} // end handle_enchantment()
-
-//---------------------------------------------------------------
-//
-// handle_movement
-//
-// Move the monster closer to its target square.
-//
-//---------------------------------------------------------------
-static void handle_movement(struct monsters *monster)
-{
- int dx, dy;
-
- // some calculations
- if (monster->type == MONS_BORING_BEETLE && monster->foe == MHITYOU)
- {
- dx = you.x_pos - monster->x;
- dy = you.y_pos - monster->y;
- }
- else
- {
- dx = monster->target_x - monster->x;
- dy = monster->target_y - monster->y;
- }
-
- // move the monster:
- mmov_x = (dx > 0) ? 1 : ((dx < 0) ? -1 : 0);
- mmov_y = (dy > 0) ? 1 : ((dy < 0) ? -1 : 0);
-
- if (monster->behaviour == BEH_FLEE)
- {
- mmov_x *= -1;
- mmov_y *= -1;
- }
-
- // bounds check: don't let fleeing monsters try to run
- // off the map
- if (monster->target_x + mmov_x < 0 || monster->target_x + mmov_x >= GXM)
- mmov_x = 0;
-
- if (monster->target_y + mmov_y < 0 || monster->target_y + mmov_y >= GYM)
- mmov_y = 0;
-
- // now quit if we're can't move
- if (mmov_x == 0 && mmov_y == 0)
- return;
-
- // 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
- if (grid_distance( dx, dy, 0, 0 ) > 3)
- {
- if (abs(dx) > abs(dy))
- {
- // sometimes we'll just move parallel the x axis
- if (coinflip())
- mmov_y = 0;
- }
-
- if (abs(dy) > abs(dx))
- {
- // sometimes we'll just move parallel the y axis
- if (coinflip())
- mmov_x = 0;
- }
- }
-} // end handle_movement()
-
-//---------------------------------------------------------------
-//
-// handle_nearby_ability
-//
-// Gives monsters a chance to use a special ability when they're
-// next to the player.
-//
-//---------------------------------------------------------------
-static void handle_nearby_ability(struct monsters *monster)
-{
- if (!mons_near( monster )
- || monster->behaviour == BEH_SLEEP
- || mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- return;
- }
-
- if (mons_class_flag(monster->type, M_SPEAKS) && one_chance_in(21)
- && monster->behaviour != BEH_WANDER)
- {
- mons_speaks(monster);
- }
-
- switch (monster->type)
- {
- case MONS_SPATIAL_VORTEX:
- case MONS_KILLER_KLOWN:
- // used for colour (butterflies too, but they don't change)
- monster->number = random_colour();
- break;
-
- case MONS_GIANT_EYEBALL:
- if (coinflip() && !mons_friendly(monster)
- && monster->behaviour != BEH_WANDER)
- {
- simple_monster_message(monster, " stares at you.");
-
- if (you.paralysis < 10)
- you.paralysis += 2 + random2(3);
- }
- break;
-
- case MONS_EYE_OF_DRAINING:
- if (coinflip() && !mons_friendly(monster)
- && monster->behaviour != BEH_WANDER)
- {
- simple_monster_message(monster, " stares at you.");
-
- dec_mp(5 + random2avg(13, 3));
-
- heal_monster(monster, 10, true); // heh heh {dlb}
- }
- break;
-
- case MONS_LAVA_WORM:
- case MONS_LAVA_FISH:
- case MONS_LAVA_SNAKE:
- case MONS_SALAMANDER:
- case MONS_BIG_FISH:
- case MONS_GIANT_GOLDFISH:
- case MONS_ELECTRICAL_EEL:
- case MONS_JELLYFISH:
- case MONS_WATER_ELEMENTAL:
- case MONS_SWAMP_WORM:
- // XXX: We're being a bit player-centric here right now...
- // really we should replace the grid_distance() check
- // with one that checks for unaligned monsters as well. -- bwr
- if (mons_has_ench( monster, ENCH_SUBMERGED))
- {
- if (grd[monster->x][monster->y] == DNGN_SHALLOW_WATER
- || grd[monster->x][monster->y] == DNGN_BLUE_FOUNTAIN
- || (!mons_friendly(monster)
- && grid_distance( monster->x, monster->y,
- you.x_pos, you.y_pos ) == 1
- && (monster->hit_points == monster->max_hit_points
- || (monster->hit_points > monster->max_hit_points / 2
- && coinflip()))))
- {
- mons_del_ench( monster, ENCH_SUBMERGED );
- }
- }
- else if (monster_habitat(monster->type) == grd[monster->x][monster->y]
- && (one_chance_in(5)
- || (grid_distance( monster->x, monster->y,
- you.x_pos, you.y_pos ) > 1
- && monster->type != MONS_ELECTRICAL_EEL
- && monster->type != MONS_LAVA_SNAKE
- && !one_chance_in(20))
- || monster->hit_points <= monster->max_hit_points / 2)
- || env.cgrid[monster->x][monster->y] != EMPTY_CLOUD)
- {
- mons_add_ench( monster, ENCH_SUBMERGED );
- }
- break;
-
- case MONS_AIR_ELEMENTAL:
- if (one_chance_in(5))
- mons_add_ench( monster, ENCH_SUBMERGED );
- break;
-
- case MONS_PANDEMONIUM_DEMON:
- if (ghost.values[ GVAL_DEMONLORD_CYCLE_COLOUR ])
- monster->number = random_colour();
- break;
- }
-} // end handle_nearby_ability()
-
-//---------------------------------------------------------------
-//
-// handle_special_ability
-//
-// $$$ not sure what to say here...
-//
-//---------------------------------------------------------------
-static bool handle_special_ability(struct monsters *monster, bolt & beem)
-{
- bool used = false;
-
- FixedArray < unsigned int, 19, 19 > show;
-
- const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN)
- ? draco_subspecies( monster )
- : static_cast<monster_type>( monster->type );
-
- if (!mons_near( monster )
- || monster->behaviour == BEH_SLEEP
- || mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- return (false);
- }
-
-// losight(show, grd, you.x_pos, you.y_pos);
-
- switch (mclass)
- {
- case MONS_BALL_LIGHTNING:
- if (monster->attitude == ATT_HOSTILE
- && distance( you.x_pos, you.y_pos, monster->x, monster->y ) <= 5)
- {
- monster->hit_points = -1;
- used = true;
- break;
- }
-
- for (int i = 0; i < MAX_MONSTERS; i++)
- {
- struct monsters *targ = &menv[i];
-
- if (targ->type == -1 || targ->type == NON_MONSTER)
- continue;
-
- if (distance( monster->x, monster->y, targ->x, targ->y ) >= 5)
- continue;
-
- if (monster->attitude == targ->attitude)
- continue;
-
- // faking LOS by checking the neighbouring square
- int dx = targ->x - monster->x;
- if (dx)
- dx /= dx;
-
- int dy = targ->y - monster->y;
- if (dy)
- dy /= dy;
-
- const int tx = monster->x + dx;
- const int ty = monster->y + dy;
-
- if (tx < 0 || tx > GXM || ty < 0 || ty > GYM)
- continue;
-
- if (!grid_is_solid(grd[tx][ty]))
- {
- monster->hit_points = -1;
- used = true;
- break;
- }
- }
- break;
-
- case MONS_LAVA_SNAKE:
- if (mons_has_ench(monster, ENCH_CONFUSION))
- break;
-
- if (!mons_player_visible( monster ))
- break;
-
- if (coinflip())
- break;
-
- // setup tracer
- strcpy(beem.beam_name, "glob of lava");
- beem.range = 4;
- beem.rangeMax = 13;
- beem.damage = dice_def( 3, 10 );
- beem.colour = RED;
- beem.type = SYM_ZAP;
- beem.flavour = BEAM_LAVA;
- beem.hit = 20;
- beem.beam_source = monster_index(monster);
- beem.thrower = KILL_MON;
- beem.aux_source = "glob of lava";
-
- // fire tracer
- fire_tracer(monster, beem);
-
- // good idea?
- if (mons_should_fire(beem))
- {
- simple_monster_message(monster, " spits lava!");
- fire_beam(beem);
- used = true;
- }
- break;
-
- case MONS_ELECTRICAL_EEL:
- if (mons_has_ench(monster, ENCH_CONFUSION))
- break;
-
- if (!mons_player_visible( monster ))
- break;
-
- if (coinflip())
- break;
-
- // setup tracer
- strcpy(beem.beam_name, "bolt of electricity");
- beem.damage = dice_def( 3, 6 );
- beem.colour = LIGHTCYAN;
- beem.type = SYM_ZAP;
- beem.flavour = BEAM_ELECTRICITY;
- beem.hit = 150;
- beem.beam_source = monster_index(monster);
- beem.thrower = KILL_MON;
- beem.aux_source = "bolt of electricity";
- beem.range = 4;
- beem.rangeMax = 13;
- beem.is_beam = true;
-
- // fire tracer
- fire_tracer(monster, beem);
-
- // good idea?
- if (mons_should_fire(beem))
- {
- simple_monster_message(monster, " shoots out a bolt of electricity!");
- fire_beam(beem);
- used = true;
- }
- break;
-
- case MONS_ACID_BLOB:
- case MONS_OKLOB_PLANT:
- case MONS_YELLOW_DRACONIAN:
- if (mons_has_ench(monster, ENCH_CONFUSION))
- break;
-
- if (!mons_player_visible( monster ))
- break;
-
- if (one_chance_in(3))
- used = plant_spit(monster, beem);
-
- break;
-
- case MONS_PIT_FIEND:
- if (one_chance_in(3))
- break;
- // deliberate fall through
- case MONS_FIEND:
- if (mons_has_ench(monster, ENCH_CONFUSION))
- 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
- if (one_chance_in(4))
- {
- int spell_cast;
-
- switch (random2(4))
- {
- case 0:
- if (!mons_friendly(monster))
- {
- spell_cast = MS_TORMENT;
- mons_cast(monster, beem, spell_cast);
- used = true;
- break;
- }
- // deliberate fallthrough -- see above
- case 1:
- case 2:
- case 3:
- spell_cast = MS_HELLFIRE;
- setup_mons_cast(monster, beem, spell_cast);
-
- // fire tracer
- fire_tracer(monster, beem);
-
- // good idea?
- if (mons_should_fire(beem))
- {
- simple_monster_message( monster, " makes a gesture!",
- MSGCH_MONSTER_SPELL );
-
- mons_cast(monster, beem, spell_cast);
- used = true;
- }
- break;
- }
-
- mmov_x = 0;
- mmov_y = 0;
- }
- break;
-
- case MONS_IMP:
- case MONS_PHANTOM:
- case MONS_INSUBSTANTIAL_WISP:
- case MONS_BLINK_FROG:
- case MONS_KILLER_KLOWN:
- if (one_chance_in(7))
- {
- simple_monster_message(monster, " blinks.");
- monster_blink(monster);
- }
- break;
-
- case MONS_MANTICORE:
- if (!mons_player_visible( monster ))
- break;
-
- if (mons_has_ench(monster, ENCH_CONFUSION))
- break;
-
- if (!mons_near(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
- strcpy(beem.beam_name, "volley of spikes");
- beem.range = 9;
- beem.rangeMax = 9;
- beem.hit = 14;
- beem.damage = dice_def( 2, 10 );
- beem.beam_source = monster_index(monster);
- beem.type = SYM_MISSILE;
- beem.colour = LIGHTGREY;
- beem.flavour = BEAM_MISSILE;
- beem.thrower = KILL_MON;
- beem.aux_source = "volley of spikes";
- beem.is_beam = false;
-
- // fire tracer
- fire_tracer(monster, beem);
-
- // good idea?
- if (mons_should_fire(beem))
- {
- simple_monster_message(monster, " flicks its tail!");
- fire_beam(beem);
- used = true;
- // decrement # of volleys left
- monster->number -= 1;
- }
- break;
-
- // dragon breath weapon:
- case MONS_DRAGON:
- case MONS_HELL_HOUND:
- case MONS_ICE_DRAGON:
- case MONS_LINDWURM:
- case MONS_FIREDRAKE:
- case MONS_XTAHUA:
- case MONS_WHITE_DRACONIAN:
- case MONS_RED_DRACONIAN:
- if (!mons_player_visible( monster ))
- break;
-
- if (mons_has_ench(monster, ENCH_CONFUSION))
- break;
-
- if ((monster->type != MONS_HELL_HOUND && random2(13) < 3)
- || one_chance_in(10))
- {
- setup_dragon(monster, beem);
-
- // fire tracer
- fire_tracer(monster, beem);
-
- // good idea?
- if (mons_should_fire(beem))
- {
- simple_monster_message(monster, " breathes.",
- MSGCH_MONSTER_SPELL);
- fire_beam(beem);
- mmov_x = 0;
- mmov_y = 0;
- used = true;
- }
- }
- break;
-
- default:
- break;
- }
-
- return (used);
-} // end handle_special_ability()
-
-//---------------------------------------------------------------
-//
-// handle_potion
-//
-// Give the monster a chance to quaff a potion. Returns true if
-// the monster imbibed.
-//
-//---------------------------------------------------------------
-static bool handle_potion(struct monsters *monster, bolt & beem)
-{
-
- // yes, there is a logic to this ordering {dlb}:
- if (monster->behaviour == BEH_SLEEP)
- return (false);
- else if (monster->inv[MSLOT_POTION] == NON_ITEM)
- return (false);
- else if (!one_chance_in(3))
- return (false);
- else
- {
- bool imbibed = false;
-
- switch (mitm[monster->inv[MSLOT_POTION]].sub_type)
- {
- case POT_HEALING:
- case POT_HEAL_WOUNDS:
- if (monster->hit_points <= monster->max_hit_points / 2
- && mons_holiness(monster) != MH_UNDEAD
- && mons_holiness(monster) != MH_NONLIVING
- && mons_holiness(monster) != MH_PLANT)
- {
- simple_monster_message(monster, " drinks a potion.");
-
- if (heal_monster(monster, 5 + random2(7), false))
- simple_monster_message(monster, " is healed!");
-
- if (mitm[monster->inv[MSLOT_POTION]].sub_type
- == POT_HEAL_WOUNDS)
- {
- heal_monster(monster, 10 + random2avg(28, 3), false);
- }
-
- imbibed = true;
- }
- break;
-
- case POT_SPEED:
- // notice that these are the same odd colours used in
- // mons_ench_f2() {dlb}
- beem.colour = BLUE;
- // intentional fall through
- case POT_INVISIBILITY:
- if (mitm[monster->inv[MSLOT_POTION]].sub_type == POT_INVISIBILITY)
- beem.colour = MAGENTA;
-
- // why only drink these if not near player? {dlb}
- if (!mons_near(monster))
- {
- simple_monster_message(monster, " drinks a potion.");
-
- mons_ench_f2(monster, beem);
-
- imbibed = true;
- }
- break;
- }
-
- if (imbibed)
- {
- if (dec_mitm_item_quantity( monster->inv[MSLOT_POTION], 1 ))
- monster->inv[MSLOT_POTION] = NON_ITEM;
- }
-
- return (imbibed);
- }
-} // end handle_potion()
-
-static bool handle_reaching(struct monsters *monster)
-{
- bool ret = false;
- const int wpn = monster->inv[MSLOT_WEAPON];
-
- if (mons_aligned(monster_index(monster), monster->foe))
- return (false);
-
- if (mons_has_ench( monster, ENCH_SUBMERGED ))
- return (false);
-
- if (wpn != NON_ITEM && get_weapon_brand( mitm[wpn] ) == SPWPN_REACHING )
- {
- if (monster->foe == MHITYOU)
- {
- // this check isn't redundant -- player may be invisible.
- if (monster->target_x == you.x_pos && monster->target_y == you.y_pos)
- {
- int dx = abs(monster->x - you.x_pos);
- int dy = abs(monster->y - you.y_pos);
-
- if ((dx == 2 && dy <= 2) || (dy == 2 && dx <= 2))
- {
- ret = true;
- monster_attack( monster_index(monster) );
- }
- }
- }
- else if (monster->foe != MHITNOT)
- {
- // same comments as to invisibility as above.
- if (monster->target_x == menv[monster->foe].x
- && monster->target_y == menv[monster->foe].y)
- {
- int dx = abs(monster->x - menv[monster->foe].x);
- int dy = abs(monster->y - menv[monster->foe].y);
- if ((dx == 2 && dy <= 2) || (dy == 2 && dx <= 2))
- {
- ret = true;
- monsters_fight( monster_index(monster), monster->foe );
- }
- }
- }
- }
-
- return ret;
-} // end handle_reaching()
-
-//---------------------------------------------------------------
-//
-// handle_scroll
-//
-// Give the monster a chance to read a scroll. Returns true if
-// the monster read something.
-//
-//---------------------------------------------------------------
-static bool handle_scroll(struct monsters *monster)
-{
- // yes, there is a logic to this ordering {dlb}:
- if (mons_has_ench(monster, ENCH_CONFUSION)
- || monster->behaviour == BEH_SLEEP
- || mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- return (false);
- }
- else if (monster->inv[MSLOT_SCROLL] == NON_ITEM)
- return (false);
- else if (!one_chance_in(3))
- return (false);
- else
- {
- bool read = false;
-
- // notice how few cases are actually accounted for here {dlb}:
- switch (mitm[monster->inv[MSLOT_SCROLL]].sub_type)
- {
- case SCR_TELEPORTATION:
- if (!mons_has_ench(monster, ENCH_TP_I))
- {
- if (monster->behaviour == BEH_FLEE)
- {
- simple_monster_message(monster, " reads a scroll.");
- monster_teleport(monster, false);
- read = true;
- }
- }
- break;
-
- case SCR_BLINKING:
- if (monster->behaviour == BEH_FLEE)
- {
- if (mons_near(monster))
- {
- simple_monster_message(monster, " reads a scroll.");
- simple_monster_message(monster, " blinks!");
- monster_blink(monster);
- read = true;
- }
- }
- break;
-
- case SCR_SUMMONING:
- if (mons_near(monster))
- {
- simple_monster_message(monster, " reads a scroll.");
- create_monster( MONS_ABOMINATION_SMALL, ENCH_ABJ_II,
- SAME_ATTITUDE(monster), monster->x, monster->y,
- monster->foe, 250 );
- read = true;
- }
- break;
- }
-
- if (read)
- {
- if (dec_mitm_item_quantity( monster->inv[MSLOT_SCROLL], 1 ))
- monster->inv[MSLOT_SCROLL] = NON_ITEM;
- }
-
- return read;
- }
-} // end handle_scroll()
-
-//---------------------------------------------------------------
-//
-// handle_wand
-//
-// Give the monster a chance to zap a wand. Returns true if the
-// monster zapped.
-//
-//---------------------------------------------------------------
-static bool handle_wand(struct monsters *monster, bolt &beem)
-{
- // yes, there is a logic to this ordering {dlb}:
- if (monster->behaviour == BEH_SLEEP)
- return (false);
- else if (!mons_near(monster))
- return (false);
- else if (mons_has_ench( monster, ENCH_SUBMERGED ))
- return (false);
- else if (monster->inv[MSLOT_WAND] == NON_ITEM
- || mitm[monster->inv[MSLOT_WAND]].plus <= 0)
- {
- return (false);
- }
- else if (coinflip())
- {
- bool niceWand = false;
- bool zap = false;
-
- // map wand type to monster spell type
- int mzap = map_wand_to_mspell(mitm[monster->inv[MSLOT_WAND]].sub_type);
- if (mzap == 0)
- return (false);
-
- // set up the beam
- int power = 30 + monster->hit_dice;
- bolt theBeam = mons_spells(mzap, power);
-
- // XXX: ugly hack this:
- static char wand_buff[ ITEMNAME_SIZE ];
-
- strcpy( beem.beam_name, theBeam.beam_name );
- beem.beam_source = monster_index(monster);
- beem.source_x = monster->x;
- beem.source_y = monster->y;
- beem.colour = theBeam.colour;
- beem.range = theBeam.range;
- beem.rangeMax = theBeam.rangeMax;
- 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;
-
- item_def item = mitm[ monster->inv[MSLOT_WAND] ];
-
-#if HISCORE_WEAPON_DETAIL
- set_ident_flags( item, ISFLAG_IDENT_MASK );
-#else
- unset_ident_flags( item, ISFLAG_IDENT_MASK );
- set_ident_flags( item, ISFLAG_KNOW_TYPE );
-#endif
-
- item_name( item, DESC_PLAIN, wand_buff );
-
- beem.aux_source = wand_buff;
-
- switch (mitm[monster->inv[MSLOT_WAND]].sub_type)
- {
- // these have been deemed "too tricky" at this time {dlb}:
- case WAND_POLYMORPH_OTHER:
- case WAND_ENSLAVEMENT:
- case WAND_DIGGING:
- case WAND_RANDOM_EFFECTS:
- return (false);
-
- // these are wands that monsters will aim at themselves {dlb}:
- case WAND_HASTING:
- if (!mons_has_ench(monster, ENCH_HASTE))
- {
- beem.target_x = monster->x;
- beem.target_y = monster->y;
-
- niceWand = true;
- break;
- }
- return (false);
-
- case WAND_HEALING:
- if (monster->hit_points <= monster->max_hit_points / 2)
- {
- beem.target_x = monster->x;
- beem.target_y = monster->y;
-
- niceWand = true;
- break;
- }
- return (false);
-
- case WAND_INVISIBILITY:
- if (!mons_has_ench( monster, ENCH_INVIS )
- && !mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- beem.target_x = monster->x;
- beem.target_y = monster->y;
-
- niceWand = true;
- break;
- }
- return (false);
-
- case WAND_TELEPORTATION:
- if (monster->hit_points <= monster->max_hit_points / 2)
- {
- if (!mons_has_ench(monster, ENCH_TP_I) && !one_chance_in(20))
- {
- beem.target_x = monster->x;
- beem.target_y = monster->y;
-
- 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 (!simple_monster_message(monster, " zaps a wand."))
- {
- if (!silenced(you.x_pos, you.y_pos))
- mpr("You hear a zap.", MSGCH_SOUND);
- }
-
- // charge expenditure {dlb}
- mitm[monster->inv[MSLOT_WAND]].plus--;
- beem.is_tracer = false;
- fire_beam( beem );
-
- return (true);
- }
- }
-
- return (false);
-} // end handle_wand()
-
-static int get_draconian_breath_spell( struct monsters *monster )
-{
- int draco_breath = MS_NO_SPELL;
-
- if (mons_genus( monster->type ) == MONS_DRACONIAN)
- {
- switch (draco_subspecies( monster ))
- {
- case MONS_BLACK_DRACONIAN:
- draco_breath = MS_LIGHTNING_BOLT;
- break;
-
- case MONS_PALE_DRACONIAN:
- draco_breath = MS_STEAM_BALL;
- break;
-
- case MONS_GREEN_DRACONIAN:
- draco_breath = MS_POISON_BLAST;
- break;
-
- case MONS_PURPLE_DRACONIAN:
- draco_breath = MS_ORB_ENERGY;
- break;
-
- case MONS_MOTTLED_DRACONIAN:
- draco_breath = MS_STICKY_FLAME;
- break;
-
- case MONS_DRACONIAN:
- case MONS_YELLOW_DRACONIAN: // already handled as ability
- case MONS_RED_DRACONIAN: // already handled as ability
- case MONS_WHITE_DRACONIAN: // already handled as ability
- default:
- break;
- }
- }
-
- return (draco_breath);
-}
-
-//---------------------------------------------------------------
-//
-// handle_spell
-//
-// Give the monster a chance to cast a spell. Returns true if
-// a spell was cast.
-//
-//---------------------------------------------------------------
-static bool handle_spell( struct monsters *monster, bolt & beem )
-{
- bool monsterNearby = mons_near(monster);
- bool finalAnswer = false; // as in: "Is that your...?" {dlb}
- const int draco_breath = get_draconian_breath_spell(monster);
-
- // yes, there is a logic to this ordering {dlb}:
- if (monster->behaviour == BEH_SLEEP
- || (!mons_class_flag(monster->type, M_SPELLCASTER)
- && draco_breath == MS_NO_SPELL)
- || mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- return (false);
- }
-
- if ((mons_class_flag(monster->type, M_ACTUAL_SPELLS)
- || mons_class_flag(monster->type, M_PRIEST))
- && (mons_has_ench(monster, ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER)))
- {
- return (false); //jmf: shapeshiftes don't get spells, just
- // physical powers.
- }
- else if (mons_has_ench(monster, ENCH_CONFUSION)
- && !mons_class_flag(monster->type, M_CONFUSED))
- {
- return (false);
- }
- else if (monster->type == MONS_PANDEMONIUM_DEMON
- && !ghost.values[ GVAL_DEMONLORD_SPELLCASTER ])
- {
- return (false);
- }
- else if (random2(200) > monster->hit_dice + 50
- || (monster->type == MONS_BALL_LIGHTNING && coinflip()))
- {
- return (false);
- }
- else
- {
- int spell_cast = MS_NO_SPELL;
- int hspell_pass[6] = { MS_NO_SPELL, MS_NO_SPELL, MS_NO_SPELL,
- MS_NO_SPELL, MS_NO_SPELL, MS_NO_SPELL };
-
- mons_spell_list(monster, hspell_pass);
-
- // forces the casting of dig when player not visible - this is EVIL!
- if (!monsterNearby)
- {
- if (hspell_pass[4] == MS_DIG && monster->behaviour == BEH_SEEK)
- {
- spell_cast = MS_DIG;
- finalAnswer = true;
- }
- else if (hspell_pass[2] == MS_HEAL
- && 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 = MS_HEAL;
- finalAnswer = true;
- }
- else if (monster->behaviour == BEH_FLEE)
- {
- // Since the player isn't around, we'll extend the monster's
- // normal fleeing choices to include the self-enchant slot.
- if (ms_useful_fleeing_out_of_sight(monster,hspell_pass[5]))
- {
- spell_cast = hspell_pass[5];
- finalAnswer = true;
- }
- else if (ms_useful_fleeing_out_of_sight(monster,hspell_pass[2]))
- {
- spell_cast = hspell_pass[2];
- finalAnswer = true;
- }
- }
- else if (monster->foe == MHITYOU)
- {
- return (false);
- }
- }
-
- // 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 it's last HP. That
- // way we don't have to worry about monsters infinitely casting
- // Healing on themselves (ie orc priests).
- if (monster->behaviour == BEH_FLEE
- && ms_low_hitpoint_cast( monster, hspell_pass[5] ))
- {
- spell_cast = hspell_pass[5];
- finalAnswer = true;
- }
- else if (ms_low_hitpoint_cast( monster, hspell_pass[2] ))
- {
- spell_cast = hspell_pass[2];
- finalAnswer = true;
- }
- }
-
- if (!finalAnswer)
- {
- // should monster not have selected dig by now, it never will:
- if (hspell_pass[4] == MS_DIG)
- hspell_pass[4] = MS_NO_SPELL;
-
- // remove healing/invis/haste if we don't need them
- int num_no_spell = 0;
-
- for (int i = 0; i < 6; i++)
- {
- if (hspell_pass[i] == MS_NO_SPELL)
- num_no_spell++;
- else if (ms_waste_of_time( monster, hspell_pass[i] ))
- {
- hspell_pass[i] = MS_NO_SPELL;
- num_no_spell++;
- }
- }
-
- // If no useful spells... cast no spell.
- if (num_no_spell == 6 && draco_breath == MS_NO_SPELL)
- return (false);
-
- // up to four tries to pick a spell.
- for (int loopy = 0; loopy < 4; loopy ++)
- {
- bool spellOK = false;
-
- // setup spell - fleeing monsters will always try to
- // choose their emergency spell.
- if (monster->behaviour == BEH_FLEE)
- {
- spell_cast = (one_chance_in(5) ? MS_NO_SPELL
- : hspell_pass[5]);
- }
- else
- {
- // Randomly picking one of the non-emergency spells:
- spell_cast = hspell_pass[random2(5)];
- }
-
- if (spell_cast == MS_NO_SPELL)
- continue;
-
- // setup the spell
- setup_mons_cast(monster, beem, spell_cast);
-
- // beam-type spells requiring tracers
- if (ms_requires_tracer(spell_cast))
- {
- fire_tracer(monster, beem);
- // 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 (!mons_player_visible( monster )
- && (monster->target_x != you.x_pos
- || monster->target_y != you.y_pos
- || coinflip()))
- {
- spellOK = false;
- }
- }
- else if (!mons_monster_visible( monster, &menv[monster->foe] ))
- {
- spellOK = false;
- }
- }
-
- // if not okay, then maybe we'll cast a defensive spell
- if (!spellOK)
- spell_cast = (coinflip() ? hspell_pass[2] : MS_NO_SPELL);
-
- if (spell_cast != MS_NO_SPELL)
- break;
- }
- }
-
- if (spell_cast == MS_NO_SPELL && draco_breath != MS_NO_SPELL)
- {
- spell_cast = draco_breath;
- finalAnswer = true;
- }
-
- // should the monster *still* not have a spell, well, too bad {dlb}:
- if (spell_cast == MS_NO_SPELL)
- return (false);
-
- // Try to animate dead: if nothing rises, pretend we didn't cast it
- if (spell_cast == MS_ANIMATE_DEAD
- && !animate_dead( 100, SAME_ATTITUDE(monster), monster->foe, 0 ))
- {
- return (false);
- }
-
- if (monsterNearby) // handle monsters within range of player
- {
- if (monster->type == MONS_GERYON)
- {
- if (silenced(monster->x, monster->y))
- return (false);
-
- simple_monster_message( monster, " winds a great silver horn.",
- MSGCH_MONSTER_SPELL );
- }
- else if (mons_is_demon( monster->type ))
- {
- simple_monster_message( monster, " gestures.",
- MSGCH_MONSTER_SPELL );
- }
- else
- {
- switch (monster->type)
- {
- default:
- if (spell_cast == draco_breath)
- {
- if (!simple_monster_message(monster, " breathes.",
- MSGCH_MONSTER_SPELL))
- {
- if (!silenced(monster->x, monster->y)
- && !silenced(you.x_pos, you.y_pos))
- {
- mpr("You hear a roar.", MSGCH_SOUND);
- }
- }
- break;
- }
-
- if (silenced(monster->x, monster->y))
- return (false);
-
- if (mons_class_flag(monster->type, M_PRIEST))
- {
- switch (random2(3))
- {
- case 0:
- simple_monster_message( monster,
- " prays.",
- MSGCH_MONSTER_SPELL );
- break;
- case 1:
- simple_monster_message( monster,
- " mumbles some strange prayers.",
- MSGCH_MONSTER_SPELL );
- break;
- case 2:
- default:
- simple_monster_message( monster,
- " utters an invocation.",
- MSGCH_MONSTER_SPELL );
- break;
- }
- }
- else
- {
- switch (random2(3))
- {
- case 0:
- // XXX: could be better, chosen to match the
- // ones in monspeak.cc... has the problem
- // that it doesn't suggest a vocal component. -- bwr
- simple_monster_message( monster,
- " gestures wildly.",
- MSGCH_MONSTER_SPELL );
- break;
- case 1:
- simple_monster_message( monster,
- " mumbles some strange words.",
- MSGCH_MONSTER_SPELL );
- break;
- case 2:
- default:
- simple_monster_message( monster,
- " casts a spell.",
- MSGCH_MONSTER_SPELL );
- break;
- }
- }
- break;
-
- case MONS_BALL_LIGHTNING:
- monster->hit_points = -1;
- break;
-
- case MONS_STEAM_DRAGON:
- case MONS_MOTTLED_DRAGON:
- case MONS_STORM_DRAGON:
- case MONS_GOLDEN_DRAGON:
- case MONS_SHADOW_DRAGON:
- case MONS_SWAMP_DRAGON:
- case MONS_SWAMP_DRAKE:
- case MONS_DEATH_DRAKE:
- case MONS_HELL_HOG:
- case MONS_SERPENT_OF_HELL:
- case MONS_QUICKSILVER_DRAGON:
- case MONS_IRON_DRAGON:
- if (!simple_monster_message(monster, " breathes.",
- MSGCH_MONSTER_SPELL))
- {
- if (!silenced(monster->x, monster->y)
- && !silenced(you.x_pos, you.y_pos))
- {
- mpr("You hear a roar.", MSGCH_SOUND);
- }
- }
- break;
-
- case MONS_VAPOUR:
- mons_add_ench( monster, ENCH_SUBMERGED );
- break;
-
- case MONS_BRAIN_WORM:
- case MONS_ELECTRIC_GOLEM:
- // These don't show any signs that they're casting a spell.
- break;
-
- case MONS_GREAT_ORB_OF_EYES:
- case MONS_SHINING_EYE:
- case MONS_EYE_OF_DEVASTATION:
- simple_monster_message(monster, " gazes.", MSGCH_MONSTER_SPELL);
- break;
-
- case MONS_GIANT_ORANGE_BRAIN:
- simple_monster_message(monster, " pulsates.",
- MSGCH_MONSTER_SPELL);
- break;
-
- case MONS_NAGA:
- case MONS_NAGA_WARRIOR:
- simple_monster_message(monster, " spits poison.",
- MSGCH_MONSTER_SPELL);
- break;
- }
- }
- }
- else // handle far-away monsters
- {
- if (monster->type == MONS_GERYON
- && !silenced(you.x_pos, you.y_pos))
- {
- mpr("You hear a weird and mournful sound.", MSGCH_SOUND);
- }
- }
-
- // FINALLY! determine primary spell effects {dlb}:
- if (spell_cast == MS_BLINK && monsterNearby)
- // why only cast blink if nearby? {dlb}
- {
- simple_monster_message(monster, " blinks!");
- monster_blink(monster);
- }
- else
- {
- mons_cast(monster, beem, spell_cast);
- mmov_x = 0;
- mmov_y = 0;
- }
- } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ...
-
- return (true);
-} // end handle_spell()
-
-//---------------------------------------------------------------
-//
-// handle_throw
-//
-// Give the monster a chance to throw something. Returns true if
-// the monster hurled.
-//
-//---------------------------------------------------------------
-static bool handle_throw(struct monsters *monster, bolt & beem)
-{
- // yes, there is a logic to this ordering {dlb}:
- if (mons_has_ench(monster, ENCH_CONFUSION)
- || monster->behaviour == BEH_SLEEP
- || mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- return (false);
- }
-
- if (mons_itemuse(monster->type) < MONUSE_OPEN_DOORS)
- return (false);
-
- const int mon_item = monster->inv[MSLOT_MISSILE];
- if (mon_item == NON_ITEM || !is_valid_item( mitm[mon_item] ))
- return (false);
-
- // don't allow offscreen throwing.. for now.
- if (monster->foe == MHITYOU && !mons_near(monster))
- return (false);
-
- // poor 2-headed ogres {dlb}
- if (monster->type == MONS_TWO_HEADED_OGRE || monster->type == MONS_ETTIN)
- return (false);
-
- // recent addition {GDL} - monsters won't throw if they can do melee.
- // wastes valuable ammo, and most monsters are better at melee anyway.
- if (adjacent( beem.target_x, beem.target_y, monster->x, monster->y ))
- return (false);
-
- if (one_chance_in(5))
- return (false);
-
- // new (GDL) - don't throw idiotic stuff. It's a waste of time.
- int wepClass = mitm[mon_item].base_type;
- int wepType = mitm[mon_item].sub_type;
-
- int weapon = monster->inv[MSLOT_WEAPON];
-
- int lnchClass = (weapon != NON_ITEM) ? mitm[weapon].base_type : -1;
- int lnchType = (weapon != NON_ITEM) ? mitm[weapon].sub_type : 0;
-
- bool thrown = false;
- bool launched = false;
-
- throw_type( lnchClass, lnchType, wepClass, wepType, launched, thrown );
-
- if (!launched && !thrown)
- return (false);
-
- // ok, we'll try it.
- setup_generic_throw( monster, beem );
-
- // fire tracer
- fire_tracer( monster, beem );
-
- // good idea?
- if (mons_should_fire( beem ))
- {
- beem.beam_name[0] = '\0';
- return (mons_throw( monster, beem, mon_item ));
- }
-
- return (false);
-} // end handle_throw()
-
-static void handle_monster_move(int i, monsters *monster)
-{
- bool brkk = false;
- struct bolt beem;
- FixedArray <unsigned int, 19, 19> show;
-
- if (monster->hit_points > monster->max_hit_points)
- monster->hit_points = monster->max_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;
- }
-
- monster->speed_increment += (monster->speed * you.time_taken) / 10;
-
- if (you.slow > 0)
- {
- monster->speed_increment += (monster->speed * you.time_taken) / 10;
- }
-
- // Handle enchantments and clouds on nonmoving monsters:
- if (monster->speed == 0)
- {
- if (env.cgrid[monster->x][monster->y] != EMPTY_CLOUD
- && !mons_has_ench( monster, ENCH_SUBMERGED ))
- {
- mons_in_cloud( monster );
- }
-
- handle_enchantment( monster );
- }
-
- // memory is decremented here for a reason -- we only want it
- // decrementing once per monster "move"
- if (monster->foe_memory > 0)
- monster->foe_memory--;
-
- if (monster->type == MONS_GLOWING_SHAPESHIFTER)
- mons_add_ench( monster, ENCH_GLOWING_SHAPESHIFTER );
-
- // otherwise there are potential problems with summonings
- if (monster->type == MONS_SHAPESHIFTER)
- mons_add_ench( monster, 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
- && monster->behaviour == BEH_WANDER
- && testbits( monster->flags, MF_BATTY ))
- {
- monster->behaviour = BEH_SEEK;
- }
-
- while (monster->speed_increment >= 80)
- { // The continues & breaks are WRT this.
- if (monster->type != -1 && monster->hit_points < 1)
- break;
-
- monster->speed_increment -= 10;
-
- if (env.cgrid[monster->x][monster->y] != EMPTY_CLOUD)
- {
- if (mons_has_ench( monster, ENCH_SUBMERGED ))
- break;
-
- if (monster->type == -1)
- break; // problem with vortices
-
- mons_in_cloud(monster);
-
- if (monster->type == -1)
- {
- monster->speed_increment = 1;
- break;
- }
- }
-
- handle_behaviour(monster);
-
- if (handle_enchantment(monster))
- continue;
-
- // submerging monsters will hide from clouds
- const int habitat = monster_habitat( monster->type );
- if (habitat != DNGN_FLOOR
- && habitat == grd[monster->x][monster->y]
- && env.cgrid[monster->x][monster->y] != EMPTY_CLOUD)
- {
- mons_add_ench( monster, ENCH_SUBMERGED );
- }
-
- // regenerate:
- if (monster_descriptor(monster->type, MDSC_REGENERATES)
-
- || (monster->type == MONS_FIRE_ELEMENTAL
- && (grd[monster->x][monster->y] == DNGN_LAVA
- || env.cgrid[monster->x][monster->y] == CLOUD_FIRE
- || env.cgrid[monster->x][monster->y] == CLOUD_FIRE_MON))
-
- || (monster->type == MONS_WATER_ELEMENTAL
- && (grd[monster->x][monster->y] == DNGN_SHALLOW_WATER
- || grd[monster->x][monster->y] == DNGN_DEEP_WATER))
-
- || (monster->type == MONS_AIR_ELEMENTAL
- && env.cgrid[monster->x][monster->y] == EMPTY_CLOUD
- && one_chance_in(3))
-
- || one_chance_in(25))
- {
- heal_monster(monster, 1, false);
- }
-
- if (monster->speed >= 100)
- continue;
-
- if (monster->type == MONS_ZOMBIE_SMALL
- || monster->type == MONS_ZOMBIE_LARGE
- || monster->type == MONS_SIMULACRUM_SMALL
- || monster->type == MONS_SIMULACRUM_LARGE
- || monster->type == MONS_SKELETON_SMALL
- || monster->type == MONS_SKELETON_LARGE)
- {
- monster->max_hit_points = monster->hit_points;
- }
-
- if (igrd[monster->x][monster->y] != NON_ITEM
- && (mons_itemuse(monster->type) == MONUSE_WEAPONS_ARMOUR
- || mons_itemuse(monster->type) == MONUSE_EATS_ITEMS
- || monster->type == MONS_NECROPHAGE
- || monster->type == MONS_GHOUL))
- {
- if (handle_pickup(monster))
- continue;
- }
-
- // calculates mmov_x, mmov_y based on monster target.
- handle_movement(monster);
-
- brkk = false;
-
- if (mons_has_ench( monster, ENCH_CONFUSION )
- || (monster->type == MONS_AIR_ELEMENTAL
- && mons_has_ench( monster, ENCH_SUBMERGED )))
- {
- mmov_x = random2(3) - 1;
- mmov_y = random2(3) - 1;
-
- // bounds check: don't let confused monsters try to run
- // off the map
- if (monster->target_x + mmov_x < 0
- || monster->target_x + mmov_x >= GXM)
- {
- mmov_x = 0;
- }
-
- if (monster->target_y + mmov_y < 0
- || monster->target_y + mmov_y >= GYM)
- {
- mmov_y = 0;
- }
-
- if (mgrd[monster->x + mmov_x][monster->y + mmov_y] != NON_MONSTER
- && (mmov_x != 0 || mmov_y != 0))
- {
- mmov_x = 0;
- mmov_y = 0;
-
- if (monsters_fight(i, mgrd[monster->x + mmov_x][monster->y + mmov_y]))
- {
- brkk = true;
- }
- }
- }
-
- if (brkk)
- continue;
-
- handle_nearby_ability( monster );
-
- beem.target_x = monster->target_x;
- beem.target_y = monster->target_y;
-
- if (monster->behaviour != BEH_SLEEP
- && monster->behaviour != BEH_WANDER)
- {
- // prevents unfriendlies from nuking you from offscreen.
- // How nice!
- if (mons_friendly(monster) || mons_near(monster))
- {
- if (handle_special_ability(monster, beem))
- continue;
-
- if (handle_potion(monster, beem))
- continue;
-
- if (handle_scroll(monster))
- continue;
-
- // shapeshifters don't get spells
- if (!mons_has_ench( monster, ENCH_GLOWING_SHAPESHIFTER,
- ENCH_SHAPESHIFTER )
- || !mons_class_flag( monster->type, M_ACTUAL_SPELLS ))
- {
- if (handle_spell(monster, beem))
- continue;
- }
-
- if (handle_wand(monster, beem))
- continue;
-
- if (handle_reaching(monster))
- continue;
- }
-
- if (handle_throw(monster, beem))
- continue;
- }
-
- // see if we move into (and fight) an unfriendly monster
- int targmon = mgrd[monster->x + mmov_x][monster->y + mmov_y];
- if (targmon != NON_MONSTER
- && targmon != i
- && !mons_aligned(i, targmon))
- {
- // figure out if they fight
- if (monsters_fight(i, targmon))
- {
- if (testbits(monster->flags, MF_BATTY))
- {
- monster->behaviour = BEH_WANDER;
- monster->target_x = 10 + random2(GXM - 10);
- monster->target_y = 10 + random2(GYM - 10);
- // monster->speed_increment -= monster->speed;
- }
-
- mmov_x = 0;
- mmov_y = 0;
- brkk = true;
- }
- }
-
- if (brkk)
- continue;
-
- if (monster->x + mmov_x == you.x_pos
- && monster->y + mmov_y == you.y_pos)
- {
- bool isFriendly = mons_friendly(monster);
- bool attacked = false;
-
- if (!isFriendly)
- {
- monster_attack(i);
- attacked = true;
-
- if (testbits(monster->flags, MF_BATTY))
- {
- monster->behaviour = BEH_WANDER;
- monster->target_x = 10 + random2(GXM - 10);
- monster->target_y = 10 + random2(GYM - 10);
- }
- }
-
- if ((monster->type == MONS_GIANT_SPORE
- || monster->type == MONS_BALL_LIGHTNING)
- && monster->hit_points < 1)
- {
-
- // detach monster from the grid first, so it
- // doesn't get hit by its own explosion (GDL)
- mgrd[monster->x][monster->y] = NON_MONSTER;
-
- spore_goes_pop(monster);
- monster_cleanup(monster);
- continue;
- }
-
- if (attacked)
- {
- mmov_x = 0;
- mmov_y = 0;
- continue; //break;
- }
- }
-
- if (invalid_monster(monster) || mons_is_stationary(monster))
- continue;
-
- monster_move(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->type != -1)
- handle_behaviour(monster);
-
- } // end while
-
- if (monster->type != -1 && monster->hit_points < 1)
- {
- if (monster->type == MONS_GIANT_SPORE
- || monster->type == MONS_BALL_LIGHTNING)
- {
- // detach monster from the grid first, so it
- // doesn't get hit by its own explosion (GDL)
- mgrd[monster->x][monster->y] = NON_MONSTER;
-
- spore_goes_pop( monster );
- monster_cleanup( monster );
- return;
- }
- else
- {
- monster_die( monster, KILL_MISC, 0 );
- }
- }
-}
-
-//---------------------------------------------------------------
-//
-// handle_monsters
-//
-// This is the routine that controls monster AI.
-//
-//---------------------------------------------------------------
-void handle_monsters(void)
-{
- // 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++)
- {
- struct monsters *monster = &menv[i];
-
- if (monster->type == -1 || immobile_monster[i])
- continue;
-
- const int mx = monster->x,
- my = monster->y;
- handle_monster_move(i, monster);
-
- if (!invalid_monster(monster)
- && (monster->x != mx || monster->y != my))
- immobile_monster[i] = true;
- } // end of for loop
-
- // 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;
- }
-} // end handle_monster()
-
-
-//---------------------------------------------------------------
-//
-// handle_pickup
-//
-// Returns false if monster doesn't spend any time pickup up
-//
-//---------------------------------------------------------------
-static bool handle_pickup(struct monsters *monster)
-{
- // single calculation permissible {dlb}
- char str_pass[ ITEMNAME_SIZE ];
- bool monsterNearby = mons_near(monster);
- int item = NON_ITEM;
-
- if (mons_has_ench( monster, ENCH_SUBMERGED ))
- return (false);
-
- if (monster->behaviour == BEH_SLEEP)
- return (false);
-
- if (monster->type == MONS_JELLY
- || monster->type == MONS_BROWN_OOZE
- || monster->type == MONS_ACID_BLOB
- || monster->type == MONS_ROYAL_JELLY)
- {
- int hps_gained = 0;
- int max_eat = roll_dice( 1, 10 );
- int eaten = 0;
-
- for (item = igrd[monster->x][monster->y];
- item != NON_ITEM && eaten < max_eat && hps_gained < 50;
- item = mitm[item].link)
- {
- int quant = mitm[item].quantity;
-
- // don't eat fixed artefacts
- if (is_fixed_artefact( mitm[item] ))
- continue;
-
- // shouldn't eat stone things
- // - but what about wands and rings?
- if (mitm[item].base_type == OBJ_MISSILES
- && (mitm[item].sub_type == MI_STONE
- || mitm[item].sub_type == MI_LARGE_ROCK))
- {
- continue;
- }
-
- // don't eat special game items
- if (mitm[item].base_type == OBJ_ORBS
- || (mitm[item].base_type == OBJ_MISCELLANY
- && mitm[item].sub_type == MISC_RUNE_OF_ZOT))
- {
- continue;
- }
-
- if (mitm[igrd[monster->x][monster->y]].base_type != OBJ_GOLD)
- {
- if (quant > max_eat - eaten)
- quant = max_eat - eaten;
-
- hps_gained += (quant * item_mass( mitm[item] )) / 20 + quant;
- eaten += quant;
- }
- 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++;
- }
-
- dec_mitm_item_quantity( item, quant );
- }
-
- if (eaten)
- {
- if (hps_gained < 1)
- hps_gained = 1;
- else if (hps_gained > 50)
- 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;
-
- if (monster->max_hit_points < monster->hit_points)
- monster->max_hit_points = monster->hit_points;
-
- if (!silenced(you.x_pos, you.y_pos)
- && !silenced(monster->x, monster->y))
- {
- strcpy(info, "You hear a");
- if (!monsterNearby)
- strcat(info, " distant");
- strcat(info, " slurping noise.");
- mpr(info, MSGCH_SOUND);
- }
-
- if (mons_class_flag( monster->type, M_SPLITS ))
- {
- const int reqd = (monster->hit_dice <= 6)
- ? 50 : monster->hit_dice * 8;
-
- if (monster->hit_points >= reqd)
- jelly_divide(monster);
- }
- }
-
- return (false);
- } // end "if jellies"
-
- // Note: Monsters only look at top of stacks.
- item = igrd[monster->x][monster->y];
-
- switch (mitm[item].base_type)
- {
- case OBJ_WEAPONS:
- if (monster->inv[MSLOT_WEAPON] != NON_ITEM)
- return (false);
-
- if (is_fixed_artefact( mitm[item] ))
- return (false);
-
- if (is_random_artefact( mitm[item] ))
- return (false);
-
- // wimpy monsters (Kob, gob) shouldn't pick up halberds etc
- // of course, this also block knives {dlb}:
- if ((mons_species(monster->type) == MONS_KOBOLD
- || mons_species(monster->type) == MONS_GOBLIN)
- && property( mitm[item], PWPN_HIT ) <= 0)
- {
- return (false);
- }
-
- // Nobody picks up giant clubs:
- if (mitm[item].sub_type == WPN_GIANT_CLUB
- || mitm[item].sub_type == WPN_GIANT_SPIKED_CLUB)
- {
- return (false);
- }
-
- monster->inv[MSLOT_WEAPON] = item;
-
- if (get_weapon_brand(mitm[monster->inv[MSLOT_WEAPON]]) == SPWPN_PROTECTION)
- {
- monster->armour_class += 3;
- }
-
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " picks up ");
- it_name(monster->inv[MSLOT_WEAPON], DESC_NOCAP_A, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
- break;
-
- case OBJ_MISSILES:
- // don't pick up if we're in combat, and there isn't much there
- if (mitm[item].quantity < 5 || monster->behaviour != BEH_WANDER)
- return (false);
-
- if (monster->inv[MSLOT_MISSILE] != NON_ITEM
- && mitm[monster->inv[MSLOT_MISSILE]].sub_type == mitm[item].sub_type
- && mitm[monster->inv[MSLOT_MISSILE]].plus == mitm[item].plus
- && mitm[monster->inv[MSLOT_MISSILE]].special == mitm[item].special)
- {
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " picks up ");
- it_name(item, DESC_NOCAP_A, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
-
- inc_mitm_item_quantity( monster->inv[MSLOT_MISSILE],
- mitm[item].quantity );
-
- dec_mitm_item_quantity( item, mitm[item].quantity );
- return (true);
- }
-
- // nobody bothers to pick up rocks if they don't already have some:
- if (mitm[item].sub_type == MI_LARGE_ROCK)
- return (false);
-
- // monsters with powerful melee attacks don't bother
- if (mons_damage(monster->type, 0) > 5)
- return (false);
-
- monster->inv[MSLOT_MISSILE] = item;
-
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " picks up ");
- it_name(monster->inv[MSLOT_MISSILE], DESC_NOCAP_A, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
- break;
-
- case OBJ_WANDS:
- if (monster->inv[MSLOT_WAND] != NON_ITEM)
- return (false);
-
- monster->inv[MSLOT_WAND] = item;
-
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " picks up ");
- it_name(monster->inv[MSLOT_WAND], DESC_NOCAP_A, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
- break;
-
- case OBJ_SCROLLS:
- if (monster->inv[MSLOT_SCROLL] != NON_ITEM)
- return (false);
-
- monster->inv[MSLOT_SCROLL] = item;
-
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " picks up ");
- it_name(monster->inv[MSLOT_SCROLL], DESC_NOCAP_A, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
- break;
-
- case OBJ_POTIONS:
- if (monster->inv[MSLOT_POTION] != NON_ITEM)
- return (false);
-
- monster->inv[MSLOT_POTION] = item;
-
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " picks up ");
- it_name(monster->inv[MSLOT_POTION], DESC_NOCAP_A, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
- break;
-
- case OBJ_CORPSES:
- if (monster->type != MONS_NECROPHAGE && monster->type != MONS_GHOUL)
- return (false);
-
- monster->hit_points += 1 + random2(mons_weight(mitm[item].plus)) / 100;
-
- // limited growth factor here -- should 77 really be the cap? {dlb}:
- if (monster->hit_points > 100)
- monster->hit_points = 100;
-
- if (monster->hit_points > monster->max_hit_points)
- monster->max_hit_points = monster->hit_points;
-
- if (monsterNearby)
- {
- strcpy(info, ptr_monam(monster, DESC_CAP_THE));
- strcat(info, " eats ");
- it_name(item, DESC_NOCAP_THE, str_pass);
- strcat(info, str_pass);
- strcat(info, ".");
- mpr(info);
- }
-
- destroy_item( item );
- return (true);
-
- case OBJ_GOLD: //mv - monsters now pick up gold (19 May 2001)
- if (monsterNearby)
- {
-
- strcpy(info, monam( monster->number, monster->type,
- player_monster_visible( monster ),
- DESC_CAP_THE ));
-
- strcat(info, " picks up some gold.");
- mpr(info);
- }
-
- if (monster->inv[MSLOT_GOLD] != NON_ITEM)
- {
- // transfer gold to monster's object, destroy ground object
- inc_mitm_item_quantity( monster->inv[MSLOT_GOLD],
- mitm[item].quantity );
-
- destroy_item( item );
- return (true);
- }
- else
- {
- monster->inv[MSLOT_GOLD] = item;
- }
- break;
-
- default:
- return (false);
- }
-
- // Item has been picked-up, move to monster inventory.
- mitm[item].x = 0;
- mitm[item].y = 0;
-
- // Monster's only take the top item of stacks, so relink the
- // top item, and unlink the item.
- igrd[monster->x][monster->y] = mitm[item].link;
- mitm[item].link = NON_ITEM;
-
- if (monster->speed_increment > 25)
- monster->speed_increment -= monster->speed;
-
- return (true);
-} // end handle_pickup()
-
-static void jelly_grows(monsters *monster)
-{
- if (!silenced(you.x_pos, you.y_pos)
- && !silenced(monster->x, monster->y))
- {
- strcpy(info, "You hear a");
- if (!mons_near(monster))
- strcat(info, " distant");
- strcat(info, " slurping noise.");
- mpr(info, MSGCH_SOUND);
- }
-
- 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;
-
- if (mons_class_flag( monster->type, M_SPLITS ))
- {
- // and here is where the jelly might divide {dlb}
- const int reqd = (monster->hit_dice < 6) ? 50
- : monster->hit_dice * 8;
-
- if (monster->hit_points >= reqd)
- 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 (ipushee < 0 || ipushee >= MAX_MONSTERS)
- 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_is_paralysed(mpusher) || mons_is_paralysed(mpushee)
- || mons_is_sleeping(mpusher))
- return (false);
-
- // Batty monsters are unpushable
- if (mons_is_batty(mpusher) || mons_is_batty(mpushee))
- return (false);
-
- if (!monster_shover(mpusher))
- return (false);
-
- if (!monster_senior(mpusher, mpushee))
- return (false);
-
- return (true);
-}
-
-static bool monster_swaps_places( monsters *mon, int mx, int my )
-{
- if (!mx && !my)
- return (false);
-
- int targmon = mgrd[mon->x + mx][mon->y + my];
- if (targmon == MHITNOT || targmon == MHITYOU)
- return (false);
-
- monsters *m2 = &menv[targmon];
- if (!mons_can_displace(mon, m2))
- return (false);
-
- if (mons_is_sleeping(m2))
- {
- if (one_chance_in(2))
- {
-#ifdef DEBUG_DIAGNOSTICS
- char mname[ITEMNAME_SIZE];
- moname(m2->type, true, DESC_PLAIN, mname);
- mprf(MSGCH_DIAGNOSTICS,
- "Alerting monster %s at (%d,%d)", mname, m2->x, m2->y);
-#endif
- behaviour_event( m2, ME_ALERT, MHITNOT );
- }
- return (false);
- }
-
- // Check that both monsters will be happy at their proposed new locations.
- const int cx = mon->x, cy = mon->y,
- nx = mon->x + mx, ny = mon->y + my;
- if (!habitat_okay(mon, grd[nx][ny])
- || !habitat_okay(m2, grd[cx][cy]))
- return (false);
-
- // Okay, do the swap!
-#ifdef DEBUG_DIAGNOSTICS
- char mname[ITEMNAME_SIZE];
- moname(mon->type, true, DESC_PLAIN, mname);
- mprf(MSGCH_DIAGNOSTICS,
- "Swap: %s (%d,%d)->(%d,%d) (%d;%d)",
- mname, mon->x, mon->y, nx, ny, mon->speed_increment, mon->speed);
-#endif
- mon->x = nx;
- mon->y = ny;
- mgrd[nx][ny] = monster_index(mon);
-
-#ifdef DEBUG_DIAGNOSTICS
- moname(m2->type, true, DESC_PLAIN, mname);
- mprf(MSGCH_DIAGNOSTICS,
- "Swap: %s (%d,%d)->(%d,%d) (%d;%d)",
- mname, m2->x, m2->y, cx, cy, mon->speed_increment, mon->speed);
-#endif
- m2->x = cx;
- m2->y = cy;
- const int m2i = monster_index(m2);
- ASSERT(m2i >= 0 && m2i < MAX_MONSTERS);
- mgrd[cx][cy] = m2i;
- immobile_monster[m2i] = true;
-
- mons_trap(mon);
- mons_trap(m2);
-
- return (false);
-}
-
-static void monster_move(struct monsters *monster)
-{
- FixedArray < bool, 3, 3 > good_move;
- int count_x, count_y, count;
- int okmove = DNGN_SHALLOW_WATER;
-
- const int habitat = monster_habitat( monster->type );
- bool deep_water_available = false;
-
- // let's not even bother with this if mmov_x and mmov_y are zero.
- if (mmov_x == 0 && mmov_y == 0)
- return;
-
- // effectively slows down monster movement across water.
- // Fire elementals can't cross at all.
- if (monster->type == MONS_FIRE_ELEMENTAL || one_chance_in(5))
- okmove = DNGN_WATER_STUCK;
-
- if (mons_flies(monster) > 0
- || habitat != DNGN_FLOOR
- || mons_class_flag( monster->type, M_AMPHIBIOUS ))
- {
- okmove = MINMOVE;
- }
-
- for (count_x = 0; count_x < 3; count_x++)
- {
- for (count_y = 0; count_y < 3; count_y++)
- {
- good_move[count_x][count_y] = true;
- const int targ_x = monster->x + count_x - 1;
- const int targ_y = monster->y + count_y - 1;
- int target_grid = grd[targ_x][targ_y];
-
- const int targ_cloud = env.cgrid[ targ_x ][ targ_y ];
- const int curr_cloud = env.cgrid[ monster->x ][ monster->y ];
-
- // bounds check - don't consider moving out of grid!
- if (targ_x < 0 || targ_x >= GXM || targ_y < 0 || targ_y >= GYM)
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- if (target_grid == DNGN_DEEP_WATER)
- deep_water_available = true;
-
- if (monster->type == MONS_BORING_BEETLE
- && target_grid == DNGN_ROCK_WALL)
- {
- // don't burrow out of bounds
- if (targ_x <= 7 || targ_x >= (GXM - 8)
- || targ_y <= 7 || targ_y >= (GYM - 8))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- // don't burrow at an angle (legacy behaviour)
- if (count_x != 1 && count_y != 1)
- {
- good_move[count_x][count_y] = false;
- continue;
- }
- }
- else if (grd[ targ_x ][ targ_y ] < okmove)
- {
- good_move[count_x][count_y] = false;
- continue;
- }
- else if (!habitat_okay( monster, target_grid ))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- if (monster->type == MONS_WANDERING_MUSHROOM
- && see_grid(targ_x, targ_y))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- // Water elementals avoid fire and heat
- if (monster->type == MONS_WATER_ELEMENTAL
- && (target_grid == DNGN_LAVA
- || targ_cloud == CLOUD_FIRE
- || targ_cloud == CLOUD_FIRE_MON
- || targ_cloud == CLOUD_STEAM
- || targ_cloud == CLOUD_STEAM_MON))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- // Fire elementals avoid water and cold
- if (monster->type == MONS_FIRE_ELEMENTAL
- && (target_grid == DNGN_DEEP_WATER
- || target_grid == DNGN_SHALLOW_WATER
- || target_grid == DNGN_BLUE_FOUNTAIN
- || targ_cloud == CLOUD_COLD
- || targ_cloud == CLOUD_COLD_MON))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- // Submerged water creatures avoid the shallows where
- // they would be forced to surface. -- bwr
- if (habitat == DNGN_DEEP_WATER
- && (targ_x != you.x_pos || targ_y != you.y_pos)
- && target_grid != DNGN_DEEP_WATER
- && grd[monster->x][monster->y] == DNGN_DEEP_WATER
- && (mons_has_ench( monster, ENCH_SUBMERGED )
- || monster->hit_points < (monster->max_hit_points * 3) / 4))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
-
- // smacking the player is always a good move if
- // we're hostile (even if we're heading somewhere
- // else)
-
- // smacking another monster is good, if the monsters
- // are aligned differently
- if (mgrd[targ_x][targ_y] != NON_MONSTER)
- {
- const int thismonster = monster_index(monster),
- targmonster = mgrd[targ_x][targ_y];
- if (mons_aligned(thismonster, targmonster)
- && targmonster != MHITNOT
- && targmonster != MHITYOU
- && !mons_can_displace(monster, &menv[targmonster]))
- {
- good_move[count_x][count_y] = false;
- continue;
- }
- }
-
- // wandering through a trap is OK if we're pretty healthy, or
- // really stupid.
- if (trap_at_xy(targ_x,targ_y) >= 0
- && mons_intel(monster->type) != I_PLANT)
- {
- if (monster->hit_points < monster->max_hit_points / 2)
- {
- good_move[count_x][count_y] = false;
- continue;
- }
- }
-
- if (targ_cloud != EMPTY_CLOUD)
- {
- if (curr_cloud != EMPTY_CLOUD
- && env.cloud[targ_cloud].type == env.cloud[curr_cloud].type)
- {
- continue;
- }
-
- switch (env.cloud[ targ_cloud ].type)
- {
- case CLOUD_FIRE:
- case CLOUD_FIRE_MON:
- if (mons_res_fire(monster) > 0)
- continue;
-
- if (monster->hit_points >= 15 + random2avg(46, 5))
- continue;
- break;
-
- case CLOUD_STINK:
- case CLOUD_STINK_MON:
- if (mons_res_poison(monster) > 0)
- continue;
- if (1 + random2(5) < monster->hit_dice)
- continue;
- if (monster->hit_points >= random2avg(19, 2))
- continue;
- break;
-
- case CLOUD_COLD:
- case CLOUD_COLD_MON:
- if (mons_res_cold(monster) > 0)
- continue;
-
- if (monster->hit_points >= 15 + random2avg(46, 5))
- continue;
- break;
-
- case CLOUD_POISON:
- case CLOUD_POISON_MON:
- if (mons_res_poison(monster) > 0)
- continue;
-
- if (monster->hit_points >= random2avg(37, 4))
- continue;
- break;
-
- // this isn't harmful, but dumb critters might think so.
- case CLOUD_GREY_SMOKE:
- case CLOUD_GREY_SMOKE_MON:
- if (mons_intel(monster->type) > I_ANIMAL || coinflip())
- continue;
-
- if (mons_res_fire(monster) > 0)
- continue;
-
- if (monster->hit_points >= random2avg(19, 2))
- continue;
- break;
-
- default:
- continue; // harmless clouds
- }
-
- // if we get here, the cloud is potentially harmful.
- // exceedingly dumb creatures will still wander in.
- if (mons_intel(monster->type) != I_PLANT)
- good_move[count_x][count_y] = false;
- }
- }
- } // now we know where we _can_ move.
-
- // normal/smart monsters know about secret doors (they _live_ in the
- // dungeon!)
- if (grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_CLOSED_DOOR
- || (grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_SECRET_DOOR
- && (mons_intel(monster_index(monster)) == I_HIGH
- || mons_intel(monster_index(monster)) == I_NORMAL)))
- {
- if (monster->type == MONS_ZOMBIE_SMALL
- || monster->type == MONS_ZOMBIE_LARGE
- || monster->type == MONS_SIMULACRUM_SMALL
- || monster->type == MONS_SIMULACRUM_LARGE
- || monster->type == MONS_SKELETON_SMALL
- || monster->type == MONS_SKELETON_LARGE
- || monster->type == MONS_SPECTRAL_THING)
- {
- // for zombies, monster type is kept in mon->number
- if (mons_itemuse(monster->number) >= MONUSE_OPEN_DOORS)
- {
- grd[monster->x + mmov_x][monster->y + mmov_y] = DNGN_OPEN_DOOR;
- return;
- }
- }
- else if (mons_itemuse(monster->type) >= MONUSE_OPEN_DOORS)
- {
- grd[monster->x + mmov_x][monster->y + mmov_y] = DNGN_OPEN_DOOR;
- return;
- }
- } // endif - secret/closed doors
-
- // jellies eat doors. Yum!
- if ((grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_CLOSED_DOOR
- || grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_OPEN_DOOR)
- && mons_itemuse(monster->type) == MONUSE_EATS_ITEMS)
- {
- grd[monster->x + mmov_x][monster->y + mmov_y] = DNGN_FLOOR;
-
- jelly_grows(monster);
- } // done door-eating jellies
-
-
- // water creatures have a preferance for water they can hide in -- bwr
- if (habitat == DNGN_DEEP_WATER
- && deep_water_available
- && grd[monster->x][monster->y] != DNGN_DEEP_WATER
- && grd[monster->x + mmov_x][monster->y + mmov_y] != DNGN_DEEP_WATER
- && (monster->x + mmov_x != you.x_pos
- || monster->y + mmov_y != you.y_pos)
- && (coinflip()
- || monster->hit_points <= (monster->max_hit_points * 3) / 4))
- {
- count = 0;
-
- for (count_x = 0; count_x < 3; count_x++)
- {
- for (count_y = 0; count_y < 3; count_y++)
- {
- if (good_move[count_x][count_y]
- && grd[monster->x + count_x - 1][monster->y + count_y - 1]
- == DNGN_DEEP_WATER)
- {
- count++;
-
- if (one_chance_in( count ))
- {
- mmov_x = count_x - 1;
- mmov_y = count_y - 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)
- {
- int current_distance = grid_distance( monster->x, monster->y,
- monster->target_x,
- monster->target_y );
-
- int dir = -1;
- int i, mod, newdir;
-
- for (i = 0; i < 8; i++)
- {
- if (compass_x[i] == mmov_x && compass_y[i] == mmov_y)
- {
- dir = i;
- break;
- }
- }
-
- if (dir < 0)
- goto forget_it;
-
- int dist[2];
-
- // first 1 away, then 2 (3 is silly)
- for (int j = 1; j <= 2; j++)
- {
- int sdir, inc;
-
- if (coinflip())
- {
- sdir = -j;
- inc = 2*j;
- }
- else
- {
- sdir = j;
- inc = -2*j;
- }
-
- // try both directions
- for (mod = sdir, i = 0; i < 2; mod += inc, i++)
- {
- newdir = (dir + 8 + mod) % 8;
- if (good_move[compass_x[newdir] + 1][compass_y[newdir] + 1])
- {
- dist[i] = grid_distance( monster->x + compass_x[newdir],
- monster->y + compass_y[newdir],
- monster->target_x,
- monster->target_y );
- }
- else
- {
- dist[i] = (monster->behaviour == BEH_FLEE) ? (-FAR_AWAY)
- : FAR_AWAY;
- }
- }
-
- // now choose
- if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY)
- continue;
-
- // which one was better? -- depends on FLEEING or not
- if (monster->behaviour == BEH_FLEE)
- {
- if (dist[0] >= dist[1] && dist[0] >= current_distance)
- {
- mmov_x = compass_x[((dir+8)+sdir)%8];
- mmov_y = compass_y[((dir+8)+sdir)%8];
- break;
- }
- if (dist[1] >= dist[0] && dist[1] >= current_distance)
- {
- mmov_x = compass_x[((dir+8)-sdir)%8];
- mmov_y = compass_y[((dir+8)-sdir)%8];
- break;
- }
- }
- else
- {
- if (dist[0] <= dist[1] && dist[0] <= current_distance)
- {
- mmov_x = compass_x[((dir+8)+sdir)%8];
- mmov_y = compass_y[((dir+8)+sdir)%8];
- break;
- }
- if (dist[1] <= dist[0] && dist[1] <= current_distance)
- {
- mmov_x = compass_x[((dir+8)-sdir)%8];
- mmov_y = compass_y[((dir+8)-sdir)%8];
- break;
- }
- }
- }
- } // end - try to find good alternate move
-
-forget_it:
-
- // ------------------------------------------------------------------
- // if we haven't found a good move by this point, we're not going to.
- // ------------------------------------------------------------------
-
- // take care of beetle burrowing
- if (monster->type == MONS_BORING_BEETLE)
- {
- if (grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_ROCK_WALL
- && good_move[mmov_x + 1][mmov_y + 1] == true)
- {
- grd[monster->x + mmov_x][monster->y + mmov_y] = DNGN_FLOOR;
-
- if (!silenced(you.x_pos, you.y_pos))
- mpr("You hear a grinding noise.", MSGCH_SOUND);
- }
- }
-
- mgrd[monster->x][monster->y] = NON_MONSTER;
-
- if (good_move[mmov_x + 1][mmov_y + 1] && !(mmov_x == 0 && mmov_y == 0))
- {
- // check for attacking player
- if (monster->x + mmov_x == you.x_pos
- && monster->y + mmov_y == you.y_pos)
- {
- monster_attack( monster_index(monster) );
- mmov_x = 0;
- mmov_y = 0;
- }
-
- // If we're following the player through stairs, the only valid
- // movement is towards the player. -- bwr
- if (testbits( monster->flags, MF_TAKING_STAIRS ))
- {
-#if DEBUG_DIAGNOSTICS
- snprintf( info, INFO_SIZE, "%s is skipping movement in order to follow.",
- ptr_monam( monster, DESC_CAP_THE ) );
-
- mpr( info, MSGCH_DIAGNOSTICS );
-#endif
- mmov_x = 0;
- mmov_y = 0;
- }
-
- // check for attacking another monster
- int targmon = mgrd[monster->x + mmov_x][monster->y + mmov_y];
- if (targmon != NON_MONSTER)
- {
- if (mons_aligned(monster_index(monster), targmon))
- monster_swaps_places(monster, mmov_x, mmov_y);
- else
- monsters_fight(monster_index(monster), targmon);
-
- // If the monster swapped places, the work's already done.
- mmov_x = 0;
- mmov_y = 0;
- }
-
- if (monster->type == MONS_EFREET
- || monster->type == MONS_FIRE_ELEMENTAL)
- {
- place_cloud( CLOUD_FIRE_MON, monster->x, monster->y,
- 2 + random2(4) );
- }
-
- if (monster->type == MONS_ROTTING_DEVIL)
- {
- place_cloud( CLOUD_MIASMA_MON, monster->x, monster->y,
- 2 + random2(3) );
- }
-
- /* this appears to be the real one, ie where the movement occurs: */
- monster->x += mmov_x;
- monster->y += mmov_y;
- }
- else
- {
- // fleeing monsters that can't move will panic and possibly
- // turn to face their attacker
- if (monster->behaviour == BEH_FLEE)
- behaviour_event(monster, ME_CORNERED);
- }
-
- /* need to put in something so that monster picks up multiple
- items (eg ammunition) identical to those it's carrying. */
- mgrd[monster->x][monster->y] = monster_index(monster);
-
- // monsters stepping on traps:
- if (mmov_x != 0 || mmov_y != 0)
- {
- mons_trap(monster);
- }
-} // end monster_move()
-
-
-static bool plant_spit(struct monsters *monster, struct bolt &pbolt)
-{
- bool didSpit = false;
-
- char spit_string[INFO_SIZE];
-
- // setup plant spit
- strcpy( pbolt.beam_name, "acid" );
- pbolt.type = SYM_ZAP;
- pbolt.range = 9;
- pbolt.rangeMax = 9;
- pbolt.colour = YELLOW;
- pbolt.flavour = BEAM_ACID;
- pbolt.beam_source = monster_index(monster);
- pbolt.damage = dice_def( 3, 7 );
- pbolt.hit = 20 + (3 * monster->hit_dice);
- pbolt.thrower = KILL_MON_MISSILE;
- pbolt.aux_source = NULL;
-
- // fire tracer
- fire_tracer(monster, pbolt);
-
- if (mons_should_fire(pbolt))
- {
- strcpy( spit_string, " spits" );
- if (pbolt.target_x == you.x_pos && pbolt.target_y == you.y_pos)
- strcat( spit_string, " at you" );
-
- strcat( spit_string, "." );
- simple_monster_message( monster, spit_string );
-
- fire_beam( pbolt );
- didSpit = true;
- }
-
- return (didSpit);
-} // end plant_spit()
-
-static void mons_in_cloud(struct monsters *monster)
-{
- int wc = env.cgrid[monster->x][monster->y];
- int hurted = 0;
- struct bolt beam;
-
- const int speed = ((monster->speed > 0) ? monster->speed : 10);
- bool wake = false;
-
- if (mons_is_mimic( monster->type ))
- {
- mimic_alert(monster);
- return;
- }
-
- switch (env.cloud[wc].type)
- {
- case CLOUD_DEBUGGING:
- cprintf("Fatal error: monster steps on nonexistent cloud!");
- exit(0);
- return;
-
- case CLOUD_FIRE:
- case CLOUD_FIRE_MON:
- if (monster->type == MONS_FIRE_VORTEX
- || monster->type == MONS_EFREET
- || monster->type == MONS_FIRE_ELEMENTAL)
- {
- return;
- }
-
- simple_monster_message(monster, " is engulfed in flame!");
-
- if (mons_res_fire(monster) > 0)
- return;
-
- hurted += ((random2avg(16, 3) + 6) * 10) / speed;
-
- if (mons_res_fire(monster) < 0)
- hurted += (random2(15) * 10) / speed;
-
- // remember that the above is in addition to the other you.damage.
- hurted -= random2(1 + monster->armour_class);
- break; // to damage routine at end {dlb}
-
- case CLOUD_STINK:
- case CLOUD_STINK_MON:
- simple_monster_message(monster, " is engulfed in noxious gasses!");
-
- if (mons_res_poison(monster) > 0)
- return;
-
- beam.flavour = BEAM_CONFUSION;
-
- if (1 + random2(27) >= monster->hit_dice)
- mons_ench_f2(monster, beam);
-
- hurted += (random2(3) * 10) / speed;
- break; // to damage routine at end {dlb}
-
- case CLOUD_COLD:
- case CLOUD_COLD_MON:
- simple_monster_message(monster, " is engulfed in freezing vapours!");
-
- if (mons_res_cold(monster) > 0)
- return;
-
- hurted += ((6 + random2avg(16, 3)) * 10) / speed;
-
- if (mons_res_cold(monster) < 0)
- hurted += (random2(15) * 10) / speed;
-
- // remember that the above is in addition to the other damage.
- hurted -= random2(1 + monster->armour_class);
- break; // to damage routine at end {dlb}
-
- // what of armour of poison resistance here? {dlb}
- case CLOUD_POISON:
- case CLOUD_POISON_MON:
- simple_monster_message(monster, " is engulfed in a cloud of poison!");
-
- if (mons_res_poison(monster) > 0)
- return;
-
- poison_monster(monster, (env.cloud[wc].type == CLOUD_POISON));
- // If the monster got poisoned, wake it up.
- wake = true;
-
- hurted += (random2(8) * 10) / speed;
-
- if (mons_res_poison(monster) < 0)
- hurted += (random2(4) * 10) / speed;
- break; // to damage routine at end {dlb}
-
- case CLOUD_STEAM:
- case CLOUD_STEAM_MON:
- // couldn't be bothered coding for armour of res fire
-
- // what of whether it is wearing steam dragon armour? {dlb}
- if (monster->type == MONS_STEAM_DRAGON)
- return;
-
- simple_monster_message(monster, " is engulfed in steam!");
-
- if (mons_res_fire(monster) > 0)
- return;
-
- hurted += (random2(6) * 10) / speed;
-
- if (mons_res_fire(monster) < 0)
- hurted += (random2(6) * 10) / speed;
-
- hurted -= random2(1 + monster->armour_class);
- break; // to damage routine at end {dlb}
-
- case CLOUD_MIASMA:
- case CLOUD_MIASMA_MON:
- simple_monster_message(monster, " is engulfed in a dark miasma!");
-
- if (mons_holiness(monster) != MH_NATURAL
- || monster->type == MONS_DEATH_DRAKE)
- return;
-
- poison_monster(monster, (env.cloud[wc].type == CLOUD_MIASMA));
-
- if (monster->max_hit_points > 4 && coinflip())
- monster->max_hit_points--;
-
- beam.flavour = BEAM_SLOW;
-
- if (one_chance_in(3))
- mons_ench_f2(monster, beam);
-
- hurted += (10 * random2avg(12, 3)) / speed; // 3
- break; // to damage routine at end {dlb}
-
- default: // 'harmless' clouds -- colored smoke, etc {dlb}.
- return;
- }
-
- // A sleeping monster that sustains damage will wake up.
- if ((wake || hurted > 0) && monster->behaviour == BEH_SLEEP)
- {
- // 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->x, monster->y);
- }
-
- if (hurted < 0)
- hurted = 0;
- else if (hurted > 0)
- {
- hurt_monster(monster, hurted);
-
- if (monster->hit_points < 1)
- {
- switch (env.cloud[wc].type)
- {
- case CLOUD_FIRE_MON:
- case CLOUD_STINK_MON:
- case CLOUD_COLD_MON:
- case CLOUD_POISON_MON:
- case CLOUD_STEAM_MON:
- case CLOUD_MIASMA_MON:
- monster_die(monster, KILL_MISC, 0);
- break;
- default:
- monster_die(monster, KILL_YOU, 0);
- }
-
- switch (env.cloud[wc].type)
- {
- case CLOUD_FIRE:
- case CLOUD_FIRE_MON:
- case CLOUD_COLD:
- case CLOUD_COLD_MON:
- case CLOUD_STEAM:
- case CLOUD_STEAM_MON:
- monster->speed_increment = 1;
- }
- }
- }
-} // end mons_in_cloud()
-
-unsigned char monster_habitat(int which_class)
-{
- switch (which_class)
- {
- case MONS_BIG_FISH:
- case MONS_GIANT_GOLDFISH:
- case MONS_ELECTRICAL_EEL:
- case MONS_JELLYFISH:
- case MONS_SWAMP_WORM:
- case MONS_WATER_ELEMENTAL:
- return (DNGN_DEEP_WATER); // no shallow water (only) monsters? {dlb}
- // must remain DEEP_WATER for now, else breaks code {dlb}
-
- case MONS_LAVA_WORM:
- case MONS_LAVA_FISH:
- case MONS_LAVA_SNAKE:
- case MONS_SALAMANDER:
- return (DNGN_LAVA);
-
- default:
- return (DNGN_FLOOR); // closest match to terra firma {dlb}
- }
-} // end monster_habitat()
-
-bool monster_descriptor(int which_class, unsigned char which_descriptor)
-{
- if (which_descriptor == MDSC_LEAVES_HIDE)
- {
- switch (which_class)
- {
- case MONS_DRAGON:
- case MONS_TROLL:
- case MONS_ICE_DRAGON:
- case MONS_STEAM_DRAGON:
- case MONS_MOTTLED_DRAGON:
- case MONS_STORM_DRAGON:
- case MONS_GOLDEN_DRAGON:
- case MONS_SWAMP_DRAGON:
- return (true);
- default:
- return (false);
- }
- }
-
- if (which_descriptor == MDSC_REGENERATES)
- {
- switch (which_class)
- {
- case MONS_CACODEMON:
- case MONS_DEEP_TROLL:
- case MONS_HELLWING:
- case MONS_IMP:
- case MONS_IRON_TROLL:
- case MONS_LEMURE:
- case MONS_ROCK_TROLL:
- case MONS_SLIME_CREATURE:
- case MONS_SNORG:
- case MONS_TROLL:
- case MONS_HYDRA:
- case MONS_KILLER_KLOWN:
- return (true);
- default:
- return (false);
- }
- }
-
- if (which_descriptor == MDSC_NOMSG_WOUNDS)
- {
- switch (which_class)
- {
- case MONS_RAKSHASA:
- case MONS_RAKSHASA_FAKE:
- case MONS_SKELETON_LARGE:
- case MONS_SKELETON_SMALL:
- case MONS_ZOMBIE_LARGE:
- case MONS_ZOMBIE_SMALL:
- case MONS_SIMULACRUM_SMALL:
- case MONS_SIMULACRUM_LARGE:
- return (true);
- default:
- return (false);
- }
- }
- return (false);
-} // end monster_descriptor()
-
-bool message_current_target(void)
-{
- if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU)
- {
- struct monsters *montarget = &menv[you.prev_targ];
-
- if (mons_near(montarget) && player_monster_visible( montarget ))
- {
- snprintf( info, INFO_SIZE,
- "You are currently targeting %s (use p/t to fire).",
- ptr_monam(montarget, DESC_NOCAP_THE) );
-
- mpr(info);
- return (true); // early return {dlb}
- }
-
- mpr("You have no current target.");
- }
-
- return (false);
-} // end message_current_target()
-
-// aaah, the simple joys of pointer arithmetic! {dlb}:
-unsigned int monster_index(const monsters *monster)
-{
- return (monster - menv.buffer());
-} // end monster_index()
-
-bool hurt_monster(struct monsters * victim, int damage_dealt)
-{
- bool just_a_scratch = true;
-
- if (damage_dealt > 0)
- {
- just_a_scratch = false;
- victim->hit_points -= damage_dealt;
- }
-
- return (!just_a_scratch);
-} // end hurt_monster()
-
-bool heal_monster(struct monsters * patient, int health_boost,
- bool permit_growth)
-{
- if (health_boost < 1)
- return (false);
- else if (!permit_growth && patient->hit_points == patient->max_hit_points)
- return (false);
- else
- {
- patient->hit_points += health_boost;
-
- if (patient->hit_points > patient->max_hit_points)
- {
- if (permit_growth)
- patient->max_hit_points++;
-
- patient->hit_points = patient->max_hit_points;
- }
- }
-
- return (true);
-} // end heal_monster()
-
-static int map_wand_to_mspell(int wand_type)
-{
- int mzap = 0;
-
- switch (wand_type)
- {
- case WAND_FLAME:
- mzap = MS_FLAME;
- break;
- case WAND_FROST:
- mzap = MS_FROST;
- break;
- case WAND_SLOWING:
- mzap = MS_SLOW;
- break;
- case WAND_HASTING:
- mzap = MS_HASTE;
- break;
- case WAND_MAGIC_DARTS:
- mzap = MS_MMISSILE;
- break;
- case WAND_HEALING:
- mzap = MS_HEAL;
- break;
- case WAND_PARALYSIS:
- mzap = MS_PARALYSIS;
- break;
- case WAND_FIRE:
- mzap = MS_FIRE_BOLT;
- break;
- case WAND_COLD:
- mzap = MS_COLD_BOLT;
- break;
- case WAND_CONFUSION:
- mzap = MS_CONFUSE;
- break;
- case WAND_INVISIBILITY:
- mzap = MS_INVIS;
- break;
- case WAND_TELEPORTATION:
- mzap = MS_TELEPORT_OTHER;
- break;
- case WAND_LIGHTNING:
- mzap = MS_LIGHTNING_BOLT;
- break;
- case WAND_DRAINING:
- mzap = MS_NEGATIVE_BOLT;
- break;
- default:
- mzap = 0;
- break;
- }
-
- return (mzap);
-}