From c5950794e9a3e338fc2568e6f0e827868c821885 Mon Sep 17 00:00:00 2001 From: Jude Brown Date: Thu, 12 Nov 2009 09:01:44 +1000 Subject: Rename monspeak/monplace/monstuff to mon-speak/place/stuff. I'm pretty sure I've managed to replace every instance of a reference to these files, so I hopefully haven't missed anything. --- crawl-ref/source/abl-show.cc | 2 +- crawl-ref/source/abyss.cc | 2 +- crawl-ref/source/acr.cc | 4 +- crawl-ref/source/arena.cc | 4 +- crawl-ref/source/art-func.h | 4 +- crawl-ref/source/attitude-change.cc | 2 +- crawl-ref/source/beam.cc | 8 +- crawl-ref/source/dbg-util.cc | 2 +- crawl-ref/source/decks.cc | 4 +- crawl-ref/source/delay.cc | 2 +- crawl-ref/source/describe.cc | 2 +- crawl-ref/source/directn.cc | 2 +- crawl-ref/source/dungeon.cc | 2 +- crawl-ref/source/effects.cc | 4 +- crawl-ref/source/enum.h | 2 +- crawl-ref/source/fight.cc | 4 +- crawl-ref/source/files.cc | 4 +- crawl-ref/source/godabil.cc | 4 +- crawl-ref/source/godwrath.cc | 4 +- crawl-ref/source/it_use3.cc | 2 +- crawl-ref/source/item_use.cc | 4 +- crawl-ref/source/itemprop.cc | 2 +- crawl-ref/source/items.cc | 4 +- crawl-ref/source/kills.cc | 2 +- crawl-ref/source/l_dgnmon.cc | 4 +- crawl-ref/source/l_mons.cc | 2 +- crawl-ref/source/lev-pand.cc | 2 +- crawl-ref/source/makefile.obj | 6 +- crawl-ref/source/mapdef.cc | 2 +- crawl-ref/source/maps.cc | 2 +- crawl-ref/source/menu.cc | 2 +- crawl-ref/source/message.cc | 2 +- crawl-ref/source/mgrow.cc | 4 +- crawl-ref/source/misc.cc | 4 +- crawl-ref/source/mon-abil.cc | 6 +- crawl-ref/source/mon-act.cc | 4 +- crawl-ref/source/mon-behv.cc | 4 +- crawl-ref/source/mon-cast.cc | 8 +- crawl-ref/source/mon-data.h | 2 +- crawl-ref/source/mon-info.h | 2 +- crawl-ref/source/mon-place.cc | 3525 +++++++++++++++++++++++++++++++ crawl-ref/source/mon-place.h | 398 ++++ crawl-ref/source/mon-speak.cc | 860 ++++++++ crawl-ref/source/mon-speak.h | 17 + crawl-ref/source/mon-stuff.cc | 3989 +++++++++++++++++++++++++++++++++++ crawl-ref/source/mon-stuff.h | 179 ++ crawl-ref/source/mon-util.cc | 4 +- crawl-ref/source/monplace.cc | 3525 ------------------------------- crawl-ref/source/monplace.h | 398 ---- crawl-ref/source/monspeak.cc | 860 -------- crawl-ref/source/monspeak.h | 17 - crawl-ref/source/monster.cc | 6 +- crawl-ref/source/monstuff.cc | 3989 ----------------------------------- crawl-ref/source/monstuff.h | 179 -- crawl-ref/source/mtransit.cc | 2 +- crawl-ref/source/ouch.cc | 4 +- crawl-ref/source/output.cc | 2 +- crawl-ref/source/religion.cc | 4 +- crawl-ref/source/shout.cc | 4 +- crawl-ref/source/spells1.cc | 2 +- crawl-ref/source/spells2.cc | 4 +- crawl-ref/source/spells3.cc | 4 +- crawl-ref/source/spells4.cc | 4 +- crawl-ref/source/spl-cast.cc | 2 +- crawl-ref/source/spl-mis.cc | 4 +- crawl-ref/source/stash.cc | 2 +- crawl-ref/source/stuff.cc | 4 +- crawl-ref/source/terrain.cc | 4 +- crawl-ref/source/tilepick.cc | 2 +- crawl-ref/source/traps.cc | 2 +- crawl-ref/source/travel.cc | 2 +- crawl-ref/source/view.cc | 4 +- crawl-ref/source/wiz-fsim.cc | 4 +- crawl-ref/source/wiz-item.cc | 2 +- crawl-ref/source/wiz-mon.cc | 6 +- crawl-ref/source/xom.cc | 4 +- 76 files changed, 9075 insertions(+), 9075 deletions(-) create mode 100644 crawl-ref/source/mon-place.cc create mode 100644 crawl-ref/source/mon-place.h create mode 100644 crawl-ref/source/mon-speak.cc create mode 100644 crawl-ref/source/mon-speak.h create mode 100644 crawl-ref/source/mon-stuff.cc create mode 100644 crawl-ref/source/mon-stuff.h delete mode 100644 crawl-ref/source/monplace.cc delete mode 100644 crawl-ref/source/monplace.h delete mode 100644 crawl-ref/source/monspeak.cc delete mode 100644 crawl-ref/source/monspeak.h delete mode 100644 crawl-ref/source/monstuff.cc delete mode 100644 crawl-ref/source/monstuff.h (limited to 'crawl-ref') diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index 73dc7b730c..9c86936570 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -37,7 +37,7 @@ #include "message.h" #include "menu.h" #include "misc.h" -#include "monplace.h" +#include "mon-place.h" #include "mutation.h" #include "notes.h" #include "ouch.h" diff --git a/crawl-ref/source/abyss.cc b/crawl-ref/source/abyss.cc index e5d64fff31..1a5db5cbdb 100644 --- a/crawl-ref/source/abyss.cc +++ b/crawl-ref/source/abyss.cc @@ -20,7 +20,7 @@ #include "misc.h" #include "mon-iter.h" #include "mon-util.h" -#include "monplace.h" +#include "mon-place.h" #include "mtransit.h" #include "player.h" #include "dungeon.h" diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index 70901ec1cb..83761a7cb6 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -90,8 +90,8 @@ #include "mon-act.h" #include "mon-cast.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "newgame.h" diff --git a/crawl-ref/source/arena.cc b/crawl-ref/source/arena.cc index dcae744e65..178a592129 100644 --- a/crawl-ref/source/arena.cc +++ b/crawl-ref/source/arena.cc @@ -24,8 +24,8 @@ #include "mon-behv.h" #include "mon-pick.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "spl-mis.h" #include "spl-util.h" #include "state.h" diff --git a/crawl-ref/source/art-func.h b/crawl-ref/source/art-func.h index f4c076e478..c2e8cc5159 100644 --- a/crawl-ref/source/art-func.h +++ b/crawl-ref/source/art-func.h @@ -23,8 +23,8 @@ #include "effects.h" // For Sceptre of Torment tormenting #include "food.h" // For evokes -#include "monplace.h" // For Sceptre of Asmodeus evoke -#include "monstuff.h" // For Scythe of Curses cursing items +#include "mon-place.h" // For Sceptre of Asmodeus evoke +#include "mon-stuff.h" // For Scythe of Curses cursing items #include "spells3.h" // For Zonguldrok animating dead #include "spl-cast.h" // For evokes #include "spl-mis.h" // For Staff of Wucad Mu miscasts diff --git a/crawl-ref/source/attitude-change.cc b/crawl-ref/source/attitude-change.cc index 518d77cb9a..0cb8d91949 100644 --- a/crawl-ref/source/attitude-change.cc +++ b/crawl-ref/source/attitude-change.cc @@ -19,7 +19,7 @@ #include "mon-iter.h" #include "mon-util.h" #include "monster.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "player.h" #include "random.h" #include "religion.h" diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc index 8203461539..265732b9d3 100644 --- a/crawl-ref/source/beam.cc +++ b/crawl-ref/source/beam.cc @@ -46,8 +46,8 @@ #include "misc.h" #include "mon-behv.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "ouch.h" @@ -2877,7 +2877,7 @@ void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only) // When a mimic is hit by a ranged attack, it teleports away (the slow // way) and changes its appearance - the appearance change is in -// monster_teleport() in monstuff.cc. +// monster_teleport() in mon-stuff.cc. void mimic_alert(monsters *mimic) { if (!mimic->alive()) @@ -5153,7 +5153,7 @@ mon_resist_type bolt::apply_enchantment_to_monster(monsters* mon) } // The monster can be no more than lightly wounded/damaged, - // using the formula from monstuff.cc:mons_get_damage_level(). + // using the formula from mon-stuff.cc:mons_get_damage_level(). if (mon->hit_points <= mon->max_hit_points * 3 / 4) { simple_monster_message(mon, "'s soul is too badly injured."); diff --git a/crawl-ref/source/dbg-util.cc b/crawl-ref/source/dbg-util.cc index 1ccabe928d..12b2bf7931 100644 --- a/crawl-ref/source/dbg-util.cc +++ b/crawl-ref/source/dbg-util.cc @@ -12,7 +12,7 @@ #include "cio.h" #include "coord.h" #include "dungeon.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "religion.h" #include "shopping.h" diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc index cdd4626e99..1c023c3e4c 100644 --- a/crawl-ref/source/decks.cc +++ b/crawl-ref/source/decks.cc @@ -32,8 +32,8 @@ #include "message.h" #include "misc.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mutation.h" #include "ouch.h" #include "player.h" diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc index 0ef9aa6aca..85e7b6ea29 100644 --- a/crawl-ref/source/delay.cc +++ b/crawl-ref/source/delay.cc @@ -34,7 +34,7 @@ #include "message.h" #include "misc.h" #include "mon-behv.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "notes.h" #include "ouch.h" diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 4a536275f7..33f285c55d 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -40,7 +40,7 @@ #include "macro.h" #include "menu.h" #include "message.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "newgame.h" #include "jobs.h" diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 01b33bd563..b0349a6721 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -44,7 +44,7 @@ #include "message.h" #include "menu.h" #include "misc.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-info.h" #include "mon-util.h" #include "output.h" diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index 763dbe7066..e98318ead2 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -41,7 +41,7 @@ #include "message.h" #include "misc.h" #include "mon-util.h" -#include "monplace.h" +#include "mon-place.h" #include "notes.h" #include "place.h" #include "player.h" diff --git a/crawl-ref/source/effects.cc b/crawl-ref/source/effects.cc index a9eb9ceee6..64bf8dce2b 100644 --- a/crawl-ref/source/effects.cc +++ b/crawl-ref/source/effects.cc @@ -44,8 +44,8 @@ #include "mon-behv.h" #include "mon-cast.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "notes.h" diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index f452a9523c..9d2eb982c0 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -1921,7 +1921,7 @@ enum monster_type // (int) menv[].type MONS_WHITE_DRACONIAN, MONS_PALE_DRACONIAN, // 320 Should always be last colour. - // Sync up with monplace.cc's draconian selection if adding more. + // Sync up with mon-place.cc's draconian selection if adding more. MONS_DRACONIAN_CALLER, MONS_DRACONIAN_MONK, MONS_DRACONIAN_ZEALOT, diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index 7bd0f6d512..8466aa7f7c 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -44,8 +44,8 @@ #include "misc.h" #include "mon-behv.h" #include "mon-cast.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "ouch.h" diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc index cd63e9cd15..9bfb072dc3 100644 --- a/crawl-ref/source/files.cc +++ b/crawl-ref/source/files.cc @@ -8,7 +8,7 @@ #include "delay.h" #include "files.h" -#include "monplace.h" +#include "mon-place.h" #include #include @@ -59,7 +59,7 @@ #include "message.h" #include "misc.h" #include "mon-act.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mtransit.h" #include "newgame.h" diff --git a/crawl-ref/source/godabil.cc b/crawl-ref/source/godabil.cc index a017265ad9..114cb53842 100644 --- a/crawl-ref/source/godabil.cc +++ b/crawl-ref/source/godabil.cc @@ -24,8 +24,8 @@ #include "mon-act.h" #include "mon-behv.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "options.h" diff --git a/crawl-ref/source/godwrath.cc b/crawl-ref/source/godwrath.cc index d896de98c3..7b0f262c20 100644 --- a/crawl-ref/source/godwrath.cc +++ b/crawl-ref/source/godwrath.cc @@ -22,8 +22,8 @@ #include "message.h" #include "misc.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mutation.h" #include "ouch.h" #include "quiver.h" diff --git a/crawl-ref/source/it_use3.cc b/crawl-ref/source/it_use3.cc index f36bae0148..8e40030f83 100644 --- a/crawl-ref/source/it_use3.cc +++ b/crawl-ref/source/it_use3.cc @@ -30,7 +30,7 @@ #include "itemprop.h" #include "mapmark.h" #include "message.h" -#include "monplace.h" +#include "mon-place.h" #include "misc.h" #include "player.h" #include "religion.h" diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc index 0620ab8d8f..60e172f553 100644 --- a/crawl-ref/source/item_use.cc +++ b/crawl-ref/source/item_use.cc @@ -45,8 +45,8 @@ #include "misc.h" #include "mon-behv.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "notes.h" #include "options.h" #include "ouch.h" diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index 565827c41c..d14d2b5d6d 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -27,7 +27,7 @@ #include "itemprop.h" #include "macro.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "notes.h" #include "player.h" #include "quiver.h" diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index 625dafd11d..2e366841c7 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -43,8 +43,8 @@ #include "libutil.h" #include "message.h" #include "misc.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "notes.h" diff --git a/crawl-ref/source/kills.cc b/crawl-ref/source/kills.cc index 589a9b3972..6857c3d82c 100644 --- a/crawl-ref/source/kills.cc +++ b/crawl-ref/source/kills.cc @@ -10,7 +10,7 @@ #include "describe.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "files.h" #include "ghost.h" #include "place.h" diff --git a/crawl-ref/source/l_dgnmon.cc b/crawl-ref/source/l_dgnmon.cc index 4b8a7bc5ba..7c37b6bdb0 100644 --- a/crawl-ref/source/l_dgnmon.cc +++ b/crawl-ref/source/l_dgnmon.cc @@ -11,8 +11,8 @@ #include "dungeon.h" #include "mapdef.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #define MONSLIST_METATABLE "crawldgn.monster_list" diff --git a/crawl-ref/source/l_mons.cc b/crawl-ref/source/l_mons.cc index 7d57a34961..5f9defaf6c 100644 --- a/crawl-ref/source/l_mons.cc +++ b/crawl-ref/source/l_mons.cc @@ -5,7 +5,7 @@ #include "delay.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" ///////////////////////////////////////////////////////////////////// // Monster handling diff --git a/crawl-ref/source/lev-pand.cc b/crawl-ref/source/lev-pand.cc index 827e753cd1..a772d6e3d6 100644 --- a/crawl-ref/source/lev-pand.cc +++ b/crawl-ref/source/lev-pand.cc @@ -12,7 +12,7 @@ #include "externs.h" #include "dungeon.h" -#include "monplace.h" +#include "mon-place.h" #include "mon-pick.h" #include "random.h" diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj index ccbfb75aeb..b8728e6839 100644 --- a/crawl-ref/source/makefile.obj +++ b/crawl-ref/source/makefile.obj @@ -106,10 +106,10 @@ mon-info.o \ mon-iter.o \ mon-pick.o \ mon-util.o \ -monplace.o \ -monspeak.o \ +mon-place.o \ +mon-speak.o \ monster.o \ -monstuff.o \ +mon-stuff.o \ mt19937ar.o \ mtransit.o \ mutation.o \ diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index a9bff3cf52..a5fa792a76 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -31,7 +31,7 @@ #include "maps.h" #include "misc.h" #include "mon-cast.h" -#include "monplace.h" +#include "mon-place.h" #include "mon-util.h" #include "place.h" #include "random.h" diff --git a/crawl-ref/source/maps.cc b/crawl-ref/source/maps.cc index bc2139681d..0eced70f21 100644 --- a/crawl-ref/source/maps.cc +++ b/crawl-ref/source/maps.cc @@ -24,7 +24,7 @@ #include "message.h" #include "mapdef.h" #include "mon-util.h" -#include "monplace.h" +#include "mon-place.h" #include "random.h" #include "state.h" #include "terrain.h" diff --git a/crawl-ref/source/menu.cc b/crawl-ref/source/menu.cc index 8cb98b8680..af1454f22f 100644 --- a/crawl-ref/source/menu.cc +++ b/crawl-ref/source/menu.cc @@ -20,7 +20,7 @@ #ifdef USE_TILE #include "coord.h" - #include "monstuff.h" + #include "mon-stuff.h" #include "mon-util.h" #include "newgame.h" #include "terrain.h" diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc index e87a988682..77fb18d697 100644 --- a/crawl-ref/source/message.cc +++ b/crawl-ref/source/message.cc @@ -27,7 +27,7 @@ #include "libutil.h" #include "macro.h" #include "message.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "notes.h" #include "random.h" #include "religion.h" diff --git a/crawl-ref/source/mgrow.cc b/crawl-ref/source/mgrow.cc index 3693e43dd4..7be571d541 100644 --- a/crawl-ref/source/mgrow.cc +++ b/crawl-ref/source/mgrow.cc @@ -9,8 +9,8 @@ #include "enum.h" #include "mgrow.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "random.h" // Base experience required by a monster to reach HD 1. diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index d1744552bb..43d9499ed5 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -55,10 +55,10 @@ #include "makeitem.h" #include "mapmark.h" #include "message.h" -#include "monplace.h" +#include "mon-place.h" #include "mon-iter.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "ouch.h" #include "output.h" #include "overmap.h" diff --git a/crawl-ref/source/mon-abil.cc b/crawl-ref/source/mon-abil.cc index bfc77357b8..73af98dd11 100644 --- a/crawl-ref/source/mon-abil.cc +++ b/crawl-ref/source/mon-abil.cc @@ -25,9 +25,9 @@ #include "mon-behv.h" #include "mon-cast.h" #include "mon-iter.h" -#include "monplace.h" -#include "monspeak.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-speak.h" +#include "mon-stuff.h" #include "random.h" #include "spl-mis.h" #include "spl-util.h" diff --git a/crawl-ref/source/mon-act.cc b/crawl-ref/source/mon-act.cc index 0f17f8c53b..1ca78665b2 100644 --- a/crawl-ref/source/mon-act.cc +++ b/crawl-ref/source/mon-act.cc @@ -32,8 +32,8 @@ #include "mon-behv.h" #include "mon-cast.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mutation.h" #include "notes.h" #include "options.h" diff --git a/crawl-ref/source/mon-behv.cc b/crawl-ref/source/mon-behv.cc index 2954e65065..7883a367b1 100644 --- a/crawl-ref/source/mon-behv.cc +++ b/crawl-ref/source/mon-behv.cc @@ -15,8 +15,8 @@ #include "fprop.h" #include "exclude.h" #include "los.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "random.h" #include "state.h" diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc index 3464517da8..08cba24f19 100644 --- a/crawl-ref/source/mon-cast.cc +++ b/crawl-ref/source/mon-cast.cc @@ -21,9 +21,9 @@ #include "los.h" #include "misc.h" #include "mon-behv.h" -#include "monplace.h" -#include "monspeak.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-speak.h" +#include "mon-stuff.h" #include "mon-util.h" #include "random.h" #include "religion.h" @@ -1764,7 +1764,7 @@ void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast, return; case SPELL_ANIMATE_DEAD: - // see special handling in monstuff::handle_spell() {dlb} + // see special handling in mon-stuff::handle_spell() {dlb} animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster), monster->foe, god); return; diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h index 2574dd8d57..be2b6482cc 100644 --- a/crawl-ref/source/mon-data.h +++ b/crawl-ref/source/mon-data.h @@ -50,7 +50,7 @@ MH_NONLIVING - golems and other constructs MH_PLANT - plants - exp_mod: see give_adjusted_experience() in monstuff.cc + exp_mod: see give_adjusted_experience() in mon-stuff.cc - the experience given for killing this monster is calculated something like this: diff --git a/crawl-ref/source/mon-info.h b/crawl-ref/source/mon-info.h index c5d559d4e9..20618caf1a 100644 --- a/crawl-ref/source/mon-info.h +++ b/crawl-ref/source/mon-info.h @@ -1,7 +1,7 @@ #ifndef MON_INFO_H #define MON_INFO_H -#include "monstuff.h" +#include "mon-stuff.h" // Monster info used by the pane; precomputes some data // to help with sorting and rendering. diff --git a/crawl-ref/source/mon-place.cc b/crawl-ref/source/mon-place.cc new file mode 100644 index 0000000000..15c805455c --- /dev/null +++ b/crawl-ref/source/mon-place.cc @@ -0,0 +1,3525 @@ +/* + * File: monplace.cc + * Summary: Functions used when placing monsters in the dungeon. + * Written by: Linley Henzell + */ + +#include "AppHdr.h" + +#include + +#include "mon-place.h" + +#include "arena.h" +#include "branch.h" +#include "directn.h" // for the Compass +#include "fprop.h" +#include "externs.h" +#include "options.h" +#include "ghost.h" +#include "lev-pand.h" +#include "los.h" +#include "makeitem.h" +#include "message.h" +#include "mon-behv.h" +#include "mon-gear.h" +#include "mon-pick.h" +#include "mon-util.h" +#include "mon-stuff.h" +#include "player.h" +#include "random.h" +#include "religion.h" +#include "state.h" +#include "stuff.h" +#include "terrain.h" +#include "traps.h" +#include "view.h" + +static std::vector vault_mon_types; +static std::vector vault_mon_bases; +static std::vector vault_mon_weights; + +#define VAULT_MON_TYPES_KEY "vault_mon_types" +#define VAULT_MON_BASES_KEY "vault_mon_bases" +#define VAULT_MON_WEIGHTS_KEY "vault_mon_weights" + +// NEW place_monster -- note that power should be set to: +// 51 for abyss +// 52 for pandemonium +// x otherwise + +// proximity is the same as for mons_place: +// 0 is no restrictions +// 1 attempts to place near player +// 2 attempts to avoid player LOS + +#define BIG_BAND 20 + +static monster_type _resolve_monster_type(monster_type mon_type, + proximity_type proximity, + monster_type &base_type, + coord_def &pos, + unsigned mmask, + dungeon_char_type *stair_type, + int *lev_mons); + +static void _define_zombie(int mid, monster_type ztype, monster_type cs, + int power, coord_def pos); +static monster_type _band_member(band_type band, int power); +static band_type _choose_band(int mon_type, int power, int &band_size); +// static int _place_monster_aux(int mon_type, beh_type behaviour, int target, +// int px, int py, int power, int extra, +// bool first_band_member, int dur = 0); + +static int _place_monster_aux(const mgen_data &mg, bool first_band_member, + bool force_pos = false); + +// Returns whether actual_feat is compatible with feat_wanted for monster +// movement and generation. +bool feat_compatible(dungeon_feature_type feat_wanted, + dungeon_feature_type actual_feat) +{ + if (feat_wanted == DNGN_FLOOR) + { + return (actual_feat >= DNGN_FLOOR + && actual_feat != DNGN_BUILDER_SPECIAL_WALL + || actual_feat == DNGN_SHALLOW_WATER); + } + + if (feat_wanted >= DNGN_ROCK_WALL + && feat_wanted <= DNGN_CLEAR_PERMAROCK_WALL) + { + // A monster can only move through or inhabit permanent rock if that's + // exactly what it's asking for. + if (actual_feat == DNGN_PERMAROCK_WALL + || actual_feat == DNGN_CLEAR_PERMAROCK_WALL) + { + return (feat_wanted == DNGN_PERMAROCK_WALL + || feat_wanted == DNGN_CLEAR_PERMAROCK_WALL); + } + + return (actual_feat >= DNGN_ROCK_WALL + && actual_feat <= DNGN_CLEAR_PERMAROCK_WALL); + } + + return (feat_wanted == actual_feat + || (feat_wanted == DNGN_DEEP_WATER + && (actual_feat == DNGN_SHALLOW_WATER + || actual_feat == DNGN_FOUNTAIN_BLUE))); +} + +// Can this monster survive on actual_grid? +// +// If you have an actual monster, use this instead of the overloaded function +// that uses only the monster class to make decisions. +bool monster_habitable_grid(const monsters *m, + dungeon_feature_type actual_grid) +{ + // Zombified monsters enjoy the same habitat as their original. + const monster_type montype = mons_is_zombified(m) ? mons_zombie_base(m) + : m->type; + + return (monster_habitable_grid(montype, actual_grid, mons_flies(m), + m->cannot_move())); +} + +inline static bool _mons_airborne(int mcls, int flies, bool paralysed) +{ + if (flies == -1) + flies = mons_class_flies(mcls); + + return (paralysed ? flies == FL_LEVITATE : flies != FL_NONE); +} + +// Can monsters of class monster_class live happily on actual_grid? +// Use flies == true to pretend the monster can fly. +// +// [dshaligram] We're trying to harmonise the checks from various places into +// one check, so we no longer care if a water elemental springs into existence +// on dry land, because they're supposed to be able to move onto dry land +// anyway. +bool monster_habitable_grid(monster_type montype, + dungeon_feature_type actual_grid, + int flies, bool paralysed) +{ + // No monster may be placed on open sea. + if (actual_grid == DNGN_OPEN_SEA) + return (false); + + const dungeon_feature_type feat_preferred = + habitat2grid(mons_class_primary_habitat(montype)); + const dungeon_feature_type feat_nonpreferred = + habitat2grid(mons_class_secondary_habitat(montype)); + + // Special check for fire elementals since their habitat is floor which + // is generally considered compatible with shallow water. + if (montype == MONS_FIRE_ELEMENTAL && feat_is_watery(actual_grid)) + return (false); + + // Krakens are too large for shallow water. + if (montype == MONS_KRAKEN && actual_grid == DNGN_SHALLOW_WATER) + return (false); + + if (feat_compatible(feat_preferred, actual_grid) + || (feat_nonpreferred != feat_preferred + && feat_compatible(feat_nonpreferred, actual_grid))) + { + return (true); + } + + // [dshaligram] Flying creatures are all DNGN_FLOOR, so we + // only have to check for the additional valid grids of deep + // water and lava. + if (_mons_airborne(montype, flies, paralysed) + && (actual_grid == DNGN_LAVA || actual_grid == DNGN_DEEP_WATER)) + { + return (true); + } + + return (false); +} + +// Returns true if the monster can submerge in the given grid. +bool monster_can_submerge(const monsters *mons, dungeon_feature_type grid) +{ + if (mons->type == MONS_TRAPDOOR_SPIDER && grid == DNGN_FLOOR) + return (!find_trap(mons->pos())); + + switch (mons_primary_habitat(mons)) + { + case HT_WATER: + // Monsters can submerge in shallow water - this is intentional. + return (feat_is_watery(grid)); + + case HT_LAVA: + return (grid == DNGN_LAVA); + + default: + return (false); + } +} + +static bool _need_moderate_ood(int lev_mons) +{ + return (env.turns_on_level > 700 - lev_mons * 117); +} + +static bool _need_super_ood(int lev_mons) +{ + return (env.turns_on_level > 1400 - lev_mons * 117 + && one_chance_in(5000)); +} + +static int _fuzz_mons_level(int level) +{ + if (one_chance_in(7)) + { + const int fuzz = random2avg(9, 2); + return (fuzz > 4? level + fuzz - 4 : level); + } + return (level); +} + +static void _hell_spawn_random_monsters() +{ + // Monster generation in the Vestibule drops off quickly. + const int taper_off_turn = 500; + int genodds = 240; + // genodds increases once you've spent more than 500 turns in Hell. + if (env.turns_on_level > taper_off_turn) + { + genodds += (env.turns_on_level - taper_off_turn); + genodds = (genodds < 0 ? 20000 : std::min(genodds, 20000)); + } + + if (x_chance_in_y(5, genodds)) + { + mgen_data mg(WANDERING_MONSTER); + mg.proximity = (one_chance_in(10) ? PROX_NEAR_STAIRS + : PROX_AWAY_FROM_PLAYER); + mons_place(mg); + viewwindow(false); + } +} + +//#define DEBUG_MON_CREATION + +// This function is now only called about once every 5 turns. (Used to be +// every turn independent of how much time an action took, which was not ideal.) +// To arrive at spawning rates close to how they used to be, replace the +// one_chance_in(value) checks with the new x_chance_in_y(5, value). (jpeg) +void spawn_random_monsters() +{ + if (crawl_state.arena) + return; + +#ifdef DEBUG_MON_CREATION + mpr("in spawn_random_monsters()", MSGCH_DIAGNOSTICS); +#endif + if (player_in_branch(BRANCH_VESTIBULE_OF_HELL)) + { + _hell_spawn_random_monsters(); + return; + } + + if (env.spawn_random_rate == 0) + { +#ifdef DEBUG_MON_CREATION + mpr("random monster gen turned off", MSGCH_DIAGNOSTICS); +#endif + return; + } + + const int rate = (you.char_direction == GDT_DESCENDING) ? + env.spawn_random_rate : 8; + + // Place normal dungeon monsters, but not in player LOS. + if (you.level_type == LEVEL_DUNGEON && x_chance_in_y(5, rate)) + { +#ifdef DEBUG_MON_CREATION + mpr("Create wandering monster...", MSGCH_DIAGNOSTICS); +#endif + proximity_type prox = (one_chance_in(10) ? PROX_NEAR_STAIRS + : PROX_AWAY_FROM_PLAYER); + + // The rules change once the player has picked up the Orb... + if (you.char_direction == GDT_ASCENDING) + prox = (one_chance_in(6) ? PROX_CLOSE_TO_PLAYER : PROX_ANYWHERE); + + mgen_data mg(WANDERING_MONSTER); + mg.proximity = prox; + mons_place(mg); + viewwindow(false); + return; + } + + // Place Abyss monsters. (Now happens regularly every 5 turns which might + // look a bit strange for a place as chaotic as the Abyss. Then again, + // the player is unlikely to meet all of them and notice this.) + if (you.level_type == LEVEL_ABYSS + && (you.char_direction != GDT_GAME_START + || x_chance_in_y(5, rate)) + && (you.religion != GOD_CHEIBRIADOS || coinflip())) + { + mons_place(mgen_data(WANDERING_MONSTER)); + viewwindow(false); + return; + } + + // Place Pandemonium monsters. + if (you.level_type == LEVEL_PANDEMONIUM && x_chance_in_y(5, rate)) + { + pandemonium_mons(); + viewwindow(false); + return; + } + + // A portal vault *might* decide to turn on random monster spawning, + // but it's off by default. + if (you.level_type == LEVEL_PORTAL_VAULT && x_chance_in_y(5, rate)) + { + mons_place(mgen_data(WANDERING_MONSTER)); + viewwindow(false); + } + + // No random monsters in the Labyrinth. +} + +monster_type pick_random_monster(const level_id &place) +{ + int level; + if (place.level_type == LEVEL_PORTAL_VAULT) + level = you.your_level; + else + level = place.absdepth(); + return pick_random_monster(place, level, level); +} + +monster_type pick_random_monster(const level_id &place, int power, + int &lev_mons) +{ + if (crawl_state.arena) + { + monster_type type = arena_pick_random_monster(place, power, lev_mons); + if (type != RANDOM_MONSTER) + return (type); + } + + if (place.level_type == LEVEL_LABYRINTH) + return (MONS_PROGRAM_BUG); + + if (place == BRANCH_ECUMENICAL_TEMPLE) + return (MONS_PROGRAM_BUG); + + if (place.level_type == LEVEL_PORTAL_VAULT) + { + monster_type base_type = (monster_type) 0; + coord_def dummy1; + dungeon_char_type dummy2; + monster_type type = + _resolve_monster_type(RANDOM_MONSTER, PROX_ANYWHERE, base_type, + dummy1, 0, &dummy2, &lev_mons); + +#if DEBUG || DEBUG_DIAGNOSTICS + if (base_type != 0 && base_type != MONS_PROGRAM_BUG) + mpr("Random portal vault mon discarding base type.", + MSGCH_ERROR); +#endif + return (type); + } + + monster_type mon_type = MONS_PROGRAM_BUG; + + lev_mons = power; + + if (place == BRANCH_MAIN_DUNGEON + && lev_mons != 51 && one_chance_in(4)) + { + lev_mons = random2(power); + } + + if (place == BRANCH_MAIN_DUNGEON + && lev_mons <= 27) + { + // If on D:1, allow moderately out-of-depth monsters only after + // a certain elapsed turn count on the level (currently 700 turns). + if (lev_mons || _need_moderate_ood(lev_mons)) + lev_mons = _fuzz_mons_level(lev_mons); + + // Potentially nasty surprise, but very rare. + if (_need_super_ood(lev_mons)) + lev_mons += random2(12); + + // Slightly out of depth monsters are more common: + // [ds] Replaced with a fuzz above for a more varied mix. + //if (need_moderate_ood(lev_mons)) + // lev_mons += random2(5); + + lev_mons = std::min(27, lev_mons); + } + + // Abyss or Pandemonium. Almost never called from Pan; probably only + // if a random demon gets summon anything spell. + if (lev_mons == 51 + || place.level_type == LEVEL_PANDEMONIUM + || place.level_type == LEVEL_ABYSS) + { + do + { + int count = 0; + + do + { + // was: random2(400) {dlb} + mon_type = static_cast( random2(NUM_MONSTERS) ); + count++; + } + while (mons_abyss(mon_type) == 0 && count < 2000); + + if (count == 2000) + return (MONS_PROGRAM_BUG); + if (crawl_state.arena && arena_veto_random_monster(mon_type)) + continue; + } + while (random2avg(100, 2) > mons_rare_abyss(mon_type) + && !one_chance_in(100)); + } + else + { + int level, diff, chance; + + lev_mons = std::min(30, lev_mons); + + int i; + for (i = 0; i < 10000; ++i) + { + int count = 0; + + do + { + mon_type = static_cast(random2(NUM_MONSTERS)); + count++; + } + while (mons_rarity(mon_type, place) == 0 && count < 2000); + + if (count == 2000) + return (MONS_PROGRAM_BUG); + + if (crawl_state.arena && arena_veto_random_monster(mon_type)) + continue; + + level = mons_level(mon_type, place); + diff = level - lev_mons; + chance = mons_rarity(mon_type, place) - (diff * diff); + + if ((lev_mons >= level - 5 && lev_mons <= level + 5) + && random2avg(100, 2) <= chance) + { + break; + } + } + + if (i == 10000) + return (MONS_PROGRAM_BUG); + } + + return (mon_type); +} + +static bool _can_place_on_trap(int mon_type, trap_type trap) +{ + if (trap == TRAP_TELEPORT) + return (false); + + if (trap == TRAP_SHAFT) + { + if (mon_type == RANDOM_MONSTER) + return (false); + + return (mons_class_flies(mon_type) != FL_NONE + || get_monster_data(mon_type)->size == SIZE_TINY); + } + + return (true); +} + +bool drac_colour_incompatible(int drac, int colour) +{ + return (drac == MONS_DRACONIAN_SCORCHER && colour == MONS_WHITE_DRACONIAN); +} + +static monster_type _resolve_monster_type(monster_type mon_type, + proximity_type proximity, + monster_type &base_type, + coord_def &pos, + unsigned mmask, + dungeon_char_type *stair_type, + int *lev_mons) +{ + if (mon_type == RANDOM_DRACONIAN) + { + // Pick any random drac, constrained by colour if requested. + do + { + mon_type = + static_cast( + random_range(MONS_BLACK_DRACONIAN, + MONS_DRACONIAN_SCORCHER)); + } + while (base_type != MONS_PROGRAM_BUG + && mon_type != base_type + && (mons_species(mon_type) == mon_type + || drac_colour_incompatible(mon_type, base_type))); + } + else if (mon_type == RANDOM_BASE_DRACONIAN) + mon_type = random_draconian_monster_species(); + else if (mon_type == RANDOM_NONBASE_DRACONIAN) + { + mon_type = + static_cast( + random_range(MONS_DRACONIAN_CALLER, MONS_DRACONIAN_SCORCHER)); + } + + // (2) Take care of non-draconian random monsters. + else if (mon_type == RANDOM_MONSTER) + { + level_id place = level_id::current(); + + // Respect destination level for staircases. + if (proximity == PROX_NEAR_STAIRS) + { + int tries = 0; + int pval = 0; + while (++tries <= 320) + { + pos = random_in_bounds(); + + if (actor_at(pos)) + continue; + + // Is the grid verboten? + if (!unforbidden( pos, mmask )) + continue; + + // Don't generate monsters on top of teleport traps. + const trap_def* ptrap = find_trap(pos); + if (ptrap && !_can_place_on_trap(mon_type, ptrap->type)) + continue; + + // Check whether there's a stair + // and whether it leads to another branch. + pval = near_stairs(pos, 1, *stair_type, place.branch); + + // No monsters spawned in the Temple. + if (branches[place.branch].id == BRANCH_ECUMENICAL_TEMPLE) + continue; + + // Found a position near the stairs! + if (pval > 0) + break; + } + + if (tries > 320) + { + // Give up and try somewhere else. + proximity = PROX_AWAY_FROM_PLAYER; + } + else + { + if (*stair_type == DCHAR_STAIRS_DOWN) // deeper level + ++*lev_mons; + else if (*stair_type == DCHAR_STAIRS_UP) // higher level + { + // Monsters don't come from outside the dungeon. + if (*lev_mons <= 0) + { + proximity = PROX_AWAY_FROM_PLAYER; + // In that case lev_mons stays as it is. + } + else + --*lev_mons; + } + } + } // end proximity check + + if (place == BRANCH_HALL_OF_BLADES) + mon_type = MONS_DANCING_WEAPON; + else + { + if (you.level_type == LEVEL_PORTAL_VAULT) + { + if (vault_mon_types.size() == 0) + return (MONS_PROGRAM_BUG); + + int i = choose_random_weighted(vault_mon_weights.begin(), + vault_mon_weights.end()); + int type = vault_mon_types[i]; + int base = vault_mon_bases[i]; + + if (type == -1) + { + place = level_id::from_packed_place(base); + // If lev_mons is set to you.your_level, it was probably + // set as a default meaning "the current dungeon depth", + // which for a portal vault using its own definition + // of random monsters means "the depth of whatever place + // we're using for picking the random monster". + if (*lev_mons == you.your_level) + *lev_mons = place.absdepth(); + // pick_random_monster() is called below + } + else + { + base_type = (monster_type) base; + mon_type = (monster_type) type; + if (mon_type == RANDOM_DRACONIAN + || mon_type == RANDOM_BASE_DRACONIAN + || mon_type == RANDOM_NONBASE_DRACONIAN) + { + mon_type = + _resolve_monster_type(mon_type, proximity, + base_type, pos, mmask, + stair_type, lev_mons); + } + return (mon_type); + } + } + + int tries = 0; + while (tries++ < 300) + { + // Now pick a monster of the given branch and level. + mon_type = pick_random_monster(place, *lev_mons, *lev_mons); + + // Don't allow monsters too stupid to use stairs (e.g. + // non-spectral zombified undead) to be placed near + // stairs. + if (proximity != PROX_NEAR_STAIRS + || mons_class_can_use_stairs(mon_type)) + { + break; + } + } + + if (proximity == PROX_NEAR_STAIRS && tries >= 300) + { + proximity = PROX_AWAY_FROM_PLAYER; + + // Reset target level. + if (*stair_type == DCHAR_STAIRS_DOWN) + --*lev_mons; + else if (*stair_type == DCHAR_STAIRS_UP) + ++*lev_mons; + + mon_type = pick_random_monster(place, *lev_mons, *lev_mons); + } + } + } + return (mon_type); +} + +// A short function to check the results of near_stairs(). +// Returns 0 if the point is not near stairs. +// Returns 1 if the point is near unoccupied stairs. +// Returns 2 if the point is near player-occupied stairs. +static int _is_near_stairs(coord_def &p) +{ + int result = 0; + + for (int i = -1; i <= 1; ++i) + for (int j = -1; j <= 1; ++j) + { + if (!in_bounds(p)) + continue; + + const dungeon_feature_type feat = grd(p); + if (feat_is_stair(feat)) + { + // Shouldn't matter for escape hatches. + if (feat_is_escape_hatch(feat)) + continue; + + // Should there be several stairs, don't overwrite the + // player on stairs info. + if (result < 2) + result = (p == you.pos()) ? 2 : 1; + } + } + + return (result); +} + +// Checks if the monster is ok to place at mg_pos. If force_location +// is true, then we'll be less rigorous in our checks, in particular +// allowing land monsters to be placed in shallow water and water +// creatures in fountains. +static bool _valid_monster_generation_location( + const mgen_data &mg, + const coord_def &mg_pos) +{ + if (!in_bounds(mg_pos) || actor_at(mg_pos)) + return (false); + + const monster_type montype = (mons_class_is_zombified(mg.cls) ? mg.base_type + : mg.cls); + if (!monster_habitable_grid(montype, grd(mg_pos), + mons_class_flies(montype), false) + || (mg.behaviour != BEH_FRIENDLY && is_sanctuary(mg_pos))) + { + return (false); + } + + // Don't generate monsters on top of teleport traps. + // (How did they get there?) + const trap_def* ptrap = find_trap(mg_pos); + if (ptrap && !_can_place_on_trap(mg.cls, ptrap->type)) + return (false); + + return (true); +} + +static bool _valid_monster_generation_location(mgen_data &mg) +{ + return _valid_monster_generation_location(mg, mg.pos); +} + +int place_monster(mgen_data mg, bool force_pos) +{ +#ifdef DEBUG_MON_CREATION + mpr("in place_monster()", MSGCH_DIAGNOSTICS); +#endif + + int tries = 0; + dungeon_char_type stair_type = NUM_DCHAR_TYPES; + int id = -1; + + // (1) Early out (summoned to occupied grid). + if (mg.use_position() && monster_at(mg.pos)) + return (-1); + + mg.cls = _resolve_monster_type(mg.cls, mg.proximity, mg.base_type, + mg.pos, mg.map_mask, + &stair_type, &mg.power); + + if (mg.cls == MONS_NO_MONSTER) + return (-1); + + // (3) Decide on banding (good lord!) + int band_size = 1; + monster_type band_monsters[BIG_BAND]; // band monster types + band_monsters[0] = mg.cls; + + // The (very) ugly thing band colour. + static unsigned char ugly_colour = BLACK; + + if (mg.permit_bands()) + { +#ifdef DEBUG_MON_CREATION + mpr("Choose band members...", MSGCH_DIAGNOSTICS); +#endif + const band_type band = _choose_band(mg.cls, mg.power, band_size); + band_size++; + + for (int i = 1; i < band_size; ++i) + { + band_monsters[i] = _band_member(band, mg.power); + + // Get the (very) ugly thing band colour, so that all (very) + // ugly things in a band will start with it. + if ((band_monsters[i] == MONS_UGLY_THING + || band_monsters[i] == MONS_VERY_UGLY_THING) + && ugly_colour == BLACK) + { + ugly_colour = ugly_thing_random_colour(); + } + } + } + + // Set the (very) ugly thing band colour. + if (ugly_colour != BLACK) + mg.colour = ugly_colour; + + // Returns 2 if the monster is placed near player-occupied stairs. + int pval = _is_near_stairs(mg.pos); + if (mg.proximity == PROX_NEAR_STAIRS) + { + // For some cases disallow monsters on stairs. + if (mons_class_is_stationary(mg.cls) + || (pval == 2 // Stairs occupied by player. + && (mons_class_base_speed(mg.cls) == 0 + || grd(mg.pos) == DNGN_LAVA + || grd(mg.pos) == DNGN_DEEP_WATER))) + { + mg.proximity = PROX_AWAY_FROM_PLAYER; + } + } + + // (4) For first monster, choose location. This is pretty intensive. + bool proxOK; + bool close_to_player; + + // Player shoved out of the way? + bool shoved = false; + + if (!mg.use_position()) + { + tries = 0; + + // Try to pick a position that is + // a) not occupied + // b) compatible + // c) in the 'correct' proximity to the player + + while (true) + { + // Dropped number of tries from 60. + if (tries++ >= 45) + return (-1); + + // Placement already decided for PROX_NEAR_STAIRS. + // Else choose a random point on the map. + if (mg.proximity != PROX_NEAR_STAIRS) + mg.pos = random_in_bounds(); + + if (!_valid_monster_generation_location(mg)) + continue; + + // Is the grid verboten? + if (!unforbidden(mg.pos, mg.map_mask)) + continue; + + // Let's recheck these even for PROX_NEAR_STAIRS, just in case. + // Check proximity to player. + proxOK = true; + + switch (mg.proximity) + { + case PROX_ANYWHERE: + if (grid_distance( you.pos(), mg.pos ) < 2 + random2(3)) + proxOK = false; + break; + + case PROX_CLOSE_TO_PLAYER: + case PROX_AWAY_FROM_PLAYER: + // If this is supposed to measure los vs not los, + // then see_cell(mg.pos) should be used instead. (jpeg) + close_to_player = (distance(you.pos(), mg.pos) < 64); + + if (mg.proximity == PROX_CLOSE_TO_PLAYER && !close_to_player + || mg.proximity == PROX_AWAY_FROM_PLAYER && close_to_player) + { + proxOK = false; + } + break; + + case PROX_NEAR_STAIRS: + if (pval == 2) // player on stairs + { + if (mons_class_base_speed(mg.cls) == 0) + { + proxOK = false; + break; + } + // Swap the monster and the player spots, unless the + // monster was generated in lava or deep water. + if (grd(mg.pos) == DNGN_LAVA + || grd(mg.pos) == DNGN_DEEP_WATER) + { + proxOK = false; + break; + } + + // You can't be shoved if you're caught in a net. + if (you.caught()) + { + proxOK = false; + break; + } + + shoved = true; + coord_def mpos = mg.pos; + mg.pos = you.pos(); + you.moveto(mpos); + } + proxOK = (pval > 0); + break; + } + + if (!proxOK) + continue; + + // Cool.. passes all tests. + break; + } // end while... place first monster + } + else if (!_valid_monster_generation_location(mg)) + { + // Sanity check that the specified position is valid. + return (-1); + } + + id = _place_monster_aux(mg, true, force_pos); + + // Reset the (very) ugly thing band colour. + if (ugly_colour != BLACK) + ugly_colour = BLACK; + + // Bail out now if we failed. + if (id == -1) + return (-1); + + monsters *mon = &menv[id]; + if (mg.needs_patrol_point()) + { + mon->patrol_point = mon->pos(); +#ifdef DEBUG_PATHFIND + mprf("Monster %s is patrolling around (%d, %d).", + mon->name(DESC_PLAIN).c_str(), mon->pos().x, mon->pos().y); +#endif + } + + // Message to player from stairwell/gate appearance. + if (observe_cell(mg.pos) && mg.proximity == PROX_NEAR_STAIRS) + { + std::string msg; + + if (menv[id].visible_to(&you)) + msg = menv[id].name(DESC_CAP_A); + else if (shoved) + msg = "Something"; + + if (shoved) + { + msg += " shoves you out of the "; + if (stair_type == DCHAR_ARCH) + msg += "gateway!"; + else + msg += "stairwell!"; + mpr(msg.c_str()); + } + else if (!msg.empty()) + { + if (stair_type == DCHAR_STAIRS_DOWN) + msg += " comes up the stairs."; + else if (stair_type == DCHAR_STAIRS_UP) + msg += " comes down the stairs."; + else if (stair_type == DCHAR_ARCH) + msg += " comes through the gate."; + else + msg = ""; + + if (!msg.empty()) + mpr(msg.c_str()); + } + + // Special case: must update the view for monsters created + // in player LOS. + viewwindow(false); + } + + // Now, forget about banding if the first placement failed, or there are + // too many monsters already, or we successfully placed by stairs. + if (id >= MAX_MONSTERS - 30 || mg.proximity == PROX_NEAR_STAIRS) + return (id); + + // Not PROX_NEAR_STAIRS, so it will be part of a band, if there is any. + if (band_size > 1) + menv[id].flags |= MF_BAND_MEMBER; + + const bool priest = mon->is_priest(); + + mgen_data band_template = mg; + // (5) For each band monster, loop call to place_monster_aux(). + for (int i = 1; i < band_size; i++) + { + if (band_monsters[i] == MONS_NO_MONSTER) + break; + + band_template.cls = band_monsters[i]; + + // We don't want to place a unique that has already been + // generated. + if (mons_is_unique(band_template.cls) + && you.unique_creatures[band_template.cls]) + { + continue; + } + + const int band_id = _place_monster_aux(band_template, false); + if (band_id != -1 && band_id != NON_MONSTER) + { + menv[band_id].flags |= MF_BAND_MEMBER; + // Priestly band leaders should have an entourage of the + // same religion. + if (priest) + menv[band_id].god = mon->god; + } + } + + // Placement of first monster, at least, was a success. + return (id); +} + +static int _place_monster_aux(const mgen_data &mg, + bool first_band_member, bool force_pos) +{ + int id = -1; + coord_def fpos; + + // Gotta be able to pick an ID. + for (id = 0; id < MAX_MONSTERS; id++) + if (menv[id].type == MONS_NO_MONSTER) + break; + + // Too many monsters on level? + if (id == MAX_MONSTERS) + return (-1); + + menv[id].reset(); + + const monster_type montype = (mons_class_is_zombified(mg.cls) ? mg.base_type + : mg.cls); + + // Setup habitat and placement. + // If the space is occupied, try some neighbouring square instead. + if (first_band_member && in_bounds(mg.pos) + && (mg.behaviour == BEH_FRIENDLY || !is_sanctuary(mg.pos)) + && actor_at(mg.pos) == NULL + && (force_pos || monster_habitable_grid(montype, grd(mg.pos)))) + { + fpos = mg.pos; + } + else + { + int i; + // We'll try 1000 times for a good spot. + for (i = 0; i < 1000; ++i) + { + fpos = mg.pos + coord_def( random_range(-3, 3), + random_range(-3, 3) ); + + if (_valid_monster_generation_location(mg, fpos)) + break; + } + + // Did we really try 1000 times? + if (i == 1000) + return (-1); + } + + ASSERT(!monster_at(fpos)); + + if (crawl_state.arena + && arena_veto_place_monster(mg, first_band_member, fpos)) + { + return (-1); + } + + monsters &mons(menv[id]); + // Now, actually create the monster. (Wheeee!) + mons.type = mg.cls; + mons.base_monster = mg.base_type; + mons.number = mg.number; + + mons.moveto(fpos); + + // Link monster into monster grid. + mgrd(fpos) = id; + + // Generate a brand shiny new monster, or zombie. + if (mons_class_is_zombified(mg.cls)) + _define_zombie(id, mg.base_type, mg.cls, mg.power, fpos); + else + define_monster(id); + + // Is it a god gift? + if (mg.god != GOD_NO_GOD) + { + mons.god = mg.god; + mons.flags |= MF_GOD_GIFT; + } + // Not a god gift, give priestly monsters a god. + else if (mons_class_flag(mg.cls, M_PRIEST)) + { + switch (mons_genus(mg.cls)) + { + case MONS_ORC: + mons.god = GOD_BEOGH; + break; + case MONS_JELLY: + mons.god = GOD_JIYVA; + break; + case MONS_MUMMY: + case MONS_DRACONIAN: + case MONS_ELF: + // [ds] Vault defs can request priest monsters of unusual types. + default: + mons.god = GOD_NAMELESS; + break; + } + } + // 1 out of 7 non-priestly orcs are unbelievers. + else if (mons_genus(mg.cls) == MONS_ORC) + { + if (!one_chance_in(7)) + mons.god = GOD_BEOGH; + } + // The royal jelly belongs to Jiyva. + else if (mg.cls == MONS_ROYAL_JELLY) + mons.god = GOD_JIYVA; + // Angels and Daevas belong to TSO, but 1 out of 7 in the Abyss are + // adopted by Xom. + else if (mons_class_holiness(mg.cls) == MH_HOLY) + { + if (mg.level_type != LEVEL_ABYSS || !one_chance_in(7)) + mons.god = GOD_SHINING_ONE; + else + mons.god = GOD_XOM; + } + // 6 out of 7 demons in the Abyss belong to Lugonu. + else if (mons_class_holiness(mg.cls) == MH_DEMONIC) + { + if (mg.level_type == LEVEL_ABYSS && !one_chance_in(7)) + mons.god = GOD_LUGONU; + } + + // If the caller requested a specific colour for this monster, apply + // it now. + if (mg.colour != BLACK) + mons.colour = mg.colour; + + if (mg.mname != "") + mons.mname = mg.mname; + + // The return of Boris is now handled in monster_die(). Not setting + // this for Boris here allows for multiple Borises in the dungeon at + // the same time. - bwr + if (mons_is_unique(mg.cls)) + you.unique_creatures[mg.cls] = true; + + if (mons_class_flag(mg.cls, M_INVIS)) + mons.add_ench(ENCH_INVIS); + + if (mons_class_flag(mg.cls, M_CONFUSED)) + mons.add_ench(ENCH_CONFUSION); + + if (mg.cls == MONS_SHAPESHIFTER) + mons.add_ench(ENCH_SHAPESHIFTER); + + if (mg.cls == MONS_GLOWING_SHAPESHIFTER) + mons.add_ench(ENCH_GLOWING_SHAPESHIFTER); + + if (mg.cls == MONS_TOADSTOOL) + { + // This enchantment is a timer that counts down until death. + // It should last longer than the lifespan of a corpse, to avoid + // spawning mushrooms in the same place over and over. Aside + // from that, the value is slightly randomised to avoid + // simultaneous die-offs of mushroom rings. + mons.add_ench(ENCH_SLOWLY_DYING); + } + + if (mg.cls == MONS_BALLISTOMYCETE) + { + // This enchantment causes giant spore production. + mons.add_ench(ENCH_SPORE_PRODUCTION); + } + + if (monster_can_submerge(&mons, grd(fpos)) && !one_chance_in(5)) + mons.add_ench(ENCH_SUBMERGED); + + mons.flags |= MF_JUST_SUMMONED; + + // Don't leave shifters in their starting shape. + if (mg.cls == MONS_SHAPESHIFTER || mg.cls == MONS_GLOWING_SHAPESHIFTER) + { + no_messages nm; + monster_polymorph(&mons, RANDOM_MONSTER); + + // It's not actually a known shapeshifter if it happened to be + // placed in LOS of the player. + mons.flags &= ~MF_KNOWN_MIMIC; + } + + // dur should always be 1-6 for monsters that can be abjured. + const bool summoned = mg.abjuration_duration >= 1 + && mg.abjuration_duration <= 6; + + if (mg.cls == MONS_DANCING_WEAPON) + { + give_item(id, mg.power, summoned); + + // Dancing weapons *always* have a weapon. Fail to create them + // otherwise. + const item_def* wpn = mons.mslot_item(MSLOT_WEAPON); + if (!wpn) + { + mons.destroy_inventory(); + mons.reset(); + mgrd(fpos) = NON_MONSTER; + return (-1); + } + else + mons.colour = wpn->colour; + } + else if (mons_class_itemuse(mg.cls) >= MONUSE_STARTING_EQUIPMENT) + { + give_item(id, mg.power, summoned); + // Give these monsters a second weapon -- bwr + if (mons_class_wields_two_weapons(mg.cls)) + give_item(id, mg.power, summoned); + + unwind_var save_speedinc(mons.speed_increment); + mons.wield_melee_weapon(false); + } + + // Give manticores 8 to 16 spike volleys. + if (mg.cls == MONS_MANTICORE) + mons.number = 8 + random2(9); + + if (mg.cls == MONS_SLIME_CREATURE) + { + if (mg.number > 1) + { + // Boost HP to what it would have been if it had grown this + // big by merging. + mons.hit_points *= mg.number; + mons.max_hit_points *= mg.number; + } + } + + // Set attitude, behaviour and target. + mons.attitude = ATT_HOSTILE; + mons.behaviour = mg.behaviour; + + // Statues cannot sleep (nor wander but it means they are a bit + // more aware of the player than they'd be otherwise). + if (mons_is_statue(mg.cls)) + mons.behaviour = BEH_WANDER; + + mons.foe_memory = 0; + + // Setting attitude will always make the monster wander... + // If you want sleeping hostiles, use BEH_SLEEP since the default + // attitude is hostile. + if (mg.behaviour > NUM_BEHAVIOURS) + { + if (mg.behaviour == BEH_FRIENDLY) + mons.attitude = ATT_FRIENDLY; + + if (mg.behaviour == BEH_GOOD_NEUTRAL) + mons.attitude = ATT_GOOD_NEUTRAL; + + if (mg.behaviour == BEH_NEUTRAL) + mons.attitude = ATT_NEUTRAL; + + if (mg.behaviour == BEH_STRICT_NEUTRAL) + mons.attitude = ATT_STRICT_NEUTRAL; + + mons.behaviour = BEH_WANDER; + } + + if (summoned) + { + mons.mark_summoned(mg.abjuration_duration, true, + mg.summon_type); + } + mons.foe = mg.foe; + + // Initialise (very) ugly things and pandemonium demons. + if (mons.type == MONS_UGLY_THING + || mons.type == MONS_VERY_UGLY_THING) + { + ghost_demon ghost; + ghost.init_ugly_thing(mons.type == MONS_VERY_UGLY_THING, false, + mg.colour); + mons.set_ghost(ghost, false); + mons.uglything_init(); + } + else if (mons.type == MONS_PANDEMONIUM_DEMON) + { + ghost_demon ghost; + ghost.init_random_demon(); + mons.set_ghost(ghost); + mons.pandemon_init(); + } + else if (mons.type == MONS_DANCING_WEAPON) + { + ghost_demon ghost; + // We can't use monsters::weapon here because it wants to look + // at attack types, which are in the ghost structure we're + // building. + ASSERT( mons.mslot_item(MSLOT_WEAPON) ); + // Dancing weapons are placed at pretty high power. Remember, the + // player is fighting them one-on-one, while he will often summon + // several. + ghost.init_dancing_weapon(*(mons.mslot_item(MSLOT_WEAPON)), 180); + mons.set_ghost(ghost); + mons.dancing_weapon_init(); + } + + mark_interesting_monst(&mons, mg.behaviour); + + if (you.can_see(&mons)) + handle_seen_interrupt(&mons); + + if (crawl_state.arena) + arena_placed_monster(&mons); + + return (id); +} + +monster_type pick_random_zombie() +{ + static std::vector zombifiable; + if (zombifiable.empty()) + { + for (int i = 0; i < NUM_MONSTERS; ++i) + { + if (mons_species(i) != i || i == MONS_PROGRAM_BUG) + continue; + + const monster_type mcls = static_cast(i); + if (!mons_zombie_size(mcls)) + continue; + + zombifiable.push_back(mcls); + } + } + return (zombifiable[random2(zombifiable.size())]); +} + +monster_type pick_local_zombifiable_monster_type(int power) +{ + // Generates a dummy zombie likely to be found. + // Ripped wholly from _define_zombie(). + + power = std::min(27, power); + // How OOD this zombie can be. + int relax = 5; + + // Pick an appropriate creature to make a zombie out of, levelwise. + // The old code was generating absolutely incredible OOD zombies. + int cls; + while (true) + { + cls = pick_random_zombie(); + + bool ignore_rarity = false; + // On certain branches, zombie creation will fail if we use the + // mons_rarity() functions, because (for example) there are NO + // zombifiable "native" abyss creatures. Other branches where + // this is a problem are Hell levels and the Crypt. We have to + // watch for summoned zombies on other levels, too, such as the + // Temple, the Hall of Blades and the Slime Pits. + if (you.level_type != LEVEL_DUNGEON + || player_in_hell() + || player_in_branch(BRANCH_HALL_OF_ZOT) + || player_in_branch(BRANCH_VESTIBULE_OF_HELL) + || player_in_branch(BRANCH_ECUMENICAL_TEMPLE) + || player_in_branch(BRANCH_CRYPT) + || player_in_branch(BRANCH_TOMB) + || player_in_branch(BRANCH_HALL_OF_BLADES) + || player_in_branch(BRANCH_SNAKE_PIT) + || player_in_branch(BRANCH_SLIME_PITS) + || one_chance_in(1000)) + { + ignore_rarity = true; + } + + // Don't make out-of-rarity zombies when we don't have to. + if (!ignore_rarity && mons_rarity(cls) == 0) + continue; + + // Check for rarity.. and OOD - identical to mons_place(). + int level, diff, chance; + + level = mons_level(cls) - 4; + diff = level - power; + + chance = (ignore_rarity) ? 100 + : mons_rarity(cls) - (diff * diff) / 2; + + if (power > level - relax && power < level + relax + && random2avg(100, 2) <= chance) + { + break; + } + + // Every so often, we'll relax the OOD restrictions. This + // avoids infinite loops. If we don't do this, things like + // creating a large skeleton on level 1 may hang the game! + if (one_chance_in(5)) + relax++; + } + + return (monster_type)cls; +} + +static void _define_zombie(int mid, monster_type ztype, monster_type cs, + int power, coord_def pos) +{ + ASSERT(mons_class_is_zombified(cs)); + + monster_type cls = MONS_PROGRAM_BUG; + monster_type mons_sec2 = MONS_PROGRAM_BUG; + zombie_size_type zombie_size = Z_NOZOMBIE; + bool ignore_rarity = false; + + power = std::min(27, power); + + // Set size based on zombie class (cs). + switch (cs) + { + case MONS_ZOMBIE_SMALL: + case MONS_SIMULACRUM_SMALL: + case MONS_SKELETON_SMALL: + zombie_size = Z_SMALL; + break; + + case MONS_ZOMBIE_LARGE: + case MONS_SIMULACRUM_LARGE: + case MONS_SKELETON_LARGE: + zombie_size = Z_BIG; + break; + + case MONS_SPECTRAL_THING: + break; + + default: + break; + } + + // That is, random creature from which to fashion undead. + if (ztype == MONS_NO_MONSTER) + { + // How OOD this zombie can be. + int relax = 5; + + // Pick an appropriate creature to make a zombie out of, + // levelwise. The old code was generating absolutely + // incredible OOD zombies. + while (true) + { + cls = pick_random_zombie(); + + // Actually pick a monster that is happy where we want to put it. + // Fish zombies on land are helpless and uncool. + if (!monster_habitable_grid(cls, grd(pos))) + continue; + + // On certain branches, zombie creation will fail if we use + // the mons_rarity() functions, because (for example) there + // are NO zombifiable "native" abyss creatures. Other branches + // where this is a problem are hell levels and the crypt. + // we have to watch for summoned zombies on other levels, too, + // such as the Temple, HoB, and Slime Pits. + if (you.level_type != LEVEL_DUNGEON + || player_in_hell() + || player_in_branch(BRANCH_HALL_OF_ZOT) + || player_in_branch(BRANCH_VESTIBULE_OF_HELL) + || player_in_branch(BRANCH_ECUMENICAL_TEMPLE) + || player_in_branch(BRANCH_CRYPT) + || player_in_branch(BRANCH_TOMB) + || player_in_branch(BRANCH_HALL_OF_BLADES) + || player_in_branch(BRANCH_SNAKE_PIT) + || player_in_branch(BRANCH_SLIME_PITS) + || one_chance_in(1000)) + { + ignore_rarity = true; + } + + // Don't make out-of-rarity zombies when we don't have to. + if (!ignore_rarity && mons_rarity(cls) == 0) + continue; + + // If skeleton, monster must have a skeleton. + if ((cs == MONS_SKELETON_SMALL || cs == MONS_SKELETON_LARGE) + && !mons_skeleton(cls)) + { + continue; + } + + // Size must match, but you can make a spectral thing out + // of anything. + if (cs != MONS_SPECTRAL_THING + && mons_zombie_size(cls) != zombie_size) + { + continue; + } + + if (cs == MONS_SKELETON_SMALL || cs == MONS_SIMULACRUM_SMALL) + { + // Skeletal or icy draconians shouldn't be coloured. + // How could you tell? + if (mons_genus(cls) == MONS_DRACONIAN + && cls != MONS_DRACONIAN) + { + cls = MONS_DRACONIAN; + } + // The same goes for rats. + else if (mons_genus(cls) == MONS_RAT + && cls != MONS_RAT) + { + cls = MONS_RAT; + } + } + + // Hack -- non-dungeon zombies are always made out of nastier + // monsters. + if (you.level_type != LEVEL_DUNGEON && mons_power(cls) > 8) + break; + + // Check for rarity.. and OOD - identical to mons_place() + int level, diff, chance; + + level = mons_level(cls) - 4; + diff = level - power; + + chance = (ignore_rarity) ? 100 + : mons_rarity(cls) - (diff * diff) / 2; + + if (power > level - relax && power < level + relax + && random2avg(100, 2) <= chance) + { + break; + } + + // Every so often, we'll relax the OOD restrictions. Avoids + // infinite loops (if we don't do this, things like creating + // a large skeleton on level 1 may hang the game!). + if (one_chance_in(5)) + relax++; + } + + // Set type and secondary appropriately. + menv[mid].base_monster = cls; + mons_sec2 = cls; + } + else + { + menv[mid].base_monster = mons_species(ztype); + mons_sec2 = menv[mid].base_monster; + } + + // Set type to the base type to calculate appropriate stats. + menv[mid].type = menv[mid].base_monster; + + define_monster(mid); + + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 6, 5); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].ac -= 2; + menv[mid].ac = std::max(0, menv[mid].ac); + + menv[mid].ev -= 5; + menv[mid].ev = std::max(0, menv[mid].ev); + + menv[mid].speed = mons_class_zombie_base_speed(menv[mid].base_monster); + + // Now override type with the required type. + if (cs == MONS_ZOMBIE_SMALL || cs == MONS_ZOMBIE_LARGE) + { + menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? + MONS_ZOMBIE_LARGE : MONS_ZOMBIE_SMALL); + } + else if (cs == MONS_SKELETON_SMALL || cs == MONS_SKELETON_LARGE) + { + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 5, 4); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].ac -= 4; + menv[mid].ac = std::max(0, menv[mid].ac); + + menv[mid].ev -= 2; + menv[mid].ev = std::max(0, menv[mid].ev); + + menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? + MONS_SKELETON_LARGE : MONS_SKELETON_SMALL); + } + else if (cs == MONS_SIMULACRUM_SMALL || cs == MONS_SIMULACRUM_LARGE) + { + // Simulacra aren't tough, but you can create piles of them. - bwr + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 1, 4); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? + MONS_SIMULACRUM_LARGE : MONS_SIMULACRUM_SMALL); + } + else if (cs == MONS_SPECTRAL_THING) + { + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 4, 4); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].ac += 4; + + menv[mid].type = MONS_SPECTRAL_THING; + } + + menv[mid].base_monster = mons_sec2; + menv[mid].colour = mons_class_colour(cs); +} + +static band_type _choose_band(int mon_type, int power, int &band_size) +{ +#ifdef DEBUG_MON_CREATION + mpr("in _choose_band()", MSGCH_DIAGNOSTICS); +#endif + // Band size describes the number of monsters in addition to + // the band leader. + band_size = 0; // Single monster, no band. + band_type band = BAND_NO_BAND; + + switch (mon_type) + { + case MONS_ORC: + if (coinflip()) + break; + // intentional fall-through {dlb} + case MONS_ORC_WIZARD: + band = BAND_ORCS; + band_size = 2 + random2(3); + break; + + case MONS_ORC_PRIEST: + case MONS_ORC_WARRIOR: + band = BAND_ORC_WARRIOR; + band_size = 2 + random2(3); + break; + + case MONS_ORC_WARLORD: + case MONS_SAINT_ROKA: + band_size = 5 + random2(5); // warlords have large bands + // intentional fall through + case MONS_ORC_KNIGHT: + band = BAND_ORC_KNIGHT; // orcs + knight + band_size += 3 + random2(4); + break; + + case MONS_ORC_HIGH_PRIEST: + band = BAND_ORC_HIGH_PRIEST; + band_size = 4 + random2(4); + break; + + case MONS_BIG_KOBOLD: + if (power > 3) + { + band = BAND_KOBOLDS; + band_size = 2 + random2(6); + } + break; + + case MONS_KILLER_BEE: + band = BAND_KILLER_BEES; + band_size = 2 + random2(4); + break; + + case MONS_FLYING_SKULL: + band = BAND_FLYING_SKULLS; + band_size = 2 + random2(4); + break; + case MONS_SLIME_CREATURE: + band = BAND_SLIME_CREATURES; + band_size = 2 + random2(4); + break; + case MONS_YAK: + band = BAND_YAKS; + band_size = 2 + random2(4); + break; + case MONS_UGLY_THING: + case MONS_VERY_UGLY_THING: + band = BAND_UGLY_THINGS; + band_size = 2 + random2(4); + break; + case MONS_HELL_HOUND: + band = BAND_HELL_HOUNDS; + band_size = 2 + random2(3); + break; + case MONS_JACKAL: + band = BAND_JACKALS; + band_size = 1 + random2(3); + break; + case MONS_HELL_KNIGHT: + case MONS_MARGERY: + band = BAND_HELL_KNIGHTS; + band_size = 4 + random2(4); + break; + case MONS_JOSEPHINE: + case MONS_NECROMANCER: + case MONS_VAMPIRE_MAGE: + band = BAND_NECROMANCER; + band_size = 4 + random2(4); + break; + case MONS_GNOLL: + band = BAND_GNOLLS; + band_size = (coinflip() ? 3 : 2); + break; + case MONS_GRUM: + band = BAND_WAR_DOGS; + band_size = 2 + random2(3); + break; + case MONS_BUMBLEBEE: + band = BAND_BUMBLEBEES; + band_size = 2 + random2(4); + break; + case MONS_CENTAUR: + case MONS_CENTAUR_WARRIOR: + if (power > 9 && one_chance_in(3)) + { + band = BAND_CENTAURS; + band_size = 2 + random2(4); + } + break; + + case MONS_YAKTAUR: + case MONS_YAKTAUR_CAPTAIN: + if (coinflip()) + { + band = BAND_YAKTAURS; + band_size = 2 + random2(3); + } + break; + + case MONS_DEATH_YAK: + band = BAND_DEATH_YAKS; + band_size = 2 + random2(4); + break; + case MONS_INSUBSTANTIAL_WISP: + band = BAND_INSUBSTANTIAL_WISPS; + band_size = 4 + random2(5); + break; + case MONS_OGRE_MAGE: + band = BAND_OGRE_MAGE; + band_size = 4 + random2(4); + break; + case MONS_BALRUG: + band = BAND_BALRUG; // RED gr demon + band_size = 2 + random2(3); + break; + case MONS_CACODEMON: + band = BAND_CACODEMON; // BROWN gr demon + band_size = 1 + random2(3); + break; + + case MONS_EXECUTIONER: + if (coinflip()) + { + band = BAND_EXECUTIONER; // DARKGREY gr demon + band_size = 1 + random2(3); + } + break; + + case MONS_PANDEMONIUM_DEMON: + band = BAND_PANDEMONIUM_DEMON; + band_size = random_range(1, 3); + break; + + case MONS_HELLWING: + if (coinflip()) + { + band = BAND_HELLWING; // LIGHTGREY gr demon + band_size = 1 + random2(4); + } + break; + + case MONS_DEEP_ELF_FIGHTER: + if (coinflip()) + { + band = BAND_DEEP_ELF_FIGHTER; + band_size = 3 + random2(4); + } + break; + + case MONS_DEEP_ELF_KNIGHT: + if (coinflip()) + { + band = BAND_DEEP_ELF_KNIGHT; + band_size = 3 + random2(4); + } + break; + + case MONS_DEEP_ELF_HIGH_PRIEST: + if (coinflip()) + { + band = BAND_DEEP_ELF_HIGH_PRIEST; + band_size = 3 + random2(4); + } + break; + + case MONS_KOBOLD_DEMONOLOGIST: + if (coinflip()) + { + band = BAND_KOBOLD_DEMONOLOGIST; + band_size = 3 + random2(6); + } + break; + + case MONS_NAGA_MAGE: + case MONS_NAGA_WARRIOR: + band = BAND_NAGAS; + band_size = 3 + random2(4); + break; + + case MONS_WAR_DOG: + band = BAND_WAR_DOGS; + band_size = 2 + random2(4); + break; + + case MONS_GREY_RAT: + band = BAND_GREY_RATS; + band_size = 4 + random2(6); + break; + + case MONS_GREEN_RAT: + band = BAND_GREEN_RATS; + band_size = 4 + random2(6); + break; + + case MONS_ORANGE_RAT: + band = BAND_ORANGE_RATS; + band_size = 3 + random2(4); + break; + + case MONS_SHEEP: + band = BAND_SHEEP; + band_size = 3 + random2(5); + break; + + case MONS_GHOUL: + band = BAND_GHOULS; + band_size = 2 + random2(3); + break; + + case MONS_KIRKE: + band_size = 2 + random2(3); + case MONS_HOG: + band = BAND_HOGS; + band_size += 1 + random2(3); + break; + + case MONS_GIANT_MOSQUITO: + band = BAND_GIANT_MOSQUITOES; + band_size = 1 + random2(3); + break; + + case MONS_DEEP_TROLL: + band = BAND_DEEP_TROLLS; + band_size = 3 + random2(3); + break; + + case MONS_HELL_HOG: + band = BAND_HELL_HOGS; + band_size = 1 + random2(3); + break; + + case MONS_BOGGART: + band = BAND_BOGGARTS; + band_size = 2 + random2(3); + break; + + case MONS_BLINK_FROG: + band = BAND_BLINK_FROGS; + band_size = 2 + random2(3); + break; + + case MONS_SKELETAL_WARRIOR: + band = BAND_SKELETAL_WARRIORS; + band_size = 2 + random2(3); + break; + + case MONS_CYCLOPS: + if (one_chance_in(5) || player_in_branch(BRANCH_SHOALS)) + { + band = BAND_SHEEP; // Odyssey reference + band_size = 2 + random2(3); + } + break; + + case MONS_POLYPHEMUS: + band = BAND_DEATH_YAKS; + band_size = 3 + random2(3); + break; + + case MONS_HARPY: + band = BAND_HARPIES; + band_size = 2 + random2(3); + break; + + // Journey -- Added Draconian Packs + case MONS_WHITE_DRACONIAN: + case MONS_RED_DRACONIAN: + case MONS_PURPLE_DRACONIAN: + case MONS_MOTTLED_DRACONIAN: + case MONS_YELLOW_DRACONIAN: + case MONS_BLACK_DRACONIAN: + case MONS_GREEN_DRACONIAN: + case MONS_PALE_DRACONIAN: + if (power > 18 && one_chance_in(3) && you.level_type == LEVEL_DUNGEON) + { + band = BAND_DRACONIAN; + band_size = random_range(2, 4); + } + break; + + case MONS_DRACONIAN_CALLER: + case MONS_DRACONIAN_MONK: + case MONS_DRACONIAN_SCORCHER: + case MONS_DRACONIAN_KNIGHT: + case MONS_DRACONIAN_ANNIHILATOR: + case MONS_DRACONIAN_ZEALOT: + case MONS_DRACONIAN_SHIFTER: + if (power > 20 && you.level_type == LEVEL_DUNGEON) + { + band = BAND_DRACONIAN; + band_size = random_range(3, 6); + } + break; + + case MONS_TIAMAT: + band = BAND_DRACONIAN; + // yup, scary + band_size = random_range(3,6) + random_range(3,6) + 2; + break; + + case MONS_ILSUIW: + band = BAND_ILSUIW; + band_size = 3 + random2(3); + break; + + case MONS_AZRAEL: + band = BAND_AZRAEL; + band_size = 4 + random2(5); + break; + + case MONS_DUVESSA: + band = BAND_DUVESSA; + band_size = 1; + break; + + case MONS_KHUFU: + band = BAND_KHUFU; + band_size = 3; + break; + + case MONS_GOLDEN_EYE: + band = BAND_GOLDEN_EYE; + band_size = 1 + random2(5); + break; + + case MONS_PIKEL: + band = BAND_PIKEL; + band_size = 1 + random2(3); + break; + + } // end switch + + if (band != BAND_NO_BAND && band_size == 0) + band = BAND_NO_BAND; + + if (band_size >= BIG_BAND) + band_size = BIG_BAND - 1; + + return (band); +} + +static monster_type _band_member(band_type band, int power) +{ + monster_type mon_type = MONS_PROGRAM_BUG; + int temp_rand; + + if (band == BAND_NO_BAND) + return (MONS_PROGRAM_BUG); + + switch (band) + { + case BAND_KOBOLDS: + mon_type = MONS_KOBOLD; + break; + + case BAND_ORCS: + mon_type = MONS_ORC; + if (one_chance_in(6)) + mon_type = MONS_ORC_WIZARD; + if (one_chance_in(8)) + mon_type = MONS_ORC_PRIEST; + break; + + case BAND_ORC_WARRIOR: + mon_type = MONS_ORC; + if (one_chance_in(5)) + mon_type = MONS_ORC_WIZARD; + if (one_chance_in(7)) + mon_type = MONS_ORC_PRIEST; + break; + + case BAND_ORC_KNIGHT: + case BAND_ORC_HIGH_PRIEST: + // XXX: For Beogh punishment, ogres and trolls look out of place... + // (For normal generation, they're okay, of course.) + temp_rand = random2(30); + mon_type = ((temp_rand > 17) ? MONS_ORC : // 12 in 30 + (temp_rand > 8) ? MONS_ORC_WARRIOR : // 9 in 30 + (temp_rand > 6) ? MONS_WARG : // 2 in 30 + (temp_rand > 4) ? MONS_ORC_WIZARD : // 2 in 30 + (temp_rand > 2) ? MONS_ORC_PRIEST : // 2 in 30 + (temp_rand > 1) ? MONS_OGRE : // 1 in 30 + (temp_rand > 0) ? MONS_TROLL // 1 in 30 + : MONS_ORC_SORCERER); // 1 in 30 + break; + + case BAND_KILLER_BEES: + mon_type = MONS_KILLER_BEE; + break; + + case BAND_FLYING_SKULLS: + mon_type = MONS_FLYING_SKULL; + break; + + case BAND_SLIME_CREATURES: + mon_type = MONS_SLIME_CREATURE; + break; + + case BAND_YAKS: + mon_type = MONS_YAK; + break; + + case BAND_HARPIES: + mon_type = MONS_HARPY; + break; + + case BAND_UGLY_THINGS: + mon_type = ((power > 21 && one_chance_in(4)) ? + MONS_VERY_UGLY_THING : MONS_UGLY_THING); + break; + + case BAND_HELL_HOUNDS: + mon_type = MONS_HELL_HOUND; + break; + + case BAND_JACKALS: + mon_type = MONS_JACKAL; + break; + + case BAND_GNOLLS: + mon_type = MONS_GNOLL; + break; + + case BAND_BUMBLEBEES: + mon_type = MONS_BUMBLEBEE; + break; + + case BAND_CENTAURS: + mon_type = MONS_CENTAUR; + break; + + case BAND_YAKTAURS: + mon_type = MONS_YAKTAUR; + break; + + case BAND_INSUBSTANTIAL_WISPS: + mon_type = MONS_INSUBSTANTIAL_WISP; + break; + + case BAND_DEATH_YAKS: + mon_type = MONS_DEATH_YAK; + break; + + case BAND_NECROMANCER: // necromancer + temp_rand = random2(13); + mon_type = ((temp_rand > 9) ? MONS_ZOMBIE_SMALL : // 3 in 13 + (temp_rand > 6) ? MONS_ZOMBIE_LARGE : // 3 in 13 + (temp_rand > 3) ? MONS_SKELETON_SMALL : // 3 in 13 + (temp_rand > 0) ? MONS_SKELETON_LARGE // 3 in 13 + : MONS_NECROPHAGE); // 1 in 13 + break; + + case BAND_BALRUG: + mon_type = (coinflip() ? MONS_NEQOXEC : MONS_ORANGE_DEMON); + break; + + case BAND_CACODEMON: + mon_type = MONS_LEMURE; + break; + + case BAND_EXECUTIONER: + mon_type = (coinflip() ? MONS_ABOMINATION_SMALL + : MONS_ABOMINATION_LARGE); + break; + + case BAND_PANDEMONIUM_DEMON: + if (one_chance_in(7)) + { + mon_type = static_cast( + random_choose_weighted(50, MONS_LICH, + 10, MONS_ANCIENT_LICH, + 0)); + } + else if (one_chance_in(6)) + { + mon_type = static_cast( + random_choose_weighted(50, MONS_ABOMINATION_SMALL, + 40, MONS_ABOMINATION_LARGE, + 10, MONS_TENTACLED_MONSTROSITY, + 0)); + } + else + { + mon_type = + summon_any_demon( + static_cast( + random_choose_weighted(50, DEMON_COMMON, + 20, DEMON_GREATER, + 10, DEMON_RANDOM, + 0))); + } + break; + + case BAND_HELLWING: + mon_type = (coinflip() ? MONS_HELLWING : MONS_SMOKE_DEMON); + break; + + case BAND_DEEP_ELF_FIGHTER: // deep elf fighter + temp_rand = random2(11); + mon_type = ((temp_rand > 4) ? MONS_DEEP_ELF_SOLDIER : // 6 in 11 + (temp_rand == 4) ? MONS_DEEP_ELF_FIGHTER : // 1 in 11 + (temp_rand == 3) ? MONS_DEEP_ELF_KNIGHT : // 1 in 11 + (temp_rand == 2) ? MONS_DEEP_ELF_CONJURER :// 1 in 11 + (temp_rand == 1) ? MONS_DEEP_ELF_MAGE // 1 in 11 + : MONS_DEEP_ELF_PRIEST); // 1 in 11 + break; + + case BAND_DEEP_ELF_KNIGHT: // deep elf knight + temp_rand = random2(208); + mon_type = + ((temp_rand > 159) ? MONS_DEEP_ELF_SOLDIER : // 23.08% + (temp_rand > 111) ? MONS_DEEP_ELF_FIGHTER : // 23.08% + (temp_rand > 79) ? MONS_DEEP_ELF_KNIGHT : // 15.38% + (temp_rand > 51) ? MONS_DEEP_ELF_MAGE : // 13.46% + (temp_rand > 35) ? MONS_DEEP_ELF_PRIEST : // 7.69% + (temp_rand > 19) ? MONS_DEEP_ELF_CONJURER : // 7.69% + (temp_rand > 3) ? MONS_DEEP_ELF_SUMMONER : // 7.69% + (temp_rand == 3) ? MONS_DEEP_ELF_DEMONOLOGIST :// 0.48% + (temp_rand == 2) ? MONS_DEEP_ELF_ANNIHILATOR : // 0.48% + (temp_rand == 1) ? MONS_DEEP_ELF_SORCERER // 0.48% + : MONS_DEEP_ELF_DEATH_MAGE); // 0.48% + break; + + case BAND_DEEP_ELF_HIGH_PRIEST: // deep elf high priest + temp_rand = random2(16); + mon_type = + ((temp_rand > 12) ? MONS_DEEP_ELF_SOLDIER : // 3 in 16 + (temp_rand > 9) ? MONS_DEEP_ELF_FIGHTER : // 3 in 16 + (temp_rand > 6) ? MONS_DEEP_ELF_PRIEST : // 3 in 16 + (temp_rand == 6) ? MONS_DEEP_ELF_MAGE : // 1 in 16 + (temp_rand == 5) ? MONS_DEEP_ELF_SUMMONER : // 1 in 16 + (temp_rand == 4) ? MONS_DEEP_ELF_CONJURER : // 1 in 16 + (temp_rand == 3) ? MONS_DEEP_ELF_DEMONOLOGIST :// 1 in 16 + (temp_rand == 2) ? MONS_DEEP_ELF_ANNIHILATOR : // 1 in 16 + (temp_rand == 1) ? MONS_DEEP_ELF_SORCERER // 1 in 16 + : MONS_DEEP_ELF_DEATH_MAGE); // 1 in 16 + break; + + case BAND_HELL_KNIGHTS: + mon_type = MONS_HELL_KNIGHT; + if (one_chance_in(4)) + mon_type = MONS_NECROMANCER; + break; + + case BAND_OGRE_MAGE: + mon_type = MONS_OGRE; + if (one_chance_in(3)) + mon_type = MONS_TWO_HEADED_OGRE; + break; // ogre mage + + case BAND_KOBOLD_DEMONOLOGIST: + temp_rand = random2(13); + mon_type = ((temp_rand > 4) ? MONS_KOBOLD : // 8 in 13 + (temp_rand > 0) ? MONS_BIG_KOBOLD // 4 in 13 + : MONS_KOBOLD_DEMONOLOGIST);// 1 in 13 + break; + + case BAND_NAGAS: + mon_type = MONS_NAGA; + break; + case BAND_WAR_DOGS: + mon_type = MONS_WAR_DOG; + break; + case BAND_GREY_RATS: + mon_type = MONS_GREY_RAT; + break; + case BAND_GREEN_RATS: + mon_type = MONS_GREEN_RAT; + break; + case BAND_ORANGE_RATS: + mon_type = MONS_ORANGE_RAT; + break; + case BAND_SHEEP: + mon_type = MONS_SHEEP; + break; + case BAND_GHOULS: + mon_type = (coinflip() ? MONS_GHOUL : MONS_NECROPHAGE); + break; + case BAND_DEEP_TROLLS: + mon_type = MONS_DEEP_TROLL; + break; + case BAND_HOGS: + mon_type = MONS_HOG; + break; + case BAND_HELL_HOGS: + mon_type = MONS_HELL_HOG; + break; + case BAND_GIANT_MOSQUITOES: + mon_type = MONS_GIANT_MOSQUITO; + break; + case BAND_BOGGARTS: + mon_type = MONS_BOGGART; + break; + case BAND_BLINK_FROGS: + mon_type = MONS_BLINK_FROG; + break; + case BAND_SKELETAL_WARRIORS: + mon_type = MONS_SKELETAL_WARRIOR; + break; + case BAND_DRACONIAN: + { + temp_rand = random2( (power < 24) ? 24 : 37 ); + mon_type = + ((temp_rand > 35) ? MONS_DRACONIAN_CALLER : // 1 in 34 + (temp_rand > 33) ? MONS_DRACONIAN_KNIGHT : // 2 in 34 + (temp_rand > 31) ? MONS_DRACONIAN_MONK : // 2 in 34 + (temp_rand > 29) ? MONS_DRACONIAN_SHIFTER : // 2 in 34 + (temp_rand > 27) ? MONS_DRACONIAN_ANNIHILATOR :// 2 in 34 + (temp_rand > 25) ? MONS_DRACONIAN_SCORCHER : // 2 in 34 + (temp_rand > 23) ? MONS_DRACONIAN_ZEALOT : // 2 in 34 + (temp_rand > 20) ? MONS_YELLOW_DRACONIAN : // 3 in 34 + (temp_rand > 17) ? MONS_GREEN_DRACONIAN : // 3 in 34 + (temp_rand > 14) ? MONS_BLACK_DRACONIAN : // 3 in 34 + (temp_rand > 11) ? MONS_WHITE_DRACONIAN : // 3 in 34 + (temp_rand > 8) ? MONS_PALE_DRACONIAN : // 3 in 34 + (temp_rand > 5) ? MONS_PURPLE_DRACONIAN : // 3 in 34 + (temp_rand > 2) ? MONS_MOTTLED_DRACONIAN : // 3 in 34 + MONS_RED_DRACONIAN ); // 3 in 34 + break; + } + case BAND_ILSUIW: + mon_type = coinflip()? MONS_MERFOLK : MONS_MERMAID; + break; + + case BAND_AZRAEL: + mon_type = coinflip()? MONS_FIRE_ELEMENTAL : MONS_HELL_HOUND; + break; + + case BAND_DUVESSA: + mon_type = MONS_DOWAN; + break; + + case BAND_KHUFU: + mon_type = coinflip()? MONS_GREATER_MUMMY : MONS_MUMMY; + break; + + case BAND_GOLDEN_EYE: + mon_type = MONS_GOLDEN_EYE; + break; + + case BAND_PIKEL: + mon_type = MONS_HUMAN; + break; + + default: + break; + } + + return (mon_type); +} + +static int _ood_limit() +{ + return Options.ood_interesting; +} + +void mark_interesting_monst(struct monsters* monster, beh_type behaviour) +{ + if (crawl_state.arena) + return; + + bool interesting = false; + + // Unique monsters are always intersting + if (mons_is_unique(monster->type)) + interesting = true; + // If it's never going to attack us, then not interesting + else if (behaviour == BEH_FRIENDLY) + interesting = false; + else if (you.where_are_you == BRANCH_MAIN_DUNGEON + && you.level_type == LEVEL_DUNGEON + && mons_level(monster->type) >= you.your_level + _ood_limit() + && mons_level(monster->type) < 99 + && !(monster->type >= MONS_EARTH_ELEMENTAL + && monster->type <= MONS_AIR_ELEMENTAL) + && !mons_class_flag( monster->type, M_NO_EXP_GAIN )) + { + interesting = true; + } + else if ((you.level_type == LEVEL_DUNGEON + || you.level_type == LEVEL_ABYSS) + && mons_rarity(monster->type) <= Options.rare_interesting + && monster->hit_dice > 2 // Don't note the really low-hd monsters. + && mons_rarity(monster->type) > 0) + { + interesting = true; + } + // Don't waste time on moname() if user isn't using this option + else if (Options.note_monsters.size() > 0) + { + const std::string iname = mons_type_name(monster->type, DESC_NOCAP_A); + for (unsigned i = 0; i < Options.note_monsters.size(); ++i) + { + if (Options.note_monsters[i].matches(iname)) + { + interesting = true; + break; + } + } + } + + if (interesting) + monster->flags |= MF_INTERESTING; +} + +// PUBLIC FUNCTION -- mons_place(). + +static monster_type _pick_zot_exit_defender() +{ + if (one_chance_in(11)) + { +#ifdef DEBUG_MON_CREATION + mpr("Create a pandemonium demon!", MSGCH_DIAGNOSTICS); +#endif + return (MONS_PANDEMONIUM_DEMON); + } + + const int temp_rand = random2(276); + const int mon_type = + ((temp_rand > 184) ? MONS_WHITE_IMP + random2(15) : // 33.33% + (temp_rand > 104) ? MONS_HELLION + random2(10) : // 28.99% + (temp_rand > 78) ? MONS_HELL_HOUND : // 9.06% + (temp_rand > 54) ? MONS_ABOMINATION_LARGE : // 8.70% + (temp_rand > 33) ? MONS_ABOMINATION_SMALL : // 7.61% + (temp_rand > 13) ? MONS_RED_DEVIL // 7.25% + : MONS_PIT_FIEND); // 5.07% + + return static_cast(mon_type); +} + +int mons_place(mgen_data mg) +{ +#ifdef DEBUG_MON_CREATION + mpr("in mons_place()", MSGCH_DIAGNOSTICS); +#endif + int mon_count = 0; + for (int il = 0; il < MAX_MONSTERS; il++) + if (menv[il].type != MONS_NO_MONSTER) + mon_count++; + + if (mg.cls == WANDERING_MONSTER) + { + if (mon_count > MAX_MONSTERS - 50) + return (-1); + +#ifdef DEBUG_MON_CREATION + mpr("Set class RANDOM_MONSTER", MSGCH_DIAGNOSTICS); +#endif + mg.cls = RANDOM_MONSTER; + } + + // All monsters have been assigned? {dlb} + if (mon_count >= MAX_MONSTERS - 1) + return (-1); + + // This gives a slight challenge to the player as they ascend the + // dungeon with the Orb. + if (you.char_direction == GDT_ASCENDING && mg.cls == RANDOM_MONSTER + && you.level_type == LEVEL_DUNGEON && !mg.summoned()) + { +#ifdef DEBUG_MON_CREATION + mpr("Call _pick_zot_exit_defender()", MSGCH_DIAGNOSTICS); +#endif + mg.cls = _pick_zot_exit_defender(); + mg.flags |= MG_PERMIT_BANDS; + } + else if (mg.cls == RANDOM_MONSTER || mg.level_type == LEVEL_PANDEMONIUM) + mg.flags |= MG_PERMIT_BANDS; + + // Translate level_type. + switch (mg.level_type) + { + case LEVEL_PANDEMONIUM: + case LEVEL_ABYSS: + mg.power = level_id(mg.level_type).absdepth(); + break; + case LEVEL_DUNGEON: + default: + mg.power = you.your_level; + break; + } + + int mid = place_monster(mg); + if (mid == -1) + return (-1); + + monsters *creation = &menv[mid]; + + // Look at special cases: CHARMED, FRIENDLY, NEUTRAL, GOOD_NEUTRAL, + // HOSTILE. + if (mg.behaviour > NUM_BEHAVIOURS) + { + if (mg.behaviour == BEH_FRIENDLY) + creation->flags |= MF_CREATED_FRIENDLY; + + if (mg.behaviour == BEH_NEUTRAL || mg.behaviour == BEH_GOOD_NEUTRAL + || mg.behaviour == BEH_STRICT_NEUTRAL) + { + creation->flags |= MF_WAS_NEUTRAL; + } + + if (mg.behaviour == BEH_CHARMED) + { + creation->attitude = ATT_HOSTILE; + creation->add_ench(ENCH_CHARM); + } + + if (creation->type == MONS_RAKSHASA_FAKE && !one_chance_in(3)) + creation->add_ench(ENCH_INVIS); + + if (!(mg.flags & MG_FORCE_BEH) && !crawl_state.arena) + player_angers_monster(creation); + + if (crawl_state.arena) + behaviour_event(creation, ME_EVAL); + else + // Make summoned being aware of player's presence. + behaviour_event(creation, ME_ALERT, MHITYOU); + } + + return (mid); +} + +static dungeon_feature_type _monster_primary_habitat_feature(int mc) +{ + if (mc == RANDOM_MONSTER) + return (DNGN_FLOOR); + return (habitat2grid(mons_class_primary_habitat(mc))); +} + +static dungeon_feature_type _monster_secondary_habitat_feature(int mc) +{ + if (mc == RANDOM_MONSTER) + return (DNGN_FLOOR); + return (habitat2grid(mons_class_secondary_habitat(mc))); +} + +class newmons_square_find : public travel_pathfind +{ +private: + dungeon_feature_type feat_wanted; + coord_def start; + int maxdistance; + + int best_distance; + int nfound; +public: + // Terrain that we can't spawn on, but that we can skip through. + std::set passable; +public: + newmons_square_find(dungeon_feature_type grdw, + const coord_def &pos, + int maxdist = 0) + : feat_wanted(grdw), start(pos), maxdistance(maxdist), + best_distance(0), nfound(0) + { + } + + coord_def pathfind() + { + set_floodseed(start); + return travel_pathfind::pathfind(RMODE_EXPLORE); + } + + bool path_flood(const coord_def &c, const coord_def &dc) + { + if (best_distance && traveled_distance > best_distance) + return (true); + + if (!in_bounds(dc) + || (maxdistance > 0 && traveled_distance > maxdistance)) + { + return (false); + } + if (!feat_compatible(feat_wanted, grd(dc))) + { + if (passable.find(grd(dc)) != passable.end()) + good_square(dc); + return (false); + } + if (actor_at(dc) == NULL && one_chance_in(++nfound)) + { + greedy_dist = traveled_distance; + greedy_place = dc; + best_distance = traveled_distance; + } + else + { + good_square(dc); + } + return (false); + } +}; + +// Finds a square for a monster of the given class, pathfinding +// through only contiguous squares of habitable terrain. +coord_def find_newmons_square_contiguous(monster_type mons_class, + const coord_def &start, + int distance) +{ + coord_def p; + + const dungeon_feature_type feat_preferred = + _monster_primary_habitat_feature(mons_class); + const dungeon_feature_type feat_nonpreferred = + _monster_secondary_habitat_feature(mons_class); + + newmons_square_find nmpfind(feat_preferred, start, distance); + const coord_def pp = nmpfind.pathfind(); + p = pp; + + if (feat_nonpreferred != feat_preferred && !in_bounds(pp)) + { + newmons_square_find nmsfind(feat_nonpreferred, start, distance); + const coord_def ps = nmsfind.pathfind(); + p = ps; + } + + return (in_bounds(p) ? p : coord_def(-1, -1)); +} + +coord_def find_newmons_square(int mons_class, const coord_def &p) +{ + coord_def empty; + coord_def pos(-1, -1); + + if (mons_class == WANDERING_MONSTER) + mons_class = RANDOM_MONSTER; + + const dungeon_feature_type feat_preferred = + _monster_primary_habitat_feature(mons_class); + const dungeon_feature_type feat_nonpreferred = + _monster_secondary_habitat_feature(mons_class); + + // Might be better if we chose a space and tried to match the monster + // to it in the case of RANDOM_MONSTER, that way if the target square + // is surrounded by water or lava this function would work. -- bwr + if (empty_surrounds(p, feat_preferred, 2, true, empty)) + pos = empty; + + if (feat_nonpreferred != feat_preferred && !in_bounds(pos) + && empty_surrounds(p, feat_nonpreferred, 2, true, empty)) + { + pos = empty; + } + + return (pos); +} + +bool player_will_anger_monster(monster_type type, bool *holy, + bool *unholy, bool *lawful, + bool *antimagical) +{ + monsters dummy; + dummy.type = type; + + return (player_will_anger_monster(&dummy, holy, unholy, lawful, + antimagical)); +} + +bool player_will_anger_monster(monsters *mon, bool *holy, + bool *unholy, bool *lawful, + bool *antimagical) +{ + const bool isHoly = + (is_good_god(you.religion) && mon->is_evil()); + const bool isUnholy = + (is_evil_god(you.religion) && mon->is_holy()); + const bool isLawful = + (you.religion == GOD_ZIN && mon->is_chaotic()); + const bool isAntimagical = + (you.religion == GOD_TROG && mon->is_actual_spellcaster()); + + if (holy) + *holy = isHoly; + if (unholy) + *unholy = isUnholy; + if (lawful) + *lawful = isLawful; + if (antimagical) + *antimagical = isAntimagical; + + return (isHoly || isUnholy || isLawful || isAntimagical); +} + +bool player_angers_monster(monsters *mon) +{ + bool holy; + bool unholy; + bool lawful; + bool antimagical; + + // Get the drawbacks, not the benefits... (to prevent e.g. demon-scumming). + if (player_will_anger_monster(mon, &holy, &unholy, &lawful, &antimagical) + && mon->wont_attack()) + { + mon->attitude = ATT_HOSTILE; + mon->del_ench(ENCH_CHARM); + behaviour_event(mon, ME_ALERT, MHITYOU); + + if (you.can_see(mon)) + { + std::string aura; + + if (holy) + aura = "holy"; + else if (unholy) + aura = "unholy"; + else if (lawful) + aura = "lawful"; + else if (antimagical) + aura = "anti-magical"; + + mprf("%s is enraged by your %s aura!", + mon->name(DESC_CAP_THE).c_str(), aura.c_str()); + } + + return (true); + } + + return (false); +} + +int create_monster(mgen_data mg, bool fail_msg) +{ + const int montype = (mons_class_is_zombified(mg.cls) ? mg.base_type + : mg.cls); + + int summd = -1; + + if (!mg.force_place() + || !in_bounds(mg.pos) + || actor_at(mg.pos) + || !mons_class_can_pass(montype, grd(mg.pos))) + { + mg.pos = find_newmons_square(montype, mg.pos); + + // Gods other than Xom will try to avoid placing their monsters + // directly in harm's way. + if (mg.god != GOD_NO_GOD && mg.god != GOD_XOM) + { + monsters dummy; + // If the type isn't known yet assume no resists or anything. + dummy.type = (mg.cls == RANDOM_MONSTER) ? MONS_HUMAN + : mg.cls; + dummy.base_monster = mg.base_type; + dummy.god = mg.god; + + int tries = 0; + while (tries++ < 50 + && (!in_bounds(mg.pos) + || mons_avoids_cloud(&dummy, env.cgrid(mg.pos), + NULL, true))) + { + mg.pos = find_newmons_square(montype, mg.pos); + } + if (!in_bounds(mg.pos)) + return (-1); + + const int cloud_num = env.cgrid(mg.pos); + // Don't place friendly god gift in a damaging cloud created by + // you if that would anger the god. + if (mons_avoids_cloud(&dummy, cloud_num, NULL, true) + && mg.behaviour == BEH_FRIENDLY + && god_hates_attacking_friend(you.religion, &dummy) + && YOU_KILL(env.cloud[cloud_num].killer)) + { + return (-1); + } + } + } + + if (in_bounds(mg.pos)) + { + summd = mons_place(mg); + // If the arena vetoed the placement then give no fail message. + if (crawl_state.arena) + fail_msg = false; + } + + // Determine whether creating a monster is successful (summd != -1) {dlb}: + // then handle the outcome. {dlb}: + if (fail_msg && summd == -1 && observe_cell(mg.pos)) + mpr("You see a puff of smoke."); + + // The return value is either -1 (failure of some sort) + // or the index of the monster placed (if I read things right). {dlb} + return (summd); +} + +bool empty_surrounds(const coord_def& where, dungeon_feature_type spc_wanted, + int radius, bool allow_centre, coord_def& empty) +{ + // Assume all player summoning originates from player x,y. + bool playerSummon = (where == you.pos()); + + int good_count = 0; + + for (radius_iterator ri(where, radius, true, false, !allow_centre); + ri; ++ri) + { + bool success = false; + + if (actor_at(*ri)) + continue; + + // Players won't summon out of LOS, or past transparent walls. + if (!you.see_cell_no_trans(*ri) && playerSummon) + continue; + + success = + (grd(*ri) == spc_wanted) || feat_compatible(spc_wanted, grd(*ri)); + + if (success && one_chance_in(++good_count)) + empty = *ri; + } + + return (good_count > 0); +} + +monster_type summon_any_demon(demon_class_type dct) +{ + monster_type mon = MONS_PROGRAM_BUG; + + if (dct == DEMON_RANDOM) + dct = static_cast(random2(DEMON_RANDOM)); + + int temp_rand; // probability determination {dlb} + + switch (dct) + { + case DEMON_LESSER: + temp_rand = random2(60); + mon = ((temp_rand > 49) ? MONS_IMP : // 10 in 60 + (temp_rand > 40) ? MONS_WHITE_IMP : // 9 in 60 + (temp_rand > 31) ? MONS_LEMURE : // 9 in 60 + (temp_rand > 22) ? MONS_UFETUBUS : // 9 in 60 + (temp_rand > 13) ? MONS_MANES : // 9 in 60 + (temp_rand > 4) ? MONS_MIDGE // 9 in 60 + : MONS_SHADOW_IMP); // 5 in 60 + break; + + case DEMON_COMMON: + temp_rand = random2(3948); + mon = ((temp_rand > 3367) ? MONS_NEQOXEC : // 14.69% + (temp_rand > 2787) ? MONS_ORANGE_DEMON : // 14.69% + (temp_rand > 2207) ? MONS_HELLWING : // 14.69% + (temp_rand > 1627) ? MONS_SMOKE_DEMON : // 14.69% + (temp_rand > 1047) ? MONS_YNOXINUL : // 14.69% + (temp_rand > 889) ? MONS_RED_DEVIL : // 4.00% + (temp_rand > 810) ? MONS_HELLION : // 2.00% + (temp_rand > 731) ? MONS_ROTTING_DEVIL : // 2.00% + (temp_rand > 652) ? MONS_TORMENTOR : // 2.00% + (temp_rand > 573) ? MONS_REAPER : // 2.00% + (temp_rand > 494) ? MONS_SOUL_EATER : // 2.00% + (temp_rand > 415) ? MONS_HAIRY_DEVIL : // 2.00% + (temp_rand > 336) ? MONS_ICE_DEVIL : // 2.00% + (temp_rand > 257) ? MONS_BLUE_DEVIL : // 2.00% + (temp_rand > 178) ? MONS_BEAST : // 2.00% + (temp_rand > 99) ? MONS_IRON_DEVIL : // 2.00% + (temp_rand > 49) ? MONS_SUN_DEMON // 1.26% + : MONS_SHADOW_IMP); // 1.26% + break; + + case DEMON_GREATER: + temp_rand = random2(1000); + mon = ((temp_rand > 868) ? MONS_CACODEMON : // 13.1% + (temp_rand > 737) ? MONS_BALRUG : // 13.1% + (temp_rand > 606) ? MONS_BLUE_DEATH : // 13.1% + (temp_rand > 475) ? MONS_GREEN_DEATH : // 13.1% + (temp_rand > 344) ? MONS_EXECUTIONER : // 13.1% + (temp_rand > 244) ? MONS_FIEND : // 10.0% + (temp_rand > 154) ? MONS_ICE_FIEND : // 9.0% + (temp_rand > 73) ? MONS_SHADOW_FIEND // 8.1% + : MONS_PIT_FIEND); // 7.4% + break; + + default: + break; + } + + return (mon); +} + +monster_type summon_any_holy_being(holy_being_class_type hbct) +{ + monster_type mon = MONS_PROGRAM_BUG; + + switch (hbct) + { + case HOLY_BEING_WARRIOR: + mon = coinflip() ? MONS_DAEVA : MONS_ANGEL; + break; + + default: + break; + } + + return (mon); +} + +monster_type summon_any_dragon(dragon_class_type dct) +{ + monster_type mon = MONS_PROGRAM_BUG; + + int temp_rand; + + switch (dct) + { + case DRAGON_LIZARD: + temp_rand = random2(100); + mon = ((temp_rand > 80) ? MONS_SWAMP_DRAKE : + (temp_rand > 59) ? MONS_KOMODO_DRAGON : + (temp_rand > 34) ? MONS_FIREDRAKE : + (temp_rand > 11) ? MONS_DEATH_DRAKE : + MONS_DRAGON); + break; + + case DRAGON_DRACONIAN: + temp_rand = random2(70); + mon = ((temp_rand > 60) ? MONS_YELLOW_DRACONIAN : + (temp_rand > 50) ? MONS_BLACK_DRACONIAN : + (temp_rand > 40) ? MONS_PALE_DRACONIAN : + (temp_rand > 30) ? MONS_GREEN_DRACONIAN : + (temp_rand > 20) ? MONS_PURPLE_DRACONIAN : + (temp_rand > 10) ? MONS_RED_DRACONIAN + : MONS_WHITE_DRACONIAN); + break; + + case DRAGON_DRAGON: + temp_rand = random2(90); + mon = ((temp_rand > 80) ? MONS_MOTTLED_DRAGON : + (temp_rand > 70) ? MONS_LINDWURM : + (temp_rand > 60) ? MONS_STORM_DRAGON : + (temp_rand > 50) ? MONS_MOTTLED_DRAGON : + (temp_rand > 40) ? MONS_STEAM_DRAGON : + (temp_rand > 30) ? MONS_DRAGON : + (temp_rand > 20) ? MONS_ICE_DRAGON : + (temp_rand > 10) ? MONS_SWAMP_DRAGON + : MONS_SHADOW_DRAGON); + break; + + default: + break; + } + + return (mon); +} + +///////////////////////////////////////////////////////////////////////////// +// monster_pathfind + +// The pathfinding is an implementation of the A* algorithm. Beginning at the +// destination square we check all neighbours of a given grid, estimate the +// distance needed for any shortest path including this grid and push the +// result into a hash. We can then easily access all points with the shortest +// distance estimates and then check _their_ neighbours and so on. +// The algorithm terminates once we reach the monster position since - because +// of the sorting of grids by shortest distance in the hash - there can be no +// path between start and target that is shorter than the current one. There +// could be other paths that have the same length but that has no real impact. +// If the hash has been cleared and the start grid has not been encountered, +// then there's no path that matches the requirements fed into monster_pathfind. +// (These requirements are usually preference of habitat of a specific monster +// or a limit of the distance between start and any grid on the path.) + +int mons_tracking_range(const monsters *mon) +{ + + int range = 0; + switch (mons_intel(mon)) + { + case I_PLANT: + range = 2; + break; + case I_INSECT: + range = 4; + break; + case I_ANIMAL: + range = 5; + break; + case I_NORMAL: + range = LOS_RADIUS; + break; + default: + // Highly intelligent monsters can find their way + // anywhere. (range == 0 means no restriction.) + break; + } + + if (range) + { + if (mons_is_native_in_branch(mon)) + range += 3; + else if (mons_class_flag(mon->type, M_BLOOD_SCENT)) + range++; + } + + return (range); +} + +//#define DEBUG_PATHFIND +monster_pathfind::monster_pathfind() + : mons(), target(), range(0), min_length(0), max_length(0), dist(), prev() +{ +} + +monster_pathfind::~monster_pathfind() +{ +} + +void monster_pathfind::set_range(int r) +{ + if (r >= 0) + range = r; +} + +coord_def monster_pathfind::next_pos(const coord_def &c) const +{ + return c + Compass[prev[c.x][c.y]]; +} + +// The main method in the monster_pathfind class. +// Returns true if a path was found, else false. +bool monster_pathfind::init_pathfind(const monsters *mon, coord_def dest, + bool diag, bool msg, bool pass_unmapped) +{ + mons = mon; + + // We're doing a reverse search from target to monster. + start = dest; + target = mon->pos(); + pos = start; + allow_diagonals = diag; + traverse_unmapped = pass_unmapped; + + // Easy enough. :P + if (start == target) + { + if (msg) + mpr("The monster is already there!"); + + return (true); + } + + return start_pathfind(msg); +} + +bool monster_pathfind::init_pathfind(coord_def src, coord_def dest, bool diag, + bool msg) +{ + start = src; + target = dest; + pos = start; + allow_diagonals = diag; + + // Easy enough. :P + if (start == target) + return (true); + + return start_pathfind(msg); +} + +bool monster_pathfind::start_pathfind(bool msg) +{ + // NOTE: We never do any traversable() check for the starting square + // (target). This means that even if the target cannot be reached + // we may still find a path leading adjacent to this position, which + // is desirable if e.g. the player is hovering over deep water + // surrounded by shallow water or floor, or if a foe is hiding in + // a wall. + // If the surrounding squares also are not traversable, we return + // early that no path could be found. + + max_length = min_length = grid_distance(pos.x, pos.y, target.x, target.y); + for (int i = 0; i < GXM; i++) + for (int j = 0; j < GYM; j++) + dist[i][j] = INFINITE_DISTANCE; + + dist[pos.x][pos.y] = 0; + + bool success = false; + do + { + // Calculate the distance to all neighbours of the current position, + // and add them to the hash, if they haven't already been looked at. + success = calc_path_to_neighbours(); + if (success) + return (true); + + // Pull the position with shortest distance estimate to our target grid. + success = get_best_position(); + + if (!success) + { + if (msg) + { + mprf("Couldn't find a path from (%d,%d) to (%d,%d).", + target.x, target.y, start.x, start.y); + } + return (false); + } + } + while (true); +} + +// Returns true as soon as we encounter the target. +bool monster_pathfind::calc_path_to_neighbours() +{ + coord_def npos; + int distance, old_dist, total; + + // For each point, we look at all neighbour points. Check the orthogonals + // last, so that, should an orthogonal and a diagonal direction have the + // same total travel cost, the orthogonal will be picked first, and thus + // zigzagging will be significantly reduced. + // + // 1 0 3 This means directions are looked at, in order, + // \ | / 1, 3, 5, 7 (diagonals) followed by 0, 2, 4, 6 + // 6--.--2 (orthogonals). This is achieved by the assignment + // / | \ of (dir = 0) once dir has passed 7. + // 7 4 5 + // + for (int dir = 1; dir < 8; (dir += 2) == 9 && (dir = 0)) + { + // Skip diagonal movement. + if (!allow_diagonals && (dir % 2)) + continue; + + npos = pos + Compass[dir]; + +#ifdef DEBUG_PATHFIND + mprf("Looking at neighbour (%d,%d)", npos.x, npos.y); +#endif + if (!in_bounds(npos)) + continue; + + if (!traversable(npos)) + continue; + + // Ignore this grid if it takes us above the allowed distance. + if (range && estimated_cost(npos) > range) + continue; + + distance = dist[pos.x][pos.y] + travel_cost(npos); + old_dist = dist[npos.x][npos.y]; +#ifdef DEBUG_PATHFIND + mprf("old dist: %d, new dist: %d, infinite: %d", old_dist, distance, + INFINITE_DISTANCE); +#endif + // If the new distance is better than the old one (initialised with + // INFINITE), update the position. + if (distance < old_dist) + { + // Calculate new total path length. + total = distance + estimated_cost(npos); + if (old_dist == INFINITE_DISTANCE) + { +#ifdef DEBUG_PATHFIND + mprf("Adding (%d,%d) to hash (total dist = %d)", + npos.x, npos.y, total); +#endif + add_new_pos(npos, total); + if (total > max_length) + max_length = total; + } + else + { +#ifdef DEBUG_PATHFIND + mprf("Improving (%d,%d) to total dist %d", + npos.x, npos.y, total); +#endif + + update_pos(npos, total); + } + + // Update distance start->pos. + dist[npos.x][npos.y] = distance; + + // Set backtracking information. + // Converts the Compass direction to its counterpart. + // 0 1 2 4 5 6 + // 7 . 3 ==> 3 . 7 e.g. (3 + 4) % 8 = 7 + // 6 5 4 2 1 0 (7 + 4) % 8 = 11 % 8 = 3 + + prev[npos.x][npos.y] = (dir + 4) % 8; + + // Are we finished? + if (npos == target) + { +#ifdef DEBUG_PATHFIND + mpr("Arrived at target."); +#endif + return (true); + } + } + } + return (false); +} + +// Starting at known min_length (minimum total estimated path distance), check +// the hash for existing vectors, then pick the last entry of the first vector +// that matches. Update min_length, if necessary. +bool monster_pathfind::get_best_position() +{ + for (int i = min_length; i <= max_length; i++) + { + if (!hash[i].empty()) + { + if (i > min_length) + min_length = i; + + std::vector &vec = hash[i]; + // Pick the last position pushed into the vector as it's most + // likely to be close to the target. + pos = vec[vec.size()-1]; + vec.pop_back(); + +#ifdef DEBUG_PATHFIND + mprf("Returning (%d, %d) as best pos with total dist %d.", + pos.x, pos.y, min_length); +#endif + + return (true); + } +#ifdef DEBUG_PATHFIND + mprf("No positions for path length %d.", i); +#endif + } + + // Nothing found? Then there's no path! :( + return (false); +} + +// Using the prev vector backtrack from start to target to find all steps to +// take along the shortest path. +std::vector monster_pathfind::backtrack() +{ +#ifdef DEBUG_PATHFIND + mpr("Backtracking..."); +#endif + std::vector path; + pos = target; + path.push_back(pos); + + if (pos == start) + return path; + + int dir; + do + { + dir = prev[pos.x][pos.y]; + pos = pos + Compass[dir]; + ASSERT(in_bounds(pos)); +#ifdef DEBUG_PATHFIND + mprf("prev: (%d, %d), pos: (%d, %d)", Compass[dir].x, Compass[dir].y, + pos.x, pos.y); +#endif + path.push_back(pos); + + if (pos.x == 0 && pos.y == 0) + break; + } + while (pos != start); + ASSERT(pos == start); + + return (path); +} + +// Reduces the path coordinates to only a couple of key waypoints needed +// to reach the target. Waypoints are chosen such that from one waypoint you +// can see (and, more importantly, reach) the next one. Note that +// can_go_straight() is probably rather too conservative in these estimates. +// This is done because Crawl's pathfinding - once a target is in sight and easy +// reach - is both very robust and natural, especially if we want to flexibly +// avoid plants and other monsters in the way. +std::vector monster_pathfind::calc_waypoints() +{ + std::vector path = backtrack(); + + // If no path found, nothing to be done. + if (path.empty()) + return path; + + dungeon_feature_type can_move; + if (mons_amphibious(mons)) + can_move = DNGN_DEEP_WATER; + else + can_move = DNGN_SHALLOW_WATER; + + std::vector waypoints; + pos = path[0]; + +#ifdef DEBUG_PATHFIND + mpr(EOL "Waypoints:"); +#endif + for (unsigned int i = 1; i < path.size(); i++) + { + if (can_go_straight(pos, path[i], can_move)) + continue; + else + { + pos = path[i-1]; + waypoints.push_back(pos); +#ifdef DEBUG_PATHFIND + mprf("waypoint: (%d, %d)", pos.x, pos.y); +#endif + } + } + + // Add the actual target to the list of waypoints, so we can later check + // whether a tracked enemy has moved too much, in case we have to update + // the path. + if (pos != path[path.size() - 1]) + waypoints.push_back(path[path.size() - 1]); + + return (waypoints); +} + +bool monster_pathfind::traversable(const coord_def p) +{ + if (traverse_unmapped && grd(p) == DNGN_UNSEEN) + return (true); + + if (mons) + return mons_traversable(p); + + return (!feat_is_solid(grd(p)) && !feat_destroys_items(grd(p))); +} + +// Checks whether a given monster can pass over a certain position, respecting +// its preferred habit and capability of flight or opening doors. +bool monster_pathfind::mons_traversable(const coord_def p) +{ + const monster_type montype = mons_is_zombified(mons) ? mons_zombie_base(mons) + : mons->type; + + if (!monster_habitable_grid(montype, grd(p))) + return (false); + + // Monsters that can't open doors won't be able to pass them. + if (feat_is_closed_door(grd(p)) || grd(p) == DNGN_SECRET_DOOR) + { + if (mons_is_zombified(mons)) + { + if (mons_class_itemuse(montype) < MONUSE_OPEN_DOORS) + return (false); + } + else if (mons_itemuse(mons) < MONUSE_OPEN_DOORS) + return (false); + } + + // Your friends only know about doors you know about, unless they feel + // at home in this branch. + if (grd(p) == DNGN_SECRET_DOOR && mons->friendly() + && (mons_intel(mons) < I_NORMAL || !mons_is_native_in_branch(mons))) + { + return (false); + } + + const trap_def* ptrap = find_trap(p); + if (ptrap) + { + const trap_type tt = ptrap->type; + + // Don't allow allies to pass over known (to them) Zot traps. + if (tt == TRAP_ZOT + && ptrap->is_known(mons) + && mons->friendly()) + { + return (false); + } + + // Monsters cannot travel over teleport traps. + if (!_can_place_on_trap(montype, tt)) + return (false); + } + + return (true); +} + +int monster_pathfind::travel_cost(coord_def npos) +{ + if (mons) + return mons_travel_cost(npos); + + return (1); +} + +// Assumes that grids that really cannot be entered don't even get here. +// (Checked by traversable().) +int monster_pathfind::mons_travel_cost(coord_def npos) +{ + ASSERT(grid_distance(pos, npos) <= 1); + + // Doors need to be opened. + if (feat_is_closed_door(grd(npos)) || grd(npos) == DNGN_SECRET_DOOR) + return 2; + + const int montype = mons_is_zombified(mons) ? mons_zombie_base(mons) + : mons->type; + + const bool airborne = _mons_airborne(montype, -1, false); + + // Travelling through water, entering or leaving water is more expensive + // for non-amphibious monsters, so they'll avoid it where possible. + // (The resulting path might not be optimal but it will lead to a path + // a monster of such habits is likely to prefer.) + // Only tested for shallow water since they can't enter deep water anyway. + if (!airborne && !mons_class_amphibious(montype) + && (grd(pos) == DNGN_SHALLOW_WATER || grd(npos) == DNGN_SHALLOW_WATER)) + { + return 2; + } + + // Try to avoid (known) traps. + const trap_def* ptrap = find_trap(npos); + if (ptrap) + { + const bool knows_trap = ptrap->is_known(mons); + const trap_type tt = ptrap->type; + if (tt == TRAP_ALARM || tt == TRAP_ZOT) + { + // Your allies take extra precautions to avoid known alarm traps. + // Zot traps are considered intraversable. + if (knows_trap && mons->friendly()) + return (3); + + // To hostile monsters, these traps are completely harmless. + return 1; + } + + // Mechanical traps can be avoided by flying, as can shafts, and + // tele traps are never traversable anyway. + if (knows_trap && !airborne) + return 2; + } + + return 1; +} + +// The estimated cost to reach a grid is simply max(dx, dy). +int monster_pathfind::estimated_cost(coord_def p) +{ + return (grid_distance(p, target)); +} + +void monster_pathfind::add_new_pos(coord_def npos, int total) +{ + hash[total].push_back(npos); +} + +void monster_pathfind::update_pos(coord_def npos, int total) +{ + // Find hash position of old distance and delete it, + // then call_add_new_pos. + int old_total = dist[npos.x][npos.y] + estimated_cost(npos); + + std::vector &vec = hash[old_total]; + for (unsigned int i = 0; i < vec.size(); i++) + { + if (vec[i] == npos) + { + vec.erase(vec.begin() + i); + break; + } + } + + add_new_pos(npos, total); +} + +///////////////////////////////////////////////////////////////////////////// +// +// Random monsters for portal vaults. +// +///////////////////////////////////////////////////////////////////////////// + +void set_vault_mon_list(const std::vector &list) +{ + CrawlHashTable &props = env.properties; + + props.erase(VAULT_MON_TYPES_KEY); + props.erase(VAULT_MON_BASES_KEY); + props.erase(VAULT_MON_WEIGHTS_KEY); + + unsigned int size = list.size(); + if (size == 0) + { + setup_vault_mon_list(); + return; + } + + props[VAULT_MON_TYPES_KEY].new_vector(SV_LONG).resize(size); + props[VAULT_MON_BASES_KEY].new_vector(SV_LONG).resize(size); + props[VAULT_MON_WEIGHTS_KEY].new_vector(SV_LONG).resize(size); + + CrawlVector &type_vec = props[VAULT_MON_TYPES_KEY].get_vector(); + CrawlVector &base_vec = props[VAULT_MON_BASES_KEY].get_vector(); + CrawlVector &weight_vec = props[VAULT_MON_WEIGHTS_KEY].get_vector(); + + for (unsigned int i = 0; i < size; i++) + { + const mons_spec &spec = list[i]; + + if (spec.place.is_valid()) + { + ASSERT(spec.place.level_type != LEVEL_LABYRINTH + && spec.place.level_type != LEVEL_PORTAL_VAULT); + type_vec[i] = (long) -1; + base_vec[i] = (long) spec.place.packed_place(); + } + else + { + ASSERT(spec.mid != RANDOM_MONSTER + && spec.monbase != RANDOM_MONSTER); + type_vec[i] = (long) spec.mid; + base_vec[i] = (long) spec.monbase; + } + weight_vec[i] = (long) spec.genweight; + } + + setup_vault_mon_list(); +} + +void get_vault_mon_list(std::vector &list) +{ + list.clear(); + + CrawlHashTable &props = env.properties; + + if (!props.exists(VAULT_MON_TYPES_KEY)) + return; + + ASSERT(props.exists(VAULT_MON_BASES_KEY)); + ASSERT(props.exists(VAULT_MON_WEIGHTS_KEY)); + + CrawlVector &type_vec = props[VAULT_MON_TYPES_KEY].get_vector(); + CrawlVector &base_vec = props[VAULT_MON_BASES_KEY].get_vector(); + CrawlVector &weight_vec = props[VAULT_MON_WEIGHTS_KEY].get_vector(); + + ASSERT(type_vec.size() == base_vec.size()); + ASSERT(type_vec.size() == weight_vec.size()); + + unsigned int size = type_vec.size(); + for (unsigned int i = 0; i < size; i++) + { + int type = (long) type_vec[i]; + int base = (long) base_vec[i]; + + mons_spec spec; + + if (type == -1) + { + spec.place = level_id::from_packed_place(base); + ASSERT(spec.place.is_valid()); + ASSERT(spec.place.level_type != LEVEL_LABYRINTH + && spec.place.level_type != LEVEL_PORTAL_VAULT); + } + else + { + spec.mid = type; + spec.monbase = (monster_type) base; + ASSERT(spec.mid != RANDOM_MONSTER + && spec.monbase != RANDOM_MONSTER); + } + spec.genweight = (long) weight_vec[i]; + + list.push_back(spec); + } +} + +void setup_vault_mon_list() +{ + vault_mon_types.clear(); + vault_mon_bases.clear(); + vault_mon_weights.clear(); + + std::vector list; + get_vault_mon_list(list); + + unsigned int size = list.size(); + + vault_mon_types.resize(size); + vault_mon_bases.resize(size); + vault_mon_weights.resize(size); + + for (unsigned int i = 0; i < size; i++) + { + if (list[i].place.is_valid()) + { + vault_mon_types[i] = -1; + vault_mon_bases[i] = list[i].place.packed_place(); + } + else + { + vault_mon_types[i] = list[i].mid; + vault_mon_bases[i] = list[i].monbase; + } + vault_mon_weights[i] = list[i].genweight; + } +} diff --git a/crawl-ref/source/mon-place.h b/crawl-ref/source/mon-place.h new file mode 100644 index 0000000000..df810c11bb --- /dev/null +++ b/crawl-ref/source/mon-place.h @@ -0,0 +1,398 @@ +/* + * File: mon-place.h + * Summary: Functions used when placing monsters in the dungeon. + * Written by: Linley Henzell + */ + + +#ifndef MONPLACE_H +#define MONPLACE_H + +#include "coord.h" +#include "enum.h" +#include "dungeon.h" +#include "fixvec.h" + +enum band_type +{ + BAND_NO_BAND = 0, + BAND_KOBOLDS, + BAND_ORCS, + BAND_ORC_WARRIOR, + BAND_ORC_KNIGHT, + BAND_KILLER_BEES, // 5 + BAND_FLYING_SKULLS, + BAND_SLIME_CREATURES, + BAND_YAKS, + BAND_UGLY_THINGS, + BAND_HELL_HOUNDS, // 10 + BAND_JACKALS, + BAND_HELL_KNIGHTS, + BAND_ORC_HIGH_PRIEST, + BAND_GNOLLS, // 14 + // 15 + BAND_BUMBLEBEES = 16, + BAND_CENTAURS, + BAND_YAKTAURS, + BAND_INSUBSTANTIAL_WISPS, + BAND_OGRE_MAGE, // 20 + BAND_DEATH_YAKS, + BAND_NECROMANCER, + BAND_BALRUG, + BAND_CACODEMON, + BAND_EXECUTIONER, // 25 + BAND_HELLWING, + BAND_DEEP_ELF_FIGHTER, + BAND_DEEP_ELF_KNIGHT, + BAND_DEEP_ELF_HIGH_PRIEST, + BAND_KOBOLD_DEMONOLOGIST, // 30 + BAND_NAGAS, + BAND_WAR_DOGS, + BAND_GREY_RATS, + BAND_GREEN_RATS, + BAND_ORANGE_RATS, // 35 + BAND_SHEEP, + BAND_GHOULS, + BAND_DEEP_TROLLS, + BAND_HOGS, + BAND_HELL_HOGS, // 40 + BAND_GIANT_MOSQUITOES, + BAND_BOGGARTS, + BAND_BLINK_FROGS, + BAND_SKELETAL_WARRIORS, + BAND_DRACONIAN, // 45 + BAND_PANDEMONIUM_DEMON, + BAND_HARPIES, + BAND_ILSUIW, + BAND_AZRAEL, + BAND_DUVESSA, // 50 + BAND_KHUFU, + BAND_GOLDEN_EYE, + BAND_PIKEL, + NUM_BANDS // always last +}; + +enum demon_class_type +{ + DEMON_LESSER, // 0: Class V + DEMON_COMMON, // 1: Class II-IV + DEMON_GREATER, // 2: Class I + DEMON_RANDOM // any of the above +}; + +enum holy_being_class_type +{ + HOLY_BEING_WARRIOR // 0: Daeva or Angel +}; + +enum dragon_class_type +{ + DRAGON_LIZARD, + DRAGON_DRACONIAN, + DRAGON_DRAGON +}; + +enum proximity_type // proximity to player to create monster +{ + PROX_ANYWHERE, + PROX_CLOSE_TO_PLAYER, + PROX_AWAY_FROM_PLAYER, + PROX_NEAR_STAIRS +}; + +enum mgen_flag_type +{ + MG_PERMIT_BANDS = 0x01, + MG_FORCE_PLACE = 0x02, + MG_FORCE_BEH = 0x04, + MG_PLAYER_MADE = 0x08, + MG_PATROLLING = 0x10 +}; + +// A structure with all the data needed to whip up a new monster. +struct mgen_data +{ + // Monster type. + monster_type cls; + + // If the monster is zombie-like, or a specialised draconian, this + // is the base monster that the monster is based on - should be + // set to MONS_NO_MONSTER when not used. + monster_type base_type; + + // Determines the behaviour of the monster after it is generated. This + // behaviour is an unholy combination of monster attitude + // (friendly, hostile) and monster initial state (asleep, wandering). + // XXX: Could use splitting up these aspects. + beh_type behaviour; + + // For summoned monsters, this is a measure of how long the summon will + // hang around, on a scale of 1-6, 6 being longest. Use 0 for monsters + // that aren't summoned. + int abjuration_duration; + + // For summoned monsters this is their type of summoning, either the + // spell which summoned them or one of the values of the enumeration + // mon_summon_type in mon-util.h. + int summon_type; + + // Where the monster will be created. + coord_def pos; + + // The monster's foe, i.e. which monster it will want to attack. foe + // may be an index into the monster array (0 - (MAX_MONSTERS-1)), or + // it may be MHITYOU to indicate that the monster wants to attack the + // player, or MHITNOT, to indicate that the monster has no foe and is + // just wandering around. + unsigned short foe; + + // Generation flags from mgen_flag_type. + unsigned flags; + + // What god the monster worships, if any. Used for monsters that + // are god gifts, to indicate which god sent them, and by priest + // monsters, to indicate whose priest they are. + god_type god; + + // The number of hydra heads, the number of manticore attack volleys, + // the number of merged slime creatures, or the indicator for when + // Khufu is entombed. + // + // Note: in older versions this field was used for both this and for + // base_type. + int number; + + // The colour of the monster. + int colour; + + // A measure of how powerful the generated monster should be (for + // randomly chosen monsters), usually equal to the absolute depth + // that the player is in the dungeon. + int power; + + // How close to or far from the player the monster should be created. + // Is usually used only when the initial position (pos) is unspecified. + proximity_type proximity; + + // What place we're in, or pretending to be in, usually the place + // the player is actually in. + level_area_type level_type; + + // Some predefined vaults (aka maps) include flags to suppress random + // generation of monsters. When generating monsters, this is a mask of + // map flags to honour (such as MMT_NO_MONS to specify that we shouldn't + // randomly generate a monster inside a map that doesn't want it). These + // map flags are usually respected only when a dungeon level is being + // constructed, since at future points vault information may no longer + // be available (vault metadata is not preserved across game saves). + unsigned map_mask; + + // XXX: Rather hackish. + std::string mname; + + mgen_data(monster_type mt = RANDOM_MONSTER, + beh_type beh = BEH_HOSTILE, + int abj = 0, + int st = 0, + const coord_def &p = coord_def(-1, -1), + unsigned short mfoe = MHITNOT, + unsigned monflags = 0, + god_type which_god = GOD_NO_GOD, + monster_type base = MONS_NO_MONSTER, + int monnumber = 0, + int moncolour = BLACK, + int monpower = you.your_level, + proximity_type prox = PROX_ANYWHERE, + level_area_type ltype = you.level_type, + std::string monname = "") + + : cls(mt), base_type(base), behaviour(beh), + abjuration_duration(abj), summon_type(st), pos(p), foe(mfoe), + flags(monflags), god(which_god), number(monnumber), colour(moncolour), + power(monpower), proximity(prox), level_type(ltype), map_mask(0), + mname(monname) + { + ASSERT(summon_type == 0 || (abj >= 1 && abj <= 6) + || mt == MONS_BALL_LIGHTNING); + } + + bool permit_bands() const { return (flags & MG_PERMIT_BANDS); } + bool force_place() const { return (flags & MG_FORCE_PLACE); } + bool needs_patrol_point() const { return (flags & MG_PATROLLING); } + + // Is there a valid position set on this struct that we want to use + // when placing the monster? + bool use_position() const { return in_bounds(pos); } + + bool summoned() const { return (abjuration_duration > 0); } + + static mgen_data sleeper_at(monster_type what, + const coord_def &where, + unsigned flags = 0) + { + return mgen_data(what, BEH_SLEEP, 0, 0, where, MHITNOT, flags); + } + + static mgen_data hostile_at(monster_type mt, + bool alert = false, + int abj = 0, + int st = 0, + const coord_def &p = coord_def(-1, -1), + unsigned monflags = 0, + god_type god = GOD_NO_GOD, + monster_type base = MONS_NO_MONSTER) + + { + return mgen_data(mt, BEH_HOSTILE, abj, st, p, + alert ? MHITYOU : MHITNOT, + monflags, god, base); + } +}; + +/* *********************************************************************** + * Creates a monster near the place specified in the mgen_data, producing + * a "puff of smoke" message if the monster cannot be placed. This is usually + * used for summons and other monsters that want to appear near a given + * position like a summon. + * *********************************************************************** */ +int create_monster(mgen_data mg, bool fail_msg = true); + +/* *********************************************************************** + * Primary function to create monsters. See mgen_data for details on monster + * placement. + * *********************************************************************** */ +int mons_place(mgen_data mg); + +/* *********************************************************************** + * This isn't really meant to be a public function. It is a low level + * monster placement function used by dungeon building routines and + * mons_place(). If you need to put a monster somewhere, use mons_place(). + * Summoned creatures can be created with create_monster(). + * *********************************************************************** */ +int place_monster(mgen_data mg, bool force_pos = false); + +monster_type pick_random_zombie(); + +/* *********************************************************************** + * Returns a monster class type of a zombie that would be generated + * on the player's current level. + * *********************************************************************** */ +monster_type pick_local_zombifiable_monster_type(int power); + +class level_id; + +monster_type pick_random_monster(const level_id &place); + +monster_type pick_random_monster(const level_id &place, + int power, + int &lev_mons); + +bool player_will_anger_monster(monster_type type, bool *holy = NULL, + bool *unholy = NULL, bool *lawful = NULL, + bool *antimagical = NULL); + +bool player_will_anger_monster(monsters *mon, bool *holy = NULL, + bool *unholy = NULL, bool *lawful = NULL, + bool *antimagical = NULL); + +bool player_angers_monster(monsters *mon); + +bool empty_surrounds( const coord_def& where, dungeon_feature_type spc_wanted, + int radius, bool allow_centre, coord_def& empty ); + +monster_type summon_any_demon(demon_class_type dct); + +monster_type summon_any_holy_being(holy_being_class_type hbct); + +monster_type summon_any_dragon(dragon_class_type dct); + +bool drac_colour_incompatible(int drac, int colour); + +void mark_interesting_monst(monsters* monster, + beh_type behaviour = BEH_SLEEP); + +bool feat_compatible(dungeon_feature_type grid_wanted, + dungeon_feature_type actual_grid); +bool monster_habitable_grid(const monsters *m, + dungeon_feature_type actual_grid); +bool monster_habitable_grid(monster_type montype, + dungeon_feature_type actual_grid, + int flies = -1, + bool paralysed = false); +bool monster_can_submerge(const monsters *mons, dungeon_feature_type grid); +coord_def find_newmons_square(int mons_class, const coord_def &p); +coord_def find_newmons_square_contiguous(monster_type mons_class, + const coord_def &start, + int maxdistance = 3); + +void spawn_random_monsters(); + +void set_vault_mon_list(const std::vector &list); + +void get_vault_mon_list(std::vector &list); + +void setup_vault_mon_list(); + +int mons_tracking_range(const monsters *mon); + +class monster_pathfind +{ +public: + monster_pathfind(); + virtual ~monster_pathfind(); + + // public methods + void set_range(int r); + coord_def next_pos(const coord_def &p) const; + bool init_pathfind(const monsters *mon, coord_def dest, + bool diag = true, bool msg = false, + bool pass_unmapped = false); + bool init_pathfind(coord_def src, coord_def dest, + bool diag = true, bool msg = false); + bool start_pathfind(bool msg = false); + std::vector backtrack(void); + std::vector calc_waypoints(void); + +protected: + // protected methods + bool calc_path_to_neighbours(void); + bool traversable(coord_def p); + int travel_cost(coord_def npos); + bool mons_traversable(coord_def p); + int mons_travel_cost(coord_def npos); + int estimated_cost(coord_def npos); + void add_new_pos(coord_def pos, int total); + void update_pos(coord_def pos, int total); + bool get_best_position(void); + + + // The monster trying to find a path. + const monsters *mons; + + // Our destination, and the current position we're looking at. + coord_def start, target, pos; + + // If false, do not move diagonally along the path. + bool allow_diagonals; + + // If true, unmapped terrain is treated as traversable no matter the + // monster involved. + // (Used for player estimates of whether a monster can travel somewhere.) + bool traverse_unmapped; + + // Maximum range to search between start and target. None, if zero. + int range; + + // Currently shortest and longest possible total length of the path. + int min_length; + int max_length; + + // The array of distances from start to any already tried point. + int dist[GXM][GYM]; + // An array to store where we came from on a given shortest path. + int prev[GXM][GYM]; + + FixedVector, GXM * GYM> hash; +}; + +#endif // MONPLACE_H diff --git a/crawl-ref/source/mon-speak.cc b/crawl-ref/source/mon-speak.cc new file mode 100644 index 0000000000..df2484850d --- /dev/null +++ b/crawl-ref/source/mon-speak.cc @@ -0,0 +1,860 @@ +/* + * File: mon-speak.cc + * Summary: Functions to handle speaking monsters + */ + +#include "AppHdr.h" + +#include "mon-speak.h" + +#include +#include +#include +#include + +#ifdef TARGET_OS_DOS +#include +#endif + +#include "externs.h" + +#include "beam.h" +#include "database.h" +#include "debug.h" +#include "ghost.h" +#include "message.h" +#include "mon-stuff.h" +#include "mon-util.h" +#include "jobs.h" +#include "player.h" +#include "religion.h" +#include "state.h" +#include "stuff.h" +#include "view.h" + +// Try the exact key lookup along with the entire prefix list. +// If that fails, start ignoring hostile/religion/silence, in that order, +// first skipping hostile, then hostile *and* religion, then all three. +static std::string __try_exact_string(const std::vector &prefixes, + const std::string &key, + bool ignore_hostile = false, + bool ignore_related = false, + bool ignore_religion = false, + bool ignore_silenced = false) +{ + bool hostile = false; + bool related = false; + bool religion = false; + bool silenced = false; + + std::string prefix = ""; + std::string msg = ""; + const int size = prefixes.size(); + for (int i = 0; i < size; i++) + { + if (prefixes[i] == "hostile") + { + if (ignore_hostile) + continue; + hostile = true; + } + else if (prefixes[i] == "related") + { + if (ignore_related) + continue; + related = true; + } + else if (prefixes[i] == "silenced") + { + if (ignore_silenced) + continue; + silenced = true; + } + else if (prefixes[i] == "beogh" || prefixes[i] == "good god" + || prefixes[i] == "evil god") + { + if (ignore_religion) + continue; + religion = true; + } + prefix += prefixes[i]; + prefix += " "; + } + msg = getSpeakString(prefix + key); + + if (msg.empty()) + { + if (hostile) // skip hostile + msg = __try_exact_string(prefixes, key, true); + else if (related) + { + if (religion) // skip hostile and religion + msg = __try_exact_string(prefixes, key, true, false, true); + else // skip hostile and related + msg = __try_exact_string(prefixes, key, true, true); + } + else if (religion) // skip hostile, related and religion + msg = __try_exact_string(prefixes, key, true, true, true); + // 50% use non-verbal monster speech, + // 50% try for more general silenced monster message instead + else if (silenced && coinflip()) // skip all + msg = __try_exact_string(prefixes, key, true, true, true, true); + } + return msg; +} + +static bool _invalid_msg(const std::string &msg, bool no_player, bool no_foe, + bool no_foe_name, bool no_god, bool unseen) +{ + if (no_player + && (msg.find("@player") != std::string::npos + || msg.find("@Player") != std::string::npos + || msg.find(":You") != std::string::npos)) + { + return (true); + } + + if (no_player) + { + std::vector lines = split_string("\n", msg); + for (unsigned int i = 0; i < lines.size(); i++) + { + if (starts_with(lines[i], "You") + || ends_with(lines[i], "you.")) + { + return (true); + } + } + } + + if (no_foe && (msg.find("@foe") != std::string::npos + || msg.find("@Foe") != std::string::npos + || msg.find("foe@") != std::string::npos + || msg.find("@species") != std::string::npos)) + { + return (true); + } + + if (no_god && (msg.find("_god@") != std::string::npos + || msg.find("@god_") != std::string::npos)) + { + return (true); + } + + if (no_foe_name && msg.find("@foe_name@") != std::string::npos) + return (true); + + if (unseen && msg.find("VISUAL") != std::string::npos) + return (true); + + return (false); +} + +static std::string _try_exact_string(const std::vector &prefixes, + const std::string &key, + bool no_player, bool no_foe, + bool no_foe_name, bool no_god, + bool unseen, + bool ignore_hostile = false, + bool ignore_related = false, + bool ignore_religion = false, + bool ignore_silenced = false) +{ + std::string msg; + for (int tries = 0; tries < 10; tries++) + { + msg = + __try_exact_string(prefixes, key, ignore_hostile, ignore_related, + ignore_religion, ignore_silenced); + + // If the first message was non-empty and discarded then discard + // all subsequent empty messages, so as to not replace an + // invalid non-empty message with an empty one. + if (msg.empty()) + { + if (tries == 0) + return (msg); + else + { + tries--; + continue; + } + } + + if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen)) + { + msg = ""; + continue; + } + break; + } + + return (msg); +} + +static std::string __get_speak_string(const std::vector &prefixes, + const std::string &key, + const monsters *monster, + bool no_player, bool no_foe, + bool no_foe_name, bool no_god, + bool unseen) +{ + std::string msg = _try_exact_string(prefixes, key, no_player, no_foe, + no_foe_name, no_god, unseen); + + if (!msg.empty()) + return msg; + + // Combinations of prefixes by threes + const int size = prefixes.size(); + std::string prefix = ""; + if (size >= 3) + { + for (int i = 0; i < (size - 2); i++) + for (int j = i + 1; j < (size - 1); j++) + for (int k = j + 1; k < size; k++) + { + prefix = prefixes[i] + " "; + prefix += prefixes[j] + " "; + prefix += prefixes[k] + " "; + + msg = getSpeakString("default " + prefix + key); + + if (!msg.empty()) + return msg; + } + } + + // Combinations of prefixes by twos + if (size >= 2) + { + for (int i = 0; i < (size - 1); i++) + for (int j = i + 1; j < size; j++) + { + prefix = prefixes[i] + " "; + prefix += prefixes[j] + " "; + + msg = getSpeakString("default " + prefix + key); + + if (!msg.empty()) + return msg; + } + } + + // Prefixes singly + if (size >= 1) + { + for (int i = 0; i < size; i++) + { + prefix = prefixes[i] + " "; + + msg = getSpeakString("default " + prefix + key); + + if (!msg.empty()) + return msg; + } + } + + // No prefixes + msg = getSpeakString("default " + key); + + return msg; +} + +static std::string _get_speak_string(const std::vector &prefixes, + std::string key, + const monsters *monster, + bool no_player, bool no_foe, + bool no_foe_name, bool no_god, + bool unseen) +{ + int duration = 1; + if (monster->hit_points <= 0) + key += " killed"; + else if ((monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS) + key += " banished"; + else if (monster->is_summoned(&duration) && duration <= 0) + key += " unsummoned"; + + std::string msg; + for (int tries = 0; tries < 10; tries++) + { + msg = + __get_speak_string(prefixes, key, monster, no_player, no_foe, + no_foe_name, no_god, unseen); + + // If the first message was non-empty and discarded then discard + // all subsequent empty messages, so as to not replace an + // invalid non-empty message with an empty one. + if (msg.empty()) + { + if (tries == 0) + return (msg); + else + { + tries--; + continue; + } + } + + if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen)) + { + msg = ""; + continue; + } + + break; + } + + return (msg); +} + +// Player ghosts with different classes can potentially speak different +// things. +static std::string _player_ghost_speak_str(const monsters *monster, + const std::vector prefixes) +{ + const ghost_demon &ghost = *(monster->ghost); + std::string ghost_class = get_class_name(ghost.job); + + std::string prefix = ""; + for (int i = 0, size = prefixes.size(); i < size; i++) + { + prefix += prefixes[i]; + prefix += " "; + } + + // first try together with class name + std::string msg = getSpeakString(prefix + ghost_class + " player ghost"); + + // else try without class name + if (msg.empty() || msg == "__NEXT") + msg = getSpeakString(prefix + "player ghost"); + + return msg; +} + +// If the monster was originally a unique which has been polymorphed into +// a non-unique, is its current monter type capable of using its old +// speech? +static bool _polyd_can_speak(const monsters* monster) +{ + // Priest and wizard monsters can always speak. + if (monster->is_priest() || monster->is_actual_spellcaster()) + return (true); + + // Silent or non-sentient monsters can't use the original speech. + if (mons_intel(monster) < I_NORMAL + || mons_shouts(monster->type) == S_SILENT) + { + return (false); + } + + // Does it have the proper vocal equipment? + const mon_body_shape shape = get_mon_shape(monster); + return (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA); +} + +// Returns true if the monster did speak, false otherwise. +// Maybe monsters will speak! +void maybe_mons_speaks (monsters *monster) +{ +#define MON_SPEAK_CHANCE 21 + + if (monster->is_patrolling() || mons_is_wandering(monster) + || monster->attitude == ATT_NEUTRAL) + { + // Very fast wandering/patrolling monsters might, in one monster turn, + // move into the player's LOS and then back out (or the player + // might move into their LOS and the monster move back out before + // the player's view has a chance to update) so prevent them + // from speaking. + ; + } + else if ((mons_class_flag(monster->type, M_SPEAKS) + || !monster->mname.empty()) + && one_chance_in(MON_SPEAK_CHANCE)) + { + mons_speaks(monster); + } + else if (monster->type == MONS_CRAZY_YIUF + && one_chance_in(MON_SPEAK_CHANCE / 3)) + { + // Yiuf gets an extra chance to speak! + mons_speaks(monster); + } + else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED) + { + // Non-humanoid-ish monsters have a low chance of speaking + // without the M_SPEAKS flag, to give the dungeon some + // atmosphere/flavour. + int chance = MON_SPEAK_CHANCE * 4; + + // Band members are a lot less likely to speak, since there's + // a lot of them. + if (testbits(monster->flags, MF_BAND_MEMBER)) + chance *= 10; + + // However, confused and fleeing monsters are more interesting. + if (mons_is_fleeing(monster)) + chance /= 2; + if (monster->has_ench(ENCH_CONFUSION)) + chance /= 2; + + if (one_chance_in(chance)) + mons_speaks(monster); + } + // Okay then, don't speak. +} + + +// Returns true if something is said. +bool mons_speaks(monsters *monster) +{ + ASSERT(!invalid_monster_type(monster->type)); + + // Monsters always talk on death, even if invisible/silenced/etc. + int duration = 1; + const bool force_speak = !monster->alive() + || (monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS + || (monster->is_summoned(&duration) && duration <= 0) + || crawl_state.prev_cmd == CMD_LOOK_AROUND; // Wizard testing + + const bool unseen = !you.can_see(monster); + const bool confused = monster->confused(); + + if (!force_speak) + { + // Invisible monster tries to remain unnoticed. Unless they're + // confused, since then they're too confused to realise they + // should stay silent, but only if the player can see them, so as + // to not have to deal with cases of speaking monsters which the + // player can't see. + if (unseen && !confused) + return (false); + + // Silenced monsters only "speak" 1/3 as often as non-silenced, + // unless they're normally silent (S_SILENT). Use + // get_monster_data(monster->type) to bypass mon_shouts() + // replacing S_RANDOM with a random value. + if (silenced(monster->pos()) + && get_monster_data(monster->type)->shouts != S_SILENT) + { + if (!one_chance_in(3)) + return (false); + } + + // Berserk monsters just want your hide. + if (monster->berserk()) + return (false); + + // Monsters in a battle frenzy are likewise occupied. + if (monster->has_ench(ENCH_BATTLE_FRENZY) && !one_chance_in(3)) + return (false); + + // Charmed monsters aren't too expressive. + if (monster->has_ench(ENCH_CHARM) && !one_chance_in(3)) + return (false); + } + + std::vector prefixes; + if (monster->neutral()) + { + if (!force_speak && coinflip()) // Neutrals speak half as often. + return (false); + + prefixes.push_back("neutral"); + } + else if (monster->friendly() && !crawl_state.arena) + prefixes.push_back("friendly"); + else + prefixes.push_back("hostile"); + + if (mons_is_fleeing(monster)) + prefixes.push_back("fleeing"); + + bool silence = silenced(you.pos()); + if (silenced(monster->pos())) + { + silence = true; + prefixes.push_back("silenced"); + } + + if (confused) + prefixes.push_back("confused"); + + const actor* foe = (!crawl_state.arena && monster->wont_attack() + && invalid_monster_index(monster->foe)) ? + &you : monster->get_foe(); + const monsters* m_foe = (foe && foe->atype() == ACT_MONSTER) ? + dynamic_cast(foe) : NULL; + + // animals only look at the current player form, smart monsters at the + // actual player genus + if (!foe || foe->atype() == ACT_PLAYER) + { + if (is_player_same_species(monster->type, + mons_intel(monster) <= I_ANIMAL)) + { + prefixes.push_back("related"); // maybe overkill for Beogh? + } + } + else + { + if (mons_genus(monster->mons_species()) == + mons_genus(foe->mons_species())) + { + prefixes.push_back("related"); + } + } + + const god_type god = foe ? foe->deity() : + crawl_state.arena ? GOD_NO_GOD : + you.religion; + + // Add Beogh to list of prefixes for orcs (hostile and friendly) if you + // worship Beogh. (This assumes your being a Hill Orc, so might have odd + // results in wizard mode.) Don't count charmed or summoned orcs. + if (you.religion == GOD_BEOGH && mons_genus(monster->type) == MONS_ORC + && !monster->has_ench(ENCH_CHARM) && !monster->is_summoned()) + { + if (monster->god == GOD_BEOGH) + prefixes.push_back("beogh"); + else + prefixes.push_back("unbeliever"); + } + else + { + if (is_good_god(god)) + prefixes.push_back("good god"); + else if (is_evil_god(god)) + prefixes.push_back("evil god"); + } + +#ifdef DEBUG_MONSPEAK + { + std::string prefix; + const int size = prefixes.size(); + for (int i = 0; i < size; i++) + { + prefix += prefixes[i]; + prefix += " "; + } + mprf(MSGCH_DIAGNOSTICS, "monster speech lookup for %s: prefix = %s", + monster->name(DESC_PLAIN).c_str(), prefix.c_str()); + } +#endif + + const bool no_foe = (foe == NULL); + const bool no_player = crawl_state.arena + || (!monster->wont_attack() + && (!foe || foe->atype() != ACT_PLAYER)); + const bool mon_foe = (m_foe != NULL); + const bool no_god = no_foe || (mon_foe && foe->deity() == GOD_NO_GOD); + const bool named_foe = !no_foe + && (!mon_foe || (m_foe->is_named() + && m_foe->type != MONS_ROYAL_JELLY)); + const bool no_foe_name = !named_foe + || (mon_foe && (m_foe->flags & MF_NAME_MASK)); + + std::string msg; + + // First, try its exact name. + if (monster->type == MONS_PLAYER_GHOST) + { + // Player ghosts are treated differently. + msg = _player_ghost_speak_str(monster, prefixes); + } + else if (monster->type == MONS_PANDEMONIUM_DEMON) + { + // Pandemonium demons have randomly generated names, so use + // "pandemonium lord" instead. + msg = _get_speak_string(prefixes, "pandemonium lord", monster, + no_player, no_foe, no_foe_name, no_god, + unseen); + } + else + { + if (!monster->mname.empty() && _polyd_can_speak(monster)) + { + msg = _get_speak_string(prefixes, monster->name(DESC_PLAIN), + monster, no_player, no_foe, no_foe_name, + no_god, unseen); + } + + if (msg.empty()) + { + msg = _get_speak_string(prefixes, monster->base_name(DESC_PLAIN), + monster, no_player, no_foe, no_foe_name, + no_god, unseen); + } + } + + // The exact name brought no results, try monster genus. + if ((msg.empty() || msg == "__NEXT") + && mons_genus(monster->type) != monster->type) + { + msg = _get_speak_string(prefixes, + mons_type_name(mons_genus(monster->type), DESC_PLAIN), + monster, no_player, no_foe, no_foe_name, no_god, + unseen); + } + + // __NONE means to be silent, and __NEXT means to try the next, + // less exact method of describing the monster to find a speech + // string. + + if (msg == "__NONE") + { +#ifdef DEBUG_MONSPEAK + mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); +#endif + return (false); + } + + // Now that we're not dealing with a specific monster name, include + // whether or not it can move in the prefix. + if (mons_is_stationary(monster)) + prefixes.insert(prefixes.begin(), "stationary"); + + // Names for the exact monster name and its genus have failed, + // so try the monster's glyph/symbol. + if (msg.empty() || msg == "__NEXT") + { + std::string key = "'"; + + // Database keys are case-insensitve. + if (isupper(mons_char(monster->type))) + key += "cap-"; + + key += mons_char(monster->type); + key += "'"; + msg = _get_speak_string(prefixes, key, monster, no_player, no_foe, + no_foe_name, no_god, unseen); + } + + if (msg == "__NONE") + { +#ifdef DEBUG_MONSPEAK + mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); +#endif + return (false); + } + + // Monster symbol didn't work, try monster shape. Since we're + // dealing with just the monster shape, change the prefix to + // include info on if the monster's intelligence is at odds with + // its shape. + mon_body_shape shape = get_mon_shape(monster); + mon_intel_type intel = mons_intel(monster); + if (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA + && intel < I_NORMAL) + { + prefixes.insert(prefixes.begin(), "stupid"); + } + else if (shape >= MON_SHAPE_QUADRUPED && shape <= MON_SHAPE_FISH) + { + if (mons_char(monster->type) == 'w') + { + if (intel > I_INSECT) + prefixes.insert(prefixes.begin(), "smart"); + else if (intel < I_INSECT) + prefixes.insert(prefixes.begin(), "stupid"); + } + else + { + if (intel > I_ANIMAL) + prefixes.insert(prefixes.begin(), "smart"); + else if (intel < I_ANIMAL) + prefixes.insert(prefixes.begin(), "stupid"); + } + } + else if (shape >= MON_SHAPE_INSECT && shape <= MON_SHAPE_SNAIL) + { + if (intel > I_INSECT) + prefixes.insert(prefixes.begin(), "smart"); + else if (intel < I_INSECT) + prefixes.insert(prefixes.begin(), "stupid"); + } + else if (shape >= MON_SHAPE_PLANT && shape <= MON_SHAPE_BLOB + && intel > I_PLANT) + { + prefixes.insert(prefixes.begin(), "smart"); + } + + if (msg.empty() || msg == "__NEXT") + { + msg = _get_speak_string(prefixes, get_mon_shape_str(shape), monster, + no_player, no_foe, no_foe_name, no_god, + unseen); + } + + if (msg == "__NONE") + { +#ifdef DEBUG_MONSPEAK + mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); +#endif + return (false); + } + + // If we failed to get a message with a winged or tailed humanoid, + // or a naga or centaur, try moving closer to plain humanoid. + if ((msg.empty() || msg == "__NEXT") && shape > MON_SHAPE_HUMANOID + && shape <= MON_SHAPE_NAGA) + { + // If a humanoid monster has both wings and a tail, try removing + // one and then the other to see if we get any results. + if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED) + { + shape = MON_SHAPE_HUMANOID_TAILED; + msg = _get_speak_string(prefixes, get_mon_shape_str(shape), + monster, no_player, no_foe, no_foe_name, + no_god, unseen); + + // Only be silent if both tailed and winged return __NONE. + if (msg.empty() || msg == "__NONE" || msg == "__NEXT") + { + shape = MON_SHAPE_HUMANOID_WINGED; + std::string msg2; + msg2 = _get_speak_string(prefixes, get_mon_shape_str(shape), + monster, no_player, no_foe, + no_foe_name, no_god, unseen); + + if (msg == "__NONE" && msg2 == "__NONE") + { +#ifdef DEBUG_MONSPEAK + mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); +#endif + return (false); + } + + if (msg2 == "__NONE") + msg2 = ""; + + msg = msg2; + } + } // if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED) + if (msg.empty() || msg == "__NONE" || msg == "__NEXT") + { + shape = MON_SHAPE_HUMANOID; + msg = _get_speak_string(prefixes, get_mon_shape_str(shape), + monster, no_player, no_foe, no_foe_name, + no_god, unseen); + } + } + if (msg.empty() || msg == "__NONE") + { +#ifdef DEBUG_MONSPEAK + mprf(MSGCH_DIAGNOSTICS, "final result: %s!", + (msg.empty() ? "empty" : "\"__NONE\"")); +#endif + return (false); + } + + if (msg == "__NEXT") + { + msg::streams(MSGCH_DIAGNOSTICS) + << "__NEXT used by shape-based speech string for monster '" + << monster->name(DESC_PLAIN) << "'" << std::endl; + return (false); + } + + if (foe == NULL) + msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS"); + else if (foe->atype() == ACT_MONSTER) + { + if (you.can_see(foe)) + msg = replace_all(msg, "__YOU_RESIST", "@The_monster@ resists."); + else + msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS"); + } + + return (mons_speaks_msg(monster, msg, MSGCH_TALK, silence)); +} + +bool mons_speaks_msg(monsters *monster, const std::string &msg, + const msg_channel_type def_chan, const bool silence) +{ + if (!mons_near(monster)) + return (false); + + mon_acting mact(monster); + + // We have a speech string, now parse and act on it. + const std::string _msg = do_mon_str_replacements(msg, monster); + const std::vector lines = split_string("\n", _msg); + + bool noticed = false; // Any messages actually printed? + + for (int i = 0, size = lines.size(); i < size; ++i) + { + std::string line = lines[i]; + + // This function is a little bit of a problem for the message + // channels since some of the messages it generates are "fake" + // warning to scare the player. In order to accomodate this + // intent, we're falsely categorizing various things in the + // function as spells and danger warning... everything else + // just goes into the talk channel -- bwr + // [jpeg] Added MSGCH_TALK_VISUAL for silent "chatter". + msg_channel_type msg_type = def_chan; + + std::string param = ""; + std::string::size_type pos = line.find(":"); + + if (pos != std::string::npos) + param = line.substr(0, pos); + + if (!param.empty()) + { + bool match = true; + + if (param == "DANGER") + msg_type = MSGCH_DANGER; + else if (param == "WARN" && !silence || param == "VISUAL WARN") + msg_type = MSGCH_WARN; + else if (param == "SOUND") + msg_type = MSGCH_SOUND; + else if (param == "VISUAL") + msg_type = MSGCH_TALK_VISUAL; + else if (param == "SPELL" && !silence || param == "VISUAL SPELL") + { + msg_type = monster->friendly() ? MSGCH_FRIEND_SPELL + : MSGCH_MONSTER_SPELL; + } + else if (param == "ENCHANT" && !silence + || param == "VISUAL ENCHANT") + { + msg_type = monster->friendly() ? MSGCH_FRIEND_ENCHANT + : MSGCH_MONSTER_ENCHANT; + } + else if (param == "PLAIN") + msg_type = MSGCH_PLAIN; + else + match = false; + + if (match) + line = line.substr(pos + 1); + } + + const bool old_noticed = noticed; + noticed = true; // Only one case is different. + + // Except for VISUAL, none of the above influence these. + if (line == "__YOU_RESIST" && (!silence || param == "VISUAL")) + canned_msg( MSG_YOU_RESIST ); + else if (line == "__NOTHING_HAPPENS" && (!silence || param == "VISUAL")) + canned_msg( MSG_NOTHING_HAPPENS ); + else if (line == "__MORE" && (!silence || param == "VISUAL")) + more(); + else if (msg_type == MSGCH_TALK_VISUAL && !you.can_see(monster)) + noticed = old_noticed; + else + { + if (you.can_see(monster)) + handle_seen_interrupt(monster); + mpr(line.c_str(), msg_type); + } + } + return (noticed); +} diff --git a/crawl-ref/source/mon-speak.h b/crawl-ref/source/mon-speak.h new file mode 100644 index 0000000000..e0e65b24ff --- /dev/null +++ b/crawl-ref/source/mon-speak.h @@ -0,0 +1,17 @@ +/* + * File: mon-speak.h + * Summary: Functions to handle speaking monsters + */ + +#ifndef MONSPEAK_H +#define MONSPEAK_H + +#include "externs.h" + +void maybe_mons_speaks(monsters *monster); +bool mons_speaks(monsters *monster); +bool mons_speaks_msg(monsters *monster, const std::string &msg, + const msg_channel_type def_chan = MSGCH_TALK, + const bool silence = false); + +#endif diff --git a/crawl-ref/source/mon-stuff.cc b/crawl-ref/source/mon-stuff.cc new file mode 100644 index 0000000000..fd9c9eec10 --- /dev/null +++ b/crawl-ref/source/mon-stuff.cc @@ -0,0 +1,3989 @@ +/* + * File: mon-stuff.cc + * Summary: Misc monster related functions. + * Written by: Linley Henzell + */ + +#include "AppHdr.h" +#include "mon-stuff.h" + +//#include +//#include +//#include +//#include + +#ifdef TARGET_OS_DOS +#include +#endif + +#include "arena.h" +#include "artefact.h" +#include "attitude-change.h" +#include "cloud.h" +#include "database.h" +#include "delay.h" +#include "dgnevent.h" +#include "directn.h" +#include "fprop.h" +#include "files.h" +#include "food.h" +#include "godabil.h" +#include "hiscores.h" +#include "items.h" +#include "kills.h" +#include "message.h" +#include "misc.h" +#include "mon-behv.h" +#include "mon-iter.h" +#include "mon-place.h" +#include "mon-speak.h" +#include "notes.h" +#include "options.h" +#include "player.h" +#include "random.h" +#include "religion.h" +#include "spl-mis.h" +#include "spl-util.h" +#include "state.h" +#include "stuff.h" +#include "terrain.h" +#include "transfor.h" +#include "traps.h" +#include "tutorial.h" +#include "view.h" +#include "shout.h" +#include "viewchar.h" +#include "stash.h" +#include "xom.h" + +static bool _wounded_damaged(monster_type mon_type); + +// This function creates an artificial item to represent a mimic's +// appearance. Eventually, mimics could be redone to be more like +// dancing weapons: there'd only be one type and it would look like the +// item it carries. - bwr +void get_mimic_item( const 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.pos = mimic->pos(); + item.link = NON_ITEM; + + int prop = 127 * mimic->pos().x + 269 * mimic->pos().y; + + rng_save_excursion exc; + seed_rng( prop ); + + switch (mimic->type) + { + case MONS_WEAPON_MIMIC: + item.base_type = OBJ_WEAPONS; + item.sub_type = (59 * mimic->pos().x + 79 * mimic->pos().y) + % (WPN_MAX_NONBLESSED + 1); + + prop %= 100; + + if (prop < 20) + make_item_randart(item); + 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->pos().x + 79 * mimic->pos().y) + % NUM_ARMOURS; + + prop %= 100; + + if (prop < 20) + make_item_randart(item); + 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/potions +} + +// 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( const 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( bool decay_potions, bool quiet ) +{ + int count = 0; + int item = ENDOFPACK; + + for (int i = 0; i < ENDOFPACK; i++) + { + if (!you.inv[i].is_valid()) + 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 + && !you_tran_can_wear(you.inv[i]) + && item_is_equipped(you.inv[i])) + { + // Melded items cannot be cursed. + continue; + } + + if (you.inv[i].base_type == OBJ_POTIONS + && (!decay_potions || 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. + if (decay_potions && !quiet) // Just for mummies. + mpr("You feel nervous for a moment...", MSGCH_MONSTER_SPELL); + + if (you.inv[item].base_type == OBJ_POTIONS) + { + int amount; + // Decay at least two of the stack. + if (you.inv[item].quantity <= 2) + amount = you.inv[item].quantity; + else + amount = 2 + random2(you.inv[item].quantity - 1); + + split_potions_into_decay(item, amount); + + if (item_value(you.inv[item], true) / amount > 2) + xom_is_stimulated(32 * amount); + } + else + { + do_curse_item( you.inv[item], false ); + } + + return (true); +} + +void monster_drop_ething(monsters *monster, bool mark_item_origins, + int owner_id) +{ + const bool hostile_grid = feat_destroys_items(grd(monster->pos())); + + bool destroyed = false; + + // Drop weapons & missiles last (ie on top) so others pick up. + for (int i = NUM_MONSTER_SLOTS - 1; i >= 0; i--) + { + int item = monster->inv[i]; + + if (item != NON_ITEM) + { + const bool summoned_item = + testbits(mitm[item].flags, ISFLAG_SUMMONED); + if (hostile_grid || summoned_item) + { + item_was_destroyed(mitm[item], monster->mindex()); + destroy_item( item ); + if (!summoned_item) + destroyed = true; + } + else + { + if (monster->friendly() && mitm[item].is_valid()) + mitm[item].flags |= ISFLAG_DROPPED_BY_ALLY; + + move_item_to_grid(&item, monster->pos()); + + if (mark_item_origins && mitm[item].is_valid()) + origin_set_monster(mitm[item], monster); + } + + monster->inv[i] = NON_ITEM; + } + } + + if (destroyed) + mprf(MSGCH_SOUND, feat_item_destruction_message(grd(monster->pos()))); +} + +monster_type fill_out_corpse(const monsters* monster, item_def& corpse, + bool allow_weightless) +{ + ASSERT(!invalid_monster_type(monster->type)); + corpse.clear(); + + int summon_type; + if (monster->is_summoned(NULL, &summon_type) + || (monster->flags & (MF_BANISHED | MF_HARD_RESET))) + { + return (MONS_NO_MONSTER); + } + + monster_type corpse_class = mons_species(monster->type); + + // If this was a corpse that was temporarily animated then turn the + // monster back into a corpse. + if (mons_class_is_zombified(monster->type) + && (summon_type == SPELL_ANIMATE_DEAD + || summon_type == SPELL_ANIMATE_SKELETON + || summon_type == MON_SUMM_ANIMATE)) + { + corpse_class = mons_zombie_base(monster); + } + + if (corpse_class == MONS_DRACONIAN) + { + if (monster->type == MONS_TIAMAT) + corpse_class = MONS_DRACONIAN; + else + corpse_class = draco_subspecies(monster); + } + + if (monster->has_ench(ENCH_SHAPESHIFTER)) + corpse_class = MONS_SHAPESHIFTER; + else if (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER)) + corpse_class = MONS_GLOWING_SHAPESHIFTER; + + // Doesn't leave a corpse. + if (mons_weight(corpse_class) == 0 && !allow_weightless) + return (MONS_NO_MONSTER); + + corpse.flags = 0; + corpse.base_type = OBJ_CORPSES; + corpse.plus = corpse_class; + corpse.plus2 = 0; // butcher work done + corpse.sub_type = CORPSE_BODY; + corpse.special = FRESHEST_CORPSE; // rot time + corpse.quantity = 1; + corpse.orig_monnum = monster->type + 1; + corpse.props[MONSTER_NUMBER] = short(monster->number); + + corpse.colour = mons_class_colour(corpse_class); + if (corpse.colour == BLACK) + corpse.colour = monster->colour; + + if (!monster->mname.empty()) + { + corpse.props[CORPSE_NAME_KEY] = monster->mname; + corpse.props[CORPSE_NAME_TYPE_KEY] + = (long) (monster->flags & MF_NAME_MASK); + } + else if (mons_is_unique(monster->type)) + { + corpse.props[CORPSE_NAME_KEY] = mons_type_name(monster->type, + DESC_PLAIN); + corpse.props[CORPSE_NAME_TYPE_KEY] = (long) 0; + } + + return (corpse_class); +} + +bool explode_corpse(item_def& corpse, const coord_def& where) +{ + // Don't want chunks to show up behind the player. + los_def ld(where, opc_no_actor); + + if (monster_descriptor(corpse.plus, MDSC_LEAVES_HIDE) + && mons_genus(corpse.plus) == MONS_DRAGON) + { + // Uh... dragon hide is tough stuff and it keeps the monster in + // one piece? More importantly, it prevents a flavor feature + // from becoming a trap for the unwary. + + return (false); + } + + ld.update(); + + int nchunks = 1 + random2(mons_weight(corpse.plus) / 150); + nchunks = stepdown_value(nchunks, 4, 4, 12, 12); + + int ntries = 0; + + corpse.base_type = OBJ_FOOD; + corpse.sub_type = FOOD_CHUNK; + + int blood = nchunks * 3; + + if (food_is_rotten(corpse)) + blood /= 3; + + blood_spray(where, static_cast(corpse.plus), blood); + + while (nchunks > 0 && ntries < 10000) + { + ++ntries; + + coord_def cp = where; + cp.x += random_range(-LOS_RADIUS, LOS_RADIUS); + cp.y += random_range(-LOS_RADIUS, LOS_RADIUS); + +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Trying to scatter chunk to %d, %d...", + cp.x, cp.y); +#endif + + if (! in_bounds(cp)) + continue; + + if (! ld.see_cell(cp)) + continue; + +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Cell is visible..."); +#endif + + if (feat_is_solid(grd(cp)) || actor_at(cp)) + continue; + + --nchunks; + + if (feat_destroys_items(grd(cp))) + { + if (!silenced(cp)) + mprf(MSGCH_SOUND, feat_item_destruction_message(grd(cp))); + + continue; + } + +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Success"); +#endif + + copy_item_to_grid(corpse, cp); + } + + return (true); +} + +// Returns the item slot of a generated corpse, or -1 if no corpse. +int place_monster_corpse(const monsters *monster, bool silent, + bool force) +{ + // The game can attempt to place a corpse for an out-of-bounds monster + // if a shifter turns into a giant spore and explodes. In this + // case we place no corpse since the explosion means anything left + // over would be scattered, tiny chunks of shifter. + if (!in_bounds(monster->pos())) + return (-1); + + // Don't attempt to place corpses within walls, either. + // Currently, this only applies to (shapeshifter) rock worms. + if (feat_is_wall(grd(monster->pos()))) + return (-1); + + item_def corpse; + const monster_type corpse_class = fill_out_corpse(monster, corpse); + + // Don't place a corpse? If a zombified monster is somehow capable + // of leaving a corpse, then always place it. + if (mons_class_is_zombified(monster->type)) + force = true; + + if (corpse_class == MONS_NO_MONSTER || (!force && coinflip())) + return (-1); + + int o = get_item_slot(); + if (o == NON_ITEM) + { + item_was_destroyed(corpse); + return (-1); + } + + mitm[o] = corpse; + + origin_set_monster(mitm[o], monster); + + if ((monster->flags & MF_EXPLODE_KILL) + && explode_corpse(corpse, monster->pos())) + { + // We already have a spray of chunks + destroy_item(o); + return (-1); + } + + if (feat_destroys_items(grd(monster->pos()))) + { + item_was_destroyed(corpse); + destroy_item(o); + return (-1); + } + + // Don't care if 'o' is changed, and it shouldn't be (corpses don't + // stack). + move_item_to_grid(&o, monster->pos()); + if (observe_cell(monster->pos())) + { + if (force && !silent) + { + if (you.can_see(monster)) + simple_monster_message(monster, " turns back into a corpse!"); + else + { + mprf("%s appears out of nowhere!", + mitm[o].name(DESC_CAP_A).c_str()); + } + } + const bool poison = (mons_corpse_effect(corpse_class) == CE_POISONOUS + && player_res_poison() <= 0); + tutorial_dissection_reminder(!poison); + } + + return (o); +} + +static void _tutorial_inspect_kill() +{ + if (Options.tutorial_events[TUT_KILLED_MONSTER]) + learned_something_new(TUT_KILLED_MONSTER); +} + +#ifdef DGL_MILESTONES +static std::string _milestone_kill_verb(killer_type killer) +{ + return (killer == KILL_RESET ? "banished " : "killed "); +} + +static void _check_kill_milestone(const monsters *mons, + killer_type killer, int i) +{ + if (mons->type == MONS_PLAYER_GHOST) + { + std::string milestone = _milestone_kill_verb(killer) + "the ghost of "; + milestone += get_ghost_description(*mons, true); + milestone += "."; + mark_milestone("ghost", milestone); + } + else if (mons_is_unique(mons->type)) + { + mark_milestone("unique", + _milestone_kill_verb(killer) + + mons->name(DESC_NOCAP_THE, true) + + "."); + } +} +#endif // DGL_MILESTONES + +static void _give_monster_experience(monsters *victim, + int killer_index, int experience, + bool victim_was_born_friendly) +{ + if (invalid_monster_index(killer_index)) + return; + + monsters *mon = &menv[killer_index]; + if (!mon->alive()) + return; + + if ((!victim_was_born_friendly || !mon->friendly()) + && !mons_aligned(killer_index, monster_index(victim))) + { + if (mon->gain_exp(experience)) + { + if (you.religion != GOD_SHINING_ONE && you.religion != GOD_BEOGH + || player_under_penance() + || !one_chance_in(3)) + { + return; + } + + // Randomly bless the follower who gained experience. + if (you.religion == GOD_SHINING_ONE + && random2(you.piety) >= piety_breakpoint(0) + || you.religion == GOD_BEOGH + && random2(you.piety) >= piety_breakpoint(2)) + { + bless_follower(mon); + } + } + } +} + +static void _give_adjusted_experience(monsters *monster, killer_type killer, + bool pet_kill, int killer_index, + unsigned int *exp_gain, + unsigned int *avail_gain) +{ + const int experience = exper_value(monster); + + const bool created_friendly = + testbits(monster->flags, MF_CREATED_FRIENDLY); + const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL); + const bool no_xp = monster->has_ench(ENCH_ABJ) || !experience; + const bool already_got_half_xp = testbits(monster->flags, MF_GOT_HALF_XP); + + bool need_xp_msg = false; + if (created_friendly || was_neutral || no_xp) + ; // No experience if monster was created friendly or summoned. + else if (YOU_KILL(killer)) + { + int old_lev = you.experience_level; + if (already_got_half_xp) + gain_exp( experience / 2, exp_gain, avail_gain ); + else + gain_exp( experience, exp_gain, avail_gain ); + + if (old_lev == you.experience_level) + need_xp_msg = true; + } + else if (pet_kill && !already_got_half_xp) + { + int old_lev = you.experience_level; + gain_exp( experience / 2 + 1, exp_gain, avail_gain ); + + if (old_lev == you.experience_level) + need_xp_msg = true; + } + + // FIXME: Since giant spores get detached from mgrd early + // on, we can't tell by this point if they were visible when + // they exploded. Rather than bothering to remember this, we + // just suppress the message. + if (monster->type == MONS_GIANT_SPORE + || monster->type == MONS_BALL_LIGHTNING) + { + need_xp_msg = false; + } + + // Give a message for monsters dying out of sight. + if (need_xp_msg + && exp_gain > 0 + && !you.can_see(monster) + && !crawl_state.arena) + { + mpr("You feel a bit more experienced."); + } + + if (MON_KILL(killer) && !no_xp) + { + _give_monster_experience( monster, killer_index, experience, + created_friendly ); + } +} + +static bool _is_pet_kill(killer_type killer, int i) +{ + if (!MON_KILL(killer)) + return (false); + + if (i == ANON_FRIENDLY_MONSTER) + return (true); + + if (invalid_monster_index(i)) + return (false); + + const monsters *m = &menv[i]; + if (m->friendly()) // This includes enslaved monsters. + return (true); + + // Check if the monster was confused by you or a friendly, which + // makes casualties to this monster collateral kills. + const mon_enchant me = m->get_ench(ENCH_CONFUSION); + return (me.ench == ENCH_CONFUSION + && (me.who == KC_YOU || me.who == KC_FRIENDLY)); +} + +// Elyvilon will occasionally (5% chance) protect the life of one of +// your allies. +static bool _ely_protect_ally(monsters *monster) +{ + if (you.religion != GOD_ELYVILON) + return (false); + + if (!monster->is_holy() + && monster->holiness() != MH_NATURAL + || !monster->friendly() + || !you.can_see(monster) // for simplicity + || !one_chance_in(20)) + { + return (false); + } + + monster->hit_points = 1; + + snprintf(info, INFO_SIZE, " protects %s from harm!%s", + monster->name(DESC_NOCAP_THE).c_str(), + coinflip() ? "" : " You feel responsible."); + + simple_god_message(info); + lose_piety(1); + + return (true); +} + +// Elyvilon retribution effect: Heal hostile monsters that were about to +// be killed by you or one of your friends. +static bool _ely_heal_monster(monsters *monster, killer_type killer, int i) +{ + if (you.religion == GOD_ELYVILON) + return (false); + + god_type god = GOD_ELYVILON; + + if (!you.penance[god] || !god_hates_your_god(god)) + return (false); + + const int ely_penance = you.penance[god]; + + if (monster->friendly() || !one_chance_in(10)) + return (false); + + if (MON_KILL(killer) && !invalid_monster_index(i)) + { + monsters *mon = &menv[i]; + if (!mon->friendly() || !one_chance_in(3)) + return (false); + + if (!mons_near(monster)) + return (false); + } + else if (!YOU_KILL(killer)) + return (false); + +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "monster hp: %d, max hp: %d", + monster->hit_points, monster->max_hit_points); +#endif + + monster->hit_points = std::min(1 + random2(ely_penance/3), + monster->max_hit_points); + +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "new hp: %d, ely penance: %d", + monster->hit_points, ely_penance); +#endif + + snprintf(info, INFO_SIZE, "%s heals %s%s", + god_name(god, false).c_str(), + monster->name(DESC_NOCAP_THE).c_str(), + monster->hit_points * 2 <= monster->max_hit_points ? "." : "!"); + + god_speaks(god, info); + dec_penance(god, 1 + random2(monster->hit_points/2)); + + return (true); +} + +static bool _yred_enslave_soul(monsters *monster, killer_type killer) +{ + if (you.religion == GOD_YREDELEMNUL && mons_enslaved_body_and_soul(monster) + && mons_near(monster) && killer != KILL_RESET + && killer != KILL_DISMISSED) + { + yred_make_enslaved_soul(monster, player_under_penance()); + return (true); + } + + return (false); +} + +static bool _beogh_forcibly_convert_orc(monsters *monster, killer_type killer, + int i) +{ + if (you.religion == GOD_BEOGH + && mons_species(monster->type) == MONS_ORC + && !monster->is_summoned() && !monster->is_shapeshifter() + && !player_under_penance() && you.piety >= piety_breakpoint(2) + && mons_near(monster)) + { + bool convert = false; + + if (YOU_KILL(killer)) + convert = true; + else if (MON_KILL(killer) && !invalid_monster_index(i)) + { + monsters *mon = &menv[i]; + if (is_follower(mon) && !one_chance_in(3)) + convert = true; + } + + // Orcs may convert to Beogh under threat of death, either from + // you or, less often, your followers. In both cases, the + // checks are made against your stats. You're the potential + // messiah, after all. + if (convert) + { +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Death convert attempt on %s, HD: %d, " + "your xl: %d", + monster->name(DESC_PLAIN).c_str(), + monster->hit_dice, + you.experience_level); +#endif + if (random2(you.piety) >= piety_breakpoint(0) + && random2(you.experience_level) >= random2(monster->hit_dice) + // Bias beaten-up-conversion towards the stronger orcs. + && random2(monster->hit_dice) > 2) + { + beogh_convert_orc(monster, true, MON_KILL(killer)); + return (true); + } + } + } + + return (false); +} + +static bool _monster_avoided_death(monsters *monster, killer_type killer, int i) +{ + if (monster->hit_points < -25 + || monster->hit_points < -monster->max_hit_points + || monster->max_hit_points <= 0 + || monster->hit_dice < 1) + { + return (false); + } + + // Elyvilon specials. + if (_ely_protect_ally(monster)) + return (true); + if (_ely_heal_monster(monster, killer, i)) + return (true); + + // Yredelemnul special. + if (_yred_enslave_soul(monster, killer)) + return (true); + + // Beogh special. + if (_beogh_forcibly_convert_orc(monster, killer, i)) + return (true); + + return (false); +} + +static bool _slime_vault_in_los() +{ + bool in_los = false; + + for (int x = 0; x < GXM && !in_los; ++x) + { + for (int y = 0; y < GYM; ++y) + { + if ((grd[x][y] == DNGN_STONE_WALL + || grd[x][y] == DNGN_CLEAR_STONE_WALL) + && observe_cell(coord_def(x, y))) + { + in_los = true; + break; + } + } + } + + return (in_los); +} + +static bool _slime_vault_to_glass(bool silent) +{ + unset_level_flags(LFLAG_NO_TELE_CONTROL, silent); + + bool in_los = false; + + if (!silent) + in_los = _slime_vault_in_los(); + + replace_area_wrapper(DNGN_STONE_WALL, DNGN_CLEAR_ROCK_WALL); + // In case it was already vitrified, but then it's less noticeable. + replace_area_wrapper(DNGN_CLEAR_STONE_WALL, DNGN_CLEAR_ROCK_WALL); + + if (!silent) + { + if (in_los) + { + mpr("Suddenly, all colour oozes out of the stone walls.", + MSGCH_MONSTER_ENCHANT); + } + else + { + mpr("You feel a strange vibration for a moment.", + MSGCH_MONSTER_ENCHANT); + } + } + + remove_all_jiyva_altars(); + + if (silenced(you.pos())) + { + god_speaks(GOD_JIYVA, "With an infernal shudder, the power ruling " + "this place vanishes!"); + } + else + { + god_speaks(GOD_JIYVA, "With infernal noise, the power ruling this " + "place vanishes!"); + } + + return (true); +} + +static bool _slime_vault_to_glass_offlevel() +{ + return _slime_vault_to_glass(true); +} + +static bool _slime_vault_to_glass_onlevel() +{ + return _slime_vault_to_glass(false); +} + +static bool _slime_vault_to_floor(bool silent) +{ + unset_level_flags(LFLAG_NO_TELE_CONTROL, silent); + + bool in_los = false; + + if (!silent) + in_los = _slime_vault_in_los(); + + replace_area_wrapper(DNGN_STONE_WALL, DNGN_FLOOR); + // In case it was already vitrified, but then it's less noticeable. + replace_area_wrapper(DNGN_CLEAR_STONE_WALL, DNGN_FLOOR); + + if (silenced(you.pos())) + mpr("An unexplained breeze blows through the dungeon.", MSGCH_GOD); + else + mpr("You hear the sound of toppling stones.", MSGCH_GOD); + + return (true); +} + +static bool _slime_vault_to_floor_offlevel() +{ + return _slime_vault_to_floor(true); +} + +static bool _slime_vault_to_floor_onlevel() +{ + return _slime_vault_to_floor(false); +} + +void slime_vault_change(bool glass) +{ + const level_id target(BRANCH_SLIME_PITS, 6); + if (is_existing_level(target)) + { + if (glass) + { + apply_to_level(target, + target == level_id::current() ? + _slime_vault_to_glass_onlevel : + _slime_vault_to_glass_offlevel); + } + else + { + apply_to_level(target, + target == level_id::current() ? + _slime_vault_to_floor_onlevel : + _slime_vault_to_floor_offlevel); + } + } +} + +static void _fire_monster_death_event(monsters *monster, + killer_type killer, + int i, bool polymorph) +{ + int type = monster->type; + + // Treat whatever the Royal Jelly polymorphed into as if it were still + // the Royal Jelly (but if a player chooses the character name + // "shaped Royal Jelly" don't unlock the vaults when the player's + // ghost is killed). + if (monster->mname == "shaped Royal Jelly" + && monster->type != MONS_PLAYER_GHOST) + { + type = MONS_ROYAL_JELLY; + } + + // Banished monsters aren't technically dead, so no death event + // for them. + if (killer == KILL_RESET) + { + // Give player a hint that banishing the Royal Jelly means the + // Slime:6 vaults stay locked. + if (type == MONS_ROYAL_JELLY) + { + if (you.can_see(monster)) + mpr("You feel a great sense of loss."); + else + mpr("You feel a great sense of loss, and the brush of the " + "Abyss."); + } + return; + } + + dungeon_events.fire_event( + dgn_event(DET_MONSTER_DIED, monster->pos(), 0, + monster_index(monster), killer)); + + // Don't unlock the Slime:6 vaults if the "death" was actually the + // Royal Jelly polymorphing into something else; the player still + // has to kill whatever it polymorphed into. + if (type == MONS_ROYAL_JELLY && !polymorph) + { + you.royal_jelly_dead = true; + + if (jiyva_is_dead()) + slime_vault_change(true); + } +} + +static void _mummy_curse(monsters* monster, killer_type killer, int index) +{ + int pow; + + switch (killer) + { + // Mummy killed by trap or something other than the player or + // another monster, so no curse. + case KILL_MISC: + // Mummy sent to the Abyss wasn't actually killed, so no curse. + case KILL_RESET: + case KILL_DISMISSED: + return; + + default: + break; + } + + switch (monster->type) + { + case MONS_MENKAURE: + case MONS_MUMMY: pow = 1; break; + case MONS_GUARDIAN_MUMMY: pow = 3; break; + case MONS_MUMMY_PRIEST: pow = 8; break; + case MONS_GREATER_MUMMY: pow = 11; break; + case MONS_KHUFU: pow = 15; break; + + default: + mpr("Unknown mummy type.", MSGCH_DIAGNOSTICS); + return; + } + + // beam code might give an index of MHITYOU for the player. + if (YOU_KILL(killer)) + index = NON_MONSTER; + + // Killed by a Zot trap, a god, etc. + if (index != NON_MONSTER && invalid_monster_index(index)) + return; + + actor* target; + if (index == NON_MONSTER) + target = &you; + else + { + // Mummies committing suicide don't cause a death curse. + if (index == monster->mindex()) + return; + target = &menv[index]; + } + + // Mummy was killed by a giant spore or ball lightning? + if (!target->alive()) + return; + + if ((monster->type == MONS_MUMMY || monster->type == MONS_MENKAURE) && YOU_KILL(killer)) + curse_an_item(true); + else + { + if (index == NON_MONSTER) + { + mpr("You feel extremely nervous for a moment...", + MSGCH_MONSTER_SPELL); + } + else if (you.can_see(target)) + { + mprf(MSGCH_MONSTER_SPELL, "A malignant aura surrounds %s.", + target->name(DESC_NOCAP_THE).c_str()); + } + MiscastEffect(target, monster_index(monster), SPTYP_NECROMANCY, + pow, random2avg(88, 3), "a mummy death curse"); + } +} + +static bool _spore_goes_pop(monsters *monster, killer_type killer, + int killer_index, bool pet_kill, bool wizard) +{ + if (monster->hit_points > 0 || monster->hit_points <= -15 || wizard + || killer == KILL_RESET || killer == KILL_DISMISSED) + { + return (false); + } + + bolt beam; + const int type = monster->type; + + beam.is_tracer = false; + beam.is_explosion = true; + beam.beam_source = monster_index(monster); + beam.type = dchar_glyph(DCHAR_FIRED_BURST); + beam.source = monster->pos(); + beam.target = monster->pos(); + beam.thrower = crawl_state.arena ? KILL_MON + : monster->attitude == ATT_FRIENDLY ? KILL_YOU : KILL_MON; + beam.aux_source.clear(); + + if (YOU_KILL(killer)) + beam.aux_source = "set off by themselves"; + else if (pet_kill) + beam.aux_source = "set off by their pet"; + + const char* msg = NULL; + const char* sanct_msg = NULL; + if (type == MONS_GIANT_SPORE) + { + beam.flavour = BEAM_SPORE; + beam.damage = dice_def(3, 15); + beam.name = "explosion of spores"; + beam.colour = LIGHTGREY; + beam.ex_size = 2; + msg = "The giant spore explodes!"; + sanct_msg = "By Zin's power, the giant spore's explosion is " + "contained."; + } + else if (type == MONS_BALL_LIGHTNING) + { + beam.flavour = BEAM_ELECTRICITY; + beam.damage = dice_def(3, 20); + beam.name = "blast of lightning"; + beam.colour = LIGHTCYAN; + beam.ex_size = coinflip() ? 3 : 2; + msg = "The ball lightning explodes!"; + sanct_msg = "By Zin's power, the ball lightning's explosion " + "is contained."; + } + else + { + msg::streams(MSGCH_DIAGNOSTICS) << "Unknown spore type: " + << static_cast(type) + << std::endl; + return (false); + } + + bool saw = false; + if (you.can_see(monster)) + { + saw = true; + viewwindow(false); + if (is_sanctuary(monster->pos())) + mpr(sanct_msg, MSGCH_GOD); + else + mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, msg); + } + + if (is_sanctuary(monster->pos())) + return (false); + + // Detach monster from the grid first, so it doesn't get hit by + // its own explosion. (GDL) + mgrd(monster->pos()) = NON_MONSTER; + + // The explosion might cause a monster to be placed where the spore + // used to be, so make sure that mgrd() doesn't get cleared a second + // time (causing the new monster to become floating) when + // monster->reset() is called. + monster->set_position(coord_def(0,0)); + + // Exploding kills the monster a bit earlier than normal. + monster->hit_points = -16; + if (saw) + viewwindow(false); + + // FIXME: show_more == mons_near(monster) + beam.explode(); + + // Monster died in explosion, so don't re-attach it to the grid. + return (true); +} + +void _monster_die_cloud(const monsters* monster, bool corpse, bool silent, + bool summoned) +{ + // Chaos spawn always leave behind a cloud of chaos. + if (monster->type == MONS_CHAOS_SPAWN) + { + summoned = true; + corpse = false; + } + + if (!summoned) + return; + + std::string prefix = " "; + if (corpse) + { + if (mons_weight(mons_species(monster->type)) == 0) + return; + + prefix = "'s corpse "; + } + + std::string msg = summoned_poof_msg(monster); + msg += "!"; + + cloud_type cloud = CLOUD_NONE; + if (msg.find("smoke") != std::string::npos) + cloud = random_smoke_type(); + else if (msg.find("chaos") != std::string::npos) + cloud = CLOUD_CHAOS; + + if (!silent) + simple_monster_message(monster, (prefix + msg).c_str()); + + if (cloud != CLOUD_NONE) + { + place_cloud(cloud, monster->pos(), 1 + random2(3), + monster->kill_alignment()); + } +} + +// XXX: Another hackish function! May do weird things if multiple copies of +// the band have been placed using wizard mode. {due} +static void _elven_twin_died(monsters* twin) +{ + bool found_duvessa = false; + bool found_dowan = false; + monsters *monster; + + for (int i = 0; i < MAX_MONSTERS; ++i) + { + monster = &menv[i]; + if (monster->alive() && monster->type == MONS_DUVESSA) + { + found_duvessa = true; + break; + } + else if (monster->alive() && monster->type == MONS_DOWAN) + { + found_dowan = true; + break; + } + } + + if ((found_duvessa || found_dowan) && mons_near(monster)) + { + // Will generate strings such as 'Duvessa_Duvessa_dies' or, alternately + // 'Dowan_Dowan_dies', but as neither will match, these can safely be + // ignored. + std::string key = "_" + monster->name(DESC_CAP_THE, true) + "_" + + twin->name(DESC_CAP_THE) + "_dies_"; + + if (!monster->observable()) + key += "invisible_"; + + std::string death_message = getSpeakString(key); + + if (!death_message.empty()) + mons_speaks_msg(monster, death_message, MSGCH_TALK, silenced(you.pos())); + } + + if (found_duvessa && mons_near(monster)) + { + // Provides its own flavour message. + monster->go_berserk(true); + } + else if (found_dowan && mons_near(monster)) + { + // Doesn't provide any message, so needs one, but only if visible. + if (monster->observable()) + simple_monster_message(monster, " turns to flee."); + monster->add_ench(mon_enchant(ENCH_FEAR, 0, KC_YOU)); + behaviour_event(monster, ME_SCARE, MHITNOT); + } +} + +void pikel_band_neutralise () +{ + // XXX: This is a really ugly hack. It should be replaced by something else + // when band tracking is available. This assumes that the only human monsters + // with MF_BAND_MEMBER are Pikel's band members. + bool message_made = false; + + for (monster_iterator mi; mi; ++mi) + { + if (mi->type == MONS_HUMAN + && testbits(mi->flags, MF_BAND_MEMBER)) + { + if (mi->observable() && !message_made) + { + mpr("Pikel's slaves thank you for their freedom."); + message_made = true; + } + mons_pacify(*mi); + } + } +} + +static void _hogs_to_humans() +{ + // Simplification: if, in a rare event, another hog which was not created + // as a part of Kirke's band happens to be on the level, the player can't + // tell them apart anyway. + // On the other hand, hogs which left the level are too far away to be + // affected by the magic of Kirke's death. + // FIXME: If another monster was polymorphed into a hog by Kirke's + // porkalator spell, they should be handled specially... + int any = 0, human = 0; + + for (monster_iterator mi; mi; ++mi) + { + if (!mi->type == MONS_HOG) + continue; + + const bool could_see = you.can_see(*mi); + + // XXX: This resets the size of slime creatures, the number + // of heads a hydra has, and the number of spikes a manticore + // has. Plus it also changes the colour of a draconian which + // has a sub-type. And it re-rolls the spellbook the monster + // has. + if (mi->number == 0) + mi->type = MONS_HUMAN; + else + mi->type = (monster_type) (mi->number - 1); + + mi->number = 0; + define_monster(**mi); + + const bool can_see = you.can_see(*mi); + + // A monster changing factions while in the arena messes up + // arena book-keeping. + if (!crawl_state.arena) + { + // * A monster's attitude shouldn't downgrade from friendly + // or good-neutral because you helped it. It'd suck to + // lose a permanent ally that way. + // + // * A monster has to be smart enough to realize that you + // helped it. + if (mi->attitude == ATT_HOSTILE + && mons_intel(*mi) >= I_NORMAL) + { + mi->attitude = ATT_GOOD_NEUTRAL; + mi->flags |= MF_WAS_NEUTRAL; + } + } + + behaviour_event(*mi, ME_EVAL); + + if (could_see && can_see) + { + any++; + if (mi->type == MONS_HUMAN) + human++; + } + else if (could_see && !can_see) + mpr("The hog vanishes!"); + else if (!could_see && can_see) + mprf("%s appears from out of thin air!", + mi->name(DESC_CAP_A).c_str()); + } + + if (any == 1) + { + if (any == human) + mpr("No longer under Kirke's spell, the hog turns into a human!"); + else + mpr("No longer under Kirke's spell, the hog returns to its " + "original form!"); + } + else if (any > 1) + { + if (any == human) + mpr("No longer under Kirke's spell, all hogs revert to their " + "human forms!"); + else + mpr("No longer under Kirke's spell, all hogs revert to their " + "original forms!"); + } + + // Revert the player as well. + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_PIG) + untransform(); +} + +static int _tentacle_too_far(monsters *head, monsters *tentacle) +{ + // The Shoals produce no disjoint bodies of water. + // If this ever changes, we'd need to check if the head and tentacle + // are still in the same pool. + // XXX: Actually, using Fedhas's Sunlight power you can separate pools... + return grid_distance(head->pos(), tentacle->pos()) > LOS_RADIUS; +} + +void mons_relocated(monsters *monster) +{ + if (monster->type == MONS_KRAKEN) + { + int headnum = monster_index(monster); + + if (invalid_monster_index(headnum)) + return; + + for (monster_iterator mi; mi; ++mi) + { + if (mi->type == MONS_KRAKEN_TENTACLE + && (int)mi->number == headnum + && _tentacle_too_far(monster, *mi)) + { + monster_die(*mi, KILL_RESET, -1, true, false); + } + } + } + else if (monster->type == MONS_KRAKEN_TENTACLE) + { + if (invalid_monster_index(monster->number) + || menv[monster->number].type != MONS_KRAKEN + || _tentacle_too_far(&menv[monster->number], monster)) + { + monster_die(monster, KILL_RESET, -1, true, false); + } + } +} + +static int _destroy_tentacles(monsters *head) +{ + int tent = 0; + int headnum = monster_index(head); + + if (invalid_monster_index(headnum)) + return 0; + + for (monster_iterator mi; mi; ++mi) + { + if (mi->type == MONS_KRAKEN_TENTACLE + && (int)mi->number == headnum) + { + if (mons_near(*mi)) + tent++; + mi->hurt(*mi, INSTANT_DEATH); + } + } + return tent; +} + +// Returns the slot of a possibly generated corpse or -1. +int monster_die(monsters *monster, killer_type killer, + int killer_index, bool silent, bool wizard) +{ + if (invalid_monster(monster)) + return (-1); + + // If a monster was banished to the Abyss and then killed there, + // then its death wasn't a banishment. + if (you.level_type == LEVEL_ABYSS) + monster->flags &= ~MF_BANISHED; + + if (!silent && _monster_avoided_death(monster, killer, killer_index)) + return (-1); + + crawl_state.inc_mon_acting(monster); + + ASSERT(!( YOU_KILL(killer) && crawl_state.arena )); + + mons_clear_trapping_net(monster); + + you.remove_beholder(monster); + + // Clear auto exclusion now the monster is killed -- if we know about it. + if (mons_near(monster) || wizard) + remove_auto_exclude(monster); + + int summon_type = 0; + int duration = 0; + const bool summoned = monster->is_summoned(&duration, &summon_type); + const int monster_killed = monster_index(monster); + const bool hard_reset = testbits(monster->flags, MF_HARD_RESET); + const bool gives_xp = (!summoned && !mons_class_flag(monster->type, + M_NO_EXP_GAIN)); + + const bool drop_items = !hard_reset; + + const bool mons_reset(killer == KILL_RESET || killer == KILL_DISMISSED); + + const bool submerged = monster->submerged(); + + bool in_transit = false; + +#ifdef DGL_MILESTONES + if (!crawl_state.arena) + _check_kill_milestone(monster, killer, killer_index); +#endif + + // Award experience for suicide if the suicide was caused by the + // player. + if (MON_KILL(killer) && monster_killed == killer_index) + { + if (monster->confused_by_you()) + { + ASSERT(!crawl_state.arena); + killer = KILL_YOU_CONF; + } + } + else if (MON_KILL(killer) && monster->has_ench(ENCH_CHARM)) + { + ASSERT(!crawl_state.arena); + killer = KILL_YOU_CONF; // Well, it was confused in a sense... (jpeg) + } + + // Take note! + if (!mons_reset && !crawl_state.arena && MONST_INTERESTING(monster)) + { + take_note(Note(NOTE_KILL_MONSTER, + monster->type, monster->friendly(), + monster->full_name(DESC_NOCAP_A).c_str())); + } + + // From time to time Trog gives you a little bonus. + if (killer == KILL_YOU && you.berserk()) + { + if (you.religion == GOD_TROG + && !player_under_penance() && you.piety > random2(1000)) + { + const int bonus = 3 + random2avg( 10, 2 ); + + you.duration[DUR_BERSERKER] += bonus; + you.duration[DUR_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)) + { + const int bonus = 2 + random2(4); + + you.duration[DUR_BERSERKER] += bonus; + you.duration[DUR_MIGHT] += bonus; + haste_player(bonus); + + mpr("Your amulet glows a violent red."); + } + } + + if (you.prev_targ == monster_killed) + { + you.prev_targ = MHITNOT; + crawl_state.cancel_cmd_repeat(); + } + + if (killer == KILL_YOU) + crawl_state.cancel_cmd_repeat(); + + const bool pet_kill = _is_pet_kill(killer, killer_index); + + bool did_death_message = false; + + if (monster->type == MONS_GIANT_SPORE + || monster->type == MONS_BALL_LIGHTNING) + { + did_death_message = + _spore_goes_pop(monster, killer, killer_index, pet_kill, wizard); + } + else if (monster->type == MONS_FIRE_VORTEX + || monster->type == MONS_SPATIAL_VORTEX) + { + if (!silent && !mons_reset) + { + simple_monster_message(monster, " dissipates!", + MSGCH_MONSTER_DAMAGE, MDAM_DEAD); + silent = true; + } + + if (monster->type == MONS_FIRE_VORTEX && !wizard && !mons_reset + && !submerged) + { + place_cloud(CLOUD_FIRE, monster->pos(), 2 + random2(4), + monster->kill_alignment()); + } + + if (killer == KILL_RESET) + killer = KILL_DISMISSED; + } + else if (monster->type == MONS_SIMULACRUM_SMALL + || monster->type == MONS_SIMULACRUM_LARGE) + { + if (!silent && !mons_reset) + { + simple_monster_message(monster, " vapourises!", + MSGCH_MONSTER_DAMAGE, MDAM_DEAD); + silent = true; + } + + if (!wizard && !mons_reset && !submerged) + { + place_cloud(CLOUD_COLD, monster->pos(), 2 + random2(4), + monster->kill_alignment()); + } + + if (killer == KILL_RESET) + killer = KILL_DISMISSED; + } + else if (monster->type == MONS_DANCING_WEAPON) + { + if (!hard_reset) + { + if (killer == KILL_RESET) + killer = KILL_DISMISSED; + } + + if (!silent && !hard_reset) + { + int w_idx = monster->inv[MSLOT_WEAPON]; + if (w_idx != NON_ITEM && !(mitm[w_idx].flags & ISFLAG_SUMMONED)) + { + simple_monster_message(monster, " falls from the air.", + MSGCH_MONSTER_DAMAGE, MDAM_DEAD); + silent = true; + } + else + killer = KILL_RESET; + } + } + + const bool death_message = !silent && !did_death_message + && mons_near(monster) + && (monster->visible_to(&you) + || crawl_state.arena); + const bool exploded = monster->flags & MF_EXPLODE_KILL; + + const bool created_friendly = testbits(monster->flags, MF_CREATED_FRIENDLY); + bool anon = (killer_index == ANON_FRIENDLY_MONSTER); + const mon_holy_type targ_holy = monster->holiness(); + + switch (killer) + { + case KILL_YOU: // You kill in combat. + case KILL_YOU_MISSILE: // You kill by missile or beam. + case KILL_YOU_CONF: // You kill by confusion. + { + const bool bad_kill = god_hates_killing(you.religion, monster); + const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL); + + if (death_message) + { + if (killer == KILL_YOU_CONF + && (anon || !invalid_monster_index(killer_index))) + { + mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "%s is %s!", + monster->name(DESC_CAP_THE).c_str(), + exploded ? "blown up" : + _wounded_damaged(monster->type) ? "destroyed" + : "killed"); + } + else + { + mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "You %s %s!", + exploded ? "blow up" : + _wounded_damaged(monster->type) ? "destroy" + : "kill", + monster->name(DESC_NOCAP_THE).c_str()); + } + + if ((created_friendly || was_neutral) && gives_xp) + mpr("That felt strangely unrewarding."); + } + + // Killing triggers tutorial lesson. + if (gives_xp) + _tutorial_inspect_kill(); + + // Prevent summoned creatures from being good kills. + if (bad_kill || !created_friendly && gives_xp) + { + if (targ_holy == MH_NATURAL) + { + did_god_conduct(DID_KILL_LIVING, + monster->hit_dice, true, monster); + + if (monster->is_evil()) + { + did_god_conduct(DID_KILL_NATURAL_EVIL, + monster->hit_dice, true, monster); + } + } + else if (targ_holy == MH_UNDEAD) + { + did_god_conduct(DID_KILL_UNDEAD, + monster->hit_dice, true, monster); + } + else if (targ_holy == MH_DEMONIC) + { + did_god_conduct(DID_KILL_DEMON, + monster->hit_dice, true, monster); + } + + // Zin hates chaotic beings. + if (monster->is_chaotic()) + { + did_god_conduct(DID_KILL_CHAOTIC, + monster->hit_dice, true, monster); + } + + // jmf: Trog hates wizards. + if (monster->is_actual_spellcaster()) + { + did_god_conduct(DID_KILL_WIZARD, + monster->hit_dice, true, monster); + } + + // Beogh hates priests of other gods. + if (monster->is_priest()) + { + did_god_conduct(DID_KILL_PRIEST, + monster->hit_dice, true, monster); + } + + if (mons_is_slime(monster)) + { + did_god_conduct(DID_KILL_SLIME, monster->hit_dice, + true, monster); + } + + if (fedhas_protects(monster)) + { + did_god_conduct(DID_KILL_PLANT, monster->hit_dice, + true, monster); + } + + // Cheibriados hates fast monsters. + if (mons_is_fast(monster)) + { + did_god_conduct(DID_KILL_FAST, monster->hit_dice, + true, monster); + } + + // Holy kills are always noticed. + if (monster->is_holy()) + { + did_god_conduct(DID_KILL_HOLY, monster->hit_dice, + true, monster); + } + } + + // Divine health and mana restoration doesn't happen when + // killing born-friendly monsters. The mutation still + // applies, however. + if (player_mutation_level(MUT_DEATH_STRENGTH) + || (!created_friendly && gives_xp + && (you.religion == GOD_MAKHLEB + || you.religion == GOD_SHINING_ONE + && monster->is_evil()) + && !player_under_penance() + && random2(you.piety) >= piety_breakpoint(0))) + { + 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 && gives_xp + && (you.religion == GOD_MAKHLEB + || you.religion == GOD_VEHUMET + || you.religion == GOD_SHINING_ONE + && monster->is_evil()) + && !player_under_penance() + && random2(you.piety) >= piety_breakpoint(0)) + { + if (you.magic_points < you.max_magic_points) + { + mpr("You feel your power returning."); + inc_mp(1 + random2(monster->hit_dice / 2), false); + } + } + + // Randomly bless a follower. + if (!created_friendly + && gives_xp + && (you.religion == GOD_BEOGH + && random2(you.piety) >= piety_breakpoint(2)) + && !player_under_penance()) + { + bless_follower(); + } + + if (you.duration[DUR_DEATH_CHANNEL] + && monster->holiness() == MH_NATURAL + && mons_can_be_zombified(monster) + && gives_xp) + { + const monster_type spectre_type = mons_species(monster->type); + + // Don't allow 0-headed hydras to become spectral hydras. + if (spectre_type != MONS_HYDRA || monster->number != 0) + { + const int spectre = + create_monster( + mgen_data(MONS_SPECTRAL_THING, BEH_FRIENDLY, + 0, 0, monster->pos(), MHITYOU, + 0, static_cast(you.attribute[ATTR_DIVINE_DEATH_CHANNEL]), + spectre_type, monster->number)); + + if (spectre != -1) + { + if (death_message) + mpr("A glowing mist starts to gather..."); + + name_zombie(&menv[spectre], monster); + } + } + } + break; + } + + case KILL_MON: // Monster kills in combat. + case KILL_MON_MISSILE: // Monster kills by missile or beam. + if (!silent) + { + const char* msg = + exploded ? " is blown up!" : + _wounded_damaged(monster->type) ? " is destroyed!" + : " dies!"; + simple_monster_message(monster, msg, MSGCH_MONSTER_DAMAGE, + MDAM_DEAD); + } + + if (crawl_state.arena) + break; + + // No piety loss if god gifts killed by other monsters. + // Also, dancing weapons aren't really friendlies. + if (monster->friendly() + && monster->type != MONS_DANCING_WEAPON) + { + did_god_conduct(DID_FRIEND_DIED, 1 + (monster->hit_dice / 2), + true, monster); + } + + if (pet_kill && fedhas_protects(monster)) + { + did_god_conduct(DID_ALLY_KILLED_PLANT, 1 + (monster->hit_dice / 2), + true, monster); + } + + // Trying to prevent summoning abuse here, so we're trying to + // prevent summoned creatures from being done_good kills. Only + // affects creatures which were friendly when summoned. + if (!created_friendly && gives_xp && pet_kill + && (anon || !invalid_monster_index(killer_index))) + { + bool notice = false; + + monsters *killer_mon = NULL; + if (!anon) + { + killer_mon = &menv[killer_index]; + + // If the killer is already dead treat it like an + // anonymous monster. + if (killer_mon->type == MONS_NO_MONSTER) + anon = true; + } + + const mon_holy_type killer_holy = + anon ? MH_NATURAL : killer_mon->holiness(); + + if (you.religion == GOD_SHINING_ONE + || you.religion == GOD_YREDELEMNUL + || you.religion == GOD_KIKUBAAQUDGHA + || you.religion == GOD_VEHUMET + || you.religion == GOD_MAKHLEB + || you.religion == GOD_LUGONU + || !anon && mons_is_god_gift(killer_mon)) + { + if (killer_holy == MH_UNDEAD) + { + const bool confused = + anon ? false : !killer_mon->friendly(); + + // Yes, these are hacks, but they make sure that + // confused monsters doing kills are not + // referred to as "slaves", and I think it's + // okay that e.g. Yredelemnul ignores kills done + // by confused monsters as opposed to enslaved + // or friendly ones. (jpeg) + if (targ_holy == MH_NATURAL) + { + notice |= did_god_conduct( + !confused ? DID_LIVING_KILLED_BY_UNDEAD_SLAVE : + DID_LIVING_KILLED_BY_SERVANT, + monster->hit_dice); + } + else if (targ_holy == MH_UNDEAD) + { + notice |= did_god_conduct( + !confused ? DID_UNDEAD_KILLED_BY_UNDEAD_SLAVE : + DID_UNDEAD_KILLED_BY_SERVANT, + monster->hit_dice); + } + else if (targ_holy == MH_DEMONIC) + { + notice |= did_god_conduct( + !confused ? DID_DEMON_KILLED_BY_UNDEAD_SLAVE : + DID_DEMON_KILLED_BY_SERVANT, + monster->hit_dice); + } + } + // Yes, we are splitting undead pets from the others + // as a way to focus Necromancy vs. Summoning + // (ignoring Haunt here)... at least we're being + // nice and putting the natural creature summons + // together with the demonic ones. Note that + // Vehumet gets a free pass here since those + // followers are assumed to come from summoning + // spells... the others are from invocations (TSO, + // Makhleb, Kiku). - bwr + else if (targ_holy == MH_NATURAL) + { + notice |= did_god_conduct(DID_LIVING_KILLED_BY_SERVANT, + monster->hit_dice); + + if (monster->is_evil()) + { + notice |= did_god_conduct( + DID_NATURAL_EVIL_KILLED_BY_SERVANT, + monster->hit_dice); + } + } + else if (targ_holy == MH_UNDEAD) + { + notice |= did_god_conduct(DID_UNDEAD_KILLED_BY_SERVANT, + monster->hit_dice); + } + else if (targ_holy == MH_DEMONIC) + { + notice |= did_god_conduct(DID_DEMON_KILLED_BY_SERVANT, + monster->hit_dice); + } + } + + // Holy kills are always noticed. + if (monster->is_holy()) + { + if (killer_holy == MH_UNDEAD) + { + const bool confused = + anon ? false : !killer_mon->friendly(); + + // Yes, this is a hack, but it makes sure that + // confused monsters doing kills are not + // referred to as "slaves", and I think it's + // okay that Yredelemnul ignores kills done by + // confused monsters as opposed to enslaved or + // friendly ones. (jpeg) + notice |= did_god_conduct( + !confused ? DID_HOLY_KILLED_BY_UNDEAD_SLAVE : + DID_HOLY_KILLED_BY_SERVANT, + monster->hit_dice); + } + else + notice |= did_god_conduct(DID_HOLY_KILLED_BY_SERVANT, + monster->hit_dice); + } + + if (you.religion == GOD_VEHUMET + && notice + && !player_under_penance() + && random2(you.piety) >= piety_breakpoint(0)) + { + // 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); + } + } + + if (you.religion == GOD_SHINING_ONE + && monster->is_evil() + && !player_under_penance() + && random2(you.piety) >= piety_breakpoint(0) + && !invalid_monster_index(killer_index)) + { + // Randomly bless the follower who killed. + if (!one_chance_in(3) && killer_mon->alive() + && bless_follower(killer_mon)) + { + break; + } + + if (killer_mon->alive() + && killer_mon->hit_points < killer_mon->max_hit_points) + { + simple_monster_message(killer_mon, + " looks invigorated."); + killer_mon->heal(1 + random2(monster->hit_dice / 4)); + } + } + + if (you.religion == GOD_BEOGH + && random2(you.piety) >= piety_breakpoint(2) + && !player_under_penance() + && !one_chance_in(3) + && !invalid_monster_index(killer_index)) + { + // Randomly bless the follower who killed. + bless_follower(killer_mon); + } + } + break; + + // Monster killed by trap/inanimate thing/itself/poison not from you. + case KILL_MISC: + if (!silent) + { + const char* msg = + exploded ? " is blown up!" : + _wounded_damaged(monster->type) ? " is destroyed!" + : " dies!"; + simple_monster_message(monster, msg, 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??? + // Pacified monsters leave the level when this happens. + + // KILL_RESET monsters no longer lose their whole inventory, only + // items they were generated with. + if (monster->pacified() || !monster->needs_transit()) + { + // A banished monster that doesn't go on the transit list + // loses all items. + if (!monster->is_summoned()) + monster->destroy_inventory(); + break; + } + + // Monster goes to the Abyss. + monster->flags |= MF_BANISHED; + monster->set_transit(level_id(LEVEL_ABYSS)); + in_transit = true; + monster->destroy_inventory(); + // Make monster stop patrolling and/or travelling. + monster->patrol_point.reset(); + monster->travel_path.clear(); + monster->travel_target = MTRAV_NONE; + break; + + case KILL_DISMISSED: + break; + + default: + monster->destroy_inventory(); + break; + } + + // Make sure Boris has a foe to address. + if (monster->foe == MHITNOT) + { + if (!monster->wont_attack() && !crawl_state.arena) + monster->foe = MHITYOU; + else if (!invalid_monster_index(killer_index)) + monster->foe = killer_index; + } + + if (!silent && !wizard && observe_cell(monster->pos())) + { + // Make sure that the monster looks dead. + if (monster->alive() && !in_transit && (!summoned || duration > 0)) + monster->hit_points = -1; + mons_speaks(monster); + } + + if (monster->type == MONS_BORIS && !in_transit) + { + // XXX: Actual blood curse effect for Boris? -- bwr + + // Now that Boris is dead, he's a valid target for monster + // creation again. -- bwr + you.unique_creatures[monster->type] = false; + // And his vault can be placed again. + you.uniq_map_names.erase("uniq_boris"); + } + else if (monster->type == MONS_KIRKE && !in_transit) + { + _hogs_to_humans(); + } + else if (monster->type == MONS_PIKEL) + { + // His slaves don't care if he's dead or not, just whether or not + // he goes away. + pikel_band_neutralise(); + } + else if (monster->type == MONS_KRAKEN) + { + if (_destroy_tentacles(monster) && !in_transit) + { + mpr("The kraken is slain, and its tentacles slide " + "back into the water like the carrion they now are."); + } + } + else if ((monster->type == MONS_DOWAN || monster->type == MONS_DUVESSA) + && mons_near(monster)) + { + _elven_twin_died(monster); + } + else if (!monster->is_summoned()) + { + if (mons_genus(monster->type) == MONS_MUMMY) + _mummy_curse(monster, killer, killer_index); + } + + if (!wizard && !submerged) + _monster_die_cloud(monster, !mons_reset, silent, summoned); + + int corpse = -1; + if (!mons_reset) + { + // Have to add case for disintegration effect here? {dlb} + if (!summoned) + corpse = place_monster_corpse(monster, silent); + } + + if (!mons_reset && !crawl_state.arena) + { + you.kills->record_kill(monster, killer, pet_kill); + + kill_category kc = + (killer == KILL_YOU || killer == KILL_YOU_MISSILE) ? KC_YOU : + (pet_kill)? KC_FRIENDLY : + KC_OTHER; + + unsigned int exp_gain = 0, avail_gain = 0; + _give_adjusted_experience(monster, killer, pet_kill, killer_index, + &exp_gain, &avail_gain); + + PlaceInfo& curr_PlaceInfo = you.get_place_info(); + PlaceInfo delta; + + delta.mon_kill_num[kc]++; + delta.mon_kill_exp += exp_gain; + delta.mon_kill_exp_avail += avail_gain; + + you.global_info += delta; + you.global_info.assert_validity(); + + curr_PlaceInfo += delta; + curr_PlaceInfo.assert_validity(); + } + + _fire_monster_death_event(monster, killer, killer_index, false); + + if (crawl_state.arena) + arena_monster_died(monster, killer, killer_index, silent, corpse); + + const coord_def mwhere = monster->pos(); + if (drop_items) + monster_drop_ething(monster, YOU_KILL(killer) || pet_kill); + else + { + // Destroy the items belonging to MF_HARD_RESET monsters so they + // don't clutter up mitm[]. + monster->destroy_inventory(); + } + + if (!silent && !wizard && !mons_reset && corpse != -1 + && !(monster->flags & MF_KNOWN_MIMIC) + && monster->is_shapeshifter()) + { + simple_monster_message(monster, "'s shape twists and changes " + "as it dies."); + } + + // If we kill an invisible monster reactivate autopickup. + if (mons_near(monster) && !monster->visible_to(&you)) + autotoggle_autopickup(false); + + crawl_state.dec_mon_acting(monster); + monster_cleanup(monster); + + // Force redraw for monsters that die. + if (observe_cell(mwhere)) + { + view_update_at(mwhere); + update_screen(); + } + + return (corpse); +} + +// Clean up after a dead monster. +void monster_cleanup(monsters *monster) +{ + crawl_state.mon_gone(monster); + + unsigned int monster_killed = monster_index(monster); + monster->reset(); + + for (monster_iterator mi; mi; ++mi) + if (mi->foe == monster_killed) + mi->foe = MHITNOT; + + if (you.pet_target == monster_killed) + you.pet_target = MHITNOT; +} + +// If you're invis and throw/zap whatever, alerts menv to your position. +void alert_nearby_monsters(void) +{ + // 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 + for (monster_iterator mi(&you.get_los()); mi; ++mi) + if (!mi->asleep()) + behaviour_event(*mi, ME_ALERT, MHITYOU); +} + +static bool _valid_morph(monsters *monster, monster_type new_mclass) +{ + const dungeon_feature_type current_tile = grd(monster->pos()); + + // 'morph targets are _always_ "base" classes, not derived ones. + new_mclass = mons_species(new_mclass); + + // [ds] Non-base draconians are much more trouble than their HD + // suggests. + if (mons_genus(new_mclass) == MONS_DRACONIAN + && new_mclass != MONS_DRACONIAN + && !player_in_branch(BRANCH_HALL_OF_ZOT) + && !one_chance_in(10)) + { + return (false); + } + + // Various inappropriate polymorph targets. + if (mons_class_holiness(new_mclass) != monster->holiness() + || mons_class_flag(new_mclass, M_UNIQUE) // no uniques + || 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 + + // These require manual setting of mons.base_monster to indicate + // what they are a skeleton/zombie/simulacrum/spectral thing of, + // which we currently aren't smart enough to handle. + || mons_class_is_zombified(new_mclass) + + // These require manual setting of the ghost demon struct to + // indicate their characteristics, which we currently aren't + // smart enough to handle. + || mons_is_ghost_demon(new_mclass) + + // Only for use by game testers or in the arena. + || new_mclass == MONS_TEST_SPAWNER + + // Other poly-unsuitable things. + || new_mclass == MONS_ORB_GUARDIAN + || mons_is_statue(new_mclass) + + // The spell on Prince Ribbit can't be broken so easily. + || (new_mclass == MONS_HUMAN && monster->type == MONS_PRINCE_RIBBIT)) + { + return (false); + } + + // Determine if the monster is happy on current tile. + return (monster_habitable_grid(new_mclass, current_tile)); +} + +static bool _is_poly_power_unsuitable( poly_power_type power, + int src_pow, int tgt_pow, int relax ) +{ + switch (power) + { + case PPT_LESS: + return (tgt_pow > src_pow - 3 + (relax * 3) / 2) + || (power == PPT_LESS && (tgt_pow < src_pow - (relax / 2))); + case PPT_MORE: + return (tgt_pow < src_pow + 2 - relax) + || (power == PPT_MORE && (tgt_pow > src_pow + relax)); + default: + case PPT_SAME: + return (tgt_pow < src_pow - relax) + || (tgt_pow > src_pow + (relax * 3) / 2); + } +} + +// If targetc == RANDOM_MONSTER, then relpower indicates the desired +// power of the new monster, relative to the current monster. +// Relaxation still takes effect when needed, no matter what relpower +// says. +bool monster_polymorph(monsters *monster, monster_type targetc, + poly_power_type power, + bool force_beh) +{ + ASSERT(!(monster->flags & MF_TAKING_STAIRS)); + ASSERT(!(monster->flags & MF_BANISHED) || you.level_type == LEVEL_ABYSS); + + std::string str_polymon; + int source_power, target_power, relax; + int tries = 1000; + + // 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 = 1; + + if (targetc == RANDOM_MONSTER) + { + do + { + // Pick a monster that's guaranteed happy at this grid. + targetc = random_monster_at_grid(monster->pos()); + + // Valid targets are always base classes ([ds] which is unfortunate + // in that well-populated monster classes will dominate polymorphs). + targetc = mons_species(targetc); + + target_power = mons_power(targetc); + + if (one_chance_in(200)) + relax++; + + if (relax > 50) + return (simple_monster_message(monster, " shudders.")); + } + while (tries-- && (!_valid_morph(monster, targetc) + || _is_poly_power_unsuitable(power, source_power, + target_power, relax))); + } + + if (!_valid_morph(monster, targetc)) + { + return (simple_monster_message(monster, + " looks momentarily different.")); + } + + // Messaging. + bool can_see = you.can_see(monster); + bool can_see_new = !mons_class_flag(targetc, M_INVIS) || you.can_see_invisible(); + + bool need_note = false; + std::string old_name = monster->full_name(DESC_CAP_A); + + // If old monster is visible to the player, and is interesting, + // then note why the interesting monster went away. + if (can_see && MONST_INTERESTING(monster)) + need_note = true; + + std::string new_name = ""; + if (monster->type == MONS_OGRE && targetc == MONS_TWO_HEADED_OGRE) + str_polymon = " grows a second head"; + else + { + if (monster->is_shapeshifter()) + str_polymon = " changes into "; + else if (targetc == MONS_PULSATING_LUMP) + str_polymon = " degenerates into "; + else if (you.religion == GOD_JIYVA + && (targetc == MONS_DEATH_OOZE + || targetc == MONS_OOZE + || targetc == MONS_JELLY + || targetc == MONS_BROWN_OOZE + || targetc == MONS_SLIME_CREATURE + || targetc == MONS_GIANT_AMOEBA + || targetc == MONS_ACID_BLOB + || targetc == MONS_AZURE_JELLY)) + { + // Message used for the Slimify ability. + str_polymon = " quivers uncontrollably and liquefies into "; + } + else + str_polymon = " evaporates and reforms as "; + + if (!can_see_new) + { + new_name = "something unseen"; + str_polymon += "something you cannot see"; + } + else + { + str_polymon += mons_type_name(targetc, DESC_NOCAP_A); + + if (targetc == MONS_PULSATING_LUMP) + str_polymon += " of flesh"; + } + } + str_polymon += "!"; + + bool player_messaged = can_see + && simple_monster_message(monster, str_polymon.c_str()); + + // Even if the monster transforms from one type that can behold the + // player into a different type which can also behold the player, + // the polymorph disrupts the beholding process. Do this before + // changing monster->type, since unbeholding can only happen while + // the monster is still a mermaid/siren. + you.remove_beholder(monster); + + // Inform listeners that the original monster is gone. + _fire_monster_death_event(monster, KILL_MISC, NON_MONSTER, true); + + // the actual polymorphing: + unsigned long flags = + monster->flags & ~(MF_INTERESTING | MF_SEEN | MF_ATT_CHANGE_ATTEMPT + | MF_WAS_IN_VIEW | MF_BAND_MEMBER + | MF_HONORARY_UNDEAD | MF_KNOWN_MIMIC); + + std::string name; + + // Preserve the names of uniques and named monsters. + if (!monster->mname.empty()) + name = monster->mname; + else if (mons_is_unique(monster->type)) + { + flags |= MF_INTERESTING; + + name = monster->name(DESC_PLAIN, true); + if (monster->type == MONS_ROYAL_JELLY) + { + name = "shaped Royal Jelly"; + flags |= MF_NAME_SUFFIX; + } + else if (monster->type == MONS_LERNAEAN_HYDRA) + { + name = "shaped Lernaean hydra"; + flags |= MF_NAME_SUFFIX; + } + + // "Blork the orc" and similar. + const size_t the_pos = name.find(" the "); + if (the_pos != std::string::npos) + name = name.substr(0, the_pos); + } + + const monster_type real_targetc = + (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER)) ? MONS_GLOWING_SHAPESHIFTER : + (monster->has_ench(ENCH_SHAPESHIFTER)) ? MONS_SHAPESHIFTER + : targetc; + + const god_type god = + (player_will_anger_monster(real_targetc) + || (you.religion == GOD_BEOGH + && mons_species(real_targetc) != MONS_ORC)) ? GOD_NO_GOD + : monster->god; + + if (god == GOD_NO_GOD) + flags &= ~MF_GOD_GIFT; + + const int old_hp = monster->hit_points; + const int old_hp_max = monster->max_hit_points; + const bool old_mon_caught = monster->caught(); + const char old_ench_countdown = monster->ench_countdown; + + mon_enchant abj = monster->get_ench(ENCH_ABJ); + mon_enchant charm = monster->get_ench(ENCH_CHARM); + mon_enchant neutral = monster->get_ench(ENCH_NEUTRAL); + mon_enchant shifter = monster->get_ench(ENCH_GLOWING_SHAPESHIFTER, + ENCH_SHAPESHIFTER); + mon_enchant sub = monster->get_ench(ENCH_SUBMERGED); + mon_enchant summon = monster->get_ench(ENCH_SUMMON); + mon_enchant tp = monster->get_ench(ENCH_TP); + + monster_spells spl = monster->spells; + const bool need_save_spells + = (!name.empty() + && (!monster->can_use_spells() + || monster->is_actual_spellcaster())); + + // deal with mons_sec + monster->type = targetc; + monster->base_monster = MONS_NO_MONSTER; + monster->number = 0; + + // Note: define_monster() will clear out all enchantments! - bwr + define_monster(monster_index(monster)); + + monster->mname = name; + monster->flags = flags; + monster->god = god; + + // Keep spells for named monsters, but don't override innate ones + // for dragons and the like. This means that Sigmund polymorphed + // into a goblin will still cast spells, but if he ends up as a + // swamp drake he'll breathe fumes and, if polymorphed further, + // won't remember his spells anymore. + if (need_save_spells + && (!monster->can_use_spells() || monster->is_actual_spellcaster())) + { + monster->spells = spl; + } + + monster->add_ench(abj); + monster->add_ench(charm); + monster->add_ench(neutral); + monster->add_ench(shifter); + monster->add_ench(sub); + monster->add_ench(summon); + monster->add_ench(tp); + + // Allows for handling of submerged monsters which polymorph into + // monsters that can't submerge on this square. + if (monster->has_ench(ENCH_SUBMERGED) + && !monster_can_submerge(monster, grd(monster->pos()))) + { + monster->del_ench(ENCH_SUBMERGED); + } + + monster->ench_countdown = old_ench_countdown; + + if (mons_class_flag(monster->type, M_INVIS)) + monster->add_ench(ENCH_INVIS); + + if (!player_messaged && you.can_see(monster)) + { + mprf("%s appears out of thin air!", monster->name(DESC_CAP_A).c_str()); + autotoggle_autopickup(false); + player_messaged = true; + } + + monster->hit_points = monster->max_hit_points + * ((old_hp * 100) / old_hp_max) / 100 + + random2(monster->max_hit_points); + + monster->hit_points = std::min(monster->max_hit_points, + monster->hit_points); + + // Don't kill it. + monster->hit_points = std::max(monster->hit_points, 1); + + monster->speed_increment = 67 + random2(6); + + monster_drop_ething(monster); + + // New monster type might be interesting. + mark_interesting_monst(monster); + if (new_name.empty()) + new_name = monster->full_name(DESC_NOCAP_A); + + if (need_note + || can_see && you.can_see(monster) && MONST_INTERESTING(monster)) + { + take_note(Note(NOTE_POLY_MONSTER, 0, 0, old_name.c_str(), + new_name.c_str())); + + if (you.can_see(monster)) + monster->flags |= MF_SEEN; + } + + // If new monster is visible to player, then we've seen it. + if (you.can_see(monster)) + { + seen_monster(monster); + // If the player saw both the beginning and end results of a + // shifter changing, then s/he knows it must be a shifter. + if (can_see && shifter.ench != ENCH_NONE) + monster->flags |= MF_KNOWN_MIMIC; + } + + if (old_mon_caught) + check_net_will_hold_monster(monster); + + if (!force_beh) + player_angers_monster(monster); + + // Xom likes watching monsters being polymorphed. + xom_is_stimulated(monster->is_shapeshifter() ? 16 : + power == PPT_LESS || monster->friendly() ? 32 : + power == PPT_SAME ? 64 : 128); + + return (player_messaged); +} + +// If the returned value is mon.pos(), then nothing was found. +static coord_def _random_monster_nearby_habitable_space(const monsters& mon, + bool allow_adjacent, + bool respect_los) +{ + const bool respect_sanctuary = mon.wont_attack(); + + coord_def target; + int tries; + + for (tries = 0; tries < 150; ++tries) + { + const coord_def delta(random2(13) - 6, random2(13) - 6); + + // Check that we don't get something too close to the + // starting point. + if (delta.origin()) + continue; + + if (delta.rdist() == 1 && !allow_adjacent) + continue; + + // Update target. + target = delta + mon.pos(); + + // Check that the target is valid and survivable. + if (!in_bounds(target)) + continue; + + if (!monster_habitable_grid(&mon, grd(target))) + continue; + + if (respect_sanctuary && is_sanctuary(target)) + continue; + + if (target == you.pos()) + continue; + + // Check that we didn't go through clear walls. + if (num_feats_between(mon.pos(), target, + DNGN_CLEAR_ROCK_WALL, + DNGN_CLEAR_PERMAROCK_WALL, + true, true) > 0) + { + continue; + } + + if (respect_los && !mon.see_cell(target)) + continue; + + // Survived everything, break out (with a good value of target.) + break; + } + + if (tries == 150) + target = mon.pos(); + + return (target); +} + +bool monster_blink(monsters *monster, bool quiet) +{ + coord_def near = _random_monster_nearby_habitable_space(*monster, false, + true); + if (near == monster->pos()) + return (false); + + if (!quiet) + simple_monster_message(monster, " blinks!"); + + if (!(monster->flags & MF_WAS_IN_VIEW)) + monster->seen_context = "thin air"; + + const coord_def oldplace = monster->pos(); + if (!monster->move_to_pos(near)) + return (false); + + // Leave a purple cloud. + place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3), + monster->kill_alignment()); + + monster->check_redraw(oldplace); + monster->apply_location_effects(oldplace); + + mons_relocated(monster); + + return (true); +} + +bool mon_can_be_slimified(monsters *monster) +{ + const mon_holy_type holi = monster->holiness(); + + return (holi == MH_UNDEAD + || holi == MH_NATURAL + && !mons_is_slime(monster)); +} + +void slimify_monster(monsters *mon, bool hostile) +{ + if (mon->holiness() == MH_UNDEAD) + monster_polymorph(mon, MONS_DEATH_OOZE); + else + { + const int x = mon->hit_dice + (coinflip() ? 1 : -1) * random2(5); + + if (x < 3) + monster_polymorph(mon, MONS_OOZE); + else if (x >= 3 && x < 5) + monster_polymorph(mon, MONS_JELLY); + else if (x >= 5 && x < 7) + monster_polymorph(mon, MONS_BROWN_OOZE); + else if (x >= 7 && x <= 11) + { + if (coinflip()) + monster_polymorph(mon, MONS_SLIME_CREATURE); + else + monster_polymorph(mon, MONS_GIANT_AMOEBA); + } + else + { + if (coinflip()) + monster_polymorph(mon, MONS_ACID_BLOB); + else + monster_polymorph(mon, MONS_AZURE_JELLY); + } + } + + if (!mons_eats_items(mon)) + mon->add_ench(ENCH_EAT_ITEMS); + + if (!hostile) + mon->attitude = ATT_STRICT_NEUTRAL; + else + mon->attitude = ATT_HOSTILE; + + mons_make_god_gift(mon, GOD_JIYVA); +} + +static bool _habitat_okay( const monsters *monster, dungeon_feature_type targ ) +{ + return (monster_habitable_grid(monster, targ)); +} + +// 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 that 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 wants to +// swap pets to their death, we can let that go). -- bwr +bool swap_places(monsters *monster) +{ + coord_def loc; + if (swap_check(monster, loc)) + { + swap_places(monster, loc); + return true; + } + return false; +} + +// Swap monster to this location. Player is swapped elsewhere. +bool swap_places(monsters *monster, const coord_def &loc) +{ + ASSERT(map_bounds(loc)); + ASSERT(_habitat_okay(monster, grd(loc))); + + if (monster_at(loc)) + { + mpr("Something prevents you from swapping places."); + return (false); + } + + mpr("You swap places."); + + mgrd(monster->pos()) = NON_MONSTER; + + monster->moveto(loc); + + mgrd(monster->pos()) = monster_index(monster); + + return true; +} + +// Returns true if this is a valid swap for this monster. If true, then +// the valid location is set in loc. (Otherwise loc becomes garbage.) +bool swap_check(monsters *monster, coord_def &loc, bool quiet) +{ + loc = you.pos(); + + // Don't move onto dangerous terrain. + if (is_feat_dangerous(grd(monster->pos()))) + { + canned_msg(MSG_UNTHINKING_ACT); + return (false); + } + + if (monster->caught()) + { + if (!quiet) + simple_monster_message(monster, " is held in a net!"); + return (false); + } + + // First try: move monster onto your position. + bool swap = _habitat_okay( monster, grd(loc) ); + + // Choose an appropriate habitat square at random around the target. + if (!swap) + { + int num_found = 0; + + for (adjacent_iterator ai(you.pos()); ai; ++ai) + if (!monster_at(*ai) && _habitat_okay(monster, grd(*ai)) + && one_chance_in(++num_found)) + { + loc = *ai; + } + + if (num_found) + swap = true; + } + + if (!swap && !quiet) + { + // Might not be ideal, but it's better than insta-killing + // the monster... maybe try for a short blink instead? -- bwr + simple_monster_message( monster, " resists." ); + // FIXME: AI_HIT_MONSTER isn't ideal. + interrupt_activity( AI_HIT_MONSTER, monster ); + } + + return (swap); +} + +// Given an adjacent monster, returns true if the monster can hit it +// (the monster should not be submerged, be submerged in shallow water +// if the monster has a polearm, or be submerged in anything if the +// monster has tentacles). +bool monster_can_hit_monster(monsters *monster, const monsters *targ) +{ + if (!targ->submerged() || monster->has_damage_type(DVORP_TENTACLE)) + return (true); + + if (grd(targ->pos()) != DNGN_SHALLOW_WATER) + return (false); + + const item_def *weapon = monster->weapon(); + return (weapon && weapon_skill(*weapon) == SK_POLEARMS); +} + +void mons_get_damage_level(const monsters* monster, std::string& desc, + mon_dam_level_type& dam_level) +{ + if (monster->hit_points <= monster->max_hit_points / 6) + { + desc += "almost "; + desc += _wounded_damaged(monster->type) ? "destroyed" : "dead"; + dam_level = MDAM_ALMOST_DEAD; + return; + } + + if (monster->hit_points <= monster->max_hit_points / 4) + { + desc += "severely "; + dam_level = MDAM_SEVERELY_DAMAGED; + } + else if (monster->hit_points <= monster->max_hit_points / 3) + { + desc += "heavily "; + dam_level = MDAM_HEAVILY_DAMAGED; + } + else if (monster->hit_points <= monster->max_hit_points * 3 / 4) + { + desc += "moderately "; + dam_level = MDAM_MODERATELY_DAMAGED; + } + else if (monster->hit_points < monster->max_hit_points) + { + desc += "lightly "; + dam_level = MDAM_LIGHTLY_DAMAGED; + } + else + { + desc += "not "; + dam_level = MDAM_OKAY; + } + + desc += _wounded_damaged(monster->type) ? "damaged" : "wounded"; +} + +std::string get_wounds_description(const monsters *monster) +{ + if (!monster->alive() || monster->hit_points == monster->max_hit_points) + return ""; + + if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS)) + return ""; + + std::string desc; + mon_dam_level_type dam_level; + mons_get_damage_level(monster, desc, dam_level); + + desc.insert(0, " is "); + desc += "."; + + return desc; +} + +void print_wounds(const monsters *monster) +{ + if (!monster->alive() || monster->hit_points == monster->max_hit_points) + return; + + if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS)) + return; + + std::string desc; + mon_dam_level_type dam_level; + mons_get_damage_level(monster, desc, dam_level); + + desc.insert(0, " is "); + desc += "."; + simple_monster_message(monster, desc.c_str(), MSGCH_MONSTER_DAMAGE, + dam_level); +} + +// (true == 'damaged') [constructs, undead, etc.] +// and (false == 'wounded') [living creatures, etc.] {dlb} +static bool _wounded_damaged(monster_type mon_type) +{ + // this schema needs to be abstracted into real categories {dlb}: + const mon_holy_type holi = mons_class_holiness(mon_type); + + return (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT); +} + +// If _mons_find_level_exits() is ever expanded to handle more grid +// types, this should be expanded along with it. +static void _mons_indicate_level_exit(const monsters *mon) +{ + const dungeon_feature_type feat = grd(mon->pos()); + const bool is_shaft = (get_trap_type(mon->pos()) == TRAP_SHAFT); + + if (feat_is_gate(feat)) + simple_monster_message(mon, " passes through the gate."); + else if (feat_is_travelable_stair(feat)) + { + command_type dir = feat_stair_direction(feat); + simple_monster_message(mon, + make_stringf(" %s the %s.", + dir == CMD_GO_UPSTAIRS ? "goes up" : + dir == CMD_GO_DOWNSTAIRS ? "goes down" + : "takes", + feat_is_escape_hatch(feat) ? "escape hatch" + : "stairs").c_str()); + } + else if (is_shaft) + { + simple_monster_message(mon, + make_stringf(" %s the shaft.", + mons_flies(mon) ? "goes down" + : "jumps into").c_str()); + } +} + +void make_mons_leave_level(monsters *mon) +{ + if (mon->pacified()) + { + if (you.can_see(mon)) + _mons_indicate_level_exit(mon); + + // Pacified monsters leaving the level take their stuff with + // them. + mon->flags |= MF_HARD_RESET; + monster_die(mon, KILL_DISMISSED, NON_MONSTER); + } +} + +// Checks whether there is a straight path from p1 to p2 that passes +// through features >= allowed. +// If it exists, such a path may be missed; on the other hand, it +// is not guaranteed that p2 is visible from p1 according to LOS rules. +// Not symmetric. +bool can_go_straight(const coord_def& p1, const coord_def& p2, + dungeon_feature_type allowed) +{ + if (distance(p1, p2) > get_los_radius_sq()) + return (false); + + dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE; + if (allowed != DNGN_UNSEEN) + max_disallowed = static_cast(allowed - 1); + + return (!num_feats_between(p1, p2, DNGN_UNSEEN, max_disallowed, + true, true)); +} + +// The default suitable() function for choose_random_nearby_monster(). +bool choose_any_monster(const monsters* mon) +{ + return (true); +} + +// Find a nearby monster and return its index, including you as a +// possibility with probability weight. suitable() should return true +// for the type of monster wanted. +// If prefer_named is true, named monsters (including uniques) are twice +// as likely to get chosen compared to non-named ones. +// If prefer_priest is true, priestly monsters (including uniques) are +// twice as likely to get chosen compared to non-priestly ones. +monsters *choose_random_nearby_monster(int weight, + bool (*suitable)(const monsters* mon), + bool in_sight, bool prefer_named, + bool prefer_priest) +{ + return choose_random_monster_on_level(weight, suitable, in_sight, true, + prefer_named, prefer_priest); +} + +monsters *choose_random_monster_on_level(int weight, + bool (*suitable)(const monsters* mon), + bool in_sight, bool near_by, + bool prefer_named, bool prefer_priest) +{ + monsters *chosen = NULL; + + // A radius_iterator with radius == max(GXM, GYM) will sweep the + // whole level. + radius_iterator ri(you.pos(), near_by ? 9 : std::max(GXM, GYM), + true, in_sight); + + for (; ri; ++ri) + { + if (monsters *mon = monster_at(*ri)) + { + if (suitable(mon)) + { + // FIXME: if the intent is to favour monsters + // named by $DEITY, we should set a flag on the + // monster (something like MF_DEITY_PREFERRED) and + // use that instead of checking the name, given + // that other monsters can also have names. + + // True, but it's currently only used for orcs, and + // Blork and Urug also being preferred to non-named orcs + // is fine, I think. Once more gods name followers (and + // prefer them) that should be changed, of course. (jpeg) + + // Named or priestly monsters have doubled chances. + int mon_weight = 1; + + if (prefer_named && mon->is_named()) + mon_weight++; + + if (prefer_priest && mon->is_priest()) + mon_weight++; + + if (x_chance_in_y(mon_weight, (weight += mon_weight))) + chosen = mon; + } + } + } + + return chosen; +} + +// 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(const monsters *monster, const char *event, + msg_channel_type channel, + int param, + description_level_type descrip) +{ + + if ((mons_near(monster) || crawl_state.arena) + && (channel == MSGCH_MONSTER_SPELL || channel == MSGCH_FRIEND_SPELL + || monster->visible_to(&you) || crawl_state.arena)) + { + std::string msg = monster->name(descrip); + msg += event; + msg = apostrophise_fixup(msg); + + if (channel == MSGCH_PLAIN && monster->wont_attack()) + channel = MSGCH_FRIEND_ACTION; + + mpr(msg.c_str(), channel, param); + return (true); + } + + return (false); +} + +bool mons_avoids_cloud(const monsters *monster, cloud_struct cloud, + bool placement) +{ + bool extra_careful = placement; + cloud_type cl_type = cloud.type; + + if (placement) + extra_careful = true; + + switch (cl_type) + { + case CLOUD_MIASMA: + // Even the dumbest monsters will avoid miasma if they can. + return (!monster->res_rotting()); + + case CLOUD_FIRE: + case CLOUD_FOREST_FIRE: + if (monster->res_fire() > 1) + return (false); + + if (extra_careful) + return (true); + + if (mons_intel(monster) >= I_ANIMAL && monster->res_fire() < 0) + return (true); + + if (monster->hit_points >= 15 + random2avg(46, 5)) + return (false); + break; + + case CLOUD_STINK: + if (monster->res_poison() > 0) + return (false); + + if (extra_careful) + return (true); + + if (mons_intel(monster) >= I_ANIMAL && monster->res_poison() < 0) + return (true); + + if (x_chance_in_y(monster->hit_dice - 1, 5)) + return (false); + + if (monster->hit_points >= random2avg(19, 2)) + return (false); + break; + + case CLOUD_COLD: + if (monster->res_cold() > 1) + return (false); + + if (extra_careful) + return (true); + + if (mons_intel(monster) >= I_ANIMAL && monster->res_cold() < 0) + return (true); + + if (monster->hit_points >= 15 + random2avg(46, 5)) + return (false); + break; + + case CLOUD_POISON: + if (monster->res_poison() > 0) + return (false); + + if (extra_careful) + return (true); + + if (mons_intel(monster) >= I_ANIMAL && monster->res_poison() < 0) + return (true); + + if (monster->hit_points >= random2avg(37, 4)) + return (false); + break; + + case CLOUD_GREY_SMOKE: + if (placement) + return (false); + + // This isn't harmful, but dumb critters might think so. + if (mons_intel(monster) > I_ANIMAL || coinflip()) + return (false); + + if (monster->res_fire() > 0) + return (false); + + if (monster->hit_points >= random2avg(19, 2)) + return (false); + break; + + case CLOUD_RAIN: + // Fiery monsters dislike the rain. + if (monster->is_fiery() && extra_careful) + return (true); + + // We don't care about what's underneath the rain cloud if we can fly. + if (monster->flight_mode() != FL_NONE) + return (false); + + // These don't care about deep water. + if (monster_habitable_grid(monster, DNGN_DEEP_WATER)) + return (false); + + // This position could become deep water, and they might drown. + if (grd(cloud.pos) == DNGN_SHALLOW_WATER) + return (true); + + // Otherwise, it's safe for everyone else. + return (false); + + break; + + default: + break; + } + + // Exceedingly dumb creatures will wander into harmful clouds. + if (is_harmless_cloud(cl_type) + || mons_intel(monster) == I_PLANT && !extra_careful) + { + return (false); + } + + // If we get here, the cloud is potentially harmful. + return (true); +} + +// Like the above, but allow a monster to move from one damaging cloud +// to another, even if they're of different types. +bool mons_avoids_cloud(const monsters *monster, int cloud_num, + cloud_type *cl_type, bool placement) +{ + if (cloud_num == EMPTY_CLOUD) + { + if (cl_type != NULL) + *cl_type = CLOUD_NONE; + + return (false); + } + + const cloud_struct &cloud = env.cloud[cloud_num]; + + if (cl_type != NULL) + *cl_type = cloud.type; + + // Is the target cloud okay? + if (!mons_avoids_cloud(monster, cloud, placement)) + return (false); + + // If we're already in a cloud that we'd want to avoid then moving + // from one to the other is okay. + if (!in_bounds(monster->pos()) || monster->pos() == cloud.pos) + return (true); + + const int our_cloud_num = env.cgrid(monster->pos()); + + if (our_cloud_num == EMPTY_CLOUD) + return (true); + + const cloud_struct &our_cloud = env.cloud[our_cloud_num]; + + return (!mons_avoids_cloud(monster, our_cloud, true)); +} + +// Returns a rough estimate of damage from throwing the wielded weapon. +int mons_thrown_weapon_damage(const item_def *weap) +{ + if (!weap || get_weapon_brand(*weap) != SPWPN_RETURNING) + return (0); + + return std::max(0, (property(*weap, PWPN_DAMAGE) + weap->plus2 / 2)); +} + +int mons_weapon_damage_rating(const item_def &launcher) +{ + return (property(launcher, PWPN_DAMAGE) + launcher.plus2); +} + +// Returns a rough estimate of damage from firing/throwing missile. +int mons_missile_damage(monsters *mons, const item_def *launch, + const item_def *missile) +{ + if (!missile || (!launch && !is_throwable(mons, *missile))) + return (0); + + const int missile_damage = property(*missile, PWPN_DAMAGE) / 2 + 1; + const int launch_damage = launch? property(*launch, PWPN_DAMAGE) : 0; + return std::max(0, launch_damage + missile_damage); +} + +// Given the monster's current weapon and alt weapon (either or both of +// which may be NULL), works out whether using missiles or throwing the +// main weapon (with returning brand) is better. If using missiles that +// need a launcher, sets *launcher to the launcher. +// +// If the monster has no ranged weapon attack, returns NON_ITEM. +// +int mons_pick_best_missile(monsters *mons, item_def **launcher, + bool ignore_melee) +{ + *launcher = NULL; + item_def *melee = NULL, *launch = NULL; + for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) + { + if (item_def *item = mons->mslot_item(static_cast(i))) + { + if (is_range_weapon(*item)) + launch = item; + else if (!ignore_melee) + melee = item; + } + } + + const item_def *missiles = mons->missiles(); + if (launch && missiles && !missiles->launched_by(*launch)) + launch = NULL; + + const int tdam = mons_thrown_weapon_damage(melee); + const int fdam = mons_missile_damage(mons, launch, missiles); + + if (!tdam && !fdam) + return (NON_ITEM); + else if (tdam >= fdam) + return (melee->index()); + else + { + *launcher = launch; + return (missiles->index()); + } +} + +int mons_natural_regen_rate(monsters *monster) +{ + // A HD divider ranging from 3 (at 1 HD) to 1 (at 8 HD). + int divider = + std::max(div_rand_round(15 - monster->hit_dice, 4), 1); + + // The undead have a harder time regenerating. Golems have it worse. + switch (monster->holiness()) + { + case MH_UNDEAD: + divider *= (mons_enslaved_soul(monster)) ? 2 : 4; + break; + + // And golems have it worse. + case MH_NONLIVING: + divider *= 5; + break; + + default: + break; + } + + return (std::max(div_rand_round(monster->hit_dice, divider), 1)); +} + +void mons_check_pool(monsters *monster, const coord_def &oldpos, + killer_type killer, int killnum) +{ + // Levitating/flying monsters don't make contact with the terrain. + if (monster->airborne()) + return; + + dungeon_feature_type grid = grd(monster->pos()); + if ((grid == DNGN_LAVA || grid == DNGN_DEEP_WATER) + && !monster_habitable_grid(monster, grid)) + { + const bool message = mons_near(monster); + + // Don't worry about invisibility. You should be able to see if + // something has fallen into the lava. + if (message && (oldpos == monster->pos() || grd(oldpos) != grid)) + { + mprf("%s falls into the %s!", + monster->name(DESC_CAP_THE).c_str(), + grid == DNGN_LAVA ? "lava" : "water"); + } + + if (grid == DNGN_LAVA && monster->res_fire() >= 2) + grid = DNGN_DEEP_WATER; + + // Even fire resistant monsters perish in lava, but inanimate + // monsters can survive deep water. + if (grid == DNGN_LAVA || monster->can_drown()) + { + if (message) + { + if (grid == DNGN_LAVA) + { + simple_monster_message(monster, " is incinerated.", + MSGCH_MONSTER_DAMAGE, MDAM_DEAD); + } + else if (mons_genus(monster->type) == MONS_MUMMY) + { + simple_monster_message(monster, " falls apart.", + MSGCH_MONSTER_DAMAGE, MDAM_DEAD); + } + else + { + simple_monster_message(monster, " drowns.", + MSGCH_MONSTER_DAMAGE, MDAM_DEAD); + } + } + + if (killer == KILL_NONE) + { + // Self-kill. + killer = KILL_MON; + killnum = monster_index(monster); + } + + // Yredelemnul special, redux: It's the only one that can + // work on drowned monsters. + if (!_yred_enslave_soul(monster, killer)) + monster_die(monster, killer, killnum, true); + } + } +} + +bool monster_descriptor(int which_class, mon_desc_type 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: + case MONS_YAK: + case MONS_SHEEP: + 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_PURGY: + case MONS_TROLL: + case MONS_HYDRA: + case MONS_KILLER_KLOWN: + case MONS_LERNAEAN_HYDRA: + case MONS_DISSOLUTION: + return (true); + default: + return (false); + } + } + + if (which_descriptor == MDSC_NOMSG_WOUNDS) + { + // Zombified monsters other than spectral things don't show + // wounds. + if (mons_class_is_zombified(which_class) + && which_class != MONS_SPECTRAL_THING) + { + return (true); + } + + switch (which_class) + { + case MONS_RAKSHASA: + case MONS_RAKSHASA_FAKE: + return (true); + default: + return (false); + } + } + + return (false); +} + +bool message_current_target() +{ + if (crawl_state.is_replaying_keys()) + { + if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU) + return (false); + + return (you.can_see(&menv[you.prev_targ])); + } + + if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU) + { + const monsters *montarget = &menv[you.prev_targ]; + + if (you.can_see(montarget)) + { + mprf(MSGCH_PROMPT, "Current target: %s " + "(use p or f to fire at it again.)", + montarget->name(DESC_PLAIN).c_str()); + return (true); + } + + mpr("You have no current target."); + } + + return (false); +} + +// aaah, the simple joys of pointer arithmetic! {dlb}: +unsigned int monster_index(const monsters *monster) +{ + return (monster - menv.buffer()); +} + +void seen_monster(monsters *monster) +{ + // If the monster is in the auto_exclude list, automatically + // set an exclusion. + set_auto_exclude(monster); + + // Monster was viewed this turn + monster->flags |= MF_WAS_IN_VIEW; + + if (monster->flags & MF_SEEN) + return; + + // First time we've seen this particular monster. + monster->flags |= MF_SEEN; + + if (!mons_is_mimic(monster->type)) + { + if (Options.tutorial_left) + tutorial_first_monster(*monster); + + if (MONST_INTERESTING(monster)) + { + take_note( + Note(NOTE_SEEN_MONSTER, monster->type, 0, + monster->name(DESC_NOCAP_A, true).c_str())); + } + } +} + +//--------------------------------------------------------------- +// +// shift_monster +// +// Moves a monster to approximately p and returns true if +// the monster was moved. +// +//--------------------------------------------------------------- +bool shift_monster(monsters *mon, coord_def p) +{ + coord_def result; + + int count = 0; + + if (p.origin()) + p = mon->pos(); + + for (adjacent_iterator ai(p); ai; ++ai) + { + // Don't drop on anything but vanilla floor right now. + if (grd(*ai) != DNGN_FLOOR) + continue; + + if (actor_at(*ai)) + continue; + + if (one_chance_in(++count)) + result = *ai; + } + + if (count > 0) + { + mgrd(mon->pos()) = NON_MONSTER; + mon->moveto(result); + mgrd(result) = mon->mindex(); + } + + return (count > 0); +} + +// Make all of the monster's original equipment disappear, unless it's a fixed +// artefact or unrand artefact. +static void _vanish_orig_eq(monsters* mons) +{ + for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) + { + if (mons->inv[i] == NON_ITEM) + continue; + + item_def &item(mitm[mons->inv[i]]); + + if (!item.is_valid()) + continue; + + if (item.orig_place != 0 || item.orig_monnum != 0 + || !item.inscription.empty() + || is_unrandom_artefact(item) + || (item.flags & (ISFLAG_DROPPED | ISFLAG_THROWN | ISFLAG_NOTED_GET + | ISFLAG_BEEN_IN_INV) ) ) + { + continue; + } + item.flags |= ISFLAG_SUMMONED; + } +} + +int dismiss_monsters(std::string pattern) { + // Make all of the monsters' original equipment disappear unless "keepitem" + // is found in the regex (except for fixed arts and unrand arts). + const bool keep_item = strip_tag(pattern, "keepitem"); + + // Dismiss by regex + text_pattern tpat(pattern); + int ndismissed = 0; + for (monster_iterator mi; mi; ++mi) + { + if (mi->alive() && + (tpat.empty() || tpat.matches(mi->name(DESC_PLAIN, true)))) + { + if (!keep_item) + _vanish_orig_eq(*mi); + monster_die(*mi, KILL_DISMISSED, NON_MONSTER, false, true); + ++ndismissed; + } + } + return (ndismissed); +} + +bool is_item_jelly_edible(const item_def &item) +{ + // Don't eat artefacts. + if (is_artefact(item)) + return (false); + + // Shouldn't eat stone things + // - but what about wands and rings? + if (item.base_type == OBJ_MISSILES + && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK)) + { + return (false); + } + + // Don't eat special game items. + if (item.base_type == OBJ_ORBS + || (item.base_type == OBJ_MISCELLANY + && (item.sub_type == MISC_RUNE_OF_ZOT + || item.sub_type == MISC_HORN_OF_GERYON))) + { + return (false); + } + + return (true); +} + +bool monster_random_space(const monsters *monster, coord_def& target, + bool forbid_sanctuary) +{ + int tries = 0; + while (tries++ < 1000) + { + target = random_in_bounds(); + + // Don't land on top of another monster. + if (actor_at(target)) + continue; + + if (is_sanctuary(target) && forbid_sanctuary) + continue; + + if (monster_habitable_grid(monster, grd(target))) + return (true); + } + + return (false); +} + +bool monster_random_space(monster_type mon, coord_def& target, + bool forbid_sanctuary) +{ + monsters dummy; + dummy.type = mon; + + return monster_random_space(&dummy, target, forbid_sanctuary); +} + +void monster_teleport(monsters *monster, bool instan, bool silent) +{ + if (!instan) + { + if (monster->del_ench(ENCH_TP)) + { + if (!silent) + simple_monster_message(monster, " seems more stable."); + } + else + { + if (!silent) + simple_monster_message(monster, " looks slightly unstable."); + + monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER, + random_range(20, 30)) ); + } + + return; + } + + bool was_seen = you.can_see(monster) && !mons_is_lurking(monster); + + if (!silent) + simple_monster_message(monster, " disappears!"); + + const coord_def oldplace = monster->pos(); + + // Pick the monster up. + mgrd(oldplace) = NON_MONSTER; + + coord_def newpos; + if (monster_random_space(monster, newpos, !monster->wont_attack())) + monster->moveto(newpos); + + mgrd(monster->pos()) = monster_index(monster); + + // Mimics change form/colour when teleported. + if (mons_is_mimic(monster->type)) + { + monster_type old_type = monster->type; + monster->type = static_cast( + MONS_GOLD_MIMIC + random2(5)); + monster->colour = get_mimic_colour(monster); + + // If it's changed form, you won't recognise it. + // This assumes that a non-gold mimic turning into another item of + // the same description is really, really unlikely. + if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC) + was_seen = false; + } + + const bool now_visible = mons_near(monster); + if (!silent && now_visible) + { + if (was_seen) + simple_monster_message(monster, " reappears nearby!"); + else + { + // Even if it doesn't interrupt an activity (the player isn't + // delayed, the monster isn't hostile) we still want to give + // a message. + activity_interrupt_data ai(monster, "thin air"); + if (!interrupt_activity(AI_SEE_MONSTER, ai)) + simple_monster_message(monster, " appears out of thin air!"); + } + } + + if (monster->visible_to(&you) && now_visible) + handle_seen_interrupt(monster); + + // Leave a purple cloud. + place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3), + monster->kill_alignment()); + + monster->check_redraw(oldplace); + monster->apply_location_effects(oldplace); + + mons_relocated(monster); + + // Teleporting mimics change form - if they reappear out of LOS, they are + // no longer known. + if (mons_is_mimic(monster->type)) + { + if (now_visible) + monster->flags |= MF_KNOWN_MIMIC; + else + monster->flags &= ~MF_KNOWN_MIMIC; + } +} + +void mons_clear_trapping_net(monsters *mon) +{ + if (!mon->caught()) + return; + + const int net = get_trapping_net(mon->pos()); + if (net != NON_ITEM) + remove_item_stationary(mitm[net]); + + mon->del_ench(ENCH_HELD, true); +} + +bool mons_clonable(const monsters* mon, bool needs_adjacent) +{ + // No uniques or ghost demon monsters. Also, figuring out the name + // for the clone of a named monster isn't worth it. + if (mons_is_unique(mon->type) + || mons_is_ghost_demon(mon->type) + || mon->is_named()) + { + return (false); + } + + if (needs_adjacent) + { + // Is there space for the clone? + bool square_found = false; + for (int i = 0; i < 8; i++) + { + const coord_def p = mon->pos() + Compass[i]; + + if (in_bounds(p) + && !actor_at(p) + && monster_habitable_grid(mon, grd(p))) + { + square_found = true; + break; + } + } + if (!square_found) + return (false); + } + + // Is the monster carrying an artefact? + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) + { + const int index = mon->inv[i]; + + if (index == NON_ITEM) + continue; + + if (is_artefact(mitm[index])) + return (false); + } + + return (true); +} + +int clone_mons(const monsters* orig, bool quiet, bool* obvious, + coord_def pos) +{ + // Is there an open slot in menv? + int midx = NON_MONSTER; + for (int i = 0; i < MAX_MONSTERS; i++) + if (menv[i].type == MONS_NO_MONSTER) + { + midx = i; + break; + } + + if (midx == NON_MONSTER) + return (NON_MONSTER); + + if (!in_bounds(pos)) + { + // Find an adjacent square. + int squares = 0; + for (int i = 0; i < 8; i++) + { + const coord_def p = orig->pos() + Compass[i]; + + if (in_bounds(p) + && !actor_at(p) + && monster_habitable_grid(orig, grd(p))) + { + if (one_chance_in(++squares)) + pos = p; + } + } + + if (squares == 0) + return (NON_MONSTER); + } + + ASSERT( !actor_at(pos) ); + + monsters &mon(menv[midx]); + + mon = *orig; + mon.set_position(pos); + mgrd(pos) = midx; + + // Duplicate objects, or unequip them if they can't be duplicated. + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) + { + const int old_index = orig->inv[i]; + + if (old_index == NON_ITEM) + continue; + + const int new_index = get_item_slot(0); + if (new_index == NON_ITEM) + { + mon.unequip(mitm[old_index], i, 0, true); + mon.inv[i] = NON_ITEM; + continue; + } + + mon.inv[i] = new_index; + mitm[new_index] = mitm[old_index]; + mitm[new_index].set_holding_monster(midx); + } + + bool _obvious; + if (obvious == NULL) + obvious = &_obvious; + *obvious = false; + + if (you.can_see(orig) && you.can_see(&mon)) + { + if (!quiet) + simple_monster_message(orig, " is duplicated!"); + *obvious = true; + } + + mark_interesting_monst(&mon, mon.behaviour); + if (you.can_see(&mon)) + { + handle_seen_interrupt(&mon); + viewwindow(false); + } + + if (crawl_state.arena) + arena_placed_monster(&mon); + + return (midx); +} + +std::string summoned_poof_msg(const monsters* monster, bool plural) +{ + int summon_type = 0; + bool valid_mon = false; + if (monster != NULL && !invalid_monster(monster)) + { + (void) monster->is_summoned(NULL, &summon_type); + valid_mon = true; + } + + std::string msg = "disappear%s in a puff of smoke"; + bool no_chaos = false; + + switch (summon_type) + { + case SPELL_SHADOW_CREATURES: + msg = "dissolve%s into shadows"; + no_chaos = true; + break; + + case MON_SUMM_CHAOS: + msg = "degenerate%s into a cloud of primal chaos"; + break; + + case MON_SUMM_WRATH: + case MON_SUMM_AID: + if (valid_mon && is_good_god(monster->god)) + { + msg = "dissolve%s into sparkling lights"; + no_chaos = true; + } + break; + } + + if (valid_mon) + { + if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10) + || monster->type == MONS_CHAOS_SPAWN) + { + msg = "degenerate%s into a cloud of primal chaos"; + } + + if (monster->is_holy() + && summon_type != SPELL_SHADOW_CREATURES + && summon_type != MON_SUMM_CHAOS) + { + msg = "dissolve%s into sparkling lights"; + } + } + + // Conjugate. + msg = make_stringf(msg.c_str(), plural ? "" : "s"); + + return (msg); +} + +std::string summoned_poof_msg(const int midx, const item_def &item) +{ + if (midx == NON_MONSTER) + return summoned_poof_msg(static_cast(NULL), item); + else + return summoned_poof_msg(&menv[midx], item); +} + +std::string summoned_poof_msg(const monsters* monster, const item_def &item) +{ + ASSERT(item.flags & ISFLAG_SUMMONED); + + return summoned_poof_msg(monster, item.quantity > 1); +} diff --git a/crawl-ref/source/mon-stuff.h b/crawl-ref/source/mon-stuff.h new file mode 100644 index 0000000000..fe82988618 --- /dev/null +++ b/crawl-ref/source/mon-stuff.h @@ -0,0 +1,179 @@ +/* + * File: mon-stuff.h + * Summary: Misc monster related functions. + * Written by: Linley Henzell + */ + + +#ifndef MONSTUFF_H +#define MONSTUFF_H + +#include "mon-util.h" + +enum mon_dam_level_type +{ + MDAM_OKAY, + MDAM_LIGHTLY_DAMAGED, + MDAM_MODERATELY_DAMAGED, + MDAM_HEAVILY_DAMAGED, + MDAM_SEVERELY_DAMAGED, + MDAM_ALMOST_DEAD, + MDAM_DEAD +}; + +enum mon_desc_type // things that cross categorical lines {dlb} +{ + MDSC_LEAVES_HIDE, // 0 + MDSC_REGENERATES, + MDSC_NOMSG_WOUNDS +}; + +struct level_exit +{ + coord_def target; + bool unreachable; + +public: + level_exit(coord_def t = coord_def(-1, -1), + bool u = true) + + : target(t), unreachable(u) + { + } +}; + +#define FRESHEST_CORPSE 210 + +#define YOU_KILL(x) ((x) == KILL_YOU || (x) == KILL_YOU_MISSILE \ + || (x) == KILL_YOU_CONF) +#define MON_KILL(x) ((x) == KILL_MON || (x) == KILL_MON_MISSILE) + +#define SAME_ATTITUDE(x) (x->friendly() ? BEH_FRIENDLY : \ + x->good_neutral() ? BEH_GOOD_NEUTRAL : \ + x->strict_neutral() ? BEH_STRICT_NEUTRAL : \ + x->neutral() ? BEH_NEUTRAL \ + : BEH_HOSTILE) + +#define MONST_INTERESTING(x) (x->flags & MF_INTERESTING) + +// for definition of type monsters {dlb} +#include "externs.h" + +void get_mimic_item( const monsters *mimic, item_def & item ); +int get_mimic_colour( const monsters *mimic ); + +void alert_nearby_monsters(void); + +enum poly_power_type { + PPT_LESS, + PPT_MORE, + PPT_SAME +}; + +bool monster_polymorph(monsters *monster, monster_type targetc, + poly_power_type power = PPT_SAME, + bool force_beh = false); + +int monster_die(monsters *monster, killer_type killer, + int killer_index, bool silent = false, bool wizard = false); + +monster_type fill_out_corpse(const monsters* monster, item_def& corpse, + bool allow_weightless = false); + +bool explode_corpse(item_def& corpse, const coord_def& where); + +int place_monster_corpse(const monsters *monster, bool silent, + bool force = false); + +void slime_vault_change(bool glass); + +void slimify_monster(monsters *monster, bool hostile = false); + +bool mon_can_be_slimified(monsters *monster); + +void mons_check_pool(monsters *monster, const coord_def &oldpos, + killer_type killer = KILL_NONE, int killnum = -1); + +void monster_cleanup(monsters *monster); + +int dismiss_monsters(std::string pattern); + +bool curse_an_item(bool decay_potions, bool quiet = false); + + +void monster_drop_ething(monsters *monster, bool mark_item_origins = false, + int owner_id = NON_ITEM); + +bool monster_blink(monsters *monster, bool quiet = false); + +bool simple_monster_message(const monsters *monster, const char *event, + msg_channel_type channel = MSGCH_PLAIN, + int param = 0, + description_level_type descrip = DESC_CAP_THE); + +bool choose_any_monster(const monsters* mon); +monsters *choose_random_nearby_monster( + int weight, + bool (*suitable)(const monsters* mon) = + choose_any_monster, + bool in_sight = true, + bool prefer_named = false, bool prefer_priest = false); + +monsters *choose_random_monster_on_level( + int weight, + bool (*suitable)(const monsters* mon) = + choose_any_monster, + bool in_sight = true, bool near_by = false, + bool prefer_named = false, bool prefer_priest = false); + +bool swap_places(monsters *monster); +bool swap_places(monsters *monster, const coord_def &loc); +bool swap_check(monsters *monster, coord_def &loc, bool quiet = false); + + +std::string get_wounds_description(const monsters *monster); +void print_wounds(const monsters *monster); +bool monster_descriptor(int which_class, mon_desc_type which_descriptor); + +unsigned int monster_index(const monsters *monster); + +void mons_get_damage_level(const monsters*, std::string& desc, + mon_dam_level_type&); + +void seen_monster(monsters *monster); + +bool shift_monster(monsters *mon, coord_def p = coord_def(0, 0)); + +int mons_weapon_damage_rating(const item_def &launcher); +int mons_missile_damage(monsters *mons, const item_def *launch, + const item_def *missile); +int mons_pick_best_missile(monsters *mons, item_def **launcher, + bool ignore_melee = false); +int mons_thrown_weapon_damage(const item_def *weap); + +int mons_natural_regen_rate(monsters *monster); + +void mons_relocated(monsters *mons); + +bool can_go_straight(const coord_def& p1, const coord_def& p2, + dungeon_feature_type allowed); + +bool is_item_jelly_edible(const item_def &item); + +bool monster_random_space(const monsters *monster, coord_def& target, + bool forbid_sanctuary = false); +bool monster_random_space(monster_type mon, coord_def& target, + bool forbid_sanctuary = false); +void monster_teleport(monsters *monster, bool instan, bool silent = false); +void mons_clear_trapping_net(monsters *mon); + +bool mons_clonable(const monsters* orig, bool needs_adjacent = true); +int clone_mons(const monsters* orig, bool quiet = false, + bool* obvious = NULL, coord_def pos = coord_def(0, 0) ); + +std::string summoned_poof_msg(const monsters* monster, bool plural = false); +std::string summoned_poof_msg(const int midx, const item_def &item); +std::string summoned_poof_msg(const monsters* monster, const item_def &item); + +void pikel_band_neutralise(); +#endif diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index af66751b1b..c6b3a51d2a 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -23,8 +23,8 @@ #include "kills.h" #include "los.h" #include "mon-behv.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "options.h" #include "random.h" #include "religion.h" diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc deleted file mode 100644 index 8d1edb789e..0000000000 --- a/crawl-ref/source/monplace.cc +++ /dev/null @@ -1,3525 +0,0 @@ -/* - * File: monplace.cc - * Summary: Functions used when placing monsters in the dungeon. - * Written by: Linley Henzell - */ - -#include "AppHdr.h" - -#include - -#include "monplace.h" - -#include "arena.h" -#include "branch.h" -#include "directn.h" // for the Compass -#include "fprop.h" -#include "externs.h" -#include "options.h" -#include "ghost.h" -#include "lev-pand.h" -#include "los.h" -#include "makeitem.h" -#include "message.h" -#include "mon-behv.h" -#include "mon-gear.h" -#include "mon-pick.h" -#include "mon-util.h" -#include "monstuff.h" -#include "player.h" -#include "random.h" -#include "religion.h" -#include "state.h" -#include "stuff.h" -#include "terrain.h" -#include "traps.h" -#include "view.h" - -static std::vector vault_mon_types; -static std::vector vault_mon_bases; -static std::vector vault_mon_weights; - -#define VAULT_MON_TYPES_KEY "vault_mon_types" -#define VAULT_MON_BASES_KEY "vault_mon_bases" -#define VAULT_MON_WEIGHTS_KEY "vault_mon_weights" - -// NEW place_monster -- note that power should be set to: -// 51 for abyss -// 52 for pandemonium -// x otherwise - -// proximity is the same as for mons_place: -// 0 is no restrictions -// 1 attempts to place near player -// 2 attempts to avoid player LOS - -#define BIG_BAND 20 - -static monster_type _resolve_monster_type(monster_type mon_type, - proximity_type proximity, - monster_type &base_type, - coord_def &pos, - unsigned mmask, - dungeon_char_type *stair_type, - int *lev_mons); - -static void _define_zombie(int mid, monster_type ztype, monster_type cs, - int power, coord_def pos); -static monster_type _band_member(band_type band, int power); -static band_type _choose_band(int mon_type, int power, int &band_size); -// static int _place_monster_aux(int mon_type, beh_type behaviour, int target, -// int px, int py, int power, int extra, -// bool first_band_member, int dur = 0); - -static int _place_monster_aux(const mgen_data &mg, bool first_band_member, - bool force_pos = false); - -// Returns whether actual_feat is compatible with feat_wanted for monster -// movement and generation. -bool feat_compatible(dungeon_feature_type feat_wanted, - dungeon_feature_type actual_feat) -{ - if (feat_wanted == DNGN_FLOOR) - { - return (actual_feat >= DNGN_FLOOR - && actual_feat != DNGN_BUILDER_SPECIAL_WALL - || actual_feat == DNGN_SHALLOW_WATER); - } - - if (feat_wanted >= DNGN_ROCK_WALL - && feat_wanted <= DNGN_CLEAR_PERMAROCK_WALL) - { - // A monster can only move through or inhabit permanent rock if that's - // exactly what it's asking for. - if (actual_feat == DNGN_PERMAROCK_WALL - || actual_feat == DNGN_CLEAR_PERMAROCK_WALL) - { - return (feat_wanted == DNGN_PERMAROCK_WALL - || feat_wanted == DNGN_CLEAR_PERMAROCK_WALL); - } - - return (actual_feat >= DNGN_ROCK_WALL - && actual_feat <= DNGN_CLEAR_PERMAROCK_WALL); - } - - return (feat_wanted == actual_feat - || (feat_wanted == DNGN_DEEP_WATER - && (actual_feat == DNGN_SHALLOW_WATER - || actual_feat == DNGN_FOUNTAIN_BLUE))); -} - -// Can this monster survive on actual_grid? -// -// If you have an actual monster, use this instead of the overloaded function -// that uses only the monster class to make decisions. -bool monster_habitable_grid(const monsters *m, - dungeon_feature_type actual_grid) -{ - // Zombified monsters enjoy the same habitat as their original. - const monster_type montype = mons_is_zombified(m) ? mons_zombie_base(m) - : m->type; - - return (monster_habitable_grid(montype, actual_grid, mons_flies(m), - m->cannot_move())); -} - -inline static bool _mons_airborne(int mcls, int flies, bool paralysed) -{ - if (flies == -1) - flies = mons_class_flies(mcls); - - return (paralysed ? flies == FL_LEVITATE : flies != FL_NONE); -} - -// Can monsters of class monster_class live happily on actual_grid? -// Use flies == true to pretend the monster can fly. -// -// [dshaligram] We're trying to harmonise the checks from various places into -// one check, so we no longer care if a water elemental springs into existence -// on dry land, because they're supposed to be able to move onto dry land -// anyway. -bool monster_habitable_grid(monster_type montype, - dungeon_feature_type actual_grid, - int flies, bool paralysed) -{ - // No monster may be placed on open sea. - if (actual_grid == DNGN_OPEN_SEA) - return (false); - - const dungeon_feature_type feat_preferred = - habitat2grid(mons_class_primary_habitat(montype)); - const dungeon_feature_type feat_nonpreferred = - habitat2grid(mons_class_secondary_habitat(montype)); - - // Special check for fire elementals since their habitat is floor which - // is generally considered compatible with shallow water. - if (montype == MONS_FIRE_ELEMENTAL && feat_is_watery(actual_grid)) - return (false); - - // Krakens are too large for shallow water. - if (montype == MONS_KRAKEN && actual_grid == DNGN_SHALLOW_WATER) - return (false); - - if (feat_compatible(feat_preferred, actual_grid) - || (feat_nonpreferred != feat_preferred - && feat_compatible(feat_nonpreferred, actual_grid))) - { - return (true); - } - - // [dshaligram] Flying creatures are all DNGN_FLOOR, so we - // only have to check for the additional valid grids of deep - // water and lava. - if (_mons_airborne(montype, flies, paralysed) - && (actual_grid == DNGN_LAVA || actual_grid == DNGN_DEEP_WATER)) - { - return (true); - } - - return (false); -} - -// Returns true if the monster can submerge in the given grid. -bool monster_can_submerge(const monsters *mons, dungeon_feature_type grid) -{ - if (mons->type == MONS_TRAPDOOR_SPIDER && grid == DNGN_FLOOR) - return (!find_trap(mons->pos())); - - switch (mons_primary_habitat(mons)) - { - case HT_WATER: - // Monsters can submerge in shallow water - this is intentional. - return (feat_is_watery(grid)); - - case HT_LAVA: - return (grid == DNGN_LAVA); - - default: - return (false); - } -} - -static bool _need_moderate_ood(int lev_mons) -{ - return (env.turns_on_level > 700 - lev_mons * 117); -} - -static bool _need_super_ood(int lev_mons) -{ - return (env.turns_on_level > 1400 - lev_mons * 117 - && one_chance_in(5000)); -} - -static int _fuzz_mons_level(int level) -{ - if (one_chance_in(7)) - { - const int fuzz = random2avg(9, 2); - return (fuzz > 4? level + fuzz - 4 : level); - } - return (level); -} - -static void _hell_spawn_random_monsters() -{ - // Monster generation in the Vestibule drops off quickly. - const int taper_off_turn = 500; - int genodds = 240; - // genodds increases once you've spent more than 500 turns in Hell. - if (env.turns_on_level > taper_off_turn) - { - genodds += (env.turns_on_level - taper_off_turn); - genodds = (genodds < 0 ? 20000 : std::min(genodds, 20000)); - } - - if (x_chance_in_y(5, genodds)) - { - mgen_data mg(WANDERING_MONSTER); - mg.proximity = (one_chance_in(10) ? PROX_NEAR_STAIRS - : PROX_AWAY_FROM_PLAYER); - mons_place(mg); - viewwindow(false); - } -} - -//#define DEBUG_MON_CREATION - -// This function is now only called about once every 5 turns. (Used to be -// every turn independent of how much time an action took, which was not ideal.) -// To arrive at spawning rates close to how they used to be, replace the -// one_chance_in(value) checks with the new x_chance_in_y(5, value). (jpeg) -void spawn_random_monsters() -{ - if (crawl_state.arena) - return; - -#ifdef DEBUG_MON_CREATION - mpr("in spawn_random_monsters()", MSGCH_DIAGNOSTICS); -#endif - if (player_in_branch(BRANCH_VESTIBULE_OF_HELL)) - { - _hell_spawn_random_monsters(); - return; - } - - if (env.spawn_random_rate == 0) - { -#ifdef DEBUG_MON_CREATION - mpr("random monster gen turned off", MSGCH_DIAGNOSTICS); -#endif - return; - } - - const int rate = (you.char_direction == GDT_DESCENDING) ? - env.spawn_random_rate : 8; - - // Place normal dungeon monsters, but not in player LOS. - if (you.level_type == LEVEL_DUNGEON && x_chance_in_y(5, rate)) - { -#ifdef DEBUG_MON_CREATION - mpr("Create wandering monster...", MSGCH_DIAGNOSTICS); -#endif - proximity_type prox = (one_chance_in(10) ? PROX_NEAR_STAIRS - : PROX_AWAY_FROM_PLAYER); - - // The rules change once the player has picked up the Orb... - if (you.char_direction == GDT_ASCENDING) - prox = (one_chance_in(6) ? PROX_CLOSE_TO_PLAYER : PROX_ANYWHERE); - - mgen_data mg(WANDERING_MONSTER); - mg.proximity = prox; - mons_place(mg); - viewwindow(false); - return; - } - - // Place Abyss monsters. (Now happens regularly every 5 turns which might - // look a bit strange for a place as chaotic as the Abyss. Then again, - // the player is unlikely to meet all of them and notice this.) - if (you.level_type == LEVEL_ABYSS - && (you.char_direction != GDT_GAME_START - || x_chance_in_y(5, rate)) - && (you.religion != GOD_CHEIBRIADOS || coinflip())) - { - mons_place(mgen_data(WANDERING_MONSTER)); - viewwindow(false); - return; - } - - // Place Pandemonium monsters. - if (you.level_type == LEVEL_PANDEMONIUM && x_chance_in_y(5, rate)) - { - pandemonium_mons(); - viewwindow(false); - return; - } - - // A portal vault *might* decide to turn on random monster spawning, - // but it's off by default. - if (you.level_type == LEVEL_PORTAL_VAULT && x_chance_in_y(5, rate)) - { - mons_place(mgen_data(WANDERING_MONSTER)); - viewwindow(false); - } - - // No random monsters in the Labyrinth. -} - -monster_type pick_random_monster(const level_id &place) -{ - int level; - if (place.level_type == LEVEL_PORTAL_VAULT) - level = you.your_level; - else - level = place.absdepth(); - return pick_random_monster(place, level, level); -} - -monster_type pick_random_monster(const level_id &place, int power, - int &lev_mons) -{ - if (crawl_state.arena) - { - monster_type type = arena_pick_random_monster(place, power, lev_mons); - if (type != RANDOM_MONSTER) - return (type); - } - - if (place.level_type == LEVEL_LABYRINTH) - return (MONS_PROGRAM_BUG); - - if (place == BRANCH_ECUMENICAL_TEMPLE) - return (MONS_PROGRAM_BUG); - - if (place.level_type == LEVEL_PORTAL_VAULT) - { - monster_type base_type = (monster_type) 0; - coord_def dummy1; - dungeon_char_type dummy2; - monster_type type = - _resolve_monster_type(RANDOM_MONSTER, PROX_ANYWHERE, base_type, - dummy1, 0, &dummy2, &lev_mons); - -#if DEBUG || DEBUG_DIAGNOSTICS - if (base_type != 0 && base_type != MONS_PROGRAM_BUG) - mpr("Random portal vault mon discarding base type.", - MSGCH_ERROR); -#endif - return (type); - } - - monster_type mon_type = MONS_PROGRAM_BUG; - - lev_mons = power; - - if (place == BRANCH_MAIN_DUNGEON - && lev_mons != 51 && one_chance_in(4)) - { - lev_mons = random2(power); - } - - if (place == BRANCH_MAIN_DUNGEON - && lev_mons <= 27) - { - // If on D:1, allow moderately out-of-depth monsters only after - // a certain elapsed turn count on the level (currently 700 turns). - if (lev_mons || _need_moderate_ood(lev_mons)) - lev_mons = _fuzz_mons_level(lev_mons); - - // Potentially nasty surprise, but very rare. - if (_need_super_ood(lev_mons)) - lev_mons += random2(12); - - // Slightly out of depth monsters are more common: - // [ds] Replaced with a fuzz above for a more varied mix. - //if (need_moderate_ood(lev_mons)) - // lev_mons += random2(5); - - lev_mons = std::min(27, lev_mons); - } - - // Abyss or Pandemonium. Almost never called from Pan; probably only - // if a random demon gets summon anything spell. - if (lev_mons == 51 - || place.level_type == LEVEL_PANDEMONIUM - || place.level_type == LEVEL_ABYSS) - { - do - { - int count = 0; - - do - { - // was: random2(400) {dlb} - mon_type = static_cast( random2(NUM_MONSTERS) ); - count++; - } - while (mons_abyss(mon_type) == 0 && count < 2000); - - if (count == 2000) - return (MONS_PROGRAM_BUG); - if (crawl_state.arena && arena_veto_random_monster(mon_type)) - continue; - } - while (random2avg(100, 2) > mons_rare_abyss(mon_type) - && !one_chance_in(100)); - } - else - { - int level, diff, chance; - - lev_mons = std::min(30, lev_mons); - - int i; - for (i = 0; i < 10000; ++i) - { - int count = 0; - - do - { - mon_type = static_cast(random2(NUM_MONSTERS)); - count++; - } - while (mons_rarity(mon_type, place) == 0 && count < 2000); - - if (count == 2000) - return (MONS_PROGRAM_BUG); - - if (crawl_state.arena && arena_veto_random_monster(mon_type)) - continue; - - level = mons_level(mon_type, place); - diff = level - lev_mons; - chance = mons_rarity(mon_type, place) - (diff * diff); - - if ((lev_mons >= level - 5 && lev_mons <= level + 5) - && random2avg(100, 2) <= chance) - { - break; - } - } - - if (i == 10000) - return (MONS_PROGRAM_BUG); - } - - return (mon_type); -} - -static bool _can_place_on_trap(int mon_type, trap_type trap) -{ - if (trap == TRAP_TELEPORT) - return (false); - - if (trap == TRAP_SHAFT) - { - if (mon_type == RANDOM_MONSTER) - return (false); - - return (mons_class_flies(mon_type) != FL_NONE - || get_monster_data(mon_type)->size == SIZE_TINY); - } - - return (true); -} - -bool drac_colour_incompatible(int drac, int colour) -{ - return (drac == MONS_DRACONIAN_SCORCHER && colour == MONS_WHITE_DRACONIAN); -} - -static monster_type _resolve_monster_type(monster_type mon_type, - proximity_type proximity, - monster_type &base_type, - coord_def &pos, - unsigned mmask, - dungeon_char_type *stair_type, - int *lev_mons) -{ - if (mon_type == RANDOM_DRACONIAN) - { - // Pick any random drac, constrained by colour if requested. - do - { - mon_type = - static_cast( - random_range(MONS_BLACK_DRACONIAN, - MONS_DRACONIAN_SCORCHER)); - } - while (base_type != MONS_PROGRAM_BUG - && mon_type != base_type - && (mons_species(mon_type) == mon_type - || drac_colour_incompatible(mon_type, base_type))); - } - else if (mon_type == RANDOM_BASE_DRACONIAN) - mon_type = random_draconian_monster_species(); - else if (mon_type == RANDOM_NONBASE_DRACONIAN) - { - mon_type = - static_cast( - random_range(MONS_DRACONIAN_CALLER, MONS_DRACONIAN_SCORCHER)); - } - - // (2) Take care of non-draconian random monsters. - else if (mon_type == RANDOM_MONSTER) - { - level_id place = level_id::current(); - - // Respect destination level for staircases. - if (proximity == PROX_NEAR_STAIRS) - { - int tries = 0; - int pval = 0; - while (++tries <= 320) - { - pos = random_in_bounds(); - - if (actor_at(pos)) - continue; - - // Is the grid verboten? - if (!unforbidden( pos, mmask )) - continue; - - // Don't generate monsters on top of teleport traps. - const trap_def* ptrap = find_trap(pos); - if (ptrap && !_can_place_on_trap(mon_type, ptrap->type)) - continue; - - // Check whether there's a stair - // and whether it leads to another branch. - pval = near_stairs(pos, 1, *stair_type, place.branch); - - // No monsters spawned in the Temple. - if (branches[place.branch].id == BRANCH_ECUMENICAL_TEMPLE) - continue; - - // Found a position near the stairs! - if (pval > 0) - break; - } - - if (tries > 320) - { - // Give up and try somewhere else. - proximity = PROX_AWAY_FROM_PLAYER; - } - else - { - if (*stair_type == DCHAR_STAIRS_DOWN) // deeper level - ++*lev_mons; - else if (*stair_type == DCHAR_STAIRS_UP) // higher level - { - // Monsters don't come from outside the dungeon. - if (*lev_mons <= 0) - { - proximity = PROX_AWAY_FROM_PLAYER; - // In that case lev_mons stays as it is. - } - else - --*lev_mons; - } - } - } // end proximity check - - if (place == BRANCH_HALL_OF_BLADES) - mon_type = MONS_DANCING_WEAPON; - else - { - if (you.level_type == LEVEL_PORTAL_VAULT) - { - if (vault_mon_types.size() == 0) - return (MONS_PROGRAM_BUG); - - int i = choose_random_weighted(vault_mon_weights.begin(), - vault_mon_weights.end()); - int type = vault_mon_types[i]; - int base = vault_mon_bases[i]; - - if (type == -1) - { - place = level_id::from_packed_place(base); - // If lev_mons is set to you.your_level, it was probably - // set as a default meaning "the current dungeon depth", - // which for a portal vault using its own definition - // of random monsters means "the depth of whatever place - // we're using for picking the random monster". - if (*lev_mons == you.your_level) - *lev_mons = place.absdepth(); - // pick_random_monster() is called below - } - else - { - base_type = (monster_type) base; - mon_type = (monster_type) type; - if (mon_type == RANDOM_DRACONIAN - || mon_type == RANDOM_BASE_DRACONIAN - || mon_type == RANDOM_NONBASE_DRACONIAN) - { - mon_type = - _resolve_monster_type(mon_type, proximity, - base_type, pos, mmask, - stair_type, lev_mons); - } - return (mon_type); - } - } - - int tries = 0; - while (tries++ < 300) - { - // Now pick a monster of the given branch and level. - mon_type = pick_random_monster(place, *lev_mons, *lev_mons); - - // Don't allow monsters too stupid to use stairs (e.g. - // non-spectral zombified undead) to be placed near - // stairs. - if (proximity != PROX_NEAR_STAIRS - || mons_class_can_use_stairs(mon_type)) - { - break; - } - } - - if (proximity == PROX_NEAR_STAIRS && tries >= 300) - { - proximity = PROX_AWAY_FROM_PLAYER; - - // Reset target level. - if (*stair_type == DCHAR_STAIRS_DOWN) - --*lev_mons; - else if (*stair_type == DCHAR_STAIRS_UP) - ++*lev_mons; - - mon_type = pick_random_monster(place, *lev_mons, *lev_mons); - } - } - } - return (mon_type); -} - -// A short function to check the results of near_stairs(). -// Returns 0 if the point is not near stairs. -// Returns 1 if the point is near unoccupied stairs. -// Returns 2 if the point is near player-occupied stairs. -static int _is_near_stairs(coord_def &p) -{ - int result = 0; - - for (int i = -1; i <= 1; ++i) - for (int j = -1; j <= 1; ++j) - { - if (!in_bounds(p)) - continue; - - const dungeon_feature_type feat = grd(p); - if (feat_is_stair(feat)) - { - // Shouldn't matter for escape hatches. - if (feat_is_escape_hatch(feat)) - continue; - - // Should there be several stairs, don't overwrite the - // player on stairs info. - if (result < 2) - result = (p == you.pos()) ? 2 : 1; - } - } - - return (result); -} - -// Checks if the monster is ok to place at mg_pos. If force_location -// is true, then we'll be less rigorous in our checks, in particular -// allowing land monsters to be placed in shallow water and water -// creatures in fountains. -static bool _valid_monster_generation_location( - const mgen_data &mg, - const coord_def &mg_pos) -{ - if (!in_bounds(mg_pos) || actor_at(mg_pos)) - return (false); - - const monster_type montype = (mons_class_is_zombified(mg.cls) ? mg.base_type - : mg.cls); - if (!monster_habitable_grid(montype, grd(mg_pos), - mons_class_flies(montype), false) - || (mg.behaviour != BEH_FRIENDLY && is_sanctuary(mg_pos))) - { - return (false); - } - - // Don't generate monsters on top of teleport traps. - // (How did they get there?) - const trap_def* ptrap = find_trap(mg_pos); - if (ptrap && !_can_place_on_trap(mg.cls, ptrap->type)) - return (false); - - return (true); -} - -static bool _valid_monster_generation_location(mgen_data &mg) -{ - return _valid_monster_generation_location(mg, mg.pos); -} - -int place_monster(mgen_data mg, bool force_pos) -{ -#ifdef DEBUG_MON_CREATION - mpr("in place_monster()", MSGCH_DIAGNOSTICS); -#endif - - int tries = 0; - dungeon_char_type stair_type = NUM_DCHAR_TYPES; - int id = -1; - - // (1) Early out (summoned to occupied grid). - if (mg.use_position() && monster_at(mg.pos)) - return (-1); - - mg.cls = _resolve_monster_type(mg.cls, mg.proximity, mg.base_type, - mg.pos, mg.map_mask, - &stair_type, &mg.power); - - if (mg.cls == MONS_NO_MONSTER) - return (-1); - - // (3) Decide on banding (good lord!) - int band_size = 1; - monster_type band_monsters[BIG_BAND]; // band monster types - band_monsters[0] = mg.cls; - - // The (very) ugly thing band colour. - static unsigned char ugly_colour = BLACK; - - if (mg.permit_bands()) - { -#ifdef DEBUG_MON_CREATION - mpr("Choose band members...", MSGCH_DIAGNOSTICS); -#endif - const band_type band = _choose_band(mg.cls, mg.power, band_size); - band_size++; - - for (int i = 1; i < band_size; ++i) - { - band_monsters[i] = _band_member(band, mg.power); - - // Get the (very) ugly thing band colour, so that all (very) - // ugly things in a band will start with it. - if ((band_monsters[i] == MONS_UGLY_THING - || band_monsters[i] == MONS_VERY_UGLY_THING) - && ugly_colour == BLACK) - { - ugly_colour = ugly_thing_random_colour(); - } - } - } - - // Set the (very) ugly thing band colour. - if (ugly_colour != BLACK) - mg.colour = ugly_colour; - - // Returns 2 if the monster is placed near player-occupied stairs. - int pval = _is_near_stairs(mg.pos); - if (mg.proximity == PROX_NEAR_STAIRS) - { - // For some cases disallow monsters on stairs. - if (mons_class_is_stationary(mg.cls) - || (pval == 2 // Stairs occupied by player. - && (mons_class_base_speed(mg.cls) == 0 - || grd(mg.pos) == DNGN_LAVA - || grd(mg.pos) == DNGN_DEEP_WATER))) - { - mg.proximity = PROX_AWAY_FROM_PLAYER; - } - } - - // (4) For first monster, choose location. This is pretty intensive. - bool proxOK; - bool close_to_player; - - // Player shoved out of the way? - bool shoved = false; - - if (!mg.use_position()) - { - tries = 0; - - // Try to pick a position that is - // a) not occupied - // b) compatible - // c) in the 'correct' proximity to the player - - while (true) - { - // Dropped number of tries from 60. - if (tries++ >= 45) - return (-1); - - // Placement already decided for PROX_NEAR_STAIRS. - // Else choose a random point on the map. - if (mg.proximity != PROX_NEAR_STAIRS) - mg.pos = random_in_bounds(); - - if (!_valid_monster_generation_location(mg)) - continue; - - // Is the grid verboten? - if (!unforbidden(mg.pos, mg.map_mask)) - continue; - - // Let's recheck these even for PROX_NEAR_STAIRS, just in case. - // Check proximity to player. - proxOK = true; - - switch (mg.proximity) - { - case PROX_ANYWHERE: - if (grid_distance( you.pos(), mg.pos ) < 2 + random2(3)) - proxOK = false; - break; - - case PROX_CLOSE_TO_PLAYER: - case PROX_AWAY_FROM_PLAYER: - // If this is supposed to measure los vs not los, - // then see_cell(mg.pos) should be used instead. (jpeg) - close_to_player = (distance(you.pos(), mg.pos) < 64); - - if (mg.proximity == PROX_CLOSE_TO_PLAYER && !close_to_player - || mg.proximity == PROX_AWAY_FROM_PLAYER && close_to_player) - { - proxOK = false; - } - break; - - case PROX_NEAR_STAIRS: - if (pval == 2) // player on stairs - { - if (mons_class_base_speed(mg.cls) == 0) - { - proxOK = false; - break; - } - // Swap the monster and the player spots, unless the - // monster was generated in lava or deep water. - if (grd(mg.pos) == DNGN_LAVA - || grd(mg.pos) == DNGN_DEEP_WATER) - { - proxOK = false; - break; - } - - // You can't be shoved if you're caught in a net. - if (you.caught()) - { - proxOK = false; - break; - } - - shoved = true; - coord_def mpos = mg.pos; - mg.pos = you.pos(); - you.moveto(mpos); - } - proxOK = (pval > 0); - break; - } - - if (!proxOK) - continue; - - // Cool.. passes all tests. - break; - } // end while... place first monster - } - else if (!_valid_monster_generation_location(mg)) - { - // Sanity check that the specified position is valid. - return (-1); - } - - id = _place_monster_aux(mg, true, force_pos); - - // Reset the (very) ugly thing band colour. - if (ugly_colour != BLACK) - ugly_colour = BLACK; - - // Bail out now if we failed. - if (id == -1) - return (-1); - - monsters *mon = &menv[id]; - if (mg.needs_patrol_point()) - { - mon->patrol_point = mon->pos(); -#ifdef DEBUG_PATHFIND - mprf("Monster %s is patrolling around (%d, %d).", - mon->name(DESC_PLAIN).c_str(), mon->pos().x, mon->pos().y); -#endif - } - - // Message to player from stairwell/gate appearance. - if (observe_cell(mg.pos) && mg.proximity == PROX_NEAR_STAIRS) - { - std::string msg; - - if (menv[id].visible_to(&you)) - msg = menv[id].name(DESC_CAP_A); - else if (shoved) - msg = "Something"; - - if (shoved) - { - msg += " shoves you out of the "; - if (stair_type == DCHAR_ARCH) - msg += "gateway!"; - else - msg += "stairwell!"; - mpr(msg.c_str()); - } - else if (!msg.empty()) - { - if (stair_type == DCHAR_STAIRS_DOWN) - msg += " comes up the stairs."; - else if (stair_type == DCHAR_STAIRS_UP) - msg += " comes down the stairs."; - else if (stair_type == DCHAR_ARCH) - msg += " comes through the gate."; - else - msg = ""; - - if (!msg.empty()) - mpr(msg.c_str()); - } - - // Special case: must update the view for monsters created - // in player LOS. - viewwindow(false); - } - - // Now, forget about banding if the first placement failed, or there are - // too many monsters already, or we successfully placed by stairs. - if (id >= MAX_MONSTERS - 30 || mg.proximity == PROX_NEAR_STAIRS) - return (id); - - // Not PROX_NEAR_STAIRS, so it will be part of a band, if there is any. - if (band_size > 1) - menv[id].flags |= MF_BAND_MEMBER; - - const bool priest = mon->is_priest(); - - mgen_data band_template = mg; - // (5) For each band monster, loop call to place_monster_aux(). - for (int i = 1; i < band_size; i++) - { - if (band_monsters[i] == MONS_NO_MONSTER) - break; - - band_template.cls = band_monsters[i]; - - // We don't want to place a unique that has already been - // generated. - if (mons_is_unique(band_template.cls) - && you.unique_creatures[band_template.cls]) - { - continue; - } - - const int band_id = _place_monster_aux(band_template, false); - if (band_id != -1 && band_id != NON_MONSTER) - { - menv[band_id].flags |= MF_BAND_MEMBER; - // Priestly band leaders should have an entourage of the - // same religion. - if (priest) - menv[band_id].god = mon->god; - } - } - - // Placement of first monster, at least, was a success. - return (id); -} - -static int _place_monster_aux(const mgen_data &mg, - bool first_band_member, bool force_pos) -{ - int id = -1; - coord_def fpos; - - // Gotta be able to pick an ID. - for (id = 0; id < MAX_MONSTERS; id++) - if (menv[id].type == MONS_NO_MONSTER) - break; - - // Too many monsters on level? - if (id == MAX_MONSTERS) - return (-1); - - menv[id].reset(); - - const monster_type montype = (mons_class_is_zombified(mg.cls) ? mg.base_type - : mg.cls); - - // Setup habitat and placement. - // If the space is occupied, try some neighbouring square instead. - if (first_band_member && in_bounds(mg.pos) - && (mg.behaviour == BEH_FRIENDLY || !is_sanctuary(mg.pos)) - && actor_at(mg.pos) == NULL - && (force_pos || monster_habitable_grid(montype, grd(mg.pos)))) - { - fpos = mg.pos; - } - else - { - int i; - // We'll try 1000 times for a good spot. - for (i = 0; i < 1000; ++i) - { - fpos = mg.pos + coord_def( random_range(-3, 3), - random_range(-3, 3) ); - - if (_valid_monster_generation_location(mg, fpos)) - break; - } - - // Did we really try 1000 times? - if (i == 1000) - return (-1); - } - - ASSERT(!monster_at(fpos)); - - if (crawl_state.arena - && arena_veto_place_monster(mg, first_band_member, fpos)) - { - return (-1); - } - - monsters &mons(menv[id]); - // Now, actually create the monster. (Wheeee!) - mons.type = mg.cls; - mons.base_monster = mg.base_type; - mons.number = mg.number; - - mons.moveto(fpos); - - // Link monster into monster grid. - mgrd(fpos) = id; - - // Generate a brand shiny new monster, or zombie. - if (mons_class_is_zombified(mg.cls)) - _define_zombie(id, mg.base_type, mg.cls, mg.power, fpos); - else - define_monster(id); - - // Is it a god gift? - if (mg.god != GOD_NO_GOD) - { - mons.god = mg.god; - mons.flags |= MF_GOD_GIFT; - } - // Not a god gift, give priestly monsters a god. - else if (mons_class_flag(mg.cls, M_PRIEST)) - { - switch (mons_genus(mg.cls)) - { - case MONS_ORC: - mons.god = GOD_BEOGH; - break; - case MONS_JELLY: - mons.god = GOD_JIYVA; - break; - case MONS_MUMMY: - case MONS_DRACONIAN: - case MONS_ELF: - // [ds] Vault defs can request priest monsters of unusual types. - default: - mons.god = GOD_NAMELESS; - break; - } - } - // 1 out of 7 non-priestly orcs are unbelievers. - else if (mons_genus(mg.cls) == MONS_ORC) - { - if (!one_chance_in(7)) - mons.god = GOD_BEOGH; - } - // The royal jelly belongs to Jiyva. - else if (mg.cls == MONS_ROYAL_JELLY) - mons.god = GOD_JIYVA; - // Angels and Daevas belong to TSO, but 1 out of 7 in the Abyss are - // adopted by Xom. - else if (mons_class_holiness(mg.cls) == MH_HOLY) - { - if (mg.level_type != LEVEL_ABYSS || !one_chance_in(7)) - mons.god = GOD_SHINING_ONE; - else - mons.god = GOD_XOM; - } - // 6 out of 7 demons in the Abyss belong to Lugonu. - else if (mons_class_holiness(mg.cls) == MH_DEMONIC) - { - if (mg.level_type == LEVEL_ABYSS && !one_chance_in(7)) - mons.god = GOD_LUGONU; - } - - // If the caller requested a specific colour for this monster, apply - // it now. - if (mg.colour != BLACK) - mons.colour = mg.colour; - - if (mg.mname != "") - mons.mname = mg.mname; - - // The return of Boris is now handled in monster_die(). Not setting - // this for Boris here allows for multiple Borises in the dungeon at - // the same time. - bwr - if (mons_is_unique(mg.cls)) - you.unique_creatures[mg.cls] = true; - - if (mons_class_flag(mg.cls, M_INVIS)) - mons.add_ench(ENCH_INVIS); - - if (mons_class_flag(mg.cls, M_CONFUSED)) - mons.add_ench(ENCH_CONFUSION); - - if (mg.cls == MONS_SHAPESHIFTER) - mons.add_ench(ENCH_SHAPESHIFTER); - - if (mg.cls == MONS_GLOWING_SHAPESHIFTER) - mons.add_ench(ENCH_GLOWING_SHAPESHIFTER); - - if (mg.cls == MONS_TOADSTOOL) - { - // This enchantment is a timer that counts down until death. - // It should last longer than the lifespan of a corpse, to avoid - // spawning mushrooms in the same place over and over. Aside - // from that, the value is slightly randomised to avoid - // simultaneous die-offs of mushroom rings. - mons.add_ench(ENCH_SLOWLY_DYING); - } - - if (mg.cls == MONS_BALLISTOMYCETE) - { - // This enchantment causes giant spore production. - mons.add_ench(ENCH_SPORE_PRODUCTION); - } - - if (monster_can_submerge(&mons, grd(fpos)) && !one_chance_in(5)) - mons.add_ench(ENCH_SUBMERGED); - - mons.flags |= MF_JUST_SUMMONED; - - // Don't leave shifters in their starting shape. - if (mg.cls == MONS_SHAPESHIFTER || mg.cls == MONS_GLOWING_SHAPESHIFTER) - { - no_messages nm; - monster_polymorph(&mons, RANDOM_MONSTER); - - // It's not actually a known shapeshifter if it happened to be - // placed in LOS of the player. - mons.flags &= ~MF_KNOWN_MIMIC; - } - - // dur should always be 1-6 for monsters that can be abjured. - const bool summoned = mg.abjuration_duration >= 1 - && mg.abjuration_duration <= 6; - - if (mg.cls == MONS_DANCING_WEAPON) - { - give_item(id, mg.power, summoned); - - // Dancing weapons *always* have a weapon. Fail to create them - // otherwise. - const item_def* wpn = mons.mslot_item(MSLOT_WEAPON); - if (!wpn) - { - mons.destroy_inventory(); - mons.reset(); - mgrd(fpos) = NON_MONSTER; - return (-1); - } - else - mons.colour = wpn->colour; - } - else if (mons_class_itemuse(mg.cls) >= MONUSE_STARTING_EQUIPMENT) - { - give_item(id, mg.power, summoned); - // Give these monsters a second weapon -- bwr - if (mons_class_wields_two_weapons(mg.cls)) - give_item(id, mg.power, summoned); - - unwind_var save_speedinc(mons.speed_increment); - mons.wield_melee_weapon(false); - } - - // Give manticores 8 to 16 spike volleys. - if (mg.cls == MONS_MANTICORE) - mons.number = 8 + random2(9); - - if (mg.cls == MONS_SLIME_CREATURE) - { - if (mg.number > 1) - { - // Boost HP to what it would have been if it had grown this - // big by merging. - mons.hit_points *= mg.number; - mons.max_hit_points *= mg.number; - } - } - - // Set attitude, behaviour and target. - mons.attitude = ATT_HOSTILE; - mons.behaviour = mg.behaviour; - - // Statues cannot sleep (nor wander but it means they are a bit - // more aware of the player than they'd be otherwise). - if (mons_is_statue(mg.cls)) - mons.behaviour = BEH_WANDER; - - mons.foe_memory = 0; - - // Setting attitude will always make the monster wander... - // If you want sleeping hostiles, use BEH_SLEEP since the default - // attitude is hostile. - if (mg.behaviour > NUM_BEHAVIOURS) - { - if (mg.behaviour == BEH_FRIENDLY) - mons.attitude = ATT_FRIENDLY; - - if (mg.behaviour == BEH_GOOD_NEUTRAL) - mons.attitude = ATT_GOOD_NEUTRAL; - - if (mg.behaviour == BEH_NEUTRAL) - mons.attitude = ATT_NEUTRAL; - - if (mg.behaviour == BEH_STRICT_NEUTRAL) - mons.attitude = ATT_STRICT_NEUTRAL; - - mons.behaviour = BEH_WANDER; - } - - if (summoned) - { - mons.mark_summoned(mg.abjuration_duration, true, - mg.summon_type); - } - mons.foe = mg.foe; - - // Initialise (very) ugly things and pandemonium demons. - if (mons.type == MONS_UGLY_THING - || mons.type == MONS_VERY_UGLY_THING) - { - ghost_demon ghost; - ghost.init_ugly_thing(mons.type == MONS_VERY_UGLY_THING, false, - mg.colour); - mons.set_ghost(ghost, false); - mons.uglything_init(); - } - else if (mons.type == MONS_PANDEMONIUM_DEMON) - { - ghost_demon ghost; - ghost.init_random_demon(); - mons.set_ghost(ghost); - mons.pandemon_init(); - } - else if (mons.type == MONS_DANCING_WEAPON) - { - ghost_demon ghost; - // We can't use monsters::weapon here because it wants to look - // at attack types, which are in the ghost structure we're - // building. - ASSERT( mons.mslot_item(MSLOT_WEAPON) ); - // Dancing weapons are placed at pretty high power. Remember, the - // player is fighting them one-on-one, while he will often summon - // several. - ghost.init_dancing_weapon(*(mons.mslot_item(MSLOT_WEAPON)), 180); - mons.set_ghost(ghost); - mons.dancing_weapon_init(); - } - - mark_interesting_monst(&mons, mg.behaviour); - - if (you.can_see(&mons)) - handle_seen_interrupt(&mons); - - if (crawl_state.arena) - arena_placed_monster(&mons); - - return (id); -} - -monster_type pick_random_zombie() -{ - static std::vector zombifiable; - if (zombifiable.empty()) - { - for (int i = 0; i < NUM_MONSTERS; ++i) - { - if (mons_species(i) != i || i == MONS_PROGRAM_BUG) - continue; - - const monster_type mcls = static_cast(i); - if (!mons_zombie_size(mcls)) - continue; - - zombifiable.push_back(mcls); - } - } - return (zombifiable[random2(zombifiable.size())]); -} - -monster_type pick_local_zombifiable_monster_type(int power) -{ - // Generates a dummy zombie likely to be found. - // Ripped wholly from _define_zombie(). - - power = std::min(27, power); - // How OOD this zombie can be. - int relax = 5; - - // Pick an appropriate creature to make a zombie out of, levelwise. - // The old code was generating absolutely incredible OOD zombies. - int cls; - while (true) - { - cls = pick_random_zombie(); - - bool ignore_rarity = false; - // On certain branches, zombie creation will fail if we use the - // mons_rarity() functions, because (for example) there are NO - // zombifiable "native" abyss creatures. Other branches where - // this is a problem are Hell levels and the Crypt. We have to - // watch for summoned zombies on other levels, too, such as the - // Temple, the Hall of Blades and the Slime Pits. - if (you.level_type != LEVEL_DUNGEON - || player_in_hell() - || player_in_branch(BRANCH_HALL_OF_ZOT) - || player_in_branch(BRANCH_VESTIBULE_OF_HELL) - || player_in_branch(BRANCH_ECUMENICAL_TEMPLE) - || player_in_branch(BRANCH_CRYPT) - || player_in_branch(BRANCH_TOMB) - || player_in_branch(BRANCH_HALL_OF_BLADES) - || player_in_branch(BRANCH_SNAKE_PIT) - || player_in_branch(BRANCH_SLIME_PITS) - || one_chance_in(1000)) - { - ignore_rarity = true; - } - - // Don't make out-of-rarity zombies when we don't have to. - if (!ignore_rarity && mons_rarity(cls) == 0) - continue; - - // Check for rarity.. and OOD - identical to mons_place(). - int level, diff, chance; - - level = mons_level(cls) - 4; - diff = level - power; - - chance = (ignore_rarity) ? 100 - : mons_rarity(cls) - (diff * diff) / 2; - - if (power > level - relax && power < level + relax - && random2avg(100, 2) <= chance) - { - break; - } - - // Every so often, we'll relax the OOD restrictions. This - // avoids infinite loops. If we don't do this, things like - // creating a large skeleton on level 1 may hang the game! - if (one_chance_in(5)) - relax++; - } - - return (monster_type)cls; -} - -static void _define_zombie(int mid, monster_type ztype, monster_type cs, - int power, coord_def pos) -{ - ASSERT(mons_class_is_zombified(cs)); - - monster_type cls = MONS_PROGRAM_BUG; - monster_type mons_sec2 = MONS_PROGRAM_BUG; - zombie_size_type zombie_size = Z_NOZOMBIE; - bool ignore_rarity = false; - - power = std::min(27, power); - - // Set size based on zombie class (cs). - switch (cs) - { - case MONS_ZOMBIE_SMALL: - case MONS_SIMULACRUM_SMALL: - case MONS_SKELETON_SMALL: - zombie_size = Z_SMALL; - break; - - case MONS_ZOMBIE_LARGE: - case MONS_SIMULACRUM_LARGE: - case MONS_SKELETON_LARGE: - zombie_size = Z_BIG; - break; - - case MONS_SPECTRAL_THING: - break; - - default: - break; - } - - // That is, random creature from which to fashion undead. - if (ztype == MONS_NO_MONSTER) - { - // How OOD this zombie can be. - int relax = 5; - - // Pick an appropriate creature to make a zombie out of, - // levelwise. The old code was generating absolutely - // incredible OOD zombies. - while (true) - { - cls = pick_random_zombie(); - - // Actually pick a monster that is happy where we want to put it. - // Fish zombies on land are helpless and uncool. - if (!monster_habitable_grid(cls, grd(pos))) - continue; - - // On certain branches, zombie creation will fail if we use - // the mons_rarity() functions, because (for example) there - // are NO zombifiable "native" abyss creatures. Other branches - // where this is a problem are hell levels and the crypt. - // we have to watch for summoned zombies on other levels, too, - // such as the Temple, HoB, and Slime Pits. - if (you.level_type != LEVEL_DUNGEON - || player_in_hell() - || player_in_branch(BRANCH_HALL_OF_ZOT) - || player_in_branch(BRANCH_VESTIBULE_OF_HELL) - || player_in_branch(BRANCH_ECUMENICAL_TEMPLE) - || player_in_branch(BRANCH_CRYPT) - || player_in_branch(BRANCH_TOMB) - || player_in_branch(BRANCH_HALL_OF_BLADES) - || player_in_branch(BRANCH_SNAKE_PIT) - || player_in_branch(BRANCH_SLIME_PITS) - || one_chance_in(1000)) - { - ignore_rarity = true; - } - - // Don't make out-of-rarity zombies when we don't have to. - if (!ignore_rarity && mons_rarity(cls) == 0) - continue; - - // If skeleton, monster must have a skeleton. - if ((cs == MONS_SKELETON_SMALL || cs == MONS_SKELETON_LARGE) - && !mons_skeleton(cls)) - { - continue; - } - - // Size must match, but you can make a spectral thing out - // of anything. - if (cs != MONS_SPECTRAL_THING - && mons_zombie_size(cls) != zombie_size) - { - continue; - } - - if (cs == MONS_SKELETON_SMALL || cs == MONS_SIMULACRUM_SMALL) - { - // Skeletal or icy draconians shouldn't be coloured. - // How could you tell? - if (mons_genus(cls) == MONS_DRACONIAN - && cls != MONS_DRACONIAN) - { - cls = MONS_DRACONIAN; - } - // The same goes for rats. - else if (mons_genus(cls) == MONS_RAT - && cls != MONS_RAT) - { - cls = MONS_RAT; - } - } - - // Hack -- non-dungeon zombies are always made out of nastier - // monsters. - if (you.level_type != LEVEL_DUNGEON && mons_power(cls) > 8) - break; - - // Check for rarity.. and OOD - identical to mons_place() - int level, diff, chance; - - level = mons_level(cls) - 4; - diff = level - power; - - chance = (ignore_rarity) ? 100 - : mons_rarity(cls) - (diff * diff) / 2; - - if (power > level - relax && power < level + relax - && random2avg(100, 2) <= chance) - { - break; - } - - // Every so often, we'll relax the OOD restrictions. Avoids - // infinite loops (if we don't do this, things like creating - // a large skeleton on level 1 may hang the game!). - if (one_chance_in(5)) - relax++; - } - - // Set type and secondary appropriately. - menv[mid].base_monster = cls; - mons_sec2 = cls; - } - else - { - menv[mid].base_monster = mons_species(ztype); - mons_sec2 = menv[mid].base_monster; - } - - // Set type to the base type to calculate appropriate stats. - menv[mid].type = menv[mid].base_monster; - - define_monster(mid); - - menv[mid].hit_points = hit_points(menv[mid].hit_dice, 6, 5); - menv[mid].max_hit_points = menv[mid].hit_points; - - menv[mid].ac -= 2; - menv[mid].ac = std::max(0, menv[mid].ac); - - menv[mid].ev -= 5; - menv[mid].ev = std::max(0, menv[mid].ev); - - menv[mid].speed = mons_class_zombie_base_speed(menv[mid].base_monster); - - // Now override type with the required type. - if (cs == MONS_ZOMBIE_SMALL || cs == MONS_ZOMBIE_LARGE) - { - menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? - MONS_ZOMBIE_LARGE : MONS_ZOMBIE_SMALL); - } - else if (cs == MONS_SKELETON_SMALL || cs == MONS_SKELETON_LARGE) - { - menv[mid].hit_points = hit_points(menv[mid].hit_dice, 5, 4); - menv[mid].max_hit_points = menv[mid].hit_points; - - menv[mid].ac -= 4; - menv[mid].ac = std::max(0, menv[mid].ac); - - menv[mid].ev -= 2; - menv[mid].ev = std::max(0, menv[mid].ev); - - menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? - MONS_SKELETON_LARGE : MONS_SKELETON_SMALL); - } - else if (cs == MONS_SIMULACRUM_SMALL || cs == MONS_SIMULACRUM_LARGE) - { - // Simulacra aren't tough, but you can create piles of them. - bwr - menv[mid].hit_points = hit_points(menv[mid].hit_dice, 1, 4); - menv[mid].max_hit_points = menv[mid].hit_points; - - menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? - MONS_SIMULACRUM_LARGE : MONS_SIMULACRUM_SMALL); - } - else if (cs == MONS_SPECTRAL_THING) - { - menv[mid].hit_points = hit_points(menv[mid].hit_dice, 4, 4); - menv[mid].max_hit_points = menv[mid].hit_points; - - menv[mid].ac += 4; - - menv[mid].type = MONS_SPECTRAL_THING; - } - - menv[mid].base_monster = mons_sec2; - menv[mid].colour = mons_class_colour(cs); -} - -static band_type _choose_band(int mon_type, int power, int &band_size) -{ -#ifdef DEBUG_MON_CREATION - mpr("in _choose_band()", MSGCH_DIAGNOSTICS); -#endif - // Band size describes the number of monsters in addition to - // the band leader. - band_size = 0; // Single monster, no band. - band_type band = BAND_NO_BAND; - - switch (mon_type) - { - case MONS_ORC: - if (coinflip()) - break; - // intentional fall-through {dlb} - case MONS_ORC_WIZARD: - band = BAND_ORCS; - band_size = 2 + random2(3); - break; - - case MONS_ORC_PRIEST: - case MONS_ORC_WARRIOR: - band = BAND_ORC_WARRIOR; - band_size = 2 + random2(3); - break; - - case MONS_ORC_WARLORD: - case MONS_SAINT_ROKA: - band_size = 5 + random2(5); // warlords have large bands - // intentional fall through - case MONS_ORC_KNIGHT: - band = BAND_ORC_KNIGHT; // orcs + knight - band_size += 3 + random2(4); - break; - - case MONS_ORC_HIGH_PRIEST: - band = BAND_ORC_HIGH_PRIEST; - band_size = 4 + random2(4); - break; - - case MONS_BIG_KOBOLD: - if (power > 3) - { - band = BAND_KOBOLDS; - band_size = 2 + random2(6); - } - break; - - case MONS_KILLER_BEE: - band = BAND_KILLER_BEES; - band_size = 2 + random2(4); - break; - - case MONS_FLYING_SKULL: - band = BAND_FLYING_SKULLS; - band_size = 2 + random2(4); - break; - case MONS_SLIME_CREATURE: - band = BAND_SLIME_CREATURES; - band_size = 2 + random2(4); - break; - case MONS_YAK: - band = BAND_YAKS; - band_size = 2 + random2(4); - break; - case MONS_UGLY_THING: - case MONS_VERY_UGLY_THING: - band = BAND_UGLY_THINGS; - band_size = 2 + random2(4); - break; - case MONS_HELL_HOUND: - band = BAND_HELL_HOUNDS; - band_size = 2 + random2(3); - break; - case MONS_JACKAL: - band = BAND_JACKALS; - band_size = 1 + random2(3); - break; - case MONS_HELL_KNIGHT: - case MONS_MARGERY: - band = BAND_HELL_KNIGHTS; - band_size = 4 + random2(4); - break; - case MONS_JOSEPHINE: - case MONS_NECROMANCER: - case MONS_VAMPIRE_MAGE: - band = BAND_NECROMANCER; - band_size = 4 + random2(4); - break; - case MONS_GNOLL: - band = BAND_GNOLLS; - band_size = (coinflip() ? 3 : 2); - break; - case MONS_GRUM: - band = BAND_WAR_DOGS; - band_size = 2 + random2(3); - break; - case MONS_BUMBLEBEE: - band = BAND_BUMBLEBEES; - band_size = 2 + random2(4); - break; - case MONS_CENTAUR: - case MONS_CENTAUR_WARRIOR: - if (power > 9 && one_chance_in(3)) - { - band = BAND_CENTAURS; - band_size = 2 + random2(4); - } - break; - - case MONS_YAKTAUR: - case MONS_YAKTAUR_CAPTAIN: - if (coinflip()) - { - band = BAND_YAKTAURS; - band_size = 2 + random2(3); - } - break; - - case MONS_DEATH_YAK: - band = BAND_DEATH_YAKS; - band_size = 2 + random2(4); - break; - case MONS_INSUBSTANTIAL_WISP: - band = BAND_INSUBSTANTIAL_WISPS; - band_size = 4 + random2(5); - break; - case MONS_OGRE_MAGE: - band = BAND_OGRE_MAGE; - band_size = 4 + random2(4); - break; - case MONS_BALRUG: - band = BAND_BALRUG; // RED gr demon - band_size = 2 + random2(3); - break; - case MONS_CACODEMON: - band = BAND_CACODEMON; // BROWN gr demon - band_size = 1 + random2(3); - break; - - case MONS_EXECUTIONER: - if (coinflip()) - { - band = BAND_EXECUTIONER; // DARKGREY gr demon - band_size = 1 + random2(3); - } - break; - - case MONS_PANDEMONIUM_DEMON: - band = BAND_PANDEMONIUM_DEMON; - band_size = random_range(1, 3); - break; - - case MONS_HELLWING: - if (coinflip()) - { - band = BAND_HELLWING; // LIGHTGREY gr demon - band_size = 1 + random2(4); - } - break; - - case MONS_DEEP_ELF_FIGHTER: - if (coinflip()) - { - band = BAND_DEEP_ELF_FIGHTER; - band_size = 3 + random2(4); - } - break; - - case MONS_DEEP_ELF_KNIGHT: - if (coinflip()) - { - band = BAND_DEEP_ELF_KNIGHT; - band_size = 3 + random2(4); - } - break; - - case MONS_DEEP_ELF_HIGH_PRIEST: - if (coinflip()) - { - band = BAND_DEEP_ELF_HIGH_PRIEST; - band_size = 3 + random2(4); - } - break; - - case MONS_KOBOLD_DEMONOLOGIST: - if (coinflip()) - { - band = BAND_KOBOLD_DEMONOLOGIST; - band_size = 3 + random2(6); - } - break; - - case MONS_NAGA_MAGE: - case MONS_NAGA_WARRIOR: - band = BAND_NAGAS; - band_size = 3 + random2(4); - break; - - case MONS_WAR_DOG: - band = BAND_WAR_DOGS; - band_size = 2 + random2(4); - break; - - case MONS_GREY_RAT: - band = BAND_GREY_RATS; - band_size = 4 + random2(6); - break; - - case MONS_GREEN_RAT: - band = BAND_GREEN_RATS; - band_size = 4 + random2(6); - break; - - case MONS_ORANGE_RAT: - band = BAND_ORANGE_RATS; - band_size = 3 + random2(4); - break; - - case MONS_SHEEP: - band = BAND_SHEEP; - band_size = 3 + random2(5); - break; - - case MONS_GHOUL: - band = BAND_GHOULS; - band_size = 2 + random2(3); - break; - - case MONS_KIRKE: - band_size = 2 + random2(3); - case MONS_HOG: - band = BAND_HOGS; - band_size += 1 + random2(3); - break; - - case MONS_GIANT_MOSQUITO: - band = BAND_GIANT_MOSQUITOES; - band_size = 1 + random2(3); - break; - - case MONS_DEEP_TROLL: - band = BAND_DEEP_TROLLS; - band_size = 3 + random2(3); - break; - - case MONS_HELL_HOG: - band = BAND_HELL_HOGS; - band_size = 1 + random2(3); - break; - - case MONS_BOGGART: - band = BAND_BOGGARTS; - band_size = 2 + random2(3); - break; - - case MONS_BLINK_FROG: - band = BAND_BLINK_FROGS; - band_size = 2 + random2(3); - break; - - case MONS_SKELETAL_WARRIOR: - band = BAND_SKELETAL_WARRIORS; - band_size = 2 + random2(3); - break; - - case MONS_CYCLOPS: - if (one_chance_in(5) || player_in_branch(BRANCH_SHOALS)) - { - band = BAND_SHEEP; // Odyssey reference - band_size = 2 + random2(3); - } - break; - - case MONS_POLYPHEMUS: - band = BAND_DEATH_YAKS; - band_size = 3 + random2(3); - break; - - case MONS_HARPY: - band = BAND_HARPIES; - band_size = 2 + random2(3); - break; - - // Journey -- Added Draconian Packs - case MONS_WHITE_DRACONIAN: - case MONS_RED_DRACONIAN: - case MONS_PURPLE_DRACONIAN: - case MONS_MOTTLED_DRACONIAN: - case MONS_YELLOW_DRACONIAN: - case MONS_BLACK_DRACONIAN: - case MONS_GREEN_DRACONIAN: - case MONS_PALE_DRACONIAN: - if (power > 18 && one_chance_in(3) && you.level_type == LEVEL_DUNGEON) - { - band = BAND_DRACONIAN; - band_size = random_range(2, 4); - } - break; - - case MONS_DRACONIAN_CALLER: - case MONS_DRACONIAN_MONK: - case MONS_DRACONIAN_SCORCHER: - case MONS_DRACONIAN_KNIGHT: - case MONS_DRACONIAN_ANNIHILATOR: - case MONS_DRACONIAN_ZEALOT: - case MONS_DRACONIAN_SHIFTER: - if (power > 20 && you.level_type == LEVEL_DUNGEON) - { - band = BAND_DRACONIAN; - band_size = random_range(3, 6); - } - break; - - case MONS_TIAMAT: - band = BAND_DRACONIAN; - // yup, scary - band_size = random_range(3,6) + random_range(3,6) + 2; - break; - - case MONS_ILSUIW: - band = BAND_ILSUIW; - band_size = 3 + random2(3); - break; - - case MONS_AZRAEL: - band = BAND_AZRAEL; - band_size = 4 + random2(5); - break; - - case MONS_DUVESSA: - band = BAND_DUVESSA; - band_size = 1; - break; - - case MONS_KHUFU: - band = BAND_KHUFU; - band_size = 3; - break; - - case MONS_GOLDEN_EYE: - band = BAND_GOLDEN_EYE; - band_size = 1 + random2(5); - break; - - case MONS_PIKEL: - band = BAND_PIKEL; - band_size = 1 + random2(3); - break; - - } // end switch - - if (band != BAND_NO_BAND && band_size == 0) - band = BAND_NO_BAND; - - if (band_size >= BIG_BAND) - band_size = BIG_BAND - 1; - - return (band); -} - -static monster_type _band_member(band_type band, int power) -{ - monster_type mon_type = MONS_PROGRAM_BUG; - int temp_rand; - - if (band == BAND_NO_BAND) - return (MONS_PROGRAM_BUG); - - switch (band) - { - case BAND_KOBOLDS: - mon_type = MONS_KOBOLD; - break; - - case BAND_ORCS: - mon_type = MONS_ORC; - if (one_chance_in(6)) - mon_type = MONS_ORC_WIZARD; - if (one_chance_in(8)) - mon_type = MONS_ORC_PRIEST; - break; - - case BAND_ORC_WARRIOR: - mon_type = MONS_ORC; - if (one_chance_in(5)) - mon_type = MONS_ORC_WIZARD; - if (one_chance_in(7)) - mon_type = MONS_ORC_PRIEST; - break; - - case BAND_ORC_KNIGHT: - case BAND_ORC_HIGH_PRIEST: - // XXX: For Beogh punishment, ogres and trolls look out of place... - // (For normal generation, they're okay, of course.) - temp_rand = random2(30); - mon_type = ((temp_rand > 17) ? MONS_ORC : // 12 in 30 - (temp_rand > 8) ? MONS_ORC_WARRIOR : // 9 in 30 - (temp_rand > 6) ? MONS_WARG : // 2 in 30 - (temp_rand > 4) ? MONS_ORC_WIZARD : // 2 in 30 - (temp_rand > 2) ? MONS_ORC_PRIEST : // 2 in 30 - (temp_rand > 1) ? MONS_OGRE : // 1 in 30 - (temp_rand > 0) ? MONS_TROLL // 1 in 30 - : MONS_ORC_SORCERER); // 1 in 30 - break; - - case BAND_KILLER_BEES: - mon_type = MONS_KILLER_BEE; - break; - - case BAND_FLYING_SKULLS: - mon_type = MONS_FLYING_SKULL; - break; - - case BAND_SLIME_CREATURES: - mon_type = MONS_SLIME_CREATURE; - break; - - case BAND_YAKS: - mon_type = MONS_YAK; - break; - - case BAND_HARPIES: - mon_type = MONS_HARPY; - break; - - case BAND_UGLY_THINGS: - mon_type = ((power > 21 && one_chance_in(4)) ? - MONS_VERY_UGLY_THING : MONS_UGLY_THING); - break; - - case BAND_HELL_HOUNDS: - mon_type = MONS_HELL_HOUND; - break; - - case BAND_JACKALS: - mon_type = MONS_JACKAL; - break; - - case BAND_GNOLLS: - mon_type = MONS_GNOLL; - break; - - case BAND_BUMBLEBEES: - mon_type = MONS_BUMBLEBEE; - break; - - case BAND_CENTAURS: - mon_type = MONS_CENTAUR; - break; - - case BAND_YAKTAURS: - mon_type = MONS_YAKTAUR; - break; - - case BAND_INSUBSTANTIAL_WISPS: - mon_type = MONS_INSUBSTANTIAL_WISP; - break; - - case BAND_DEATH_YAKS: - mon_type = MONS_DEATH_YAK; - break; - - case BAND_NECROMANCER: // necromancer - temp_rand = random2(13); - mon_type = ((temp_rand > 9) ? MONS_ZOMBIE_SMALL : // 3 in 13 - (temp_rand > 6) ? MONS_ZOMBIE_LARGE : // 3 in 13 - (temp_rand > 3) ? MONS_SKELETON_SMALL : // 3 in 13 - (temp_rand > 0) ? MONS_SKELETON_LARGE // 3 in 13 - : MONS_NECROPHAGE); // 1 in 13 - break; - - case BAND_BALRUG: - mon_type = (coinflip() ? MONS_NEQOXEC : MONS_ORANGE_DEMON); - break; - - case BAND_CACODEMON: - mon_type = MONS_LEMURE; - break; - - case BAND_EXECUTIONER: - mon_type = (coinflip() ? MONS_ABOMINATION_SMALL - : MONS_ABOMINATION_LARGE); - break; - - case BAND_PANDEMONIUM_DEMON: - if (one_chance_in(7)) - { - mon_type = static_cast( - random_choose_weighted(50, MONS_LICH, - 10, MONS_ANCIENT_LICH, - 0)); - } - else if (one_chance_in(6)) - { - mon_type = static_cast( - random_choose_weighted(50, MONS_ABOMINATION_SMALL, - 40, MONS_ABOMINATION_LARGE, - 10, MONS_TENTACLED_MONSTROSITY, - 0)); - } - else - { - mon_type = - summon_any_demon( - static_cast( - random_choose_weighted(50, DEMON_COMMON, - 20, DEMON_GREATER, - 10, DEMON_RANDOM, - 0))); - } - break; - - case BAND_HELLWING: - mon_type = (coinflip() ? MONS_HELLWING : MONS_SMOKE_DEMON); - break; - - case BAND_DEEP_ELF_FIGHTER: // deep elf fighter - temp_rand = random2(11); - mon_type = ((temp_rand > 4) ? MONS_DEEP_ELF_SOLDIER : // 6 in 11 - (temp_rand == 4) ? MONS_DEEP_ELF_FIGHTER : // 1 in 11 - (temp_rand == 3) ? MONS_DEEP_ELF_KNIGHT : // 1 in 11 - (temp_rand == 2) ? MONS_DEEP_ELF_CONJURER :// 1 in 11 - (temp_rand == 1) ? MONS_DEEP_ELF_MAGE // 1 in 11 - : MONS_DEEP_ELF_PRIEST); // 1 in 11 - break; - - case BAND_DEEP_ELF_KNIGHT: // deep elf knight - temp_rand = random2(208); - mon_type = - ((temp_rand > 159) ? MONS_DEEP_ELF_SOLDIER : // 23.08% - (temp_rand > 111) ? MONS_DEEP_ELF_FIGHTER : // 23.08% - (temp_rand > 79) ? MONS_DEEP_ELF_KNIGHT : // 15.38% - (temp_rand > 51) ? MONS_DEEP_ELF_MAGE : // 13.46% - (temp_rand > 35) ? MONS_DEEP_ELF_PRIEST : // 7.69% - (temp_rand > 19) ? MONS_DEEP_ELF_CONJURER : // 7.69% - (temp_rand > 3) ? MONS_DEEP_ELF_SUMMONER : // 7.69% - (temp_rand == 3) ? MONS_DEEP_ELF_DEMONOLOGIST :// 0.48% - (temp_rand == 2) ? MONS_DEEP_ELF_ANNIHILATOR : // 0.48% - (temp_rand == 1) ? MONS_DEEP_ELF_SORCERER // 0.48% - : MONS_DEEP_ELF_DEATH_MAGE); // 0.48% - break; - - case BAND_DEEP_ELF_HIGH_PRIEST: // deep elf high priest - temp_rand = random2(16); - mon_type = - ((temp_rand > 12) ? MONS_DEEP_ELF_SOLDIER : // 3 in 16 - (temp_rand > 9) ? MONS_DEEP_ELF_FIGHTER : // 3 in 16 - (temp_rand > 6) ? MONS_DEEP_ELF_PRIEST : // 3 in 16 - (temp_rand == 6) ? MONS_DEEP_ELF_MAGE : // 1 in 16 - (temp_rand == 5) ? MONS_DEEP_ELF_SUMMONER : // 1 in 16 - (temp_rand == 4) ? MONS_DEEP_ELF_CONJURER : // 1 in 16 - (temp_rand == 3) ? MONS_DEEP_ELF_DEMONOLOGIST :// 1 in 16 - (temp_rand == 2) ? MONS_DEEP_ELF_ANNIHILATOR : // 1 in 16 - (temp_rand == 1) ? MONS_DEEP_ELF_SORCERER // 1 in 16 - : MONS_DEEP_ELF_DEATH_MAGE); // 1 in 16 - break; - - case BAND_HELL_KNIGHTS: - mon_type = MONS_HELL_KNIGHT; - if (one_chance_in(4)) - mon_type = MONS_NECROMANCER; - break; - - case BAND_OGRE_MAGE: - mon_type = MONS_OGRE; - if (one_chance_in(3)) - mon_type = MONS_TWO_HEADED_OGRE; - break; // ogre mage - - case BAND_KOBOLD_DEMONOLOGIST: - temp_rand = random2(13); - mon_type = ((temp_rand > 4) ? MONS_KOBOLD : // 8 in 13 - (temp_rand > 0) ? MONS_BIG_KOBOLD // 4 in 13 - : MONS_KOBOLD_DEMONOLOGIST);// 1 in 13 - break; - - case BAND_NAGAS: - mon_type = MONS_NAGA; - break; - case BAND_WAR_DOGS: - mon_type = MONS_WAR_DOG; - break; - case BAND_GREY_RATS: - mon_type = MONS_GREY_RAT; - break; - case BAND_GREEN_RATS: - mon_type = MONS_GREEN_RAT; - break; - case BAND_ORANGE_RATS: - mon_type = MONS_ORANGE_RAT; - break; - case BAND_SHEEP: - mon_type = MONS_SHEEP; - break; - case BAND_GHOULS: - mon_type = (coinflip() ? MONS_GHOUL : MONS_NECROPHAGE); - break; - case BAND_DEEP_TROLLS: - mon_type = MONS_DEEP_TROLL; - break; - case BAND_HOGS: - mon_type = MONS_HOG; - break; - case BAND_HELL_HOGS: - mon_type = MONS_HELL_HOG; - break; - case BAND_GIANT_MOSQUITOES: - mon_type = MONS_GIANT_MOSQUITO; - break; - case BAND_BOGGARTS: - mon_type = MONS_BOGGART; - break; - case BAND_BLINK_FROGS: - mon_type = MONS_BLINK_FROG; - break; - case BAND_SKELETAL_WARRIORS: - mon_type = MONS_SKELETAL_WARRIOR; - break; - case BAND_DRACONIAN: - { - temp_rand = random2( (power < 24) ? 24 : 37 ); - mon_type = - ((temp_rand > 35) ? MONS_DRACONIAN_CALLER : // 1 in 34 - (temp_rand > 33) ? MONS_DRACONIAN_KNIGHT : // 2 in 34 - (temp_rand > 31) ? MONS_DRACONIAN_MONK : // 2 in 34 - (temp_rand > 29) ? MONS_DRACONIAN_SHIFTER : // 2 in 34 - (temp_rand > 27) ? MONS_DRACONIAN_ANNIHILATOR :// 2 in 34 - (temp_rand > 25) ? MONS_DRACONIAN_SCORCHER : // 2 in 34 - (temp_rand > 23) ? MONS_DRACONIAN_ZEALOT : // 2 in 34 - (temp_rand > 20) ? MONS_YELLOW_DRACONIAN : // 3 in 34 - (temp_rand > 17) ? MONS_GREEN_DRACONIAN : // 3 in 34 - (temp_rand > 14) ? MONS_BLACK_DRACONIAN : // 3 in 34 - (temp_rand > 11) ? MONS_WHITE_DRACONIAN : // 3 in 34 - (temp_rand > 8) ? MONS_PALE_DRACONIAN : // 3 in 34 - (temp_rand > 5) ? MONS_PURPLE_DRACONIAN : // 3 in 34 - (temp_rand > 2) ? MONS_MOTTLED_DRACONIAN : // 3 in 34 - MONS_RED_DRACONIAN ); // 3 in 34 - break; - } - case BAND_ILSUIW: - mon_type = coinflip()? MONS_MERFOLK : MONS_MERMAID; - break; - - case BAND_AZRAEL: - mon_type = coinflip()? MONS_FIRE_ELEMENTAL : MONS_HELL_HOUND; - break; - - case BAND_DUVESSA: - mon_type = MONS_DOWAN; - break; - - case BAND_KHUFU: - mon_type = coinflip()? MONS_GREATER_MUMMY : MONS_MUMMY; - break; - - case BAND_GOLDEN_EYE: - mon_type = MONS_GOLDEN_EYE; - break; - - case BAND_PIKEL: - mon_type = MONS_HUMAN; - break; - - default: - break; - } - - return (mon_type); -} - -static int _ood_limit() -{ - return Options.ood_interesting; -} - -void mark_interesting_monst(struct monsters* monster, beh_type behaviour) -{ - if (crawl_state.arena) - return; - - bool interesting = false; - - // Unique monsters are always intersting - if (mons_is_unique(monster->type)) - interesting = true; - // If it's never going to attack us, then not interesting - else if (behaviour == BEH_FRIENDLY) - interesting = false; - else if (you.where_are_you == BRANCH_MAIN_DUNGEON - && you.level_type == LEVEL_DUNGEON - && mons_level(monster->type) >= you.your_level + _ood_limit() - && mons_level(monster->type) < 99 - && !(monster->type >= MONS_EARTH_ELEMENTAL - && monster->type <= MONS_AIR_ELEMENTAL) - && !mons_class_flag( monster->type, M_NO_EXP_GAIN )) - { - interesting = true; - } - else if ((you.level_type == LEVEL_DUNGEON - || you.level_type == LEVEL_ABYSS) - && mons_rarity(monster->type) <= Options.rare_interesting - && monster->hit_dice > 2 // Don't note the really low-hd monsters. - && mons_rarity(monster->type) > 0) - { - interesting = true; - } - // Don't waste time on moname() if user isn't using this option - else if (Options.note_monsters.size() > 0) - { - const std::string iname = mons_type_name(monster->type, DESC_NOCAP_A); - for (unsigned i = 0; i < Options.note_monsters.size(); ++i) - { - if (Options.note_monsters[i].matches(iname)) - { - interesting = true; - break; - } - } - } - - if (interesting) - monster->flags |= MF_INTERESTING; -} - -// PUBLIC FUNCTION -- mons_place(). - -static monster_type _pick_zot_exit_defender() -{ - if (one_chance_in(11)) - { -#ifdef DEBUG_MON_CREATION - mpr("Create a pandemonium demon!", MSGCH_DIAGNOSTICS); -#endif - return (MONS_PANDEMONIUM_DEMON); - } - - const int temp_rand = random2(276); - const int mon_type = - ((temp_rand > 184) ? MONS_WHITE_IMP + random2(15) : // 33.33% - (temp_rand > 104) ? MONS_HELLION + random2(10) : // 28.99% - (temp_rand > 78) ? MONS_HELL_HOUND : // 9.06% - (temp_rand > 54) ? MONS_ABOMINATION_LARGE : // 8.70% - (temp_rand > 33) ? MONS_ABOMINATION_SMALL : // 7.61% - (temp_rand > 13) ? MONS_RED_DEVIL // 7.25% - : MONS_PIT_FIEND); // 5.07% - - return static_cast(mon_type); -} - -int mons_place(mgen_data mg) -{ -#ifdef DEBUG_MON_CREATION - mpr("in mons_place()", MSGCH_DIAGNOSTICS); -#endif - int mon_count = 0; - for (int il = 0; il < MAX_MONSTERS; il++) - if (menv[il].type != MONS_NO_MONSTER) - mon_count++; - - if (mg.cls == WANDERING_MONSTER) - { - if (mon_count > MAX_MONSTERS - 50) - return (-1); - -#ifdef DEBUG_MON_CREATION - mpr("Set class RANDOM_MONSTER", MSGCH_DIAGNOSTICS); -#endif - mg.cls = RANDOM_MONSTER; - } - - // All monsters have been assigned? {dlb} - if (mon_count >= MAX_MONSTERS - 1) - return (-1); - - // This gives a slight challenge to the player as they ascend the - // dungeon with the Orb. - if (you.char_direction == GDT_ASCENDING && mg.cls == RANDOM_MONSTER - && you.level_type == LEVEL_DUNGEON && !mg.summoned()) - { -#ifdef DEBUG_MON_CREATION - mpr("Call _pick_zot_exit_defender()", MSGCH_DIAGNOSTICS); -#endif - mg.cls = _pick_zot_exit_defender(); - mg.flags |= MG_PERMIT_BANDS; - } - else if (mg.cls == RANDOM_MONSTER || mg.level_type == LEVEL_PANDEMONIUM) - mg.flags |= MG_PERMIT_BANDS; - - // Translate level_type. - switch (mg.level_type) - { - case LEVEL_PANDEMONIUM: - case LEVEL_ABYSS: - mg.power = level_id(mg.level_type).absdepth(); - break; - case LEVEL_DUNGEON: - default: - mg.power = you.your_level; - break; - } - - int mid = place_monster(mg); - if (mid == -1) - return (-1); - - monsters *creation = &menv[mid]; - - // Look at special cases: CHARMED, FRIENDLY, NEUTRAL, GOOD_NEUTRAL, - // HOSTILE. - if (mg.behaviour > NUM_BEHAVIOURS) - { - if (mg.behaviour == BEH_FRIENDLY) - creation->flags |= MF_CREATED_FRIENDLY; - - if (mg.behaviour == BEH_NEUTRAL || mg.behaviour == BEH_GOOD_NEUTRAL - || mg.behaviour == BEH_STRICT_NEUTRAL) - { - creation->flags |= MF_WAS_NEUTRAL; - } - - if (mg.behaviour == BEH_CHARMED) - { - creation->attitude = ATT_HOSTILE; - creation->add_ench(ENCH_CHARM); - } - - if (creation->type == MONS_RAKSHASA_FAKE && !one_chance_in(3)) - creation->add_ench(ENCH_INVIS); - - if (!(mg.flags & MG_FORCE_BEH) && !crawl_state.arena) - player_angers_monster(creation); - - if (crawl_state.arena) - behaviour_event(creation, ME_EVAL); - else - // Make summoned being aware of player's presence. - behaviour_event(creation, ME_ALERT, MHITYOU); - } - - return (mid); -} - -static dungeon_feature_type _monster_primary_habitat_feature(int mc) -{ - if (mc == RANDOM_MONSTER) - return (DNGN_FLOOR); - return (habitat2grid(mons_class_primary_habitat(mc))); -} - -static dungeon_feature_type _monster_secondary_habitat_feature(int mc) -{ - if (mc == RANDOM_MONSTER) - return (DNGN_FLOOR); - return (habitat2grid(mons_class_secondary_habitat(mc))); -} - -class newmons_square_find : public travel_pathfind -{ -private: - dungeon_feature_type feat_wanted; - coord_def start; - int maxdistance; - - int best_distance; - int nfound; -public: - // Terrain that we can't spawn on, but that we can skip through. - std::set passable; -public: - newmons_square_find(dungeon_feature_type grdw, - const coord_def &pos, - int maxdist = 0) - : feat_wanted(grdw), start(pos), maxdistance(maxdist), - best_distance(0), nfound(0) - { - } - - coord_def pathfind() - { - set_floodseed(start); - return travel_pathfind::pathfind(RMODE_EXPLORE); - } - - bool path_flood(const coord_def &c, const coord_def &dc) - { - if (best_distance && traveled_distance > best_distance) - return (true); - - if (!in_bounds(dc) - || (maxdistance > 0 && traveled_distance > maxdistance)) - { - return (false); - } - if (!feat_compatible(feat_wanted, grd(dc))) - { - if (passable.find(grd(dc)) != passable.end()) - good_square(dc); - return (false); - } - if (actor_at(dc) == NULL && one_chance_in(++nfound)) - { - greedy_dist = traveled_distance; - greedy_place = dc; - best_distance = traveled_distance; - } - else - { - good_square(dc); - } - return (false); - } -}; - -// Finds a square for a monster of the given class, pathfinding -// through only contiguous squares of habitable terrain. -coord_def find_newmons_square_contiguous(monster_type mons_class, - const coord_def &start, - int distance) -{ - coord_def p; - - const dungeon_feature_type feat_preferred = - _monster_primary_habitat_feature(mons_class); - const dungeon_feature_type feat_nonpreferred = - _monster_secondary_habitat_feature(mons_class); - - newmons_square_find nmpfind(feat_preferred, start, distance); - const coord_def pp = nmpfind.pathfind(); - p = pp; - - if (feat_nonpreferred != feat_preferred && !in_bounds(pp)) - { - newmons_square_find nmsfind(feat_nonpreferred, start, distance); - const coord_def ps = nmsfind.pathfind(); - p = ps; - } - - return (in_bounds(p) ? p : coord_def(-1, -1)); -} - -coord_def find_newmons_square(int mons_class, const coord_def &p) -{ - coord_def empty; - coord_def pos(-1, -1); - - if (mons_class == WANDERING_MONSTER) - mons_class = RANDOM_MONSTER; - - const dungeon_feature_type feat_preferred = - _monster_primary_habitat_feature(mons_class); - const dungeon_feature_type feat_nonpreferred = - _monster_secondary_habitat_feature(mons_class); - - // Might be better if we chose a space and tried to match the monster - // to it in the case of RANDOM_MONSTER, that way if the target square - // is surrounded by water or lava this function would work. -- bwr - if (empty_surrounds(p, feat_preferred, 2, true, empty)) - pos = empty; - - if (feat_nonpreferred != feat_preferred && !in_bounds(pos) - && empty_surrounds(p, feat_nonpreferred, 2, true, empty)) - { - pos = empty; - } - - return (pos); -} - -bool player_will_anger_monster(monster_type type, bool *holy, - bool *unholy, bool *lawful, - bool *antimagical) -{ - monsters dummy; - dummy.type = type; - - return (player_will_anger_monster(&dummy, holy, unholy, lawful, - antimagical)); -} - -bool player_will_anger_monster(monsters *mon, bool *holy, - bool *unholy, bool *lawful, - bool *antimagical) -{ - const bool isHoly = - (is_good_god(you.religion) && mon->is_evil()); - const bool isUnholy = - (is_evil_god(you.religion) && mon->is_holy()); - const bool isLawful = - (you.religion == GOD_ZIN && mon->is_chaotic()); - const bool isAntimagical = - (you.religion == GOD_TROG && mon->is_actual_spellcaster()); - - if (holy) - *holy = isHoly; - if (unholy) - *unholy = isUnholy; - if (lawful) - *lawful = isLawful; - if (antimagical) - *antimagical = isAntimagical; - - return (isHoly || isUnholy || isLawful || isAntimagical); -} - -bool player_angers_monster(monsters *mon) -{ - bool holy; - bool unholy; - bool lawful; - bool antimagical; - - // Get the drawbacks, not the benefits... (to prevent e.g. demon-scumming). - if (player_will_anger_monster(mon, &holy, &unholy, &lawful, &antimagical) - && mon->wont_attack()) - { - mon->attitude = ATT_HOSTILE; - mon->del_ench(ENCH_CHARM); - behaviour_event(mon, ME_ALERT, MHITYOU); - - if (you.can_see(mon)) - { - std::string aura; - - if (holy) - aura = "holy"; - else if (unholy) - aura = "unholy"; - else if (lawful) - aura = "lawful"; - else if (antimagical) - aura = "anti-magical"; - - mprf("%s is enraged by your %s aura!", - mon->name(DESC_CAP_THE).c_str(), aura.c_str()); - } - - return (true); - } - - return (false); -} - -int create_monster(mgen_data mg, bool fail_msg) -{ - const int montype = (mons_class_is_zombified(mg.cls) ? mg.base_type - : mg.cls); - - int summd = -1; - - if (!mg.force_place() - || !in_bounds(mg.pos) - || actor_at(mg.pos) - || !mons_class_can_pass(montype, grd(mg.pos))) - { - mg.pos = find_newmons_square(montype, mg.pos); - - // Gods other than Xom will try to avoid placing their monsters - // directly in harm's way. - if (mg.god != GOD_NO_GOD && mg.god != GOD_XOM) - { - monsters dummy; - // If the type isn't known yet assume no resists or anything. - dummy.type = (mg.cls == RANDOM_MONSTER) ? MONS_HUMAN - : mg.cls; - dummy.base_monster = mg.base_type; - dummy.god = mg.god; - - int tries = 0; - while (tries++ < 50 - && (!in_bounds(mg.pos) - || mons_avoids_cloud(&dummy, env.cgrid(mg.pos), - NULL, true))) - { - mg.pos = find_newmons_square(montype, mg.pos); - } - if (!in_bounds(mg.pos)) - return (-1); - - const int cloud_num = env.cgrid(mg.pos); - // Don't place friendly god gift in a damaging cloud created by - // you if that would anger the god. - if (mons_avoids_cloud(&dummy, cloud_num, NULL, true) - && mg.behaviour == BEH_FRIENDLY - && god_hates_attacking_friend(you.religion, &dummy) - && YOU_KILL(env.cloud[cloud_num].killer)) - { - return (-1); - } - } - } - - if (in_bounds(mg.pos)) - { - summd = mons_place(mg); - // If the arena vetoed the placement then give no fail message. - if (crawl_state.arena) - fail_msg = false; - } - - // Determine whether creating a monster is successful (summd != -1) {dlb}: - // then handle the outcome. {dlb}: - if (fail_msg && summd == -1 && observe_cell(mg.pos)) - mpr("You see a puff of smoke."); - - // The return value is either -1 (failure of some sort) - // or the index of the monster placed (if I read things right). {dlb} - return (summd); -} - -bool empty_surrounds(const coord_def& where, dungeon_feature_type spc_wanted, - int radius, bool allow_centre, coord_def& empty) -{ - // Assume all player summoning originates from player x,y. - bool playerSummon = (where == you.pos()); - - int good_count = 0; - - for (radius_iterator ri(where, radius, true, false, !allow_centre); - ri; ++ri) - { - bool success = false; - - if (actor_at(*ri)) - continue; - - // Players won't summon out of LOS, or past transparent walls. - if (!you.see_cell_no_trans(*ri) && playerSummon) - continue; - - success = - (grd(*ri) == spc_wanted) || feat_compatible(spc_wanted, grd(*ri)); - - if (success && one_chance_in(++good_count)) - empty = *ri; - } - - return (good_count > 0); -} - -monster_type summon_any_demon(demon_class_type dct) -{ - monster_type mon = MONS_PROGRAM_BUG; - - if (dct == DEMON_RANDOM) - dct = static_cast(random2(DEMON_RANDOM)); - - int temp_rand; // probability determination {dlb} - - switch (dct) - { - case DEMON_LESSER: - temp_rand = random2(60); - mon = ((temp_rand > 49) ? MONS_IMP : // 10 in 60 - (temp_rand > 40) ? MONS_WHITE_IMP : // 9 in 60 - (temp_rand > 31) ? MONS_LEMURE : // 9 in 60 - (temp_rand > 22) ? MONS_UFETUBUS : // 9 in 60 - (temp_rand > 13) ? MONS_MANES : // 9 in 60 - (temp_rand > 4) ? MONS_MIDGE // 9 in 60 - : MONS_SHADOW_IMP); // 5 in 60 - break; - - case DEMON_COMMON: - temp_rand = random2(3948); - mon = ((temp_rand > 3367) ? MONS_NEQOXEC : // 14.69% - (temp_rand > 2787) ? MONS_ORANGE_DEMON : // 14.69% - (temp_rand > 2207) ? MONS_HELLWING : // 14.69% - (temp_rand > 1627) ? MONS_SMOKE_DEMON : // 14.69% - (temp_rand > 1047) ? MONS_YNOXINUL : // 14.69% - (temp_rand > 889) ? MONS_RED_DEVIL : // 4.00% - (temp_rand > 810) ? MONS_HELLION : // 2.00% - (temp_rand > 731) ? MONS_ROTTING_DEVIL : // 2.00% - (temp_rand > 652) ? MONS_TORMENTOR : // 2.00% - (temp_rand > 573) ? MONS_REAPER : // 2.00% - (temp_rand > 494) ? MONS_SOUL_EATER : // 2.00% - (temp_rand > 415) ? MONS_HAIRY_DEVIL : // 2.00% - (temp_rand > 336) ? MONS_ICE_DEVIL : // 2.00% - (temp_rand > 257) ? MONS_BLUE_DEVIL : // 2.00% - (temp_rand > 178) ? MONS_BEAST : // 2.00% - (temp_rand > 99) ? MONS_IRON_DEVIL : // 2.00% - (temp_rand > 49) ? MONS_SUN_DEMON // 1.26% - : MONS_SHADOW_IMP); // 1.26% - break; - - case DEMON_GREATER: - temp_rand = random2(1000); - mon = ((temp_rand > 868) ? MONS_CACODEMON : // 13.1% - (temp_rand > 737) ? MONS_BALRUG : // 13.1% - (temp_rand > 606) ? MONS_BLUE_DEATH : // 13.1% - (temp_rand > 475) ? MONS_GREEN_DEATH : // 13.1% - (temp_rand > 344) ? MONS_EXECUTIONER : // 13.1% - (temp_rand > 244) ? MONS_FIEND : // 10.0% - (temp_rand > 154) ? MONS_ICE_FIEND : // 9.0% - (temp_rand > 73) ? MONS_SHADOW_FIEND // 8.1% - : MONS_PIT_FIEND); // 7.4% - break; - - default: - break; - } - - return (mon); -} - -monster_type summon_any_holy_being(holy_being_class_type hbct) -{ - monster_type mon = MONS_PROGRAM_BUG; - - switch (hbct) - { - case HOLY_BEING_WARRIOR: - mon = coinflip() ? MONS_DAEVA : MONS_ANGEL; - break; - - default: - break; - } - - return (mon); -} - -monster_type summon_any_dragon(dragon_class_type dct) -{ - monster_type mon = MONS_PROGRAM_BUG; - - int temp_rand; - - switch (dct) - { - case DRAGON_LIZARD: - temp_rand = random2(100); - mon = ((temp_rand > 80) ? MONS_SWAMP_DRAKE : - (temp_rand > 59) ? MONS_KOMODO_DRAGON : - (temp_rand > 34) ? MONS_FIREDRAKE : - (temp_rand > 11) ? MONS_DEATH_DRAKE : - MONS_DRAGON); - break; - - case DRAGON_DRACONIAN: - temp_rand = random2(70); - mon = ((temp_rand > 60) ? MONS_YELLOW_DRACONIAN : - (temp_rand > 50) ? MONS_BLACK_DRACONIAN : - (temp_rand > 40) ? MONS_PALE_DRACONIAN : - (temp_rand > 30) ? MONS_GREEN_DRACONIAN : - (temp_rand > 20) ? MONS_PURPLE_DRACONIAN : - (temp_rand > 10) ? MONS_RED_DRACONIAN - : MONS_WHITE_DRACONIAN); - break; - - case DRAGON_DRAGON: - temp_rand = random2(90); - mon = ((temp_rand > 80) ? MONS_MOTTLED_DRAGON : - (temp_rand > 70) ? MONS_LINDWURM : - (temp_rand > 60) ? MONS_STORM_DRAGON : - (temp_rand > 50) ? MONS_MOTTLED_DRAGON : - (temp_rand > 40) ? MONS_STEAM_DRAGON : - (temp_rand > 30) ? MONS_DRAGON : - (temp_rand > 20) ? MONS_ICE_DRAGON : - (temp_rand > 10) ? MONS_SWAMP_DRAGON - : MONS_SHADOW_DRAGON); - break; - - default: - break; - } - - return (mon); -} - -///////////////////////////////////////////////////////////////////////////// -// monster_pathfind - -// The pathfinding is an implementation of the A* algorithm. Beginning at the -// destination square we check all neighbours of a given grid, estimate the -// distance needed for any shortest path including this grid and push the -// result into a hash. We can then easily access all points with the shortest -// distance estimates and then check _their_ neighbours and so on. -// The algorithm terminates once we reach the monster position since - because -// of the sorting of grids by shortest distance in the hash - there can be no -// path between start and target that is shorter than the current one. There -// could be other paths that have the same length but that has no real impact. -// If the hash has been cleared and the start grid has not been encountered, -// then there's no path that matches the requirements fed into monster_pathfind. -// (These requirements are usually preference of habitat of a specific monster -// or a limit of the distance between start and any grid on the path.) - -int mons_tracking_range(const monsters *mon) -{ - - int range = 0; - switch (mons_intel(mon)) - { - case I_PLANT: - range = 2; - break; - case I_INSECT: - range = 4; - break; - case I_ANIMAL: - range = 5; - break; - case I_NORMAL: - range = LOS_RADIUS; - break; - default: - // Highly intelligent monsters can find their way - // anywhere. (range == 0 means no restriction.) - break; - } - - if (range) - { - if (mons_is_native_in_branch(mon)) - range += 3; - else if (mons_class_flag(mon->type, M_BLOOD_SCENT)) - range++; - } - - return (range); -} - -//#define DEBUG_PATHFIND -monster_pathfind::monster_pathfind() - : mons(), target(), range(0), min_length(0), max_length(0), dist(), prev() -{ -} - -monster_pathfind::~monster_pathfind() -{ -} - -void monster_pathfind::set_range(int r) -{ - if (r >= 0) - range = r; -} - -coord_def monster_pathfind::next_pos(const coord_def &c) const -{ - return c + Compass[prev[c.x][c.y]]; -} - -// The main method in the monster_pathfind class. -// Returns true if a path was found, else false. -bool monster_pathfind::init_pathfind(const monsters *mon, coord_def dest, - bool diag, bool msg, bool pass_unmapped) -{ - mons = mon; - - // We're doing a reverse search from target to monster. - start = dest; - target = mon->pos(); - pos = start; - allow_diagonals = diag; - traverse_unmapped = pass_unmapped; - - // Easy enough. :P - if (start == target) - { - if (msg) - mpr("The monster is already there!"); - - return (true); - } - - return start_pathfind(msg); -} - -bool monster_pathfind::init_pathfind(coord_def src, coord_def dest, bool diag, - bool msg) -{ - start = src; - target = dest; - pos = start; - allow_diagonals = diag; - - // Easy enough. :P - if (start == target) - return (true); - - return start_pathfind(msg); -} - -bool monster_pathfind::start_pathfind(bool msg) -{ - // NOTE: We never do any traversable() check for the starting square - // (target). This means that even if the target cannot be reached - // we may still find a path leading adjacent to this position, which - // is desirable if e.g. the player is hovering over deep water - // surrounded by shallow water or floor, or if a foe is hiding in - // a wall. - // If the surrounding squares also are not traversable, we return - // early that no path could be found. - - max_length = min_length = grid_distance(pos.x, pos.y, target.x, target.y); - for (int i = 0; i < GXM; i++) - for (int j = 0; j < GYM; j++) - dist[i][j] = INFINITE_DISTANCE; - - dist[pos.x][pos.y] = 0; - - bool success = false; - do - { - // Calculate the distance to all neighbours of the current position, - // and add them to the hash, if they haven't already been looked at. - success = calc_path_to_neighbours(); - if (success) - return (true); - - // Pull the position with shortest distance estimate to our target grid. - success = get_best_position(); - - if (!success) - { - if (msg) - { - mprf("Couldn't find a path from (%d,%d) to (%d,%d).", - target.x, target.y, start.x, start.y); - } - return (false); - } - } - while (true); -} - -// Returns true as soon as we encounter the target. -bool monster_pathfind::calc_path_to_neighbours() -{ - coord_def npos; - int distance, old_dist, total; - - // For each point, we look at all neighbour points. Check the orthogonals - // last, so that, should an orthogonal and a diagonal direction have the - // same total travel cost, the orthogonal will be picked first, and thus - // zigzagging will be significantly reduced. - // - // 1 0 3 This means directions are looked at, in order, - // \ | / 1, 3, 5, 7 (diagonals) followed by 0, 2, 4, 6 - // 6--.--2 (orthogonals). This is achieved by the assignment - // / | \ of (dir = 0) once dir has passed 7. - // 7 4 5 - // - for (int dir = 1; dir < 8; (dir += 2) == 9 && (dir = 0)) - { - // Skip diagonal movement. - if (!allow_diagonals && (dir % 2)) - continue; - - npos = pos + Compass[dir]; - -#ifdef DEBUG_PATHFIND - mprf("Looking at neighbour (%d,%d)", npos.x, npos.y); -#endif - if (!in_bounds(npos)) - continue; - - if (!traversable(npos)) - continue; - - // Ignore this grid if it takes us above the allowed distance. - if (range && estimated_cost(npos) > range) - continue; - - distance = dist[pos.x][pos.y] + travel_cost(npos); - old_dist = dist[npos.x][npos.y]; -#ifdef DEBUG_PATHFIND - mprf("old dist: %d, new dist: %d, infinite: %d", old_dist, distance, - INFINITE_DISTANCE); -#endif - // If the new distance is better than the old one (initialised with - // INFINITE), update the position. - if (distance < old_dist) - { - // Calculate new total path length. - total = distance + estimated_cost(npos); - if (old_dist == INFINITE_DISTANCE) - { -#ifdef DEBUG_PATHFIND - mprf("Adding (%d,%d) to hash (total dist = %d)", - npos.x, npos.y, total); -#endif - add_new_pos(npos, total); - if (total > max_length) - max_length = total; - } - else - { -#ifdef DEBUG_PATHFIND - mprf("Improving (%d,%d) to total dist %d", - npos.x, npos.y, total); -#endif - - update_pos(npos, total); - } - - // Update distance start->pos. - dist[npos.x][npos.y] = distance; - - // Set backtracking information. - // Converts the Compass direction to its counterpart. - // 0 1 2 4 5 6 - // 7 . 3 ==> 3 . 7 e.g. (3 + 4) % 8 = 7 - // 6 5 4 2 1 0 (7 + 4) % 8 = 11 % 8 = 3 - - prev[npos.x][npos.y] = (dir + 4) % 8; - - // Are we finished? - if (npos == target) - { -#ifdef DEBUG_PATHFIND - mpr("Arrived at target."); -#endif - return (true); - } - } - } - return (false); -} - -// Starting at known min_length (minimum total estimated path distance), check -// the hash for existing vectors, then pick the last entry of the first vector -// that matches. Update min_length, if necessary. -bool monster_pathfind::get_best_position() -{ - for (int i = min_length; i <= max_length; i++) - { - if (!hash[i].empty()) - { - if (i > min_length) - min_length = i; - - std::vector &vec = hash[i]; - // Pick the last position pushed into the vector as it's most - // likely to be close to the target. - pos = vec[vec.size()-1]; - vec.pop_back(); - -#ifdef DEBUG_PATHFIND - mprf("Returning (%d, %d) as best pos with total dist %d.", - pos.x, pos.y, min_length); -#endif - - return (true); - } -#ifdef DEBUG_PATHFIND - mprf("No positions for path length %d.", i); -#endif - } - - // Nothing found? Then there's no path! :( - return (false); -} - -// Using the prev vector backtrack from start to target to find all steps to -// take along the shortest path. -std::vector monster_pathfind::backtrack() -{ -#ifdef DEBUG_PATHFIND - mpr("Backtracking..."); -#endif - std::vector path; - pos = target; - path.push_back(pos); - - if (pos == start) - return path; - - int dir; - do - { - dir = prev[pos.x][pos.y]; - pos = pos + Compass[dir]; - ASSERT(in_bounds(pos)); -#ifdef DEBUG_PATHFIND - mprf("prev: (%d, %d), pos: (%d, %d)", Compass[dir].x, Compass[dir].y, - pos.x, pos.y); -#endif - path.push_back(pos); - - if (pos.x == 0 && pos.y == 0) - break; - } - while (pos != start); - ASSERT(pos == start); - - return (path); -} - -// Reduces the path coordinates to only a couple of key waypoints needed -// to reach the target. Waypoints are chosen such that from one waypoint you -// can see (and, more importantly, reach) the next one. Note that -// can_go_straight() is probably rather too conservative in these estimates. -// This is done because Crawl's pathfinding - once a target is in sight and easy -// reach - is both very robust and natural, especially if we want to flexibly -// avoid plants and other monsters in the way. -std::vector monster_pathfind::calc_waypoints() -{ - std::vector path = backtrack(); - - // If no path found, nothing to be done. - if (path.empty()) - return path; - - dungeon_feature_type can_move; - if (mons_amphibious(mons)) - can_move = DNGN_DEEP_WATER; - else - can_move = DNGN_SHALLOW_WATER; - - std::vector waypoints; - pos = path[0]; - -#ifdef DEBUG_PATHFIND - mpr(EOL "Waypoints:"); -#endif - for (unsigned int i = 1; i < path.size(); i++) - { - if (can_go_straight(pos, path[i], can_move)) - continue; - else - { - pos = path[i-1]; - waypoints.push_back(pos); -#ifdef DEBUG_PATHFIND - mprf("waypoint: (%d, %d)", pos.x, pos.y); -#endif - } - } - - // Add the actual target to the list of waypoints, so we can later check - // whether a tracked enemy has moved too much, in case we have to update - // the path. - if (pos != path[path.size() - 1]) - waypoints.push_back(path[path.size() - 1]); - - return (waypoints); -} - -bool monster_pathfind::traversable(const coord_def p) -{ - if (traverse_unmapped && grd(p) == DNGN_UNSEEN) - return (true); - - if (mons) - return mons_traversable(p); - - return (!feat_is_solid(grd(p)) && !feat_destroys_items(grd(p))); -} - -// Checks whether a given monster can pass over a certain position, respecting -// its preferred habit and capability of flight or opening doors. -bool monster_pathfind::mons_traversable(const coord_def p) -{ - const monster_type montype = mons_is_zombified(mons) ? mons_zombie_base(mons) - : mons->type; - - if (!monster_habitable_grid(montype, grd(p))) - return (false); - - // Monsters that can't open doors won't be able to pass them. - if (feat_is_closed_door(grd(p)) || grd(p) == DNGN_SECRET_DOOR) - { - if (mons_is_zombified(mons)) - { - if (mons_class_itemuse(montype) < MONUSE_OPEN_DOORS) - return (false); - } - else if (mons_itemuse(mons) < MONUSE_OPEN_DOORS) - return (false); - } - - // Your friends only know about doors you know about, unless they feel - // at home in this branch. - if (grd(p) == DNGN_SECRET_DOOR && mons->friendly() - && (mons_intel(mons) < I_NORMAL || !mons_is_native_in_branch(mons))) - { - return (false); - } - - const trap_def* ptrap = find_trap(p); - if (ptrap) - { - const trap_type tt = ptrap->type; - - // Don't allow allies to pass over known (to them) Zot traps. - if (tt == TRAP_ZOT - && ptrap->is_known(mons) - && mons->friendly()) - { - return (false); - } - - // Monsters cannot travel over teleport traps. - if (!_can_place_on_trap(montype, tt)) - return (false); - } - - return (true); -} - -int monster_pathfind::travel_cost(coord_def npos) -{ - if (mons) - return mons_travel_cost(npos); - - return (1); -} - -// Assumes that grids that really cannot be entered don't even get here. -// (Checked by traversable().) -int monster_pathfind::mons_travel_cost(coord_def npos) -{ - ASSERT(grid_distance(pos, npos) <= 1); - - // Doors need to be opened. - if (feat_is_closed_door(grd(npos)) || grd(npos) == DNGN_SECRET_DOOR) - return 2; - - const int montype = mons_is_zombified(mons) ? mons_zombie_base(mons) - : mons->type; - - const bool airborne = _mons_airborne(montype, -1, false); - - // Travelling through water, entering or leaving water is more expensive - // for non-amphibious monsters, so they'll avoid it where possible. - // (The resulting path might not be optimal but it will lead to a path - // a monster of such habits is likely to prefer.) - // Only tested for shallow water since they can't enter deep water anyway. - if (!airborne && !mons_class_amphibious(montype) - && (grd(pos) == DNGN_SHALLOW_WATER || grd(npos) == DNGN_SHALLOW_WATER)) - { - return 2; - } - - // Try to avoid (known) traps. - const trap_def* ptrap = find_trap(npos); - if (ptrap) - { - const bool knows_trap = ptrap->is_known(mons); - const trap_type tt = ptrap->type; - if (tt == TRAP_ALARM || tt == TRAP_ZOT) - { - // Your allies take extra precautions to avoid known alarm traps. - // Zot traps are considered intraversable. - if (knows_trap && mons->friendly()) - return (3); - - // To hostile monsters, these traps are completely harmless. - return 1; - } - - // Mechanical traps can be avoided by flying, as can shafts, and - // tele traps are never traversable anyway. - if (knows_trap && !airborne) - return 2; - } - - return 1; -} - -// The estimated cost to reach a grid is simply max(dx, dy). -int monster_pathfind::estimated_cost(coord_def p) -{ - return (grid_distance(p, target)); -} - -void monster_pathfind::add_new_pos(coord_def npos, int total) -{ - hash[total].push_back(npos); -} - -void monster_pathfind::update_pos(coord_def npos, int total) -{ - // Find hash position of old distance and delete it, - // then call_add_new_pos. - int old_total = dist[npos.x][npos.y] + estimated_cost(npos); - - std::vector &vec = hash[old_total]; - for (unsigned int i = 0; i < vec.size(); i++) - { - if (vec[i] == npos) - { - vec.erase(vec.begin() + i); - break; - } - } - - add_new_pos(npos, total); -} - -///////////////////////////////////////////////////////////////////////////// -// -// Random monsters for portal vaults. -// -///////////////////////////////////////////////////////////////////////////// - -void set_vault_mon_list(const std::vector &list) -{ - CrawlHashTable &props = env.properties; - - props.erase(VAULT_MON_TYPES_KEY); - props.erase(VAULT_MON_BASES_KEY); - props.erase(VAULT_MON_WEIGHTS_KEY); - - unsigned int size = list.size(); - if (size == 0) - { - setup_vault_mon_list(); - return; - } - - props[VAULT_MON_TYPES_KEY].new_vector(SV_LONG).resize(size); - props[VAULT_MON_BASES_KEY].new_vector(SV_LONG).resize(size); - props[VAULT_MON_WEIGHTS_KEY].new_vector(SV_LONG).resize(size); - - CrawlVector &type_vec = props[VAULT_MON_TYPES_KEY].get_vector(); - CrawlVector &base_vec = props[VAULT_MON_BASES_KEY].get_vector(); - CrawlVector &weight_vec = props[VAULT_MON_WEIGHTS_KEY].get_vector(); - - for (unsigned int i = 0; i < size; i++) - { - const mons_spec &spec = list[i]; - - if (spec.place.is_valid()) - { - ASSERT(spec.place.level_type != LEVEL_LABYRINTH - && spec.place.level_type != LEVEL_PORTAL_VAULT); - type_vec[i] = (long) -1; - base_vec[i] = (long) spec.place.packed_place(); - } - else - { - ASSERT(spec.mid != RANDOM_MONSTER - && spec.monbase != RANDOM_MONSTER); - type_vec[i] = (long) spec.mid; - base_vec[i] = (long) spec.monbase; - } - weight_vec[i] = (long) spec.genweight; - } - - setup_vault_mon_list(); -} - -void get_vault_mon_list(std::vector &list) -{ - list.clear(); - - CrawlHashTable &props = env.properties; - - if (!props.exists(VAULT_MON_TYPES_KEY)) - return; - - ASSERT(props.exists(VAULT_MON_BASES_KEY)); - ASSERT(props.exists(VAULT_MON_WEIGHTS_KEY)); - - CrawlVector &type_vec = props[VAULT_MON_TYPES_KEY].get_vector(); - CrawlVector &base_vec = props[VAULT_MON_BASES_KEY].get_vector(); - CrawlVector &weight_vec = props[VAULT_MON_WEIGHTS_KEY].get_vector(); - - ASSERT(type_vec.size() == base_vec.size()); - ASSERT(type_vec.size() == weight_vec.size()); - - unsigned int size = type_vec.size(); - for (unsigned int i = 0; i < size; i++) - { - int type = (long) type_vec[i]; - int base = (long) base_vec[i]; - - mons_spec spec; - - if (type == -1) - { - spec.place = level_id::from_packed_place(base); - ASSERT(spec.place.is_valid()); - ASSERT(spec.place.level_type != LEVEL_LABYRINTH - && spec.place.level_type != LEVEL_PORTAL_VAULT); - } - else - { - spec.mid = type; - spec.monbase = (monster_type) base; - ASSERT(spec.mid != RANDOM_MONSTER - && spec.monbase != RANDOM_MONSTER); - } - spec.genweight = (long) weight_vec[i]; - - list.push_back(spec); - } -} - -void setup_vault_mon_list() -{ - vault_mon_types.clear(); - vault_mon_bases.clear(); - vault_mon_weights.clear(); - - std::vector list; - get_vault_mon_list(list); - - unsigned int size = list.size(); - - vault_mon_types.resize(size); - vault_mon_bases.resize(size); - vault_mon_weights.resize(size); - - for (unsigned int i = 0; i < size; i++) - { - if (list[i].place.is_valid()) - { - vault_mon_types[i] = -1; - vault_mon_bases[i] = list[i].place.packed_place(); - } - else - { - vault_mon_types[i] = list[i].mid; - vault_mon_bases[i] = list[i].monbase; - } - vault_mon_weights[i] = list[i].genweight; - } -} diff --git a/crawl-ref/source/monplace.h b/crawl-ref/source/monplace.h deleted file mode 100644 index 7e35108863..0000000000 --- a/crawl-ref/source/monplace.h +++ /dev/null @@ -1,398 +0,0 @@ -/* - * File: monplace.h - * Summary: Functions used when placing monsters in the dungeon. - * Written by: Linley Henzell - */ - - -#ifndef MONPLACE_H -#define MONPLACE_H - -#include "coord.h" -#include "enum.h" -#include "dungeon.h" -#include "fixvec.h" - -enum band_type -{ - BAND_NO_BAND = 0, - BAND_KOBOLDS, - BAND_ORCS, - BAND_ORC_WARRIOR, - BAND_ORC_KNIGHT, - BAND_KILLER_BEES, // 5 - BAND_FLYING_SKULLS, - BAND_SLIME_CREATURES, - BAND_YAKS, - BAND_UGLY_THINGS, - BAND_HELL_HOUNDS, // 10 - BAND_JACKALS, - BAND_HELL_KNIGHTS, - BAND_ORC_HIGH_PRIEST, - BAND_GNOLLS, // 14 - // 15 - BAND_BUMBLEBEES = 16, - BAND_CENTAURS, - BAND_YAKTAURS, - BAND_INSUBSTANTIAL_WISPS, - BAND_OGRE_MAGE, // 20 - BAND_DEATH_YAKS, - BAND_NECROMANCER, - BAND_BALRUG, - BAND_CACODEMON, - BAND_EXECUTIONER, // 25 - BAND_HELLWING, - BAND_DEEP_ELF_FIGHTER, - BAND_DEEP_ELF_KNIGHT, - BAND_DEEP_ELF_HIGH_PRIEST, - BAND_KOBOLD_DEMONOLOGIST, // 30 - BAND_NAGAS, - BAND_WAR_DOGS, - BAND_GREY_RATS, - BAND_GREEN_RATS, - BAND_ORANGE_RATS, // 35 - BAND_SHEEP, - BAND_GHOULS, - BAND_DEEP_TROLLS, - BAND_HOGS, - BAND_HELL_HOGS, // 40 - BAND_GIANT_MOSQUITOES, - BAND_BOGGARTS, - BAND_BLINK_FROGS, - BAND_SKELETAL_WARRIORS, - BAND_DRACONIAN, // 45 - BAND_PANDEMONIUM_DEMON, - BAND_HARPIES, - BAND_ILSUIW, - BAND_AZRAEL, - BAND_DUVESSA, // 50 - BAND_KHUFU, - BAND_GOLDEN_EYE, - BAND_PIKEL, - NUM_BANDS // always last -}; - -enum demon_class_type -{ - DEMON_LESSER, // 0: Class V - DEMON_COMMON, // 1: Class II-IV - DEMON_GREATER, // 2: Class I - DEMON_RANDOM // any of the above -}; - -enum holy_being_class_type -{ - HOLY_BEING_WARRIOR // 0: Daeva or Angel -}; - -enum dragon_class_type -{ - DRAGON_LIZARD, - DRAGON_DRACONIAN, - DRAGON_DRAGON -}; - -enum proximity_type // proximity to player to create monster -{ - PROX_ANYWHERE, - PROX_CLOSE_TO_PLAYER, - PROX_AWAY_FROM_PLAYER, - PROX_NEAR_STAIRS -}; - -enum mgen_flag_type -{ - MG_PERMIT_BANDS = 0x01, - MG_FORCE_PLACE = 0x02, - MG_FORCE_BEH = 0x04, - MG_PLAYER_MADE = 0x08, - MG_PATROLLING = 0x10 -}; - -// A structure with all the data needed to whip up a new monster. -struct mgen_data -{ - // Monster type. - monster_type cls; - - // If the monster is zombie-like, or a specialised draconian, this - // is the base monster that the monster is based on - should be - // set to MONS_NO_MONSTER when not used. - monster_type base_type; - - // Determines the behaviour of the monster after it is generated. This - // behaviour is an unholy combination of monster attitude - // (friendly, hostile) and monster initial state (asleep, wandering). - // XXX: Could use splitting up these aspects. - beh_type behaviour; - - // For summoned monsters, this is a measure of how long the summon will - // hang around, on a scale of 1-6, 6 being longest. Use 0 for monsters - // that aren't summoned. - int abjuration_duration; - - // For summoned monsters this is their type of summoning, either the - // spell which summoned them or one of the values of the enumeration - // mon_summon_type in mon-util.h. - int summon_type; - - // Where the monster will be created. - coord_def pos; - - // The monster's foe, i.e. which monster it will want to attack. foe - // may be an index into the monster array (0 - (MAX_MONSTERS-1)), or - // it may be MHITYOU to indicate that the monster wants to attack the - // player, or MHITNOT, to indicate that the monster has no foe and is - // just wandering around. - unsigned short foe; - - // Generation flags from mgen_flag_type. - unsigned flags; - - // What god the monster worships, if any. Used for monsters that - // are god gifts, to indicate which god sent them, and by priest - // monsters, to indicate whose priest they are. - god_type god; - - // The number of hydra heads, the number of manticore attack volleys, - // the number of merged slime creatures, or the indicator for when - // Khufu is entombed. - // - // Note: in older versions this field was used for both this and for - // base_type. - int number; - - // The colour of the monster. - int colour; - - // A measure of how powerful the generated monster should be (for - // randomly chosen monsters), usually equal to the absolute depth - // that the player is in the dungeon. - int power; - - // How close to or far from the player the monster should be created. - // Is usually used only when the initial position (pos) is unspecified. - proximity_type proximity; - - // What place we're in, or pretending to be in, usually the place - // the player is actually in. - level_area_type level_type; - - // Some predefined vaults (aka maps) include flags to suppress random - // generation of monsters. When generating monsters, this is a mask of - // map flags to honour (such as MMT_NO_MONS to specify that we shouldn't - // randomly generate a monster inside a map that doesn't want it). These - // map flags are usually respected only when a dungeon level is being - // constructed, since at future points vault information may no longer - // be available (vault metadata is not preserved across game saves). - unsigned map_mask; - - // XXX: Rather hackish. - std::string mname; - - mgen_data(monster_type mt = RANDOM_MONSTER, - beh_type beh = BEH_HOSTILE, - int abj = 0, - int st = 0, - const coord_def &p = coord_def(-1, -1), - unsigned short mfoe = MHITNOT, - unsigned monflags = 0, - god_type which_god = GOD_NO_GOD, - monster_type base = MONS_NO_MONSTER, - int monnumber = 0, - int moncolour = BLACK, - int monpower = you.your_level, - proximity_type prox = PROX_ANYWHERE, - level_area_type ltype = you.level_type, - std::string monname = "") - - : cls(mt), base_type(base), behaviour(beh), - abjuration_duration(abj), summon_type(st), pos(p), foe(mfoe), - flags(monflags), god(which_god), number(monnumber), colour(moncolour), - power(monpower), proximity(prox), level_type(ltype), map_mask(0), - mname(monname) - { - ASSERT(summon_type == 0 || (abj >= 1 && abj <= 6) - || mt == MONS_BALL_LIGHTNING); - } - - bool permit_bands() const { return (flags & MG_PERMIT_BANDS); } - bool force_place() const { return (flags & MG_FORCE_PLACE); } - bool needs_patrol_point() const { return (flags & MG_PATROLLING); } - - // Is there a valid position set on this struct that we want to use - // when placing the monster? - bool use_position() const { return in_bounds(pos); } - - bool summoned() const { return (abjuration_duration > 0); } - - static mgen_data sleeper_at(monster_type what, - const coord_def &where, - unsigned flags = 0) - { - return mgen_data(what, BEH_SLEEP, 0, 0, where, MHITNOT, flags); - } - - static mgen_data hostile_at(monster_type mt, - bool alert = false, - int abj = 0, - int st = 0, - const coord_def &p = coord_def(-1, -1), - unsigned monflags = 0, - god_type god = GOD_NO_GOD, - monster_type base = MONS_NO_MONSTER) - - { - return mgen_data(mt, BEH_HOSTILE, abj, st, p, - alert ? MHITYOU : MHITNOT, - monflags, god, base); - } -}; - -/* *********************************************************************** - * Creates a monster near the place specified in the mgen_data, producing - * a "puff of smoke" message if the monster cannot be placed. This is usually - * used for summons and other monsters that want to appear near a given - * position like a summon. - * *********************************************************************** */ -int create_monster(mgen_data mg, bool fail_msg = true); - -/* *********************************************************************** - * Primary function to create monsters. See mgen_data for details on monster - * placement. - * *********************************************************************** */ -int mons_place(mgen_data mg); - -/* *********************************************************************** - * This isn't really meant to be a public function. It is a low level - * monster placement function used by dungeon building routines and - * mons_place(). If you need to put a monster somewhere, use mons_place(). - * Summoned creatures can be created with create_monster(). - * *********************************************************************** */ -int place_monster(mgen_data mg, bool force_pos = false); - -monster_type pick_random_zombie(); - -/* *********************************************************************** - * Returns a monster class type of a zombie that would be generated - * on the player's current level. - * *********************************************************************** */ -monster_type pick_local_zombifiable_monster_type(int power); - -class level_id; - -monster_type pick_random_monster(const level_id &place); - -monster_type pick_random_monster(const level_id &place, - int power, - int &lev_mons); - -bool player_will_anger_monster(monster_type type, bool *holy = NULL, - bool *unholy = NULL, bool *lawful = NULL, - bool *antimagical = NULL); - -bool player_will_anger_monster(monsters *mon, bool *holy = NULL, - bool *unholy = NULL, bool *lawful = NULL, - bool *antimagical = NULL); - -bool player_angers_monster(monsters *mon); - -bool empty_surrounds( const coord_def& where, dungeon_feature_type spc_wanted, - int radius, bool allow_centre, coord_def& empty ); - -monster_type summon_any_demon(demon_class_type dct); - -monster_type summon_any_holy_being(holy_being_class_type hbct); - -monster_type summon_any_dragon(dragon_class_type dct); - -bool drac_colour_incompatible(int drac, int colour); - -void mark_interesting_monst(monsters* monster, - beh_type behaviour = BEH_SLEEP); - -bool feat_compatible(dungeon_feature_type grid_wanted, - dungeon_feature_type actual_grid); -bool monster_habitable_grid(const monsters *m, - dungeon_feature_type actual_grid); -bool monster_habitable_grid(monster_type montype, - dungeon_feature_type actual_grid, - int flies = -1, - bool paralysed = false); -bool monster_can_submerge(const monsters *mons, dungeon_feature_type grid); -coord_def find_newmons_square(int mons_class, const coord_def &p); -coord_def find_newmons_square_contiguous(monster_type mons_class, - const coord_def &start, - int maxdistance = 3); - -void spawn_random_monsters(); - -void set_vault_mon_list(const std::vector &list); - -void get_vault_mon_list(std::vector &list); - -void setup_vault_mon_list(); - -int mons_tracking_range(const monsters *mon); - -class monster_pathfind -{ -public: - monster_pathfind(); - virtual ~monster_pathfind(); - - // public methods - void set_range(int r); - coord_def next_pos(const coord_def &p) const; - bool init_pathfind(const monsters *mon, coord_def dest, - bool diag = true, bool msg = false, - bool pass_unmapped = false); - bool init_pathfind(coord_def src, coord_def dest, - bool diag = true, bool msg = false); - bool start_pathfind(bool msg = false); - std::vector backtrack(void); - std::vector calc_waypoints(void); - -protected: - // protected methods - bool calc_path_to_neighbours(void); - bool traversable(coord_def p); - int travel_cost(coord_def npos); - bool mons_traversable(coord_def p); - int mons_travel_cost(coord_def npos); - int estimated_cost(coord_def npos); - void add_new_pos(coord_def pos, int total); - void update_pos(coord_def pos, int total); - bool get_best_position(void); - - - // The monster trying to find a path. - const monsters *mons; - - // Our destination, and the current position we're looking at. - coord_def start, target, pos; - - // If false, do not move diagonally along the path. - bool allow_diagonals; - - // If true, unmapped terrain is treated as traversable no matter the - // monster involved. - // (Used for player estimates of whether a monster can travel somewhere.) - bool traverse_unmapped; - - // Maximum range to search between start and target. None, if zero. - int range; - - // Currently shortest and longest possible total length of the path. - int min_length; - int max_length; - - // The array of distances from start to any already tried point. - int dist[GXM][GYM]; - // An array to store where we came from on a given shortest path. - int prev[GXM][GYM]; - - FixedVector, GXM * GYM> hash; -}; - -#endif // MONPLACE_H diff --git a/crawl-ref/source/monspeak.cc b/crawl-ref/source/monspeak.cc deleted file mode 100644 index 1d9650bbca..0000000000 --- a/crawl-ref/source/monspeak.cc +++ /dev/null @@ -1,860 +0,0 @@ -/* - * File: monspeak.cc - * Summary: Functions to handle speaking monsters - */ - -#include "AppHdr.h" - -#include "monspeak.h" - -#include -#include -#include -#include - -#ifdef TARGET_OS_DOS -#include -#endif - -#include "externs.h" - -#include "beam.h" -#include "database.h" -#include "debug.h" -#include "ghost.h" -#include "message.h" -#include "monstuff.h" -#include "mon-util.h" -#include "jobs.h" -#include "player.h" -#include "religion.h" -#include "state.h" -#include "stuff.h" -#include "view.h" - -// Try the exact key lookup along with the entire prefix list. -// If that fails, start ignoring hostile/religion/silence, in that order, -// first skipping hostile, then hostile *and* religion, then all three. -static std::string __try_exact_string(const std::vector &prefixes, - const std::string &key, - bool ignore_hostile = false, - bool ignore_related = false, - bool ignore_religion = false, - bool ignore_silenced = false) -{ - bool hostile = false; - bool related = false; - bool religion = false; - bool silenced = false; - - std::string prefix = ""; - std::string msg = ""; - const int size = prefixes.size(); - for (int i = 0; i < size; i++) - { - if (prefixes[i] == "hostile") - { - if (ignore_hostile) - continue; - hostile = true; - } - else if (prefixes[i] == "related") - { - if (ignore_related) - continue; - related = true; - } - else if (prefixes[i] == "silenced") - { - if (ignore_silenced) - continue; - silenced = true; - } - else if (prefixes[i] == "beogh" || prefixes[i] == "good god" - || prefixes[i] == "evil god") - { - if (ignore_religion) - continue; - religion = true; - } - prefix += prefixes[i]; - prefix += " "; - } - msg = getSpeakString(prefix + key); - - if (msg.empty()) - { - if (hostile) // skip hostile - msg = __try_exact_string(prefixes, key, true); - else if (related) - { - if (religion) // skip hostile and religion - msg = __try_exact_string(prefixes, key, true, false, true); - else // skip hostile and related - msg = __try_exact_string(prefixes, key, true, true); - } - else if (religion) // skip hostile, related and religion - msg = __try_exact_string(prefixes, key, true, true, true); - // 50% use non-verbal monster speech, - // 50% try for more general silenced monster message instead - else if (silenced && coinflip()) // skip all - msg = __try_exact_string(prefixes, key, true, true, true, true); - } - return msg; -} - -static bool _invalid_msg(const std::string &msg, bool no_player, bool no_foe, - bool no_foe_name, bool no_god, bool unseen) -{ - if (no_player - && (msg.find("@player") != std::string::npos - || msg.find("@Player") != std::string::npos - || msg.find(":You") != std::string::npos)) - { - return (true); - } - - if (no_player) - { - std::vector lines = split_string("\n", msg); - for (unsigned int i = 0; i < lines.size(); i++) - { - if (starts_with(lines[i], "You") - || ends_with(lines[i], "you.")) - { - return (true); - } - } - } - - if (no_foe && (msg.find("@foe") != std::string::npos - || msg.find("@Foe") != std::string::npos - || msg.find("foe@") != std::string::npos - || msg.find("@species") != std::string::npos)) - { - return (true); - } - - if (no_god && (msg.find("_god@") != std::string::npos - || msg.find("@god_") != std::string::npos)) - { - return (true); - } - - if (no_foe_name && msg.find("@foe_name@") != std::string::npos) - return (true); - - if (unseen && msg.find("VISUAL") != std::string::npos) - return (true); - - return (false); -} - -static std::string _try_exact_string(const std::vector &prefixes, - const std::string &key, - bool no_player, bool no_foe, - bool no_foe_name, bool no_god, - bool unseen, - bool ignore_hostile = false, - bool ignore_related = false, - bool ignore_religion = false, - bool ignore_silenced = false) -{ - std::string msg; - for (int tries = 0; tries < 10; tries++) - { - msg = - __try_exact_string(prefixes, key, ignore_hostile, ignore_related, - ignore_religion, ignore_silenced); - - // If the first message was non-empty and discarded then discard - // all subsequent empty messages, so as to not replace an - // invalid non-empty message with an empty one. - if (msg.empty()) - { - if (tries == 0) - return (msg); - else - { - tries--; - continue; - } - } - - if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen)) - { - msg = ""; - continue; - } - break; - } - - return (msg); -} - -static std::string __get_speak_string(const std::vector &prefixes, - const std::string &key, - const monsters *monster, - bool no_player, bool no_foe, - bool no_foe_name, bool no_god, - bool unseen) -{ - std::string msg = _try_exact_string(prefixes, key, no_player, no_foe, - no_foe_name, no_god, unseen); - - if (!msg.empty()) - return msg; - - // Combinations of prefixes by threes - const int size = prefixes.size(); - std::string prefix = ""; - if (size >= 3) - { - for (int i = 0; i < (size - 2); i++) - for (int j = i + 1; j < (size - 1); j++) - for (int k = j + 1; k < size; k++) - { - prefix = prefixes[i] + " "; - prefix += prefixes[j] + " "; - prefix += prefixes[k] + " "; - - msg = getSpeakString("default " + prefix + key); - - if (!msg.empty()) - return msg; - } - } - - // Combinations of prefixes by twos - if (size >= 2) - { - for (int i = 0; i < (size - 1); i++) - for (int j = i + 1; j < size; j++) - { - prefix = prefixes[i] + " "; - prefix += prefixes[j] + " "; - - msg = getSpeakString("default " + prefix + key); - - if (!msg.empty()) - return msg; - } - } - - // Prefixes singly - if (size >= 1) - { - for (int i = 0; i < size; i++) - { - prefix = prefixes[i] + " "; - - msg = getSpeakString("default " + prefix + key); - - if (!msg.empty()) - return msg; - } - } - - // No prefixes - msg = getSpeakString("default " + key); - - return msg; -} - -static std::string _get_speak_string(const std::vector &prefixes, - std::string key, - const monsters *monster, - bool no_player, bool no_foe, - bool no_foe_name, bool no_god, - bool unseen) -{ - int duration = 1; - if (monster->hit_points <= 0) - key += " killed"; - else if ((monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS) - key += " banished"; - else if (monster->is_summoned(&duration) && duration <= 0) - key += " unsummoned"; - - std::string msg; - for (int tries = 0; tries < 10; tries++) - { - msg = - __get_speak_string(prefixes, key, monster, no_player, no_foe, - no_foe_name, no_god, unseen); - - // If the first message was non-empty and discarded then discard - // all subsequent empty messages, so as to not replace an - // invalid non-empty message with an empty one. - if (msg.empty()) - { - if (tries == 0) - return (msg); - else - { - tries--; - continue; - } - } - - if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen)) - { - msg = ""; - continue; - } - - break; - } - - return (msg); -} - -// Player ghosts with different classes can potentially speak different -// things. -static std::string _player_ghost_speak_str(const monsters *monster, - const std::vector prefixes) -{ - const ghost_demon &ghost = *(monster->ghost); - std::string ghost_class = get_class_name(ghost.job); - - std::string prefix = ""; - for (int i = 0, size = prefixes.size(); i < size; i++) - { - prefix += prefixes[i]; - prefix += " "; - } - - // first try together with class name - std::string msg = getSpeakString(prefix + ghost_class + " player ghost"); - - // else try without class name - if (msg.empty() || msg == "__NEXT") - msg = getSpeakString(prefix + "player ghost"); - - return msg; -} - -// If the monster was originally a unique which has been polymorphed into -// a non-unique, is its current monter type capable of using its old -// speech? -static bool _polyd_can_speak(const monsters* monster) -{ - // Priest and wizard monsters can always speak. - if (monster->is_priest() || monster->is_actual_spellcaster()) - return (true); - - // Silent or non-sentient monsters can't use the original speech. - if (mons_intel(monster) < I_NORMAL - || mons_shouts(monster->type) == S_SILENT) - { - return (false); - } - - // Does it have the proper vocal equipment? - const mon_body_shape shape = get_mon_shape(monster); - return (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA); -} - -// Returns true if the monster did speak, false otherwise. -// Maybe monsters will speak! -void maybe_mons_speaks (monsters *monster) -{ -#define MON_SPEAK_CHANCE 21 - - if (monster->is_patrolling() || mons_is_wandering(monster) - || monster->attitude == ATT_NEUTRAL) - { - // Very fast wandering/patrolling monsters might, in one monster turn, - // move into the player's LOS and then back out (or the player - // might move into their LOS and the monster move back out before - // the player's view has a chance to update) so prevent them - // from speaking. - ; - } - else if ((mons_class_flag(monster->type, M_SPEAKS) - || !monster->mname.empty()) - && one_chance_in(MON_SPEAK_CHANCE)) - { - mons_speaks(monster); - } - else if (monster->type == MONS_CRAZY_YIUF - && one_chance_in(MON_SPEAK_CHANCE / 3)) - { - // Yiuf gets an extra chance to speak! - mons_speaks(monster); - } - else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED) - { - // Non-humanoid-ish monsters have a low chance of speaking - // without the M_SPEAKS flag, to give the dungeon some - // atmosphere/flavour. - int chance = MON_SPEAK_CHANCE * 4; - - // Band members are a lot less likely to speak, since there's - // a lot of them. - if (testbits(monster->flags, MF_BAND_MEMBER)) - chance *= 10; - - // However, confused and fleeing monsters are more interesting. - if (mons_is_fleeing(monster)) - chance /= 2; - if (monster->has_ench(ENCH_CONFUSION)) - chance /= 2; - - if (one_chance_in(chance)) - mons_speaks(monster); - } - // Okay then, don't speak. -} - - -// Returns true if something is said. -bool mons_speaks(monsters *monster) -{ - ASSERT(!invalid_monster_type(monster->type)); - - // Monsters always talk on death, even if invisible/silenced/etc. - int duration = 1; - const bool force_speak = !monster->alive() - || (monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS - || (monster->is_summoned(&duration) && duration <= 0) - || crawl_state.prev_cmd == CMD_LOOK_AROUND; // Wizard testing - - const bool unseen = !you.can_see(monster); - const bool confused = monster->confused(); - - if (!force_speak) - { - // Invisible monster tries to remain unnoticed. Unless they're - // confused, since then they're too confused to realise they - // should stay silent, but only if the player can see them, so as - // to not have to deal with cases of speaking monsters which the - // player can't see. - if (unseen && !confused) - return (false); - - // Silenced monsters only "speak" 1/3 as often as non-silenced, - // unless they're normally silent (S_SILENT). Use - // get_monster_data(monster->type) to bypass mon_shouts() - // replacing S_RANDOM with a random value. - if (silenced(monster->pos()) - && get_monster_data(monster->type)->shouts != S_SILENT) - { - if (!one_chance_in(3)) - return (false); - } - - // Berserk monsters just want your hide. - if (monster->berserk()) - return (false); - - // Monsters in a battle frenzy are likewise occupied. - if (monster->has_ench(ENCH_BATTLE_FRENZY) && !one_chance_in(3)) - return (false); - - // Charmed monsters aren't too expressive. - if (monster->has_ench(ENCH_CHARM) && !one_chance_in(3)) - return (false); - } - - std::vector prefixes; - if (monster->neutral()) - { - if (!force_speak && coinflip()) // Neutrals speak half as often. - return (false); - - prefixes.push_back("neutral"); - } - else if (monster->friendly() && !crawl_state.arena) - prefixes.push_back("friendly"); - else - prefixes.push_back("hostile"); - - if (mons_is_fleeing(monster)) - prefixes.push_back("fleeing"); - - bool silence = silenced(you.pos()); - if (silenced(monster->pos())) - { - silence = true; - prefixes.push_back("silenced"); - } - - if (confused) - prefixes.push_back("confused"); - - const actor* foe = (!crawl_state.arena && monster->wont_attack() - && invalid_monster_index(monster->foe)) ? - &you : monster->get_foe(); - const monsters* m_foe = (foe && foe->atype() == ACT_MONSTER) ? - dynamic_cast(foe) : NULL; - - // animals only look at the current player form, smart monsters at the - // actual player genus - if (!foe || foe->atype() == ACT_PLAYER) - { - if (is_player_same_species(monster->type, - mons_intel(monster) <= I_ANIMAL)) - { - prefixes.push_back("related"); // maybe overkill for Beogh? - } - } - else - { - if (mons_genus(monster->mons_species()) == - mons_genus(foe->mons_species())) - { - prefixes.push_back("related"); - } - } - - const god_type god = foe ? foe->deity() : - crawl_state.arena ? GOD_NO_GOD : - you.religion; - - // Add Beogh to list of prefixes for orcs (hostile and friendly) if you - // worship Beogh. (This assumes your being a Hill Orc, so might have odd - // results in wizard mode.) Don't count charmed or summoned orcs. - if (you.religion == GOD_BEOGH && mons_genus(monster->type) == MONS_ORC - && !monster->has_ench(ENCH_CHARM) && !monster->is_summoned()) - { - if (monster->god == GOD_BEOGH) - prefixes.push_back("beogh"); - else - prefixes.push_back("unbeliever"); - } - else - { - if (is_good_god(god)) - prefixes.push_back("good god"); - else if (is_evil_god(god)) - prefixes.push_back("evil god"); - } - -#ifdef DEBUG_MONSPEAK - { - std::string prefix; - const int size = prefixes.size(); - for (int i = 0; i < size; i++) - { - prefix += prefixes[i]; - prefix += " "; - } - mprf(MSGCH_DIAGNOSTICS, "monster speech lookup for %s: prefix = %s", - monster->name(DESC_PLAIN).c_str(), prefix.c_str()); - } -#endif - - const bool no_foe = (foe == NULL); - const bool no_player = crawl_state.arena - || (!monster->wont_attack() - && (!foe || foe->atype() != ACT_PLAYER)); - const bool mon_foe = (m_foe != NULL); - const bool no_god = no_foe || (mon_foe && foe->deity() == GOD_NO_GOD); - const bool named_foe = !no_foe - && (!mon_foe || (m_foe->is_named() - && m_foe->type != MONS_ROYAL_JELLY)); - const bool no_foe_name = !named_foe - || (mon_foe && (m_foe->flags & MF_NAME_MASK)); - - std::string msg; - - // First, try its exact name. - if (monster->type == MONS_PLAYER_GHOST) - { - // Player ghosts are treated differently. - msg = _player_ghost_speak_str(monster, prefixes); - } - else if (monster->type == MONS_PANDEMONIUM_DEMON) - { - // Pandemonium demons have randomly generated names, so use - // "pandemonium lord" instead. - msg = _get_speak_string(prefixes, "pandemonium lord", monster, - no_player, no_foe, no_foe_name, no_god, - unseen); - } - else - { - if (!monster->mname.empty() && _polyd_can_speak(monster)) - { - msg = _get_speak_string(prefixes, monster->name(DESC_PLAIN), - monster, no_player, no_foe, no_foe_name, - no_god, unseen); - } - - if (msg.empty()) - { - msg = _get_speak_string(prefixes, monster->base_name(DESC_PLAIN), - monster, no_player, no_foe, no_foe_name, - no_god, unseen); - } - } - - // The exact name brought no results, try monster genus. - if ((msg.empty() || msg == "__NEXT") - && mons_genus(monster->type) != monster->type) - { - msg = _get_speak_string(prefixes, - mons_type_name(mons_genus(monster->type), DESC_PLAIN), - monster, no_player, no_foe, no_foe_name, no_god, - unseen); - } - - // __NONE means to be silent, and __NEXT means to try the next, - // less exact method of describing the monster to find a speech - // string. - - if (msg == "__NONE") - { -#ifdef DEBUG_MONSPEAK - mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); -#endif - return (false); - } - - // Now that we're not dealing with a specific monster name, include - // whether or not it can move in the prefix. - if (mons_is_stationary(monster)) - prefixes.insert(prefixes.begin(), "stationary"); - - // Names for the exact monster name and its genus have failed, - // so try the monster's glyph/symbol. - if (msg.empty() || msg == "__NEXT") - { - std::string key = "'"; - - // Database keys are case-insensitve. - if (isupper(mons_char(monster->type))) - key += "cap-"; - - key += mons_char(monster->type); - key += "'"; - msg = _get_speak_string(prefixes, key, monster, no_player, no_foe, - no_foe_name, no_god, unseen); - } - - if (msg == "__NONE") - { -#ifdef DEBUG_MONSPEAK - mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); -#endif - return (false); - } - - // Monster symbol didn't work, try monster shape. Since we're - // dealing with just the monster shape, change the prefix to - // include info on if the monster's intelligence is at odds with - // its shape. - mon_body_shape shape = get_mon_shape(monster); - mon_intel_type intel = mons_intel(monster); - if (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA - && intel < I_NORMAL) - { - prefixes.insert(prefixes.begin(), "stupid"); - } - else if (shape >= MON_SHAPE_QUADRUPED && shape <= MON_SHAPE_FISH) - { - if (mons_char(monster->type) == 'w') - { - if (intel > I_INSECT) - prefixes.insert(prefixes.begin(), "smart"); - else if (intel < I_INSECT) - prefixes.insert(prefixes.begin(), "stupid"); - } - else - { - if (intel > I_ANIMAL) - prefixes.insert(prefixes.begin(), "smart"); - else if (intel < I_ANIMAL) - prefixes.insert(prefixes.begin(), "stupid"); - } - } - else if (shape >= MON_SHAPE_INSECT && shape <= MON_SHAPE_SNAIL) - { - if (intel > I_INSECT) - prefixes.insert(prefixes.begin(), "smart"); - else if (intel < I_INSECT) - prefixes.insert(prefixes.begin(), "stupid"); - } - else if (shape >= MON_SHAPE_PLANT && shape <= MON_SHAPE_BLOB - && intel > I_PLANT) - { - prefixes.insert(prefixes.begin(), "smart"); - } - - if (msg.empty() || msg == "__NEXT") - { - msg = _get_speak_string(prefixes, get_mon_shape_str(shape), monster, - no_player, no_foe, no_foe_name, no_god, - unseen); - } - - if (msg == "__NONE") - { -#ifdef DEBUG_MONSPEAK - mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); -#endif - return (false); - } - - // If we failed to get a message with a winged or tailed humanoid, - // or a naga or centaur, try moving closer to plain humanoid. - if ((msg.empty() || msg == "__NEXT") && shape > MON_SHAPE_HUMANOID - && shape <= MON_SHAPE_NAGA) - { - // If a humanoid monster has both wings and a tail, try removing - // one and then the other to see if we get any results. - if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED) - { - shape = MON_SHAPE_HUMANOID_TAILED; - msg = _get_speak_string(prefixes, get_mon_shape_str(shape), - monster, no_player, no_foe, no_foe_name, - no_god, unseen); - - // Only be silent if both tailed and winged return __NONE. - if (msg.empty() || msg == "__NONE" || msg == "__NEXT") - { - shape = MON_SHAPE_HUMANOID_WINGED; - std::string msg2; - msg2 = _get_speak_string(prefixes, get_mon_shape_str(shape), - monster, no_player, no_foe, - no_foe_name, no_god, unseen); - - if (msg == "__NONE" && msg2 == "__NONE") - { -#ifdef DEBUG_MONSPEAK - mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS); -#endif - return (false); - } - - if (msg2 == "__NONE") - msg2 = ""; - - msg = msg2; - } - } // if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED) - if (msg.empty() || msg == "__NONE" || msg == "__NEXT") - { - shape = MON_SHAPE_HUMANOID; - msg = _get_speak_string(prefixes, get_mon_shape_str(shape), - monster, no_player, no_foe, no_foe_name, - no_god, unseen); - } - } - if (msg.empty() || msg == "__NONE") - { -#ifdef DEBUG_MONSPEAK - mprf(MSGCH_DIAGNOSTICS, "final result: %s!", - (msg.empty() ? "empty" : "\"__NONE\"")); -#endif - return (false); - } - - if (msg == "__NEXT") - { - msg::streams(MSGCH_DIAGNOSTICS) - << "__NEXT used by shape-based speech string for monster '" - << monster->name(DESC_PLAIN) << "'" << std::endl; - return (false); - } - - if (foe == NULL) - msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS"); - else if (foe->atype() == ACT_MONSTER) - { - if (you.can_see(foe)) - msg = replace_all(msg, "__YOU_RESIST", "@The_monster@ resists."); - else - msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS"); - } - - return (mons_speaks_msg(monster, msg, MSGCH_TALK, silence)); -} - -bool mons_speaks_msg(monsters *monster, const std::string &msg, - const msg_channel_type def_chan, const bool silence) -{ - if (!mons_near(monster)) - return (false); - - mon_acting mact(monster); - - // We have a speech string, now parse and act on it. - const std::string _msg = do_mon_str_replacements(msg, monster); - const std::vector lines = split_string("\n", _msg); - - bool noticed = false; // Any messages actually printed? - - for (int i = 0, size = lines.size(); i < size; ++i) - { - std::string line = lines[i]; - - // This function is a little bit of a problem for the message - // channels since some of the messages it generates are "fake" - // warning to scare the player. In order to accomodate this - // intent, we're falsely categorizing various things in the - // function as spells and danger warning... everything else - // just goes into the talk channel -- bwr - // [jpeg] Added MSGCH_TALK_VISUAL for silent "chatter". - msg_channel_type msg_type = def_chan; - - std::string param = ""; - std::string::size_type pos = line.find(":"); - - if (pos != std::string::npos) - param = line.substr(0, pos); - - if (!param.empty()) - { - bool match = true; - - if (param == "DANGER") - msg_type = MSGCH_DANGER; - else if (param == "WARN" && !silence || param == "VISUAL WARN") - msg_type = MSGCH_WARN; - else if (param == "SOUND") - msg_type = MSGCH_SOUND; - else if (param == "VISUAL") - msg_type = MSGCH_TALK_VISUAL; - else if (param == "SPELL" && !silence || param == "VISUAL SPELL") - { - msg_type = monster->friendly() ? MSGCH_FRIEND_SPELL - : MSGCH_MONSTER_SPELL; - } - else if (param == "ENCHANT" && !silence - || param == "VISUAL ENCHANT") - { - msg_type = monster->friendly() ? MSGCH_FRIEND_ENCHANT - : MSGCH_MONSTER_ENCHANT; - } - else if (param == "PLAIN") - msg_type = MSGCH_PLAIN; - else - match = false; - - if (match) - line = line.substr(pos + 1); - } - - const bool old_noticed = noticed; - noticed = true; // Only one case is different. - - // Except for VISUAL, none of the above influence these. - if (line == "__YOU_RESIST" && (!silence || param == "VISUAL")) - canned_msg( MSG_YOU_RESIST ); - else if (line == "__NOTHING_HAPPENS" && (!silence || param == "VISUAL")) - canned_msg( MSG_NOTHING_HAPPENS ); - else if (line == "__MORE" && (!silence || param == "VISUAL")) - more(); - else if (msg_type == MSGCH_TALK_VISUAL && !you.can_see(monster)) - noticed = old_noticed; - else - { - if (you.can_see(monster)) - handle_seen_interrupt(monster); - mpr(line.c_str(), msg_type); - } - } - return (noticed); -} diff --git a/crawl-ref/source/monspeak.h b/crawl-ref/source/monspeak.h deleted file mode 100644 index 9191cebce2..0000000000 --- a/crawl-ref/source/monspeak.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * File: monspeak.h - * Summary: Functions to handle speaking monsters - */ - -#ifndef MONSPEAK_H -#define MONSPEAK_H - -#include "externs.h" - -void maybe_mons_speaks(monsters *monster); -bool mons_speaks(monsters *monster); -bool mons_speaks_msg(monsters *monster, const std::string &msg, - const msg_channel_type def_chan = MSGCH_TALK, - const bool silence = false); - -#endif diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc index ab8dc0dba3..1a9e9e1737 100644 --- a/crawl-ref/source/monster.cc +++ b/crawl-ref/source/monster.cc @@ -19,8 +19,8 @@ #include "misc.h" #include "mon-abil.h" #include "mon-behv.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mtransit.h" #include "random.h" #include "religion.h" @@ -1758,7 +1758,7 @@ bool monsters::pickup_misc(item_def &item, int near) return (pickup(item, MSLOT_MISCELLANY, near)); } -// Eaten items are handled elsewhere, in _handle_pickup() in monstuff.cc. +// Eaten items are handled elsewhere, in _handle_pickup() in mon-stuff.cc. bool monsters::pickup_item(item_def &item, int near, bool force) { // Equipping stuff can be forced when initially equipping monsters. diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc deleted file mode 100644 index 3e37144226..0000000000 --- a/crawl-ref/source/monstuff.cc +++ /dev/null @@ -1,3989 +0,0 @@ -/* - * File: monstuff.cc - * Summary: Misc monster related functions. - * Written by: Linley Henzell - */ - -#include "AppHdr.h" -#include "monstuff.h" - -//#include -//#include -//#include -//#include - -#ifdef TARGET_OS_DOS -#include -#endif - -#include "arena.h" -#include "artefact.h" -#include "attitude-change.h" -#include "cloud.h" -#include "database.h" -#include "delay.h" -#include "dgnevent.h" -#include "directn.h" -#include "fprop.h" -#include "files.h" -#include "food.h" -#include "godabil.h" -#include "hiscores.h" -#include "items.h" -#include "kills.h" -#include "message.h" -#include "misc.h" -#include "mon-behv.h" -#include "mon-iter.h" -#include "monplace.h" -#include "monspeak.h" -#include "notes.h" -#include "options.h" -#include "player.h" -#include "random.h" -#include "religion.h" -#include "spl-mis.h" -#include "spl-util.h" -#include "state.h" -#include "stuff.h" -#include "terrain.h" -#include "transfor.h" -#include "traps.h" -#include "tutorial.h" -#include "view.h" -#include "shout.h" -#include "viewchar.h" -#include "stash.h" -#include "xom.h" - -static bool _wounded_damaged(monster_type mon_type); - -// This function creates an artificial item to represent a mimic's -// appearance. Eventually, mimics could be redone to be more like -// dancing weapons: there'd only be one type and it would look like the -// item it carries. - bwr -void get_mimic_item( const 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.pos = mimic->pos(); - item.link = NON_ITEM; - - int prop = 127 * mimic->pos().x + 269 * mimic->pos().y; - - rng_save_excursion exc; - seed_rng( prop ); - - switch (mimic->type) - { - case MONS_WEAPON_MIMIC: - item.base_type = OBJ_WEAPONS; - item.sub_type = (59 * mimic->pos().x + 79 * mimic->pos().y) - % (WPN_MAX_NONBLESSED + 1); - - prop %= 100; - - if (prop < 20) - make_item_randart(item); - 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->pos().x + 79 * mimic->pos().y) - % NUM_ARMOURS; - - prop %= 100; - - if (prop < 20) - make_item_randart(item); - 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/potions -} - -// 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( const 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( bool decay_potions, bool quiet ) -{ - int count = 0; - int item = ENDOFPACK; - - for (int i = 0; i < ENDOFPACK; i++) - { - if (!you.inv[i].is_valid()) - 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 - && !you_tran_can_wear(you.inv[i]) - && item_is_equipped(you.inv[i])) - { - // Melded items cannot be cursed. - continue; - } - - if (you.inv[i].base_type == OBJ_POTIONS - && (!decay_potions || 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. - if (decay_potions && !quiet) // Just for mummies. - mpr("You feel nervous for a moment...", MSGCH_MONSTER_SPELL); - - if (you.inv[item].base_type == OBJ_POTIONS) - { - int amount; - // Decay at least two of the stack. - if (you.inv[item].quantity <= 2) - amount = you.inv[item].quantity; - else - amount = 2 + random2(you.inv[item].quantity - 1); - - split_potions_into_decay(item, amount); - - if (item_value(you.inv[item], true) / amount > 2) - xom_is_stimulated(32 * amount); - } - else - { - do_curse_item( you.inv[item], false ); - } - - return (true); -} - -void monster_drop_ething(monsters *monster, bool mark_item_origins, - int owner_id) -{ - const bool hostile_grid = feat_destroys_items(grd(monster->pos())); - - bool destroyed = false; - - // Drop weapons & missiles last (ie on top) so others pick up. - for (int i = NUM_MONSTER_SLOTS - 1; i >= 0; i--) - { - int item = monster->inv[i]; - - if (item != NON_ITEM) - { - const bool summoned_item = - testbits(mitm[item].flags, ISFLAG_SUMMONED); - if (hostile_grid || summoned_item) - { - item_was_destroyed(mitm[item], monster->mindex()); - destroy_item( item ); - if (!summoned_item) - destroyed = true; - } - else - { - if (monster->friendly() && mitm[item].is_valid()) - mitm[item].flags |= ISFLAG_DROPPED_BY_ALLY; - - move_item_to_grid(&item, monster->pos()); - - if (mark_item_origins && mitm[item].is_valid()) - origin_set_monster(mitm[item], monster); - } - - monster->inv[i] = NON_ITEM; - } - } - - if (destroyed) - mprf(MSGCH_SOUND, feat_item_destruction_message(grd(monster->pos()))); -} - -monster_type fill_out_corpse(const monsters* monster, item_def& corpse, - bool allow_weightless) -{ - ASSERT(!invalid_monster_type(monster->type)); - corpse.clear(); - - int summon_type; - if (monster->is_summoned(NULL, &summon_type) - || (monster->flags & (MF_BANISHED | MF_HARD_RESET))) - { - return (MONS_NO_MONSTER); - } - - monster_type corpse_class = mons_species(monster->type); - - // If this was a corpse that was temporarily animated then turn the - // monster back into a corpse. - if (mons_class_is_zombified(monster->type) - && (summon_type == SPELL_ANIMATE_DEAD - || summon_type == SPELL_ANIMATE_SKELETON - || summon_type == MON_SUMM_ANIMATE)) - { - corpse_class = mons_zombie_base(monster); - } - - if (corpse_class == MONS_DRACONIAN) - { - if (monster->type == MONS_TIAMAT) - corpse_class = MONS_DRACONIAN; - else - corpse_class = draco_subspecies(monster); - } - - if (monster->has_ench(ENCH_SHAPESHIFTER)) - corpse_class = MONS_SHAPESHIFTER; - else if (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER)) - corpse_class = MONS_GLOWING_SHAPESHIFTER; - - // Doesn't leave a corpse. - if (mons_weight(corpse_class) == 0 && !allow_weightless) - return (MONS_NO_MONSTER); - - corpse.flags = 0; - corpse.base_type = OBJ_CORPSES; - corpse.plus = corpse_class; - corpse.plus2 = 0; // butcher work done - corpse.sub_type = CORPSE_BODY; - corpse.special = FRESHEST_CORPSE; // rot time - corpse.quantity = 1; - corpse.orig_monnum = monster->type + 1; - corpse.props[MONSTER_NUMBER] = short(monster->number); - - corpse.colour = mons_class_colour(corpse_class); - if (corpse.colour == BLACK) - corpse.colour = monster->colour; - - if (!monster->mname.empty()) - { - corpse.props[CORPSE_NAME_KEY] = monster->mname; - corpse.props[CORPSE_NAME_TYPE_KEY] - = (long) (monster->flags & MF_NAME_MASK); - } - else if (mons_is_unique(monster->type)) - { - corpse.props[CORPSE_NAME_KEY] = mons_type_name(monster->type, - DESC_PLAIN); - corpse.props[CORPSE_NAME_TYPE_KEY] = (long) 0; - } - - return (corpse_class); -} - -bool explode_corpse(item_def& corpse, const coord_def& where) -{ - // Don't want chunks to show up behind the player. - los_def ld(where, opc_no_actor); - - if (monster_descriptor(corpse.plus, MDSC_LEAVES_HIDE) - && mons_genus(corpse.plus) == MONS_DRAGON) - { - // Uh... dragon hide is tough stuff and it keeps the monster in - // one piece? More importantly, it prevents a flavor feature - // from becoming a trap for the unwary. - - return (false); - } - - ld.update(); - - int nchunks = 1 + random2(mons_weight(corpse.plus) / 150); - nchunks = stepdown_value(nchunks, 4, 4, 12, 12); - - int ntries = 0; - - corpse.base_type = OBJ_FOOD; - corpse.sub_type = FOOD_CHUNK; - - int blood = nchunks * 3; - - if (food_is_rotten(corpse)) - blood /= 3; - - blood_spray(where, static_cast(corpse.plus), blood); - - while (nchunks > 0 && ntries < 10000) - { - ++ntries; - - coord_def cp = where; - cp.x += random_range(-LOS_RADIUS, LOS_RADIUS); - cp.y += random_range(-LOS_RADIUS, LOS_RADIUS); - -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "Trying to scatter chunk to %d, %d...", - cp.x, cp.y); -#endif - - if (! in_bounds(cp)) - continue; - - if (! ld.see_cell(cp)) - continue; - -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "Cell is visible..."); -#endif - - if (feat_is_solid(grd(cp)) || actor_at(cp)) - continue; - - --nchunks; - - if (feat_destroys_items(grd(cp))) - { - if (!silenced(cp)) - mprf(MSGCH_SOUND, feat_item_destruction_message(grd(cp))); - - continue; - } - -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "Success"); -#endif - - copy_item_to_grid(corpse, cp); - } - - return (true); -} - -// Returns the item slot of a generated corpse, or -1 if no corpse. -int place_monster_corpse(const monsters *monster, bool silent, - bool force) -{ - // The game can attempt to place a corpse for an out-of-bounds monster - // if a shifter turns into a giant spore and explodes. In this - // case we place no corpse since the explosion means anything left - // over would be scattered, tiny chunks of shifter. - if (!in_bounds(monster->pos())) - return (-1); - - // Don't attempt to place corpses within walls, either. - // Currently, this only applies to (shapeshifter) rock worms. - if (feat_is_wall(grd(monster->pos()))) - return (-1); - - item_def corpse; - const monster_type corpse_class = fill_out_corpse(monster, corpse); - - // Don't place a corpse? If a zombified monster is somehow capable - // of leaving a corpse, then always place it. - if (mons_class_is_zombified(monster->type)) - force = true; - - if (corpse_class == MONS_NO_MONSTER || (!force && coinflip())) - return (-1); - - int o = get_item_slot(); - if (o == NON_ITEM) - { - item_was_destroyed(corpse); - return (-1); - } - - mitm[o] = corpse; - - origin_set_monster(mitm[o], monster); - - if ((monster->flags & MF_EXPLODE_KILL) - && explode_corpse(corpse, monster->pos())) - { - // We already have a spray of chunks - destroy_item(o); - return (-1); - } - - if (feat_destroys_items(grd(monster->pos()))) - { - item_was_destroyed(corpse); - destroy_item(o); - return (-1); - } - - // Don't care if 'o' is changed, and it shouldn't be (corpses don't - // stack). - move_item_to_grid(&o, monster->pos()); - if (observe_cell(monster->pos())) - { - if (force && !silent) - { - if (you.can_see(monster)) - simple_monster_message(monster, " turns back into a corpse!"); - else - { - mprf("%s appears out of nowhere!", - mitm[o].name(DESC_CAP_A).c_str()); - } - } - const bool poison = (mons_corpse_effect(corpse_class) == CE_POISONOUS - && player_res_poison() <= 0); - tutorial_dissection_reminder(!poison); - } - - return (o); -} - -static void _tutorial_inspect_kill() -{ - if (Options.tutorial_events[TUT_KILLED_MONSTER]) - learned_something_new(TUT_KILLED_MONSTER); -} - -#ifdef DGL_MILESTONES -static std::string _milestone_kill_verb(killer_type killer) -{ - return (killer == KILL_RESET ? "banished " : "killed "); -} - -static void _check_kill_milestone(const monsters *mons, - killer_type killer, int i) -{ - if (mons->type == MONS_PLAYER_GHOST) - { - std::string milestone = _milestone_kill_verb(killer) + "the ghost of "; - milestone += get_ghost_description(*mons, true); - milestone += "."; - mark_milestone("ghost", milestone); - } - else if (mons_is_unique(mons->type)) - { - mark_milestone("unique", - _milestone_kill_verb(killer) - + mons->name(DESC_NOCAP_THE, true) - + "."); - } -} -#endif // DGL_MILESTONES - -static void _give_monster_experience(monsters *victim, - int killer_index, int experience, - bool victim_was_born_friendly) -{ - if (invalid_monster_index(killer_index)) - return; - - monsters *mon = &menv[killer_index]; - if (!mon->alive()) - return; - - if ((!victim_was_born_friendly || !mon->friendly()) - && !mons_aligned(killer_index, monster_index(victim))) - { - if (mon->gain_exp(experience)) - { - if (you.religion != GOD_SHINING_ONE && you.religion != GOD_BEOGH - || player_under_penance() - || !one_chance_in(3)) - { - return; - } - - // Randomly bless the follower who gained experience. - if (you.religion == GOD_SHINING_ONE - && random2(you.piety) >= piety_breakpoint(0) - || you.religion == GOD_BEOGH - && random2(you.piety) >= piety_breakpoint(2)) - { - bless_follower(mon); - } - } - } -} - -static void _give_adjusted_experience(monsters *monster, killer_type killer, - bool pet_kill, int killer_index, - unsigned int *exp_gain, - unsigned int *avail_gain) -{ - const int experience = exper_value(monster); - - const bool created_friendly = - testbits(monster->flags, MF_CREATED_FRIENDLY); - const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL); - const bool no_xp = monster->has_ench(ENCH_ABJ) || !experience; - const bool already_got_half_xp = testbits(monster->flags, MF_GOT_HALF_XP); - - bool need_xp_msg = false; - if (created_friendly || was_neutral || no_xp) - ; // No experience if monster was created friendly or summoned. - else if (YOU_KILL(killer)) - { - int old_lev = you.experience_level; - if (already_got_half_xp) - gain_exp( experience / 2, exp_gain, avail_gain ); - else - gain_exp( experience, exp_gain, avail_gain ); - - if (old_lev == you.experience_level) - need_xp_msg = true; - } - else if (pet_kill && !already_got_half_xp) - { - int old_lev = you.experience_level; - gain_exp( experience / 2 + 1, exp_gain, avail_gain ); - - if (old_lev == you.experience_level) - need_xp_msg = true; - } - - // FIXME: Since giant spores get detached from mgrd early - // on, we can't tell by this point if they were visible when - // they exploded. Rather than bothering to remember this, we - // just suppress the message. - if (monster->type == MONS_GIANT_SPORE - || monster->type == MONS_BALL_LIGHTNING) - { - need_xp_msg = false; - } - - // Give a message for monsters dying out of sight. - if (need_xp_msg - && exp_gain > 0 - && !you.can_see(monster) - && !crawl_state.arena) - { - mpr("You feel a bit more experienced."); - } - - if (MON_KILL(killer) && !no_xp) - { - _give_monster_experience( monster, killer_index, experience, - created_friendly ); - } -} - -static bool _is_pet_kill(killer_type killer, int i) -{ - if (!MON_KILL(killer)) - return (false); - - if (i == ANON_FRIENDLY_MONSTER) - return (true); - - if (invalid_monster_index(i)) - return (false); - - const monsters *m = &menv[i]; - if (m->friendly()) // This includes enslaved monsters. - return (true); - - // Check if the monster was confused by you or a friendly, which - // makes casualties to this monster collateral kills. - const mon_enchant me = m->get_ench(ENCH_CONFUSION); - return (me.ench == ENCH_CONFUSION - && (me.who == KC_YOU || me.who == KC_FRIENDLY)); -} - -// Elyvilon will occasionally (5% chance) protect the life of one of -// your allies. -static bool _ely_protect_ally(monsters *monster) -{ - if (you.religion != GOD_ELYVILON) - return (false); - - if (!monster->is_holy() - && monster->holiness() != MH_NATURAL - || !monster->friendly() - || !you.can_see(monster) // for simplicity - || !one_chance_in(20)) - { - return (false); - } - - monster->hit_points = 1; - - snprintf(info, INFO_SIZE, " protects %s from harm!%s", - monster->name(DESC_NOCAP_THE).c_str(), - coinflip() ? "" : " You feel responsible."); - - simple_god_message(info); - lose_piety(1); - - return (true); -} - -// Elyvilon retribution effect: Heal hostile monsters that were about to -// be killed by you or one of your friends. -static bool _ely_heal_monster(monsters *monster, killer_type killer, int i) -{ - if (you.religion == GOD_ELYVILON) - return (false); - - god_type god = GOD_ELYVILON; - - if (!you.penance[god] || !god_hates_your_god(god)) - return (false); - - const int ely_penance = you.penance[god]; - - if (monster->friendly() || !one_chance_in(10)) - return (false); - - if (MON_KILL(killer) && !invalid_monster_index(i)) - { - monsters *mon = &menv[i]; - if (!mon->friendly() || !one_chance_in(3)) - return (false); - - if (!mons_near(monster)) - return (false); - } - else if (!YOU_KILL(killer)) - return (false); - -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "monster hp: %d, max hp: %d", - monster->hit_points, monster->max_hit_points); -#endif - - monster->hit_points = std::min(1 + random2(ely_penance/3), - monster->max_hit_points); - -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "new hp: %d, ely penance: %d", - monster->hit_points, ely_penance); -#endif - - snprintf(info, INFO_SIZE, "%s heals %s%s", - god_name(god, false).c_str(), - monster->name(DESC_NOCAP_THE).c_str(), - monster->hit_points * 2 <= monster->max_hit_points ? "." : "!"); - - god_speaks(god, info); - dec_penance(god, 1 + random2(monster->hit_points/2)); - - return (true); -} - -static bool _yred_enslave_soul(monsters *monster, killer_type killer) -{ - if (you.religion == GOD_YREDELEMNUL && mons_enslaved_body_and_soul(monster) - && mons_near(monster) && killer != KILL_RESET - && killer != KILL_DISMISSED) - { - yred_make_enslaved_soul(monster, player_under_penance()); - return (true); - } - - return (false); -} - -static bool _beogh_forcibly_convert_orc(monsters *monster, killer_type killer, - int i) -{ - if (you.religion == GOD_BEOGH - && mons_species(monster->type) == MONS_ORC - && !monster->is_summoned() && !monster->is_shapeshifter() - && !player_under_penance() && you.piety >= piety_breakpoint(2) - && mons_near(monster)) - { - bool convert = false; - - if (YOU_KILL(killer)) - convert = true; - else if (MON_KILL(killer) && !invalid_monster_index(i)) - { - monsters *mon = &menv[i]; - if (is_follower(mon) && !one_chance_in(3)) - convert = true; - } - - // Orcs may convert to Beogh under threat of death, either from - // you or, less often, your followers. In both cases, the - // checks are made against your stats. You're the potential - // messiah, after all. - if (convert) - { -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "Death convert attempt on %s, HD: %d, " - "your xl: %d", - monster->name(DESC_PLAIN).c_str(), - monster->hit_dice, - you.experience_level); -#endif - if (random2(you.piety) >= piety_breakpoint(0) - && random2(you.experience_level) >= random2(monster->hit_dice) - // Bias beaten-up-conversion towards the stronger orcs. - && random2(monster->hit_dice) > 2) - { - beogh_convert_orc(monster, true, MON_KILL(killer)); - return (true); - } - } - } - - return (false); -} - -static bool _monster_avoided_death(monsters *monster, killer_type killer, int i) -{ - if (monster->hit_points < -25 - || monster->hit_points < -monster->max_hit_points - || monster->max_hit_points <= 0 - || monster->hit_dice < 1) - { - return (false); - } - - // Elyvilon specials. - if (_ely_protect_ally(monster)) - return (true); - if (_ely_heal_monster(monster, killer, i)) - return (true); - - // Yredelemnul special. - if (_yred_enslave_soul(monster, killer)) - return (true); - - // Beogh special. - if (_beogh_forcibly_convert_orc(monster, killer, i)) - return (true); - - return (false); -} - -static bool _slime_vault_in_los() -{ - bool in_los = false; - - for (int x = 0; x < GXM && !in_los; ++x) - { - for (int y = 0; y < GYM; ++y) - { - if ((grd[x][y] == DNGN_STONE_WALL - || grd[x][y] == DNGN_CLEAR_STONE_WALL) - && observe_cell(coord_def(x, y))) - { - in_los = true; - break; - } - } - } - - return (in_los); -} - -static bool _slime_vault_to_glass(bool silent) -{ - unset_level_flags(LFLAG_NO_TELE_CONTROL, silent); - - bool in_los = false; - - if (!silent) - in_los = _slime_vault_in_los(); - - replace_area_wrapper(DNGN_STONE_WALL, DNGN_CLEAR_ROCK_WALL); - // In case it was already vitrified, but then it's less noticeable. - replace_area_wrapper(DNGN_CLEAR_STONE_WALL, DNGN_CLEAR_ROCK_WALL); - - if (!silent) - { - if (in_los) - { - mpr("Suddenly, all colour oozes out of the stone walls.", - MSGCH_MONSTER_ENCHANT); - } - else - { - mpr("You feel a strange vibration for a moment.", - MSGCH_MONSTER_ENCHANT); - } - } - - remove_all_jiyva_altars(); - - if (silenced(you.pos())) - { - god_speaks(GOD_JIYVA, "With an infernal shudder, the power ruling " - "this place vanishes!"); - } - else - { - god_speaks(GOD_JIYVA, "With infernal noise, the power ruling this " - "place vanishes!"); - } - - return (true); -} - -static bool _slime_vault_to_glass_offlevel() -{ - return _slime_vault_to_glass(true); -} - -static bool _slime_vault_to_glass_onlevel() -{ - return _slime_vault_to_glass(false); -} - -static bool _slime_vault_to_floor(bool silent) -{ - unset_level_flags(LFLAG_NO_TELE_CONTROL, silent); - - bool in_los = false; - - if (!silent) - in_los = _slime_vault_in_los(); - - replace_area_wrapper(DNGN_STONE_WALL, DNGN_FLOOR); - // In case it was already vitrified, but then it's less noticeable. - replace_area_wrapper(DNGN_CLEAR_STONE_WALL, DNGN_FLOOR); - - if (silenced(you.pos())) - mpr("An unexplained breeze blows through the dungeon.", MSGCH_GOD); - else - mpr("You hear the sound of toppling stones.", MSGCH_GOD); - - return (true); -} - -static bool _slime_vault_to_floor_offlevel() -{ - return _slime_vault_to_floor(true); -} - -static bool _slime_vault_to_floor_onlevel() -{ - return _slime_vault_to_floor(false); -} - -void slime_vault_change(bool glass) -{ - const level_id target(BRANCH_SLIME_PITS, 6); - if (is_existing_level(target)) - { - if (glass) - { - apply_to_level(target, - target == level_id::current() ? - _slime_vault_to_glass_onlevel : - _slime_vault_to_glass_offlevel); - } - else - { - apply_to_level(target, - target == level_id::current() ? - _slime_vault_to_floor_onlevel : - _slime_vault_to_floor_offlevel); - } - } -} - -static void _fire_monster_death_event(monsters *monster, - killer_type killer, - int i, bool polymorph) -{ - int type = monster->type; - - // Treat whatever the Royal Jelly polymorphed into as if it were still - // the Royal Jelly (but if a player chooses the character name - // "shaped Royal Jelly" don't unlock the vaults when the player's - // ghost is killed). - if (monster->mname == "shaped Royal Jelly" - && monster->type != MONS_PLAYER_GHOST) - { - type = MONS_ROYAL_JELLY; - } - - // Banished monsters aren't technically dead, so no death event - // for them. - if (killer == KILL_RESET) - { - // Give player a hint that banishing the Royal Jelly means the - // Slime:6 vaults stay locked. - if (type == MONS_ROYAL_JELLY) - { - if (you.can_see(monster)) - mpr("You feel a great sense of loss."); - else - mpr("You feel a great sense of loss, and the brush of the " - "Abyss."); - } - return; - } - - dungeon_events.fire_event( - dgn_event(DET_MONSTER_DIED, monster->pos(), 0, - monster_index(monster), killer)); - - // Don't unlock the Slime:6 vaults if the "death" was actually the - // Royal Jelly polymorphing into something else; the player still - // has to kill whatever it polymorphed into. - if (type == MONS_ROYAL_JELLY && !polymorph) - { - you.royal_jelly_dead = true; - - if (jiyva_is_dead()) - slime_vault_change(true); - } -} - -static void _mummy_curse(monsters* monster, killer_type killer, int index) -{ - int pow; - - switch (killer) - { - // Mummy killed by trap or something other than the player or - // another monster, so no curse. - case KILL_MISC: - // Mummy sent to the Abyss wasn't actually killed, so no curse. - case KILL_RESET: - case KILL_DISMISSED: - return; - - default: - break; - } - - switch (monster->type) - { - case MONS_MENKAURE: - case MONS_MUMMY: pow = 1; break; - case MONS_GUARDIAN_MUMMY: pow = 3; break; - case MONS_MUMMY_PRIEST: pow = 8; break; - case MONS_GREATER_MUMMY: pow = 11; break; - case MONS_KHUFU: pow = 15; break; - - default: - mpr("Unknown mummy type.", MSGCH_DIAGNOSTICS); - return; - } - - // beam code might give an index of MHITYOU for the player. - if (YOU_KILL(killer)) - index = NON_MONSTER; - - // Killed by a Zot trap, a god, etc. - if (index != NON_MONSTER && invalid_monster_index(index)) - return; - - actor* target; - if (index == NON_MONSTER) - target = &you; - else - { - // Mummies committing suicide don't cause a death curse. - if (index == monster->mindex()) - return; - target = &menv[index]; - } - - // Mummy was killed by a giant spore or ball lightning? - if (!target->alive()) - return; - - if ((monster->type == MONS_MUMMY || monster->type == MONS_MENKAURE) && YOU_KILL(killer)) - curse_an_item(true); - else - { - if (index == NON_MONSTER) - { - mpr("You feel extremely nervous for a moment...", - MSGCH_MONSTER_SPELL); - } - else if (you.can_see(target)) - { - mprf(MSGCH_MONSTER_SPELL, "A malignant aura surrounds %s.", - target->name(DESC_NOCAP_THE).c_str()); - } - MiscastEffect(target, monster_index(monster), SPTYP_NECROMANCY, - pow, random2avg(88, 3), "a mummy death curse"); - } -} - -static bool _spore_goes_pop(monsters *monster, killer_type killer, - int killer_index, bool pet_kill, bool wizard) -{ - if (monster->hit_points > 0 || monster->hit_points <= -15 || wizard - || killer == KILL_RESET || killer == KILL_DISMISSED) - { - return (false); - } - - bolt beam; - const int type = monster->type; - - beam.is_tracer = false; - beam.is_explosion = true; - beam.beam_source = monster_index(monster); - beam.type = dchar_glyph(DCHAR_FIRED_BURST); - beam.source = monster->pos(); - beam.target = monster->pos(); - beam.thrower = crawl_state.arena ? KILL_MON - : monster->attitude == ATT_FRIENDLY ? KILL_YOU : KILL_MON; - beam.aux_source.clear(); - - if (YOU_KILL(killer)) - beam.aux_source = "set off by themselves"; - else if (pet_kill) - beam.aux_source = "set off by their pet"; - - const char* msg = NULL; - const char* sanct_msg = NULL; - if (type == MONS_GIANT_SPORE) - { - beam.flavour = BEAM_SPORE; - beam.damage = dice_def(3, 15); - beam.name = "explosion of spores"; - beam.colour = LIGHTGREY; - beam.ex_size = 2; - msg = "The giant spore explodes!"; - sanct_msg = "By Zin's power, the giant spore's explosion is " - "contained."; - } - else if (type == MONS_BALL_LIGHTNING) - { - beam.flavour = BEAM_ELECTRICITY; - beam.damage = dice_def(3, 20); - beam.name = "blast of lightning"; - beam.colour = LIGHTCYAN; - beam.ex_size = coinflip() ? 3 : 2; - msg = "The ball lightning explodes!"; - sanct_msg = "By Zin's power, the ball lightning's explosion " - "is contained."; - } - else - { - msg::streams(MSGCH_DIAGNOSTICS) << "Unknown spore type: " - << static_cast(type) - << std::endl; - return (false); - } - - bool saw = false; - if (you.can_see(monster)) - { - saw = true; - viewwindow(false); - if (is_sanctuary(monster->pos())) - mpr(sanct_msg, MSGCH_GOD); - else - mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, msg); - } - - if (is_sanctuary(monster->pos())) - return (false); - - // Detach monster from the grid first, so it doesn't get hit by - // its own explosion. (GDL) - mgrd(monster->pos()) = NON_MONSTER; - - // The explosion might cause a monster to be placed where the spore - // used to be, so make sure that mgrd() doesn't get cleared a second - // time (causing the new monster to become floating) when - // monster->reset() is called. - monster->set_position(coord_def(0,0)); - - // Exploding kills the monster a bit earlier than normal. - monster->hit_points = -16; - if (saw) - viewwindow(false); - - // FIXME: show_more == mons_near(monster) - beam.explode(); - - // Monster died in explosion, so don't re-attach it to the grid. - return (true); -} - -void _monster_die_cloud(const monsters* monster, bool corpse, bool silent, - bool summoned) -{ - // Chaos spawn always leave behind a cloud of chaos. - if (monster->type == MONS_CHAOS_SPAWN) - { - summoned = true; - corpse = false; - } - - if (!summoned) - return; - - std::string prefix = " "; - if (corpse) - { - if (mons_weight(mons_species(monster->type)) == 0) - return; - - prefix = "'s corpse "; - } - - std::string msg = summoned_poof_msg(monster); - msg += "!"; - - cloud_type cloud = CLOUD_NONE; - if (msg.find("smoke") != std::string::npos) - cloud = random_smoke_type(); - else if (msg.find("chaos") != std::string::npos) - cloud = CLOUD_CHAOS; - - if (!silent) - simple_monster_message(monster, (prefix + msg).c_str()); - - if (cloud != CLOUD_NONE) - { - place_cloud(cloud, monster->pos(), 1 + random2(3), - monster->kill_alignment()); - } -} - -// XXX: Another hackish function! May do weird things if multiple copies of -// the band have been placed using wizard mode. {due} -static void _elven_twin_died(monsters* twin) -{ - bool found_duvessa = false; - bool found_dowan = false; - monsters *monster; - - for (int i = 0; i < MAX_MONSTERS; ++i) - { - monster = &menv[i]; - if (monster->alive() && monster->type == MONS_DUVESSA) - { - found_duvessa = true; - break; - } - else if (monster->alive() && monster->type == MONS_DOWAN) - { - found_dowan = true; - break; - } - } - - if ((found_duvessa || found_dowan) && mons_near(monster)) - { - // Will generate strings such as 'Duvessa_Duvessa_dies' or, alternately - // 'Dowan_Dowan_dies', but as neither will match, these can safely be - // ignored. - std::string key = "_" + monster->name(DESC_CAP_THE, true) + "_" - + twin->name(DESC_CAP_THE) + "_dies_"; - - if (!monster->observable()) - key += "invisible_"; - - std::string death_message = getSpeakString(key); - - if (!death_message.empty()) - mons_speaks_msg(monster, death_message, MSGCH_TALK, silenced(you.pos())); - } - - if (found_duvessa && mons_near(monster)) - { - // Provides its own flavour message. - monster->go_berserk(true); - } - else if (found_dowan && mons_near(monster)) - { - // Doesn't provide any message, so needs one, but only if visible. - if (monster->observable()) - simple_monster_message(monster, " turns to flee."); - monster->add_ench(mon_enchant(ENCH_FEAR, 0, KC_YOU)); - behaviour_event(monster, ME_SCARE, MHITNOT); - } -} - -void pikel_band_neutralise () -{ - // XXX: This is a really ugly hack. It should be replaced by something else - // when band tracking is available. This assumes that the only human monsters - // with MF_BAND_MEMBER are Pikel's band members. - bool message_made = false; - - for (monster_iterator mi; mi; ++mi) - { - if (mi->type == MONS_HUMAN - && testbits(mi->flags, MF_BAND_MEMBER)) - { - if (mi->observable() && !message_made) - { - mpr("Pikel's slaves thank you for their freedom."); - message_made = true; - } - mons_pacify(*mi); - } - } -} - -static void _hogs_to_humans() -{ - // Simplification: if, in a rare event, another hog which was not created - // as a part of Kirke's band happens to be on the level, the player can't - // tell them apart anyway. - // On the other hand, hogs which left the level are too far away to be - // affected by the magic of Kirke's death. - // FIXME: If another monster was polymorphed into a hog by Kirke's - // porkalator spell, they should be handled specially... - int any = 0, human = 0; - - for (monster_iterator mi; mi; ++mi) - { - if (!mi->type == MONS_HOG) - continue; - - const bool could_see = you.can_see(*mi); - - // XXX: This resets the size of slime creatures, the number - // of heads a hydra has, and the number of spikes a manticore - // has. Plus it also changes the colour of a draconian which - // has a sub-type. And it re-rolls the spellbook the monster - // has. - if (mi->number == 0) - mi->type = MONS_HUMAN; - else - mi->type = (monster_type) (mi->number - 1); - - mi->number = 0; - define_monster(**mi); - - const bool can_see = you.can_see(*mi); - - // A monster changing factions while in the arena messes up - // arena book-keeping. - if (!crawl_state.arena) - { - // * A monster's attitude shouldn't downgrade from friendly - // or good-neutral because you helped it. It'd suck to - // lose a permanent ally that way. - // - // * A monster has to be smart enough to realize that you - // helped it. - if (mi->attitude == ATT_HOSTILE - && mons_intel(*mi) >= I_NORMAL) - { - mi->attitude = ATT_GOOD_NEUTRAL; - mi->flags |= MF_WAS_NEUTRAL; - } - } - - behaviour_event(*mi, ME_EVAL); - - if (could_see && can_see) - { - any++; - if (mi->type == MONS_HUMAN) - human++; - } - else if (could_see && !can_see) - mpr("The hog vanishes!"); - else if (!could_see && can_see) - mprf("%s appears from out of thin air!", - mi->name(DESC_CAP_A).c_str()); - } - - if (any == 1) - { - if (any == human) - mpr("No longer under Kirke's spell, the hog turns into a human!"); - else - mpr("No longer under Kirke's spell, the hog returns to its " - "original form!"); - } - else if (any > 1) - { - if (any == human) - mpr("No longer under Kirke's spell, all hogs revert to their " - "human forms!"); - else - mpr("No longer under Kirke's spell, all hogs revert to their " - "original forms!"); - } - - // Revert the player as well. - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_PIG) - untransform(); -} - -static int _tentacle_too_far(monsters *head, monsters *tentacle) -{ - // The Shoals produce no disjoint bodies of water. - // If this ever changes, we'd need to check if the head and tentacle - // are still in the same pool. - // XXX: Actually, using Fedhas's Sunlight power you can separate pools... - return grid_distance(head->pos(), tentacle->pos()) > LOS_RADIUS; -} - -void mons_relocated(monsters *monster) -{ - if (monster->type == MONS_KRAKEN) - { - int headnum = monster_index(monster); - - if (invalid_monster_index(headnum)) - return; - - for (monster_iterator mi; mi; ++mi) - { - if (mi->type == MONS_KRAKEN_TENTACLE - && (int)mi->number == headnum - && _tentacle_too_far(monster, *mi)) - { - monster_die(*mi, KILL_RESET, -1, true, false); - } - } - } - else if (monster->type == MONS_KRAKEN_TENTACLE) - { - if (invalid_monster_index(monster->number) - || menv[monster->number].type != MONS_KRAKEN - || _tentacle_too_far(&menv[monster->number], monster)) - { - monster_die(monster, KILL_RESET, -1, true, false); - } - } -} - -static int _destroy_tentacles(monsters *head) -{ - int tent = 0; - int headnum = monster_index(head); - - if (invalid_monster_index(headnum)) - return 0; - - for (monster_iterator mi; mi; ++mi) - { - if (mi->type == MONS_KRAKEN_TENTACLE - && (int)mi->number == headnum) - { - if (mons_near(*mi)) - tent++; - mi->hurt(*mi, INSTANT_DEATH); - } - } - return tent; -} - -// Returns the slot of a possibly generated corpse or -1. -int monster_die(monsters *monster, killer_type killer, - int killer_index, bool silent, bool wizard) -{ - if (invalid_monster(monster)) - return (-1); - - // If a monster was banished to the Abyss and then killed there, - // then its death wasn't a banishment. - if (you.level_type == LEVEL_ABYSS) - monster->flags &= ~MF_BANISHED; - - if (!silent && _monster_avoided_death(monster, killer, killer_index)) - return (-1); - - crawl_state.inc_mon_acting(monster); - - ASSERT(!( YOU_KILL(killer) && crawl_state.arena )); - - mons_clear_trapping_net(monster); - - you.remove_beholder(monster); - - // Clear auto exclusion now the monster is killed -- if we know about it. - if (mons_near(monster) || wizard) - remove_auto_exclude(monster); - - int summon_type = 0; - int duration = 0; - const bool summoned = monster->is_summoned(&duration, &summon_type); - const int monster_killed = monster_index(monster); - const bool hard_reset = testbits(monster->flags, MF_HARD_RESET); - const bool gives_xp = (!summoned && !mons_class_flag(monster->type, - M_NO_EXP_GAIN)); - - const bool drop_items = !hard_reset; - - const bool mons_reset(killer == KILL_RESET || killer == KILL_DISMISSED); - - const bool submerged = monster->submerged(); - - bool in_transit = false; - -#ifdef DGL_MILESTONES - if (!crawl_state.arena) - _check_kill_milestone(monster, killer, killer_index); -#endif - - // Award experience for suicide if the suicide was caused by the - // player. - if (MON_KILL(killer) && monster_killed == killer_index) - { - if (monster->confused_by_you()) - { - ASSERT(!crawl_state.arena); - killer = KILL_YOU_CONF; - } - } - else if (MON_KILL(killer) && monster->has_ench(ENCH_CHARM)) - { - ASSERT(!crawl_state.arena); - killer = KILL_YOU_CONF; // Well, it was confused in a sense... (jpeg) - } - - // Take note! - if (!mons_reset && !crawl_state.arena && MONST_INTERESTING(monster)) - { - take_note(Note(NOTE_KILL_MONSTER, - monster->type, monster->friendly(), - monster->full_name(DESC_NOCAP_A).c_str())); - } - - // From time to time Trog gives you a little bonus. - if (killer == KILL_YOU && you.berserk()) - { - if (you.religion == GOD_TROG - && !player_under_penance() && you.piety > random2(1000)) - { - const int bonus = 3 + random2avg( 10, 2 ); - - you.duration[DUR_BERSERKER] += bonus; - you.duration[DUR_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)) - { - const int bonus = 2 + random2(4); - - you.duration[DUR_BERSERKER] += bonus; - you.duration[DUR_MIGHT] += bonus; - haste_player(bonus); - - mpr("Your amulet glows a violent red."); - } - } - - if (you.prev_targ == monster_killed) - { - you.prev_targ = MHITNOT; - crawl_state.cancel_cmd_repeat(); - } - - if (killer == KILL_YOU) - crawl_state.cancel_cmd_repeat(); - - const bool pet_kill = _is_pet_kill(killer, killer_index); - - bool did_death_message = false; - - if (monster->type == MONS_GIANT_SPORE - || monster->type == MONS_BALL_LIGHTNING) - { - did_death_message = - _spore_goes_pop(monster, killer, killer_index, pet_kill, wizard); - } - else if (monster->type == MONS_FIRE_VORTEX - || monster->type == MONS_SPATIAL_VORTEX) - { - if (!silent && !mons_reset) - { - simple_monster_message(monster, " dissipates!", - MSGCH_MONSTER_DAMAGE, MDAM_DEAD); - silent = true; - } - - if (monster->type == MONS_FIRE_VORTEX && !wizard && !mons_reset - && !submerged) - { - place_cloud(CLOUD_FIRE, monster->pos(), 2 + random2(4), - monster->kill_alignment()); - } - - if (killer == KILL_RESET) - killer = KILL_DISMISSED; - } - else if (monster->type == MONS_SIMULACRUM_SMALL - || monster->type == MONS_SIMULACRUM_LARGE) - { - if (!silent && !mons_reset) - { - simple_monster_message(monster, " vapourises!", - MSGCH_MONSTER_DAMAGE, MDAM_DEAD); - silent = true; - } - - if (!wizard && !mons_reset && !submerged) - { - place_cloud(CLOUD_COLD, monster->pos(), 2 + random2(4), - monster->kill_alignment()); - } - - if (killer == KILL_RESET) - killer = KILL_DISMISSED; - } - else if (monster->type == MONS_DANCING_WEAPON) - { - if (!hard_reset) - { - if (killer == KILL_RESET) - killer = KILL_DISMISSED; - } - - if (!silent && !hard_reset) - { - int w_idx = monster->inv[MSLOT_WEAPON]; - if (w_idx != NON_ITEM && !(mitm[w_idx].flags & ISFLAG_SUMMONED)) - { - simple_monster_message(monster, " falls from the air.", - MSGCH_MONSTER_DAMAGE, MDAM_DEAD); - silent = true; - } - else - killer = KILL_RESET; - } - } - - const bool death_message = !silent && !did_death_message - && mons_near(monster) - && (monster->visible_to(&you) - || crawl_state.arena); - const bool exploded = monster->flags & MF_EXPLODE_KILL; - - const bool created_friendly = testbits(monster->flags, MF_CREATED_FRIENDLY); - bool anon = (killer_index == ANON_FRIENDLY_MONSTER); - const mon_holy_type targ_holy = monster->holiness(); - - switch (killer) - { - case KILL_YOU: // You kill in combat. - case KILL_YOU_MISSILE: // You kill by missile or beam. - case KILL_YOU_CONF: // You kill by confusion. - { - const bool bad_kill = god_hates_killing(you.religion, monster); - const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL); - - if (death_message) - { - if (killer == KILL_YOU_CONF - && (anon || !invalid_monster_index(killer_index))) - { - mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "%s is %s!", - monster->name(DESC_CAP_THE).c_str(), - exploded ? "blown up" : - _wounded_damaged(monster->type) ? "destroyed" - : "killed"); - } - else - { - mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "You %s %s!", - exploded ? "blow up" : - _wounded_damaged(monster->type) ? "destroy" - : "kill", - monster->name(DESC_NOCAP_THE).c_str()); - } - - if ((created_friendly || was_neutral) && gives_xp) - mpr("That felt strangely unrewarding."); - } - - // Killing triggers tutorial lesson. - if (gives_xp) - _tutorial_inspect_kill(); - - // Prevent summoned creatures from being good kills. - if (bad_kill || !created_friendly && gives_xp) - { - if (targ_holy == MH_NATURAL) - { - did_god_conduct(DID_KILL_LIVING, - monster->hit_dice, true, monster); - - if (monster->is_evil()) - { - did_god_conduct(DID_KILL_NATURAL_EVIL, - monster->hit_dice, true, monster); - } - } - else if (targ_holy == MH_UNDEAD) - { - did_god_conduct(DID_KILL_UNDEAD, - monster->hit_dice, true, monster); - } - else if (targ_holy == MH_DEMONIC) - { - did_god_conduct(DID_KILL_DEMON, - monster->hit_dice, true, monster); - } - - // Zin hates chaotic beings. - if (monster->is_chaotic()) - { - did_god_conduct(DID_KILL_CHAOTIC, - monster->hit_dice, true, monster); - } - - // jmf: Trog hates wizards. - if (monster->is_actual_spellcaster()) - { - did_god_conduct(DID_KILL_WIZARD, - monster->hit_dice, true, monster); - } - - // Beogh hates priests of other gods. - if (monster->is_priest()) - { - did_god_conduct(DID_KILL_PRIEST, - monster->hit_dice, true, monster); - } - - if (mons_is_slime(monster)) - { - did_god_conduct(DID_KILL_SLIME, monster->hit_dice, - true, monster); - } - - if (fedhas_protects(monster)) - { - did_god_conduct(DID_KILL_PLANT, monster->hit_dice, - true, monster); - } - - // Cheibriados hates fast monsters. - if (mons_is_fast(monster)) - { - did_god_conduct(DID_KILL_FAST, monster->hit_dice, - true, monster); - } - - // Holy kills are always noticed. - if (monster->is_holy()) - { - did_god_conduct(DID_KILL_HOLY, monster->hit_dice, - true, monster); - } - } - - // Divine health and mana restoration doesn't happen when - // killing born-friendly monsters. The mutation still - // applies, however. - if (player_mutation_level(MUT_DEATH_STRENGTH) - || (!created_friendly && gives_xp - && (you.religion == GOD_MAKHLEB - || you.religion == GOD_SHINING_ONE - && monster->is_evil()) - && !player_under_penance() - && random2(you.piety) >= piety_breakpoint(0))) - { - 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 && gives_xp - && (you.religion == GOD_MAKHLEB - || you.religion == GOD_VEHUMET - || you.religion == GOD_SHINING_ONE - && monster->is_evil()) - && !player_under_penance() - && random2(you.piety) >= piety_breakpoint(0)) - { - if (you.magic_points < you.max_magic_points) - { - mpr("You feel your power returning."); - inc_mp(1 + random2(monster->hit_dice / 2), false); - } - } - - // Randomly bless a follower. - if (!created_friendly - && gives_xp - && (you.religion == GOD_BEOGH - && random2(you.piety) >= piety_breakpoint(2)) - && !player_under_penance()) - { - bless_follower(); - } - - if (you.duration[DUR_DEATH_CHANNEL] - && monster->holiness() == MH_NATURAL - && mons_can_be_zombified(monster) - && gives_xp) - { - const monster_type spectre_type = mons_species(monster->type); - - // Don't allow 0-headed hydras to become spectral hydras. - if (spectre_type != MONS_HYDRA || monster->number != 0) - { - const int spectre = - create_monster( - mgen_data(MONS_SPECTRAL_THING, BEH_FRIENDLY, - 0, 0, monster->pos(), MHITYOU, - 0, static_cast(you.attribute[ATTR_DIVINE_DEATH_CHANNEL]), - spectre_type, monster->number)); - - if (spectre != -1) - { - if (death_message) - mpr("A glowing mist starts to gather..."); - - name_zombie(&menv[spectre], monster); - } - } - } - break; - } - - case KILL_MON: // Monster kills in combat. - case KILL_MON_MISSILE: // Monster kills by missile or beam. - if (!silent) - { - const char* msg = - exploded ? " is blown up!" : - _wounded_damaged(monster->type) ? " is destroyed!" - : " dies!"; - simple_monster_message(monster, msg, MSGCH_MONSTER_DAMAGE, - MDAM_DEAD); - } - - if (crawl_state.arena) - break; - - // No piety loss if god gifts killed by other monsters. - // Also, dancing weapons aren't really friendlies. - if (monster->friendly() - && monster->type != MONS_DANCING_WEAPON) - { - did_god_conduct(DID_FRIEND_DIED, 1 + (monster->hit_dice / 2), - true, monster); - } - - if (pet_kill && fedhas_protects(monster)) - { - did_god_conduct(DID_ALLY_KILLED_PLANT, 1 + (monster->hit_dice / 2), - true, monster); - } - - // Trying to prevent summoning abuse here, so we're trying to - // prevent summoned creatures from being done_good kills. Only - // affects creatures which were friendly when summoned. - if (!created_friendly && gives_xp && pet_kill - && (anon || !invalid_monster_index(killer_index))) - { - bool notice = false; - - monsters *killer_mon = NULL; - if (!anon) - { - killer_mon = &menv[killer_index]; - - // If the killer is already dead treat it like an - // anonymous monster. - if (killer_mon->type == MONS_NO_MONSTER) - anon = true; - } - - const mon_holy_type killer_holy = - anon ? MH_NATURAL : killer_mon->holiness(); - - if (you.religion == GOD_SHINING_ONE - || you.religion == GOD_YREDELEMNUL - || you.religion == GOD_KIKUBAAQUDGHA - || you.religion == GOD_VEHUMET - || you.religion == GOD_MAKHLEB - || you.religion == GOD_LUGONU - || !anon && mons_is_god_gift(killer_mon)) - { - if (killer_holy == MH_UNDEAD) - { - const bool confused = - anon ? false : !killer_mon->friendly(); - - // Yes, these are hacks, but they make sure that - // confused monsters doing kills are not - // referred to as "slaves", and I think it's - // okay that e.g. Yredelemnul ignores kills done - // by confused monsters as opposed to enslaved - // or friendly ones. (jpeg) - if (targ_holy == MH_NATURAL) - { - notice |= did_god_conduct( - !confused ? DID_LIVING_KILLED_BY_UNDEAD_SLAVE : - DID_LIVING_KILLED_BY_SERVANT, - monster->hit_dice); - } - else if (targ_holy == MH_UNDEAD) - { - notice |= did_god_conduct( - !confused ? DID_UNDEAD_KILLED_BY_UNDEAD_SLAVE : - DID_UNDEAD_KILLED_BY_SERVANT, - monster->hit_dice); - } - else if (targ_holy == MH_DEMONIC) - { - notice |= did_god_conduct( - !confused ? DID_DEMON_KILLED_BY_UNDEAD_SLAVE : - DID_DEMON_KILLED_BY_SERVANT, - monster->hit_dice); - } - } - // Yes, we are splitting undead pets from the others - // as a way to focus Necromancy vs. Summoning - // (ignoring Haunt here)... at least we're being - // nice and putting the natural creature summons - // together with the demonic ones. Note that - // Vehumet gets a free pass here since those - // followers are assumed to come from summoning - // spells... the others are from invocations (TSO, - // Makhleb, Kiku). - bwr - else if (targ_holy == MH_NATURAL) - { - notice |= did_god_conduct(DID_LIVING_KILLED_BY_SERVANT, - monster->hit_dice); - - if (monster->is_evil()) - { - notice |= did_god_conduct( - DID_NATURAL_EVIL_KILLED_BY_SERVANT, - monster->hit_dice); - } - } - else if (targ_holy == MH_UNDEAD) - { - notice |= did_god_conduct(DID_UNDEAD_KILLED_BY_SERVANT, - monster->hit_dice); - } - else if (targ_holy == MH_DEMONIC) - { - notice |= did_god_conduct(DID_DEMON_KILLED_BY_SERVANT, - monster->hit_dice); - } - } - - // Holy kills are always noticed. - if (monster->is_holy()) - { - if (killer_holy == MH_UNDEAD) - { - const bool confused = - anon ? false : !killer_mon->friendly(); - - // Yes, this is a hack, but it makes sure that - // confused monsters doing kills are not - // referred to as "slaves", and I think it's - // okay that Yredelemnul ignores kills done by - // confused monsters as opposed to enslaved or - // friendly ones. (jpeg) - notice |= did_god_conduct( - !confused ? DID_HOLY_KILLED_BY_UNDEAD_SLAVE : - DID_HOLY_KILLED_BY_SERVANT, - monster->hit_dice); - } - else - notice |= did_god_conduct(DID_HOLY_KILLED_BY_SERVANT, - monster->hit_dice); - } - - if (you.religion == GOD_VEHUMET - && notice - && !player_under_penance() - && random2(you.piety) >= piety_breakpoint(0)) - { - // 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); - } - } - - if (you.religion == GOD_SHINING_ONE - && monster->is_evil() - && !player_under_penance() - && random2(you.piety) >= piety_breakpoint(0) - && !invalid_monster_index(killer_index)) - { - // Randomly bless the follower who killed. - if (!one_chance_in(3) && killer_mon->alive() - && bless_follower(killer_mon)) - { - break; - } - - if (killer_mon->alive() - && killer_mon->hit_points < killer_mon->max_hit_points) - { - simple_monster_message(killer_mon, - " looks invigorated."); - killer_mon->heal(1 + random2(monster->hit_dice / 4)); - } - } - - if (you.religion == GOD_BEOGH - && random2(you.piety) >= piety_breakpoint(2) - && !player_under_penance() - && !one_chance_in(3) - && !invalid_monster_index(killer_index)) - { - // Randomly bless the follower who killed. - bless_follower(killer_mon); - } - } - break; - - // Monster killed by trap/inanimate thing/itself/poison not from you. - case KILL_MISC: - if (!silent) - { - const char* msg = - exploded ? " is blown up!" : - _wounded_damaged(monster->type) ? " is destroyed!" - : " dies!"; - simple_monster_message(monster, msg, 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??? - // Pacified monsters leave the level when this happens. - - // KILL_RESET monsters no longer lose their whole inventory, only - // items they were generated with. - if (monster->pacified() || !monster->needs_transit()) - { - // A banished monster that doesn't go on the transit list - // loses all items. - if (!monster->is_summoned()) - monster->destroy_inventory(); - break; - } - - // Monster goes to the Abyss. - monster->flags |= MF_BANISHED; - monster->set_transit(level_id(LEVEL_ABYSS)); - in_transit = true; - monster->destroy_inventory(); - // Make monster stop patrolling and/or travelling. - monster->patrol_point.reset(); - monster->travel_path.clear(); - monster->travel_target = MTRAV_NONE; - break; - - case KILL_DISMISSED: - break; - - default: - monster->destroy_inventory(); - break; - } - - // Make sure Boris has a foe to address. - if (monster->foe == MHITNOT) - { - if (!monster->wont_attack() && !crawl_state.arena) - monster->foe = MHITYOU; - else if (!invalid_monster_index(killer_index)) - monster->foe = killer_index; - } - - if (!silent && !wizard && observe_cell(monster->pos())) - { - // Make sure that the monster looks dead. - if (monster->alive() && !in_transit && (!summoned || duration > 0)) - monster->hit_points = -1; - mons_speaks(monster); - } - - if (monster->type == MONS_BORIS && !in_transit) - { - // XXX: Actual blood curse effect for Boris? -- bwr - - // Now that Boris is dead, he's a valid target for monster - // creation again. -- bwr - you.unique_creatures[monster->type] = false; - // And his vault can be placed again. - you.uniq_map_names.erase("uniq_boris"); - } - else if (monster->type == MONS_KIRKE && !in_transit) - { - _hogs_to_humans(); - } - else if (monster->type == MONS_PIKEL) - { - // His slaves don't care if he's dead or not, just whether or not - // he goes away. - pikel_band_neutralise(); - } - else if (monster->type == MONS_KRAKEN) - { - if (_destroy_tentacles(monster) && !in_transit) - { - mpr("The kraken is slain, and its tentacles slide " - "back into the water like the carrion they now are."); - } - } - else if ((monster->type == MONS_DOWAN || monster->type == MONS_DUVESSA) - && mons_near(monster)) - { - _elven_twin_died(monster); - } - else if (!monster->is_summoned()) - { - if (mons_genus(monster->type) == MONS_MUMMY) - _mummy_curse(monster, killer, killer_index); - } - - if (!wizard && !submerged) - _monster_die_cloud(monster, !mons_reset, silent, summoned); - - int corpse = -1; - if (!mons_reset) - { - // Have to add case for disintegration effect here? {dlb} - if (!summoned) - corpse = place_monster_corpse(monster, silent); - } - - if (!mons_reset && !crawl_state.arena) - { - you.kills->record_kill(monster, killer, pet_kill); - - kill_category kc = - (killer == KILL_YOU || killer == KILL_YOU_MISSILE) ? KC_YOU : - (pet_kill)? KC_FRIENDLY : - KC_OTHER; - - unsigned int exp_gain = 0, avail_gain = 0; - _give_adjusted_experience(monster, killer, pet_kill, killer_index, - &exp_gain, &avail_gain); - - PlaceInfo& curr_PlaceInfo = you.get_place_info(); - PlaceInfo delta; - - delta.mon_kill_num[kc]++; - delta.mon_kill_exp += exp_gain; - delta.mon_kill_exp_avail += avail_gain; - - you.global_info += delta; - you.global_info.assert_validity(); - - curr_PlaceInfo += delta; - curr_PlaceInfo.assert_validity(); - } - - _fire_monster_death_event(monster, killer, killer_index, false); - - if (crawl_state.arena) - arena_monster_died(monster, killer, killer_index, silent, corpse); - - const coord_def mwhere = monster->pos(); - if (drop_items) - monster_drop_ething(monster, YOU_KILL(killer) || pet_kill); - else - { - // Destroy the items belonging to MF_HARD_RESET monsters so they - // don't clutter up mitm[]. - monster->destroy_inventory(); - } - - if (!silent && !wizard && !mons_reset && corpse != -1 - && !(monster->flags & MF_KNOWN_MIMIC) - && monster->is_shapeshifter()) - { - simple_monster_message(monster, "'s shape twists and changes " - "as it dies."); - } - - // If we kill an invisible monster reactivate autopickup. - if (mons_near(monster) && !monster->visible_to(&you)) - autotoggle_autopickup(false); - - crawl_state.dec_mon_acting(monster); - monster_cleanup(monster); - - // Force redraw for monsters that die. - if (observe_cell(mwhere)) - { - view_update_at(mwhere); - update_screen(); - } - - return (corpse); -} - -// Clean up after a dead monster. -void monster_cleanup(monsters *monster) -{ - crawl_state.mon_gone(monster); - - unsigned int monster_killed = monster_index(monster); - monster->reset(); - - for (monster_iterator mi; mi; ++mi) - if (mi->foe == monster_killed) - mi->foe = MHITNOT; - - if (you.pet_target == monster_killed) - you.pet_target = MHITNOT; -} - -// If you're invis and throw/zap whatever, alerts menv to your position. -void alert_nearby_monsters(void) -{ - // 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 - for (monster_iterator mi(&you.get_los()); mi; ++mi) - if (!mi->asleep()) - behaviour_event(*mi, ME_ALERT, MHITYOU); -} - -static bool _valid_morph(monsters *monster, monster_type new_mclass) -{ - const dungeon_feature_type current_tile = grd(monster->pos()); - - // 'morph targets are _always_ "base" classes, not derived ones. - new_mclass = mons_species(new_mclass); - - // [ds] Non-base draconians are much more trouble than their HD - // suggests. - if (mons_genus(new_mclass) == MONS_DRACONIAN - && new_mclass != MONS_DRACONIAN - && !player_in_branch(BRANCH_HALL_OF_ZOT) - && !one_chance_in(10)) - { - return (false); - } - - // Various inappropriate polymorph targets. - if (mons_class_holiness(new_mclass) != monster->holiness() - || mons_class_flag(new_mclass, M_UNIQUE) // no uniques - || 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 - - // These require manual setting of mons.base_monster to indicate - // what they are a skeleton/zombie/simulacrum/spectral thing of, - // which we currently aren't smart enough to handle. - || mons_class_is_zombified(new_mclass) - - // These require manual setting of the ghost demon struct to - // indicate their characteristics, which we currently aren't - // smart enough to handle. - || mons_is_ghost_demon(new_mclass) - - // Only for use by game testers or in the arena. - || new_mclass == MONS_TEST_SPAWNER - - // Other poly-unsuitable things. - || new_mclass == MONS_ORB_GUARDIAN - || mons_is_statue(new_mclass) - - // The spell on Prince Ribbit can't be broken so easily. - || (new_mclass == MONS_HUMAN && monster->type == MONS_PRINCE_RIBBIT)) - { - return (false); - } - - // Determine if the monster is happy on current tile. - return (monster_habitable_grid(new_mclass, current_tile)); -} - -static bool _is_poly_power_unsuitable( poly_power_type power, - int src_pow, int tgt_pow, int relax ) -{ - switch (power) - { - case PPT_LESS: - return (tgt_pow > src_pow - 3 + (relax * 3) / 2) - || (power == PPT_LESS && (tgt_pow < src_pow - (relax / 2))); - case PPT_MORE: - return (tgt_pow < src_pow + 2 - relax) - || (power == PPT_MORE && (tgt_pow > src_pow + relax)); - default: - case PPT_SAME: - return (tgt_pow < src_pow - relax) - || (tgt_pow > src_pow + (relax * 3) / 2); - } -} - -// If targetc == RANDOM_MONSTER, then relpower indicates the desired -// power of the new monster, relative to the current monster. -// Relaxation still takes effect when needed, no matter what relpower -// says. -bool monster_polymorph(monsters *monster, monster_type targetc, - poly_power_type power, - bool force_beh) -{ - ASSERT(!(monster->flags & MF_TAKING_STAIRS)); - ASSERT(!(monster->flags & MF_BANISHED) || you.level_type == LEVEL_ABYSS); - - std::string str_polymon; - int source_power, target_power, relax; - int tries = 1000; - - // 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 = 1; - - if (targetc == RANDOM_MONSTER) - { - do - { - // Pick a monster that's guaranteed happy at this grid. - targetc = random_monster_at_grid(monster->pos()); - - // Valid targets are always base classes ([ds] which is unfortunate - // in that well-populated monster classes will dominate polymorphs). - targetc = mons_species(targetc); - - target_power = mons_power(targetc); - - if (one_chance_in(200)) - relax++; - - if (relax > 50) - return (simple_monster_message(monster, " shudders.")); - } - while (tries-- && (!_valid_morph(monster, targetc) - || _is_poly_power_unsuitable(power, source_power, - target_power, relax))); - } - - if (!_valid_morph(monster, targetc)) - { - return (simple_monster_message(monster, - " looks momentarily different.")); - } - - // Messaging. - bool can_see = you.can_see(monster); - bool can_see_new = !mons_class_flag(targetc, M_INVIS) || you.can_see_invisible(); - - bool need_note = false; - std::string old_name = monster->full_name(DESC_CAP_A); - - // If old monster is visible to the player, and is interesting, - // then note why the interesting monster went away. - if (can_see && MONST_INTERESTING(monster)) - need_note = true; - - std::string new_name = ""; - if (monster->type == MONS_OGRE && targetc == MONS_TWO_HEADED_OGRE) - str_polymon = " grows a second head"; - else - { - if (monster->is_shapeshifter()) - str_polymon = " changes into "; - else if (targetc == MONS_PULSATING_LUMP) - str_polymon = " degenerates into "; - else if (you.religion == GOD_JIYVA - && (targetc == MONS_DEATH_OOZE - || targetc == MONS_OOZE - || targetc == MONS_JELLY - || targetc == MONS_BROWN_OOZE - || targetc == MONS_SLIME_CREATURE - || targetc == MONS_GIANT_AMOEBA - || targetc == MONS_ACID_BLOB - || targetc == MONS_AZURE_JELLY)) - { - // Message used for the Slimify ability. - str_polymon = " quivers uncontrollably and liquefies into "; - } - else - str_polymon = " evaporates and reforms as "; - - if (!can_see_new) - { - new_name = "something unseen"; - str_polymon += "something you cannot see"; - } - else - { - str_polymon += mons_type_name(targetc, DESC_NOCAP_A); - - if (targetc == MONS_PULSATING_LUMP) - str_polymon += " of flesh"; - } - } - str_polymon += "!"; - - bool player_messaged = can_see - && simple_monster_message(monster, str_polymon.c_str()); - - // Even if the monster transforms from one type that can behold the - // player into a different type which can also behold the player, - // the polymorph disrupts the beholding process. Do this before - // changing monster->type, since unbeholding can only happen while - // the monster is still a mermaid/siren. - you.remove_beholder(monster); - - // Inform listeners that the original monster is gone. - _fire_monster_death_event(monster, KILL_MISC, NON_MONSTER, true); - - // the actual polymorphing: - unsigned long flags = - monster->flags & ~(MF_INTERESTING | MF_SEEN | MF_ATT_CHANGE_ATTEMPT - | MF_WAS_IN_VIEW | MF_BAND_MEMBER - | MF_HONORARY_UNDEAD | MF_KNOWN_MIMIC); - - std::string name; - - // Preserve the names of uniques and named monsters. - if (!monster->mname.empty()) - name = monster->mname; - else if (mons_is_unique(monster->type)) - { - flags |= MF_INTERESTING; - - name = monster->name(DESC_PLAIN, true); - if (monster->type == MONS_ROYAL_JELLY) - { - name = "shaped Royal Jelly"; - flags |= MF_NAME_SUFFIX; - } - else if (monster->type == MONS_LERNAEAN_HYDRA) - { - name = "shaped Lernaean hydra"; - flags |= MF_NAME_SUFFIX; - } - - // "Blork the orc" and similar. - const size_t the_pos = name.find(" the "); - if (the_pos != std::string::npos) - name = name.substr(0, the_pos); - } - - const monster_type real_targetc = - (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER)) ? MONS_GLOWING_SHAPESHIFTER : - (monster->has_ench(ENCH_SHAPESHIFTER)) ? MONS_SHAPESHIFTER - : targetc; - - const god_type god = - (player_will_anger_monster(real_targetc) - || (you.religion == GOD_BEOGH - && mons_species(real_targetc) != MONS_ORC)) ? GOD_NO_GOD - : monster->god; - - if (god == GOD_NO_GOD) - flags &= ~MF_GOD_GIFT; - - const int old_hp = monster->hit_points; - const int old_hp_max = monster->max_hit_points; - const bool old_mon_caught = monster->caught(); - const char old_ench_countdown = monster->ench_countdown; - - mon_enchant abj = monster->get_ench(ENCH_ABJ); - mon_enchant charm = monster->get_ench(ENCH_CHARM); - mon_enchant neutral = monster->get_ench(ENCH_NEUTRAL); - mon_enchant shifter = monster->get_ench(ENCH_GLOWING_SHAPESHIFTER, - ENCH_SHAPESHIFTER); - mon_enchant sub = monster->get_ench(ENCH_SUBMERGED); - mon_enchant summon = monster->get_ench(ENCH_SUMMON); - mon_enchant tp = monster->get_ench(ENCH_TP); - - monster_spells spl = monster->spells; - const bool need_save_spells - = (!name.empty() - && (!monster->can_use_spells() - || monster->is_actual_spellcaster())); - - // deal with mons_sec - monster->type = targetc; - monster->base_monster = MONS_NO_MONSTER; - monster->number = 0; - - // Note: define_monster() will clear out all enchantments! - bwr - define_monster(monster_index(monster)); - - monster->mname = name; - monster->flags = flags; - monster->god = god; - - // Keep spells for named monsters, but don't override innate ones - // for dragons and the like. This means that Sigmund polymorphed - // into a goblin will still cast spells, but if he ends up as a - // swamp drake he'll breathe fumes and, if polymorphed further, - // won't remember his spells anymore. - if (need_save_spells - && (!monster->can_use_spells() || monster->is_actual_spellcaster())) - { - monster->spells = spl; - } - - monster->add_ench(abj); - monster->add_ench(charm); - monster->add_ench(neutral); - monster->add_ench(shifter); - monster->add_ench(sub); - monster->add_ench(summon); - monster->add_ench(tp); - - // Allows for handling of submerged monsters which polymorph into - // monsters that can't submerge on this square. - if (monster->has_ench(ENCH_SUBMERGED) - && !monster_can_submerge(monster, grd(monster->pos()))) - { - monster->del_ench(ENCH_SUBMERGED); - } - - monster->ench_countdown = old_ench_countdown; - - if (mons_class_flag(monster->type, M_INVIS)) - monster->add_ench(ENCH_INVIS); - - if (!player_messaged && you.can_see(monster)) - { - mprf("%s appears out of thin air!", monster->name(DESC_CAP_A).c_str()); - autotoggle_autopickup(false); - player_messaged = true; - } - - monster->hit_points = monster->max_hit_points - * ((old_hp * 100) / old_hp_max) / 100 - + random2(monster->max_hit_points); - - monster->hit_points = std::min(monster->max_hit_points, - monster->hit_points); - - // Don't kill it. - monster->hit_points = std::max(monster->hit_points, 1); - - monster->speed_increment = 67 + random2(6); - - monster_drop_ething(monster); - - // New monster type might be interesting. - mark_interesting_monst(monster); - if (new_name.empty()) - new_name = monster->full_name(DESC_NOCAP_A); - - if (need_note - || can_see && you.can_see(monster) && MONST_INTERESTING(monster)) - { - take_note(Note(NOTE_POLY_MONSTER, 0, 0, old_name.c_str(), - new_name.c_str())); - - if (you.can_see(monster)) - monster->flags |= MF_SEEN; - } - - // If new monster is visible to player, then we've seen it. - if (you.can_see(monster)) - { - seen_monster(monster); - // If the player saw both the beginning and end results of a - // shifter changing, then s/he knows it must be a shifter. - if (can_see && shifter.ench != ENCH_NONE) - monster->flags |= MF_KNOWN_MIMIC; - } - - if (old_mon_caught) - check_net_will_hold_monster(monster); - - if (!force_beh) - player_angers_monster(monster); - - // Xom likes watching monsters being polymorphed. - xom_is_stimulated(monster->is_shapeshifter() ? 16 : - power == PPT_LESS || monster->friendly() ? 32 : - power == PPT_SAME ? 64 : 128); - - return (player_messaged); -} - -// If the returned value is mon.pos(), then nothing was found. -static coord_def _random_monster_nearby_habitable_space(const monsters& mon, - bool allow_adjacent, - bool respect_los) -{ - const bool respect_sanctuary = mon.wont_attack(); - - coord_def target; - int tries; - - for (tries = 0; tries < 150; ++tries) - { - const coord_def delta(random2(13) - 6, random2(13) - 6); - - // Check that we don't get something too close to the - // starting point. - if (delta.origin()) - continue; - - if (delta.rdist() == 1 && !allow_adjacent) - continue; - - // Update target. - target = delta + mon.pos(); - - // Check that the target is valid and survivable. - if (!in_bounds(target)) - continue; - - if (!monster_habitable_grid(&mon, grd(target))) - continue; - - if (respect_sanctuary && is_sanctuary(target)) - continue; - - if (target == you.pos()) - continue; - - // Check that we didn't go through clear walls. - if (num_feats_between(mon.pos(), target, - DNGN_CLEAR_ROCK_WALL, - DNGN_CLEAR_PERMAROCK_WALL, - true, true) > 0) - { - continue; - } - - if (respect_los && !mon.see_cell(target)) - continue; - - // Survived everything, break out (with a good value of target.) - break; - } - - if (tries == 150) - target = mon.pos(); - - return (target); -} - -bool monster_blink(monsters *monster, bool quiet) -{ - coord_def near = _random_monster_nearby_habitable_space(*monster, false, - true); - if (near == monster->pos()) - return (false); - - if (!quiet) - simple_monster_message(monster, " blinks!"); - - if (!(monster->flags & MF_WAS_IN_VIEW)) - monster->seen_context = "thin air"; - - const coord_def oldplace = monster->pos(); - if (!monster->move_to_pos(near)) - return (false); - - // Leave a purple cloud. - place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3), - monster->kill_alignment()); - - monster->check_redraw(oldplace); - monster->apply_location_effects(oldplace); - - mons_relocated(monster); - - return (true); -} - -bool mon_can_be_slimified(monsters *monster) -{ - const mon_holy_type holi = monster->holiness(); - - return (holi == MH_UNDEAD - || holi == MH_NATURAL - && !mons_is_slime(monster)); -} - -void slimify_monster(monsters *mon, bool hostile) -{ - if (mon->holiness() == MH_UNDEAD) - monster_polymorph(mon, MONS_DEATH_OOZE); - else - { - const int x = mon->hit_dice + (coinflip() ? 1 : -1) * random2(5); - - if (x < 3) - monster_polymorph(mon, MONS_OOZE); - else if (x >= 3 && x < 5) - monster_polymorph(mon, MONS_JELLY); - else if (x >= 5 && x < 7) - monster_polymorph(mon, MONS_BROWN_OOZE); - else if (x >= 7 && x <= 11) - { - if (coinflip()) - monster_polymorph(mon, MONS_SLIME_CREATURE); - else - monster_polymorph(mon, MONS_GIANT_AMOEBA); - } - else - { - if (coinflip()) - monster_polymorph(mon, MONS_ACID_BLOB); - else - monster_polymorph(mon, MONS_AZURE_JELLY); - } - } - - if (!mons_eats_items(mon)) - mon->add_ench(ENCH_EAT_ITEMS); - - if (!hostile) - mon->attitude = ATT_STRICT_NEUTRAL; - else - mon->attitude = ATT_HOSTILE; - - mons_make_god_gift(mon, GOD_JIYVA); -} - -static bool _habitat_okay( const monsters *monster, dungeon_feature_type targ ) -{ - return (monster_habitable_grid(monster, targ)); -} - -// 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 that 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 wants to -// swap pets to their death, we can let that go). -- bwr -bool swap_places(monsters *monster) -{ - coord_def loc; - if (swap_check(monster, loc)) - { - swap_places(monster, loc); - return true; - } - return false; -} - -// Swap monster to this location. Player is swapped elsewhere. -bool swap_places(monsters *monster, const coord_def &loc) -{ - ASSERT(map_bounds(loc)); - ASSERT(_habitat_okay(monster, grd(loc))); - - if (monster_at(loc)) - { - mpr("Something prevents you from swapping places."); - return (false); - } - - mpr("You swap places."); - - mgrd(monster->pos()) = NON_MONSTER; - - monster->moveto(loc); - - mgrd(monster->pos()) = monster_index(monster); - - return true; -} - -// Returns true if this is a valid swap for this monster. If true, then -// the valid location is set in loc. (Otherwise loc becomes garbage.) -bool swap_check(monsters *monster, coord_def &loc, bool quiet) -{ - loc = you.pos(); - - // Don't move onto dangerous terrain. - if (is_feat_dangerous(grd(monster->pos()))) - { - canned_msg(MSG_UNTHINKING_ACT); - return (false); - } - - if (monster->caught()) - { - if (!quiet) - simple_monster_message(monster, " is held in a net!"); - return (false); - } - - // First try: move monster onto your position. - bool swap = _habitat_okay( monster, grd(loc) ); - - // Choose an appropriate habitat square at random around the target. - if (!swap) - { - int num_found = 0; - - for (adjacent_iterator ai(you.pos()); ai; ++ai) - if (!monster_at(*ai) && _habitat_okay(monster, grd(*ai)) - && one_chance_in(++num_found)) - { - loc = *ai; - } - - if (num_found) - swap = true; - } - - if (!swap && !quiet) - { - // Might not be ideal, but it's better than insta-killing - // the monster... maybe try for a short blink instead? -- bwr - simple_monster_message( monster, " resists." ); - // FIXME: AI_HIT_MONSTER isn't ideal. - interrupt_activity( AI_HIT_MONSTER, monster ); - } - - return (swap); -} - -// Given an adjacent monster, returns true if the monster can hit it -// (the monster should not be submerged, be submerged in shallow water -// if the monster has a polearm, or be submerged in anything if the -// monster has tentacles). -bool monster_can_hit_monster(monsters *monster, const monsters *targ) -{ - if (!targ->submerged() || monster->has_damage_type(DVORP_TENTACLE)) - return (true); - - if (grd(targ->pos()) != DNGN_SHALLOW_WATER) - return (false); - - const item_def *weapon = monster->weapon(); - return (weapon && weapon_skill(*weapon) == SK_POLEARMS); -} - -void mons_get_damage_level(const monsters* monster, std::string& desc, - mon_dam_level_type& dam_level) -{ - if (monster->hit_points <= monster->max_hit_points / 6) - { - desc += "almost "; - desc += _wounded_damaged(monster->type) ? "destroyed" : "dead"; - dam_level = MDAM_ALMOST_DEAD; - return; - } - - if (monster->hit_points <= monster->max_hit_points / 4) - { - desc += "severely "; - dam_level = MDAM_SEVERELY_DAMAGED; - } - else if (monster->hit_points <= monster->max_hit_points / 3) - { - desc += "heavily "; - dam_level = MDAM_HEAVILY_DAMAGED; - } - else if (monster->hit_points <= monster->max_hit_points * 3 / 4) - { - desc += "moderately "; - dam_level = MDAM_MODERATELY_DAMAGED; - } - else if (monster->hit_points < monster->max_hit_points) - { - desc += "lightly "; - dam_level = MDAM_LIGHTLY_DAMAGED; - } - else - { - desc += "not "; - dam_level = MDAM_OKAY; - } - - desc += _wounded_damaged(monster->type) ? "damaged" : "wounded"; -} - -std::string get_wounds_description(const monsters *monster) -{ - if (!monster->alive() || monster->hit_points == monster->max_hit_points) - return ""; - - if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS)) - return ""; - - std::string desc; - mon_dam_level_type dam_level; - mons_get_damage_level(monster, desc, dam_level); - - desc.insert(0, " is "); - desc += "."; - - return desc; -} - -void print_wounds(const monsters *monster) -{ - if (!monster->alive() || monster->hit_points == monster->max_hit_points) - return; - - if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS)) - return; - - std::string desc; - mon_dam_level_type dam_level; - mons_get_damage_level(monster, desc, dam_level); - - desc.insert(0, " is "); - desc += "."; - simple_monster_message(monster, desc.c_str(), MSGCH_MONSTER_DAMAGE, - dam_level); -} - -// (true == 'damaged') [constructs, undead, etc.] -// and (false == 'wounded') [living creatures, etc.] {dlb} -static bool _wounded_damaged(monster_type mon_type) -{ - // this schema needs to be abstracted into real categories {dlb}: - const mon_holy_type holi = mons_class_holiness(mon_type); - - return (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT); -} - -// If _mons_find_level_exits() is ever expanded to handle more grid -// types, this should be expanded along with it. -static void _mons_indicate_level_exit(const monsters *mon) -{ - const dungeon_feature_type feat = grd(mon->pos()); - const bool is_shaft = (get_trap_type(mon->pos()) == TRAP_SHAFT); - - if (feat_is_gate(feat)) - simple_monster_message(mon, " passes through the gate."); - else if (feat_is_travelable_stair(feat)) - { - command_type dir = feat_stair_direction(feat); - simple_monster_message(mon, - make_stringf(" %s the %s.", - dir == CMD_GO_UPSTAIRS ? "goes up" : - dir == CMD_GO_DOWNSTAIRS ? "goes down" - : "takes", - feat_is_escape_hatch(feat) ? "escape hatch" - : "stairs").c_str()); - } - else if (is_shaft) - { - simple_monster_message(mon, - make_stringf(" %s the shaft.", - mons_flies(mon) ? "goes down" - : "jumps into").c_str()); - } -} - -void make_mons_leave_level(monsters *mon) -{ - if (mon->pacified()) - { - if (you.can_see(mon)) - _mons_indicate_level_exit(mon); - - // Pacified monsters leaving the level take their stuff with - // them. - mon->flags |= MF_HARD_RESET; - monster_die(mon, KILL_DISMISSED, NON_MONSTER); - } -} - -// Checks whether there is a straight path from p1 to p2 that passes -// through features >= allowed. -// If it exists, such a path may be missed; on the other hand, it -// is not guaranteed that p2 is visible from p1 according to LOS rules. -// Not symmetric. -bool can_go_straight(const coord_def& p1, const coord_def& p2, - dungeon_feature_type allowed) -{ - if (distance(p1, p2) > get_los_radius_sq()) - return (false); - - dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE; - if (allowed != DNGN_UNSEEN) - max_disallowed = static_cast(allowed - 1); - - return (!num_feats_between(p1, p2, DNGN_UNSEEN, max_disallowed, - true, true)); -} - -// The default suitable() function for choose_random_nearby_monster(). -bool choose_any_monster(const monsters* mon) -{ - return (true); -} - -// Find a nearby monster and return its index, including you as a -// possibility with probability weight. suitable() should return true -// for the type of monster wanted. -// If prefer_named is true, named monsters (including uniques) are twice -// as likely to get chosen compared to non-named ones. -// If prefer_priest is true, priestly monsters (including uniques) are -// twice as likely to get chosen compared to non-priestly ones. -monsters *choose_random_nearby_monster(int weight, - bool (*suitable)(const monsters* mon), - bool in_sight, bool prefer_named, - bool prefer_priest) -{ - return choose_random_monster_on_level(weight, suitable, in_sight, true, - prefer_named, prefer_priest); -} - -monsters *choose_random_monster_on_level(int weight, - bool (*suitable)(const monsters* mon), - bool in_sight, bool near_by, - bool prefer_named, bool prefer_priest) -{ - monsters *chosen = NULL; - - // A radius_iterator with radius == max(GXM, GYM) will sweep the - // whole level. - radius_iterator ri(you.pos(), near_by ? 9 : std::max(GXM, GYM), - true, in_sight); - - for (; ri; ++ri) - { - if (monsters *mon = monster_at(*ri)) - { - if (suitable(mon)) - { - // FIXME: if the intent is to favour monsters - // named by $DEITY, we should set a flag on the - // monster (something like MF_DEITY_PREFERRED) and - // use that instead of checking the name, given - // that other monsters can also have names. - - // True, but it's currently only used for orcs, and - // Blork and Urug also being preferred to non-named orcs - // is fine, I think. Once more gods name followers (and - // prefer them) that should be changed, of course. (jpeg) - - // Named or priestly monsters have doubled chances. - int mon_weight = 1; - - if (prefer_named && mon->is_named()) - mon_weight++; - - if (prefer_priest && mon->is_priest()) - mon_weight++; - - if (x_chance_in_y(mon_weight, (weight += mon_weight))) - chosen = mon; - } - } - } - - return chosen; -} - -// 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(const monsters *monster, const char *event, - msg_channel_type channel, - int param, - description_level_type descrip) -{ - - if ((mons_near(monster) || crawl_state.arena) - && (channel == MSGCH_MONSTER_SPELL || channel == MSGCH_FRIEND_SPELL - || monster->visible_to(&you) || crawl_state.arena)) - { - std::string msg = monster->name(descrip); - msg += event; - msg = apostrophise_fixup(msg); - - if (channel == MSGCH_PLAIN && monster->wont_attack()) - channel = MSGCH_FRIEND_ACTION; - - mpr(msg.c_str(), channel, param); - return (true); - } - - return (false); -} - -bool mons_avoids_cloud(const monsters *monster, cloud_struct cloud, - bool placement) -{ - bool extra_careful = placement; - cloud_type cl_type = cloud.type; - - if (placement) - extra_careful = true; - - switch (cl_type) - { - case CLOUD_MIASMA: - // Even the dumbest monsters will avoid miasma if they can. - return (!monster->res_rotting()); - - case CLOUD_FIRE: - case CLOUD_FOREST_FIRE: - if (monster->res_fire() > 1) - return (false); - - if (extra_careful) - return (true); - - if (mons_intel(monster) >= I_ANIMAL && monster->res_fire() < 0) - return (true); - - if (monster->hit_points >= 15 + random2avg(46, 5)) - return (false); - break; - - case CLOUD_STINK: - if (monster->res_poison() > 0) - return (false); - - if (extra_careful) - return (true); - - if (mons_intel(monster) >= I_ANIMAL && monster->res_poison() < 0) - return (true); - - if (x_chance_in_y(monster->hit_dice - 1, 5)) - return (false); - - if (monster->hit_points >= random2avg(19, 2)) - return (false); - break; - - case CLOUD_COLD: - if (monster->res_cold() > 1) - return (false); - - if (extra_careful) - return (true); - - if (mons_intel(monster) >= I_ANIMAL && monster->res_cold() < 0) - return (true); - - if (monster->hit_points >= 15 + random2avg(46, 5)) - return (false); - break; - - case CLOUD_POISON: - if (monster->res_poison() > 0) - return (false); - - if (extra_careful) - return (true); - - if (mons_intel(monster) >= I_ANIMAL && monster->res_poison() < 0) - return (true); - - if (monster->hit_points >= random2avg(37, 4)) - return (false); - break; - - case CLOUD_GREY_SMOKE: - if (placement) - return (false); - - // This isn't harmful, but dumb critters might think so. - if (mons_intel(monster) > I_ANIMAL || coinflip()) - return (false); - - if (monster->res_fire() > 0) - return (false); - - if (monster->hit_points >= random2avg(19, 2)) - return (false); - break; - - case CLOUD_RAIN: - // Fiery monsters dislike the rain. - if (monster->is_fiery() && extra_careful) - return (true); - - // We don't care about what's underneath the rain cloud if we can fly. - if (monster->flight_mode() != FL_NONE) - return (false); - - // These don't care about deep water. - if (monster_habitable_grid(monster, DNGN_DEEP_WATER)) - return (false); - - // This position could become deep water, and they might drown. - if (grd(cloud.pos) == DNGN_SHALLOW_WATER) - return (true); - - // Otherwise, it's safe for everyone else. - return (false); - - break; - - default: - break; - } - - // Exceedingly dumb creatures will wander into harmful clouds. - if (is_harmless_cloud(cl_type) - || mons_intel(monster) == I_PLANT && !extra_careful) - { - return (false); - } - - // If we get here, the cloud is potentially harmful. - return (true); -} - -// Like the above, but allow a monster to move from one damaging cloud -// to another, even if they're of different types. -bool mons_avoids_cloud(const monsters *monster, int cloud_num, - cloud_type *cl_type, bool placement) -{ - if (cloud_num == EMPTY_CLOUD) - { - if (cl_type != NULL) - *cl_type = CLOUD_NONE; - - return (false); - } - - const cloud_struct &cloud = env.cloud[cloud_num]; - - if (cl_type != NULL) - *cl_type = cloud.type; - - // Is the target cloud okay? - if (!mons_avoids_cloud(monster, cloud, placement)) - return (false); - - // If we're already in a cloud that we'd want to avoid then moving - // from one to the other is okay. - if (!in_bounds(monster->pos()) || monster->pos() == cloud.pos) - return (true); - - const int our_cloud_num = env.cgrid(monster->pos()); - - if (our_cloud_num == EMPTY_CLOUD) - return (true); - - const cloud_struct &our_cloud = env.cloud[our_cloud_num]; - - return (!mons_avoids_cloud(monster, our_cloud, true)); -} - -// Returns a rough estimate of damage from throwing the wielded weapon. -int mons_thrown_weapon_damage(const item_def *weap) -{ - if (!weap || get_weapon_brand(*weap) != SPWPN_RETURNING) - return (0); - - return std::max(0, (property(*weap, PWPN_DAMAGE) + weap->plus2 / 2)); -} - -int mons_weapon_damage_rating(const item_def &launcher) -{ - return (property(launcher, PWPN_DAMAGE) + launcher.plus2); -} - -// Returns a rough estimate of damage from firing/throwing missile. -int mons_missile_damage(monsters *mons, const item_def *launch, - const item_def *missile) -{ - if (!missile || (!launch && !is_throwable(mons, *missile))) - return (0); - - const int missile_damage = property(*missile, PWPN_DAMAGE) / 2 + 1; - const int launch_damage = launch? property(*launch, PWPN_DAMAGE) : 0; - return std::max(0, launch_damage + missile_damage); -} - -// Given the monster's current weapon and alt weapon (either or both of -// which may be NULL), works out whether using missiles or throwing the -// main weapon (with returning brand) is better. If using missiles that -// need a launcher, sets *launcher to the launcher. -// -// If the monster has no ranged weapon attack, returns NON_ITEM. -// -int mons_pick_best_missile(monsters *mons, item_def **launcher, - bool ignore_melee) -{ - *launcher = NULL; - item_def *melee = NULL, *launch = NULL; - for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) - { - if (item_def *item = mons->mslot_item(static_cast(i))) - { - if (is_range_weapon(*item)) - launch = item; - else if (!ignore_melee) - melee = item; - } - } - - const item_def *missiles = mons->missiles(); - if (launch && missiles && !missiles->launched_by(*launch)) - launch = NULL; - - const int tdam = mons_thrown_weapon_damage(melee); - const int fdam = mons_missile_damage(mons, launch, missiles); - - if (!tdam && !fdam) - return (NON_ITEM); - else if (tdam >= fdam) - return (melee->index()); - else - { - *launcher = launch; - return (missiles->index()); - } -} - -int mons_natural_regen_rate(monsters *monster) -{ - // A HD divider ranging from 3 (at 1 HD) to 1 (at 8 HD). - int divider = - std::max(div_rand_round(15 - monster->hit_dice, 4), 1); - - // The undead have a harder time regenerating. Golems have it worse. - switch (monster->holiness()) - { - case MH_UNDEAD: - divider *= (mons_enslaved_soul(monster)) ? 2 : 4; - break; - - // And golems have it worse. - case MH_NONLIVING: - divider *= 5; - break; - - default: - break; - } - - return (std::max(div_rand_round(monster->hit_dice, divider), 1)); -} - -void mons_check_pool(monsters *monster, const coord_def &oldpos, - killer_type killer, int killnum) -{ - // Levitating/flying monsters don't make contact with the terrain. - if (monster->airborne()) - return; - - dungeon_feature_type grid = grd(monster->pos()); - if ((grid == DNGN_LAVA || grid == DNGN_DEEP_WATER) - && !monster_habitable_grid(monster, grid)) - { - const bool message = mons_near(monster); - - // Don't worry about invisibility. You should be able to see if - // something has fallen into the lava. - if (message && (oldpos == monster->pos() || grd(oldpos) != grid)) - { - mprf("%s falls into the %s!", - monster->name(DESC_CAP_THE).c_str(), - grid == DNGN_LAVA ? "lava" : "water"); - } - - if (grid == DNGN_LAVA && monster->res_fire() >= 2) - grid = DNGN_DEEP_WATER; - - // Even fire resistant monsters perish in lava, but inanimate - // monsters can survive deep water. - if (grid == DNGN_LAVA || monster->can_drown()) - { - if (message) - { - if (grid == DNGN_LAVA) - { - simple_monster_message(monster, " is incinerated.", - MSGCH_MONSTER_DAMAGE, MDAM_DEAD); - } - else if (mons_genus(monster->type) == MONS_MUMMY) - { - simple_monster_message(monster, " falls apart.", - MSGCH_MONSTER_DAMAGE, MDAM_DEAD); - } - else - { - simple_monster_message(monster, " drowns.", - MSGCH_MONSTER_DAMAGE, MDAM_DEAD); - } - } - - if (killer == KILL_NONE) - { - // Self-kill. - killer = KILL_MON; - killnum = monster_index(monster); - } - - // Yredelemnul special, redux: It's the only one that can - // work on drowned monsters. - if (!_yred_enslave_soul(monster, killer)) - monster_die(monster, killer, killnum, true); - } - } -} - -bool monster_descriptor(int which_class, mon_desc_type 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: - case MONS_YAK: - case MONS_SHEEP: - 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_PURGY: - case MONS_TROLL: - case MONS_HYDRA: - case MONS_KILLER_KLOWN: - case MONS_LERNAEAN_HYDRA: - case MONS_DISSOLUTION: - return (true); - default: - return (false); - } - } - - if (which_descriptor == MDSC_NOMSG_WOUNDS) - { - // Zombified monsters other than spectral things don't show - // wounds. - if (mons_class_is_zombified(which_class) - && which_class != MONS_SPECTRAL_THING) - { - return (true); - } - - switch (which_class) - { - case MONS_RAKSHASA: - case MONS_RAKSHASA_FAKE: - return (true); - default: - return (false); - } - } - - return (false); -} - -bool message_current_target() -{ - if (crawl_state.is_replaying_keys()) - { - if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU) - return (false); - - return (you.can_see(&menv[you.prev_targ])); - } - - if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU) - { - const monsters *montarget = &menv[you.prev_targ]; - - if (you.can_see(montarget)) - { - mprf(MSGCH_PROMPT, "Current target: %s " - "(use p or f to fire at it again.)", - montarget->name(DESC_PLAIN).c_str()); - return (true); - } - - mpr("You have no current target."); - } - - return (false); -} - -// aaah, the simple joys of pointer arithmetic! {dlb}: -unsigned int monster_index(const monsters *monster) -{ - return (monster - menv.buffer()); -} - -void seen_monster(monsters *monster) -{ - // If the monster is in the auto_exclude list, automatically - // set an exclusion. - set_auto_exclude(monster); - - // Monster was viewed this turn - monster->flags |= MF_WAS_IN_VIEW; - - if (monster->flags & MF_SEEN) - return; - - // First time we've seen this particular monster. - monster->flags |= MF_SEEN; - - if (!mons_is_mimic(monster->type)) - { - if (Options.tutorial_left) - tutorial_first_monster(*monster); - - if (MONST_INTERESTING(monster)) - { - take_note( - Note(NOTE_SEEN_MONSTER, monster->type, 0, - monster->name(DESC_NOCAP_A, true).c_str())); - } - } -} - -//--------------------------------------------------------------- -// -// shift_monster -// -// Moves a monster to approximately p and returns true if -// the monster was moved. -// -//--------------------------------------------------------------- -bool shift_monster(monsters *mon, coord_def p) -{ - coord_def result; - - int count = 0; - - if (p.origin()) - p = mon->pos(); - - for (adjacent_iterator ai(p); ai; ++ai) - { - // Don't drop on anything but vanilla floor right now. - if (grd(*ai) != DNGN_FLOOR) - continue; - - if (actor_at(*ai)) - continue; - - if (one_chance_in(++count)) - result = *ai; - } - - if (count > 0) - { - mgrd(mon->pos()) = NON_MONSTER; - mon->moveto(result); - mgrd(result) = mon->mindex(); - } - - return (count > 0); -} - -// Make all of the monster's original equipment disappear, unless it's a fixed -// artefact or unrand artefact. -static void _vanish_orig_eq(monsters* mons) -{ - for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) - { - if (mons->inv[i] == NON_ITEM) - continue; - - item_def &item(mitm[mons->inv[i]]); - - if (!item.is_valid()) - continue; - - if (item.orig_place != 0 || item.orig_monnum != 0 - || !item.inscription.empty() - || is_unrandom_artefact(item) - || (item.flags & (ISFLAG_DROPPED | ISFLAG_THROWN | ISFLAG_NOTED_GET - | ISFLAG_BEEN_IN_INV) ) ) - { - continue; - } - item.flags |= ISFLAG_SUMMONED; - } -} - -int dismiss_monsters(std::string pattern) { - // Make all of the monsters' original equipment disappear unless "keepitem" - // is found in the regex (except for fixed arts and unrand arts). - const bool keep_item = strip_tag(pattern, "keepitem"); - - // Dismiss by regex - text_pattern tpat(pattern); - int ndismissed = 0; - for (monster_iterator mi; mi; ++mi) - { - if (mi->alive() && - (tpat.empty() || tpat.matches(mi->name(DESC_PLAIN, true)))) - { - if (!keep_item) - _vanish_orig_eq(*mi); - monster_die(*mi, KILL_DISMISSED, NON_MONSTER, false, true); - ++ndismissed; - } - } - return (ndismissed); -} - -bool is_item_jelly_edible(const item_def &item) -{ - // Don't eat artefacts. - if (is_artefact(item)) - return (false); - - // Shouldn't eat stone things - // - but what about wands and rings? - if (item.base_type == OBJ_MISSILES - && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK)) - { - return (false); - } - - // Don't eat special game items. - if (item.base_type == OBJ_ORBS - || (item.base_type == OBJ_MISCELLANY - && (item.sub_type == MISC_RUNE_OF_ZOT - || item.sub_type == MISC_HORN_OF_GERYON))) - { - return (false); - } - - return (true); -} - -bool monster_random_space(const monsters *monster, coord_def& target, - bool forbid_sanctuary) -{ - int tries = 0; - while (tries++ < 1000) - { - target = random_in_bounds(); - - // Don't land on top of another monster. - if (actor_at(target)) - continue; - - if (is_sanctuary(target) && forbid_sanctuary) - continue; - - if (monster_habitable_grid(monster, grd(target))) - return (true); - } - - return (false); -} - -bool monster_random_space(monster_type mon, coord_def& target, - bool forbid_sanctuary) -{ - monsters dummy; - dummy.type = mon; - - return monster_random_space(&dummy, target, forbid_sanctuary); -} - -void monster_teleport(monsters *monster, bool instan, bool silent) -{ - if (!instan) - { - if (monster->del_ench(ENCH_TP)) - { - if (!silent) - simple_monster_message(monster, " seems more stable."); - } - else - { - if (!silent) - simple_monster_message(monster, " looks slightly unstable."); - - monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER, - random_range(20, 30)) ); - } - - return; - } - - bool was_seen = you.can_see(monster) && !mons_is_lurking(monster); - - if (!silent) - simple_monster_message(monster, " disappears!"); - - const coord_def oldplace = monster->pos(); - - // Pick the monster up. - mgrd(oldplace) = NON_MONSTER; - - coord_def newpos; - if (monster_random_space(monster, newpos, !monster->wont_attack())) - monster->moveto(newpos); - - mgrd(monster->pos()) = monster_index(monster); - - // Mimics change form/colour when teleported. - if (mons_is_mimic(monster->type)) - { - monster_type old_type = monster->type; - monster->type = static_cast( - MONS_GOLD_MIMIC + random2(5)); - monster->colour = get_mimic_colour(monster); - - // If it's changed form, you won't recognise it. - // This assumes that a non-gold mimic turning into another item of - // the same description is really, really unlikely. - if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC) - was_seen = false; - } - - const bool now_visible = mons_near(monster); - if (!silent && now_visible) - { - if (was_seen) - simple_monster_message(monster, " reappears nearby!"); - else - { - // Even if it doesn't interrupt an activity (the player isn't - // delayed, the monster isn't hostile) we still want to give - // a message. - activity_interrupt_data ai(monster, "thin air"); - if (!interrupt_activity(AI_SEE_MONSTER, ai)) - simple_monster_message(monster, " appears out of thin air!"); - } - } - - if (monster->visible_to(&you) && now_visible) - handle_seen_interrupt(monster); - - // Leave a purple cloud. - place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3), - monster->kill_alignment()); - - monster->check_redraw(oldplace); - monster->apply_location_effects(oldplace); - - mons_relocated(monster); - - // Teleporting mimics change form - if they reappear out of LOS, they are - // no longer known. - if (mons_is_mimic(monster->type)) - { - if (now_visible) - monster->flags |= MF_KNOWN_MIMIC; - else - monster->flags &= ~MF_KNOWN_MIMIC; - } -} - -void mons_clear_trapping_net(monsters *mon) -{ - if (!mon->caught()) - return; - - const int net = get_trapping_net(mon->pos()); - if (net != NON_ITEM) - remove_item_stationary(mitm[net]); - - mon->del_ench(ENCH_HELD, true); -} - -bool mons_clonable(const monsters* mon, bool needs_adjacent) -{ - // No uniques or ghost demon monsters. Also, figuring out the name - // for the clone of a named monster isn't worth it. - if (mons_is_unique(mon->type) - || mons_is_ghost_demon(mon->type) - || mon->is_named()) - { - return (false); - } - - if (needs_adjacent) - { - // Is there space for the clone? - bool square_found = false; - for (int i = 0; i < 8; i++) - { - const coord_def p = mon->pos() + Compass[i]; - - if (in_bounds(p) - && !actor_at(p) - && monster_habitable_grid(mon, grd(p))) - { - square_found = true; - break; - } - } - if (!square_found) - return (false); - } - - // Is the monster carrying an artefact? - for (int i = 0; i < NUM_MONSTER_SLOTS; i++) - { - const int index = mon->inv[i]; - - if (index == NON_ITEM) - continue; - - if (is_artefact(mitm[index])) - return (false); - } - - return (true); -} - -int clone_mons(const monsters* orig, bool quiet, bool* obvious, - coord_def pos) -{ - // Is there an open slot in menv? - int midx = NON_MONSTER; - for (int i = 0; i < MAX_MONSTERS; i++) - if (menv[i].type == MONS_NO_MONSTER) - { - midx = i; - break; - } - - if (midx == NON_MONSTER) - return (NON_MONSTER); - - if (!in_bounds(pos)) - { - // Find an adjacent square. - int squares = 0; - for (int i = 0; i < 8; i++) - { - const coord_def p = orig->pos() + Compass[i]; - - if (in_bounds(p) - && !actor_at(p) - && monster_habitable_grid(orig, grd(p))) - { - if (one_chance_in(++squares)) - pos = p; - } - } - - if (squares == 0) - return (NON_MONSTER); - } - - ASSERT( !actor_at(pos) ); - - monsters &mon(menv[midx]); - - mon = *orig; - mon.set_position(pos); - mgrd(pos) = midx; - - // Duplicate objects, or unequip them if they can't be duplicated. - for (int i = 0; i < NUM_MONSTER_SLOTS; i++) - { - const int old_index = orig->inv[i]; - - if (old_index == NON_ITEM) - continue; - - const int new_index = get_item_slot(0); - if (new_index == NON_ITEM) - { - mon.unequip(mitm[old_index], i, 0, true); - mon.inv[i] = NON_ITEM; - continue; - } - - mon.inv[i] = new_index; - mitm[new_index] = mitm[old_index]; - mitm[new_index].set_holding_monster(midx); - } - - bool _obvious; - if (obvious == NULL) - obvious = &_obvious; - *obvious = false; - - if (you.can_see(orig) && you.can_see(&mon)) - { - if (!quiet) - simple_monster_message(orig, " is duplicated!"); - *obvious = true; - } - - mark_interesting_monst(&mon, mon.behaviour); - if (you.can_see(&mon)) - { - handle_seen_interrupt(&mon); - viewwindow(false); - } - - if (crawl_state.arena) - arena_placed_monster(&mon); - - return (midx); -} - -std::string summoned_poof_msg(const monsters* monster, bool plural) -{ - int summon_type = 0; - bool valid_mon = false; - if (monster != NULL && !invalid_monster(monster)) - { - (void) monster->is_summoned(NULL, &summon_type); - valid_mon = true; - } - - std::string msg = "disappear%s in a puff of smoke"; - bool no_chaos = false; - - switch (summon_type) - { - case SPELL_SHADOW_CREATURES: - msg = "dissolve%s into shadows"; - no_chaos = true; - break; - - case MON_SUMM_CHAOS: - msg = "degenerate%s into a cloud of primal chaos"; - break; - - case MON_SUMM_WRATH: - case MON_SUMM_AID: - if (valid_mon && is_good_god(monster->god)) - { - msg = "dissolve%s into sparkling lights"; - no_chaos = true; - } - break; - } - - if (valid_mon) - { - if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10) - || monster->type == MONS_CHAOS_SPAWN) - { - msg = "degenerate%s into a cloud of primal chaos"; - } - - if (monster->is_holy() - && summon_type != SPELL_SHADOW_CREATURES - && summon_type != MON_SUMM_CHAOS) - { - msg = "dissolve%s into sparkling lights"; - } - } - - // Conjugate. - msg = make_stringf(msg.c_str(), plural ? "" : "s"); - - return (msg); -} - -std::string summoned_poof_msg(const int midx, const item_def &item) -{ - if (midx == NON_MONSTER) - return summoned_poof_msg(static_cast(NULL), item); - else - return summoned_poof_msg(&menv[midx], item); -} - -std::string summoned_poof_msg(const monsters* monster, const item_def &item) -{ - ASSERT(item.flags & ISFLAG_SUMMONED); - - return summoned_poof_msg(monster, item.quantity > 1); -} diff --git a/crawl-ref/source/monstuff.h b/crawl-ref/source/monstuff.h deleted file mode 100644 index 75656c73a1..0000000000 --- a/crawl-ref/source/monstuff.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * File: monstuff.h - * Summary: Misc monster related functions. - * Written by: Linley Henzell - */ - - -#ifndef MONSTUFF_H -#define MONSTUFF_H - -#include "mon-util.h" - -enum mon_dam_level_type -{ - MDAM_OKAY, - MDAM_LIGHTLY_DAMAGED, - MDAM_MODERATELY_DAMAGED, - MDAM_HEAVILY_DAMAGED, - MDAM_SEVERELY_DAMAGED, - MDAM_ALMOST_DEAD, - MDAM_DEAD -}; - -enum mon_desc_type // things that cross categorical lines {dlb} -{ - MDSC_LEAVES_HIDE, // 0 - MDSC_REGENERATES, - MDSC_NOMSG_WOUNDS -}; - -struct level_exit -{ - coord_def target; - bool unreachable; - -public: - level_exit(coord_def t = coord_def(-1, -1), - bool u = true) - - : target(t), unreachable(u) - { - } -}; - -#define FRESHEST_CORPSE 210 - -#define YOU_KILL(x) ((x) == KILL_YOU || (x) == KILL_YOU_MISSILE \ - || (x) == KILL_YOU_CONF) -#define MON_KILL(x) ((x) == KILL_MON || (x) == KILL_MON_MISSILE) - -#define SAME_ATTITUDE(x) (x->friendly() ? BEH_FRIENDLY : \ - x->good_neutral() ? BEH_GOOD_NEUTRAL : \ - x->strict_neutral() ? BEH_STRICT_NEUTRAL : \ - x->neutral() ? BEH_NEUTRAL \ - : BEH_HOSTILE) - -#define MONST_INTERESTING(x) (x->flags & MF_INTERESTING) - -// for definition of type monsters {dlb} -#include "externs.h" - -void get_mimic_item( const monsters *mimic, item_def & item ); -int get_mimic_colour( const monsters *mimic ); - -void alert_nearby_monsters(void); - -enum poly_power_type { - PPT_LESS, - PPT_MORE, - PPT_SAME -}; - -bool monster_polymorph(monsters *monster, monster_type targetc, - poly_power_type power = PPT_SAME, - bool force_beh = false); - -int monster_die(monsters *monster, killer_type killer, - int killer_index, bool silent = false, bool wizard = false); - -monster_type fill_out_corpse(const monsters* monster, item_def& corpse, - bool allow_weightless = false); - -bool explode_corpse(item_def& corpse, const coord_def& where); - -int place_monster_corpse(const monsters *monster, bool silent, - bool force = false); - -void slime_vault_change(bool glass); - -void slimify_monster(monsters *monster, bool hostile = false); - -bool mon_can_be_slimified(monsters *monster); - -void mons_check_pool(monsters *monster, const coord_def &oldpos, - killer_type killer = KILL_NONE, int killnum = -1); - -void monster_cleanup(monsters *monster); - -int dismiss_monsters(std::string pattern); - -bool curse_an_item(bool decay_potions, bool quiet = false); - - -void monster_drop_ething(monsters *monster, bool mark_item_origins = false, - int owner_id = NON_ITEM); - -bool monster_blink(monsters *monster, bool quiet = false); - -bool simple_monster_message(const monsters *monster, const char *event, - msg_channel_type channel = MSGCH_PLAIN, - int param = 0, - description_level_type descrip = DESC_CAP_THE); - -bool choose_any_monster(const monsters* mon); -monsters *choose_random_nearby_monster( - int weight, - bool (*suitable)(const monsters* mon) = - choose_any_monster, - bool in_sight = true, - bool prefer_named = false, bool prefer_priest = false); - -monsters *choose_random_monster_on_level( - int weight, - bool (*suitable)(const monsters* mon) = - choose_any_monster, - bool in_sight = true, bool near_by = false, - bool prefer_named = false, bool prefer_priest = false); - -bool swap_places(monsters *monster); -bool swap_places(monsters *monster, const coord_def &loc); -bool swap_check(monsters *monster, coord_def &loc, bool quiet = false); - - -std::string get_wounds_description(const monsters *monster); -void print_wounds(const monsters *monster); -bool monster_descriptor(int which_class, mon_desc_type which_descriptor); - -unsigned int monster_index(const monsters *monster); - -void mons_get_damage_level(const monsters*, std::string& desc, - mon_dam_level_type&); - -void seen_monster(monsters *monster); - -bool shift_monster(monsters *mon, coord_def p = coord_def(0, 0)); - -int mons_weapon_damage_rating(const item_def &launcher); -int mons_missile_damage(monsters *mons, const item_def *launch, - const item_def *missile); -int mons_pick_best_missile(monsters *mons, item_def **launcher, - bool ignore_melee = false); -int mons_thrown_weapon_damage(const item_def *weap); - -int mons_natural_regen_rate(monsters *monster); - -void mons_relocated(monsters *mons); - -bool can_go_straight(const coord_def& p1, const coord_def& p2, - dungeon_feature_type allowed); - -bool is_item_jelly_edible(const item_def &item); - -bool monster_random_space(const monsters *monster, coord_def& target, - bool forbid_sanctuary = false); -bool monster_random_space(monster_type mon, coord_def& target, - bool forbid_sanctuary = false); -void monster_teleport(monsters *monster, bool instan, bool silent = false); -void mons_clear_trapping_net(monsters *mon); - -bool mons_clonable(const monsters* orig, bool needs_adjacent = true); -int clone_mons(const monsters* orig, bool quiet = false, - bool* obvious = NULL, coord_def pos = coord_def(0, 0) ); - -std::string summoned_poof_msg(const monsters* monster, bool plural = false); -std::string summoned_poof_msg(const int midx, const item_def &item); -std::string summoned_poof_msg(const monsters* monster, const item_def &item); - -void pikel_band_neutralise(); -#endif diff --git a/crawl-ref/source/mtransit.cc b/crawl-ref/source/mtransit.cc index a743278398..e9bf292df2 100644 --- a/crawl-ref/source/mtransit.cc +++ b/crawl-ref/source/mtransit.cc @@ -13,7 +13,7 @@ #include "artefact.h" #include "dungeon.h" #include "items.h" -#include "monplace.h" +#include "mon-place.h" #include "mon-util.h" #include "random.h" diff --git a/crawl-ref/source/ouch.cc b/crawl-ref/source/ouch.cc index e4230464af..52ace7b1a8 100644 --- a/crawl-ref/source/ouch.cc +++ b/crawl-ref/source/ouch.cc @@ -51,8 +51,8 @@ #include "message.h" #include "misc.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "notes.h" #include "output.h" #include "player.h" diff --git a/crawl-ref/source/output.cc b/crawl-ref/source/output.cc index 9e6978bb87..a1494fe726 100644 --- a/crawl-ref/source/output.cc +++ b/crawl-ref/source/output.cc @@ -34,7 +34,7 @@ #include "item_use.h" #include "menu.h" #include "message.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-info.h" #include "mon-util.h" #include "newgame.h" diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc index dd00639ef6..a4d9f06bb6 100644 --- a/crawl-ref/source/religion.cc +++ b/crawl-ref/source/religion.cc @@ -54,8 +54,8 @@ #include "mon-behv.h" #include "mon-iter.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mutation.h" #include "newgame.h" #include "notes.h" diff --git a/crawl-ref/source/shout.cc b/crawl-ref/source/shout.cc index 163b013423..d9dea1473c 100644 --- a/crawl-ref/source/shout.cc +++ b/crawl-ref/source/shout.cc @@ -16,9 +16,9 @@ #include "misc.h" #include "mon-behv.h" #include "mon-iter.h" -#include "monplace.h" +#include "mon-place.h" #include "monster.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "options.h" #include "player.h" #include "random.h" diff --git a/crawl-ref/source/spells1.cc b/crawl-ref/source/spells1.cc index 1b2e1b14df..59e8163ae6 100644 --- a/crawl-ref/source/spells1.cc +++ b/crawl-ref/source/spells1.cc @@ -33,7 +33,7 @@ #include "los.h" #include "message.h" #include "misc.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "options.h" #include "player.h" diff --git a/crawl-ref/source/spells2.cc b/crawl-ref/source/spells2.cc index f2c92572a7..44448142b3 100644 --- a/crawl-ref/source/spells2.cc +++ b/crawl-ref/source/spells2.cc @@ -34,8 +34,8 @@ #include "misc.h" #include "mon-behv.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "ouch.h" #include "player.h" diff --git a/crawl-ref/source/spells3.cc b/crawl-ref/source/spells3.cc index edca2202c5..812b7d00d0 100644 --- a/crawl-ref/source/spells3.cc +++ b/crawl-ref/source/spells3.cc @@ -36,8 +36,8 @@ #include "misc.h" #include "mon-behv.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "player.h" #include "quiver.h" diff --git a/crawl-ref/source/spells4.cc b/crawl-ref/source/spells4.cc index 027359417e..b046c0ac29 100644 --- a/crawl-ref/source/spells4.cc +++ b/crawl-ref/source/spells4.cc @@ -33,8 +33,8 @@ #include "message.h" #include "misc.h" #include "mon-behv.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "ouch.h" #include "player.h" diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc index 9a48b2ad31..94d6aa27cc 100644 --- a/crawl-ref/source/spl-cast.cc +++ b/crawl-ref/source/spl-cast.cc @@ -34,7 +34,7 @@ #include "misc.h" #include "message.h" #include "mon-cast.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mutation.h" #include "ouch.h" #include "player.h" diff --git a/crawl-ref/source/spl-mis.cc b/crawl-ref/source/spl-mis.cc index bbab3c230a..e348a4c61c 100644 --- a/crawl-ref/source/spl-mis.cc +++ b/crawl-ref/source/spl-mis.cc @@ -18,8 +18,8 @@ #include "it_use2.h" #include "kills.h" #include "misc.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "player.h" diff --git a/crawl-ref/source/stash.cc b/crawl-ref/source/stash.cc index 283d022339..7b861de749 100644 --- a/crawl-ref/source/stash.cc +++ b/crawl-ref/source/stash.cc @@ -26,7 +26,7 @@ #include "message.h" #include "misc.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "notes.h" #include "options.h" #include "place.h" diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc index d7f211bdea..3be83efe20 100644 --- a/crawl-ref/source/stuff.cc +++ b/crawl-ref/source/stuff.cc @@ -14,7 +14,7 @@ #include "los.h" #include "message.h" #include "misc.h" -#include "monplace.h" +#include "mon-place.h" #include "state.h" #include "stuff.h" #include "view.h" @@ -53,7 +53,7 @@ #include "items.h" #include "macro.h" #include "misc.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "notes.h" #include "output.h" diff --git a/crawl-ref/source/terrain.cc b/crawl-ref/source/terrain.cc index 16b7db05b7..45ea54fafe 100644 --- a/crawl-ref/source/terrain.cc +++ b/crawl-ref/source/terrain.cc @@ -23,8 +23,8 @@ #include "los.h" #include "message.h" #include "misc.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "ouch.h" #include "overmap.h" diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index e0b76d4b87..64b2f7e0bc 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -24,7 +24,7 @@ #include "kills.h" #include "los.h" #include "macro.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "player.h" #include "shopping.h" diff --git a/crawl-ref/source/traps.cc b/crawl-ref/source/traps.cc index 40410f8362..47b245f71d 100644 --- a/crawl-ref/source/traps.cc +++ b/crawl-ref/source/traps.cc @@ -27,7 +27,7 @@ #include "message.h" #include "misc.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mtransit.h" #include "ouch.h" #include "player.h" diff --git a/crawl-ref/source/travel.cc b/crawl-ref/source/travel.cc index 0046863591..51848b200e 100644 --- a/crawl-ref/source/travel.cc +++ b/crawl-ref/source/travel.cc @@ -32,7 +32,7 @@ #include "message.h" #include "misc.h" #include "mon-util.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "options.h" #ifdef USE_TILE #include "output.h" diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc index 3ee1f5e6bb..83cc0df5d2 100644 --- a/crawl-ref/source/view.cc +++ b/crawl-ref/source/view.cc @@ -49,8 +49,8 @@ #include "misc.h" #include "mon-behv.h" #include "mon-iter.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mon-util.h" #include "newgame.h" #include "options.h" diff --git a/crawl-ref/source/wiz-fsim.cc b/crawl-ref/source/wiz-fsim.cc index 4aa2b2bac7..1e3b784ce8 100644 --- a/crawl-ref/source/wiz-fsim.cc +++ b/crawl-ref/source/wiz-fsim.cc @@ -17,9 +17,9 @@ #include "item_use.h" #include "it_use2.h" #include "message.h" -#include "monplace.h" +#include "mon-place.h" #include "monster.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "options.h" #include "player.h" #include "quiver.h" diff --git a/crawl-ref/source/wiz-item.cc b/crawl-ref/source/wiz-item.cc index 8b11fb100a..eb800da140 100644 --- a/crawl-ref/source/wiz-item.cc +++ b/crawl-ref/source/wiz-item.cc @@ -22,7 +22,7 @@ #include "it_use2.h" #include "invent.h" #include "makeitem.h" -#include "monstuff.h" +#include "mon-stuff.h" #include "mon-util.h" #include "options.h" #include "religion.h" diff --git a/crawl-ref/source/wiz-mon.cc b/crawl-ref/source/wiz-mon.cc index 7817b29e2d..3701d344a0 100644 --- a/crawl-ref/source/wiz-mon.cc +++ b/crawl-ref/source/wiz-mon.cc @@ -20,9 +20,9 @@ #include "jobs.h" #include "macro.h" #include "message.h" -#include "monplace.h" -#include "monspeak.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-speak.h" +#include "mon-stuff.h" #include "mon-iter.h" #include "mon-util.h" #include "output.h" diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc index 34f8392ee6..60e7dc50bf 100644 --- a/crawl-ref/source/xom.cc +++ b/crawl-ref/source/xom.cc @@ -28,8 +28,8 @@ #include "mon-behv.h" #include "mon-iter.h" #include "mon-util.h" -#include "monplace.h" -#include "monstuff.h" +#include "mon-place.h" +#include "mon-stuff.h" #include "mutation.h" #include "notes.h" #include "options.h" -- cgit v1.2.3-54-g00ecf