--- /dev/null
+/*! \file monsters.cc
+ * \brief Monster-related functions
+ */
+
+/* Copyright 2005-2014 Martin Read
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define MONSTERS_CC
+#include "obumbrata.hh"
+#include "monsters.hh"
+#include "objects.hh"
+
+const Mon_handle NO_MON = 0u;
+
+std::map<Mon_handle, Mon> monsters;
+static bool reject_mon(int pm);
+
+/*! \brief Summon some monsters
+ *
+ * \return Number of monsters summoned
+ * \param how_many Maximum number of monsters to summon
+ */
+int summoning(Coord c, int how_many)
+{
+ int i;
+ Offset delta;
+ Coord testpos;
+ int tryct;
+ Mon_handle mon;
+ int created = 0;
+ int pmon;
+ for (i = 0; i < how_many; i++)
+ {
+ for (tryct = 0; tryct < 20; tryct++)
+ {
+ delta = random_step();
+ testpos = c + delta;
+ if ((lvl.terrain_at(testpos) == FLOOR) &&
+ (lvl.mon_at(testpos) == NO_MON) &&
+ (testpos != u.pos))
+ {
+ pmon = get_random_pmon();
+ if (pmon_is_magician(pmon))
+ {
+ /* Never summon magicians! */
+ continue;
+ }
+ mon = create_mon(NO_PMON, testpos);
+ if (mon != NO_MON)
+ {
+ created++;
+ break;
+ }
+ }
+ }
+ }
+ return created;
+}
+
+int ood(int power, int ratio)
+{
+ return (depth - power + ratio - 1) / ratio;
+}
+
+int get_random_pmon(void)
+{
+ int tryct;
+ int pm;
+ for (tryct = 0; tryct < 200; tryct++)
+ {
+ pm = zero_die(NUM_OF_PERMONS);
+ if (reject_mon(pm))
+ {
+ pm = NO_PMON;
+ continue;
+ }
+ break;
+ }
+ return pm;
+}
+
+Mon_handle first_free_mon_handle = 1u;
+static Mon_handle get_first_free_mon(void)
+{
+ if (first_free_mon_handle == NO_MON)
+ {
+ return NO_MON;
+ }
+ return first_free_mon_handle++;
+}
+
+Mon_handle create_mon(int pm_idx, Coord c)
+{
+ Mon_handle mon;
+ if (lvl.mon_at(c) != NO_MON)
+ {
+ debug_create_mon_occupied(c);
+ return NO_MON;
+ }
+ if (pm_idx == NO_PMON)
+ {
+ pm_idx = get_random_pmon();
+ if (pm_idx == NO_PMON)
+ {
+ debug_pmon_select_failed();
+ return NO_MON;
+ }
+ }
+ mon = get_first_free_mon();
+ if (mon != NO_MON)
+ {
+ Mon m;
+ m.self = mon;
+ m.pm_ref = pm_idx;
+ m.flags = MF_USED | MF_ALIVE;
+ m.pos = c;
+ m.ai_lastpos = Nowhere;
+ m.hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
+ m.hpcur = m.hpmax;
+ m.mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
+ m.defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
+ m.mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
+ if (permons[pm_idx].rdam != NO_ATK)
+ {
+ m.rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
+ m.rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
+ }
+ else
+ {
+ m.rtohit = NO_ATK;
+ m.rdam = NO_ATK;
+ }
+ monsters[mon] = m;
+ lvl.set_mon_at(c, mon);
+ if (mon_visible(mon))
+ {
+ notify_new_mon_at(c, mon);
+ }
+ return mon;
+ }
+ return NO_MON;
+}
+
+/* The rules:
+ * A monster gets eight entries.
+ */
+
+struct Death_drop_entry
+{
+ int pm_ref;
+ void (*func)(Coord c);
+};
+
+void goblin_death_drop(Coord c)
+{
+ if (!zero_die(4))
+ {
+ create_obj_near(PO_DAGGER, 1, c);
+ }
+}
+
+void thug_death_drop(Coord c)
+{
+ if (!zero_die(4))
+ {
+ create_obj_near(PO_MACE, 1, c);
+ }
+ else if (!zero_die(3))
+ {
+ create_obj_near(PO_LEATHER_ARMOUR, 1, c);
+ }
+}
+
+void hunter_death_drop(Coord c)
+{
+ if (!zero_die(6))
+ {
+ create_obj_near(PO_BOW, 1, c);
+ }
+}
+
+void duellist_death_drop(Coord c)
+{
+ if (!zero_die(6))
+ {
+ create_obj_near(PO_LONG_SWORD, 1, c);
+ }
+}
+
+void wizard_death_drop(Coord c)
+{
+ if (!zero_die(4))
+ {
+ create_obj_class_near(POCLASS_SCROLL, 1, false, c);
+ }
+ else if (!zero_die(3))
+ {
+ create_obj_class_near(POCLASS_POTION, 1, false, c);
+ }
+}
+
+void warlord_death_drop(Coord c)
+{
+ if (!zero_die(3))
+ {
+ create_obj_near(PO_RUNESWORD, 1, c);
+ }
+}
+
+void demon_death_drop(Coord c)
+{
+ if (!zero_die(100))
+ {
+ create_obj_near(PO_DEVIL_SPLEEN, 1, c);
+ }
+}
+
+Death_drop_entry death_drops[] =
+{
+ { PM_GOBLIN, goblin_death_drop },
+ { PM_THUG, thug_death_drop },
+ { PM_GOON, thug_death_drop },
+ { PM_HUNTER, hunter_death_drop },
+ { PM_DUELLIST, duellist_death_drop },
+ { PM_WIZARD, wizard_death_drop },
+ { PM_WARLORD, warlord_death_drop },
+ { PM_DEMON, demon_death_drop },
+ { NO_PMON, nullptr }
+};
+
+void death_drop(Mon_handle mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ for (int i = 0; death_drops[i].func != nullptr; ++i)
+ {
+ if (death_drops[i].pm_ref == mptr->pm_ref)
+ {
+ death_drops[i].func(mptr->pos);
+ }
+ }
+}
+
+bool Mon::can_pass(Coord c) const
+{
+ Terrain terr;
+ if (!lvl.in_bounds(c))
+ {
+ return false;
+ }
+ if (lvl.mon_at(c) != NO_MON)
+ {
+ return false;
+ }
+ if (c == u.pos)
+ {
+ /* Sanity check! */
+ return false;
+ }
+ if (is_ethereal())
+ {
+ return true;
+ }
+ terr = lvl.terrain_at(c);
+ if (terrain_blocks_beings(terr))
+ {
+ /* Let's *not* stuff all the wall types into a switch, eh? */
+ return false;
+ }
+ /* Keep the switch, so that we can maintain a convenient distinction
+ * between floor hazards and volumetric hazards. */
+ switch (terr)
+ {
+ case LAVA:
+ if (!can_fly() && !resists(DT_FIRE))
+ {
+ return false;
+ }
+ break;
+ case WATER:
+ if (!can_fly() && !resists(DT_DROWNING))
+ {
+ return false;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+
+void heal_mon(Mon_handle mon, int amount, int cansee)
+{
+ if (amount > (monsters[mon].hpmax - monsters[mon].hpcur))
+ {
+ amount = monsters[mon].hpmax - monsters[mon].hpcur;
+ }
+ if (amount > 0)
+ {
+ if (cansee)
+ {
+ notify_mon_healed(mon);
+ }
+ monsters[mon].hpcur += amount;
+ }
+}
+
+void unplace_mon(Mon_handle mon)
+{
+ lvl.set_mon_at(monsters[mon].pos, NO_MON);
+ monsters[mon].flags &= ~MF_USED;
+}
+
+void apply_liquid(int pm, Coord c)
+{
+ if (pmon_bleeds(pm))
+ {
+ lvl.set_decal_at(c, Decal_blood);
+ }
+ else if (pmon_suppurates(pm))
+ {
+ lvl.set_decal_at(c, Decal_pus);
+ }
+}
+
+/*! \brief Handle the death of a monster
+ *
+ * \todo Support special effects on monster death
+ */
+void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse)
+{
+ Mon *mptr = mon_snapv(mon);
+ int pm = mptr->pm_ref;
+ apply_liquid(pm, mptr->pos);
+ if (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse))
+ {
+ // TODO shower the surroundings with the monster's liquid
+ }
+ else if (pmon_leaves_corpse(pm))
+ {
+ create_corpse(pm, mptr->pos);
+ }
+ death_drop(mon); // phat lewt!
+ mptr->flags &= ~MF_ALIVE;
+ monsters[mon].hpcur = -1;
+ unplace_mon(mon); // cleanup
+ if (by_you)
+ {
+ notify_player_killed_mon(mon);
+ gain_experience(permons[monsters[mon].pm_ref].exp);
+ }
+ else if (mon_visible(mon))
+ {
+ notify_mon_dies(mon);
+ }
+}
+
+void damage_mon(Mon_handle mon, int amount, bool by_you)
+{
+ Mon *mptr;
+ mptr = mon_snapv(mon);
+ if (amount >= mptr->hpcur)
+ {
+ kill_mon(mon, by_you);
+ }
+ else
+ {
+ mptr->hpcur -= amount;
+ }
+}
+
+bool reject_mon(int pm)
+{
+ if ((permons[pm].power > depth) || (zero_die(100) < permons[pm].rarity))
+ {
+ return true;
+ }
+ return false;
+}
+
+Pass_fail teleport_mon_to_you(Mon_handle mon)
+{
+ int tryct;
+ Offset delta;
+ Coord c;
+ int success = 0;
+ Mon *mptr = mon_snapv(mon);
+ Coord oldpos = mptr->pos;
+ for (tryct = 0; tryct < 40; tryct++)
+ {
+ delta = random_step();
+ c = u.pos + delta;
+ if (mptr->can_pass(c))
+ {
+ success = 1;
+ break;
+ }
+ }
+ if (success)
+ {
+ reloc_mon(mon, c);
+ notify_mon_appears(mon);
+ return You_pass;
+ }
+ return You_fail;
+}
+
+Pass_fail teleport_mon(Mon_handle mon)
+{
+ Pass_fail rval = You_fail;
+ int cell_try;
+ Coord c;
+ for (cell_try = 0; cell_try < 200; cell_try++)
+ {
+ c = lvl.random_point(1);
+ if ((lvl.mon_at(c) == NO_MON) && (lvl.terrain_at(c) == FLOOR) && (c != u.pos))
+ {
+ reloc_mon(mon, c);
+ rval = You_pass;
+ break;
+ }
+ }
+ return rval;
+}
+
+int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you)
+{
+ /* 0 = blocked, 1 = knocked, 2 = killed */
+ Mon *mptr = mon_snapv(mon);
+ Coord c = mptr->pos + step;
+ Coord savedpos = mptr->pos;
+ Terrain terr = lvl.terrain_at(c);
+
+ if (mptr->resists(DT_KNOCKBACK))
+ {
+ if (cansee)
+ {
+ notify_knockback_mon_resisted(mon);
+ }
+ return 0;
+ }
+ if (terrain_blocks_beings(terr))
+ {
+ if (cansee)
+ {
+ notify_knockback_mon_blocked(mon);
+ }
+ return 0;
+ }
+ reloc_mon(mon, c);
+ switch (terr)
+ {
+ case LAVA:
+ if (mptr->can_fly())
+ {
+ if (cansee)
+ {
+ notify_knockback_mon_hover_lava(mon);
+ }
+ }
+ else
+ {
+ if (cansee)
+ {
+ notify_knockback_mon_immersed_lava(mon);
+ }
+ else
+ {
+ notify_knockback_mon_lava_offscreen();
+ }
+ if (!mptr->resists(DT_FIRE))
+ {
+ kill_mon(mon, by_you);
+ return 2;
+ }
+ }
+ break;
+ case WATER:
+ if (mptr->can_fly())
+ {
+ if (cansee)
+ {
+ notify_knockback_mon_hover_water(mon);
+ }
+ }
+ else
+ {
+ if (cansee)
+ {
+ notify_knockback_mon_immersed_water(mon);
+ }
+ else
+ {
+ notify_knockback_mon_water_offscreen();
+ }
+ if (!mptr->resists(DT_DROWNING))
+ {
+ kill_mon(mon, by_you);
+ return 2;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+void reloc_mon(Mon_handle mon, Coord newpos)
+{
+ Mon *mptr = mon_snapv(mon);
+ lvl.set_mon_at(mptr->pos, NO_MON);
+ notify_new_mon_at(mptr->pos, NO_MON);
+ mptr->pos = newpos;
+ lvl.set_mon_at(mptr->pos, mon);
+ notify_new_mon_at(mptr->pos, mon);
+}
+
+void move_mon(Mon_handle mon, Coord c)
+{
+ Mon *mptr;
+ mptr = mon_snapv(mon);
+ if (!mptr->can_pass(c))
+ {
+ debug_mon_invalid_move(mon, c);
+ return;
+ }
+ if (lvl.mon_at(mptr->pos) != mon)
+ {
+ debug_misplaced_monster();
+ return;
+ }
+ reloc_mon(mon, c);
+}
+
+void summon_demon_near(Coord c)
+{
+ Coord c2 = c + random_step();
+ Mon_handle mon;
+ if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) &&
+ (c2 != u.pos))
+ {
+ mon = create_mon(PM_DEMON, c2);
+ notify_summon_demon(mon);
+ }
+}
+
+bool mon_visible(Mon_handle mon)
+{
+ Offset delta;
+ Mon const *mptr = mon_snap(mon);
+ if (!(mptr->flags & MF_USED))
+ {
+ return false;
+ }
+ delta = mptr->pos.delta(u.pos);
+ if (delta.len_cheb() <= MAX_FOV_RADIUS)
+ {
+ return (player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x]) & Visflag_central;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void update_mon(Mon_handle mon)
+{
+ int cansee;
+ Mon *mptr = mon_snapv(mon);
+ if (mptr->hpcur < mptr->hpmax)
+ {
+ cansee = mon_visible(mon);
+ // TODO modify regen handling to use flags/fields instead of switching on pm_ref
+ switch (mptr->pm_ref)
+ {
+ case PM_TROLL:
+ if (!(game_tick % 10))
+ {
+ if (cansee)
+ {
+ notify_mon_regenerates(mon);
+ }
+ heal_mon(mon, one_die(3) + 3, 0);
+ }
+ break;
+
+ case PM_ZOMBIE:
+ /* Zombies don't recover from their injuries. */
+ break;
+
+ default:
+ if (!(game_tick % 20))
+ {
+ heal_mon(mon, 1, cansee);
+ }
+ break;
+ }
+ }
+}
+
+bool Mon::resists(Damtyp dt) const
+{
+ switch (dt)
+ {
+ case DT_COLD:
+ return pmon_resists_cold(pm_ref);
+ case DT_FIRE:
+ return pmon_resists_fire(pm_ref);
+ case DT_POISON:
+ return pmon_resists_poison(pm_ref);
+ case DT_NECRO:
+ return pmon_resists_necro(pm_ref);
+ case DT_ELEC:
+ return pmon_resists_elec(pm_ref);
+ case DT_KNOCKBACK:
+ return pmon_resists_knockback(pm_ref);
+ case DT_DROWNING:
+ return pmon_resists_drowning(pm_ref);
+ default:
+ return false;
+ }
+}
+
+bool Mon::is_ethereal(void) const
+{
+ return pmon_is_ethereal(pm_ref);
+}
+
+bool Mon::can_fly(void) const
+{
+ return pmon_can_fly(pm_ref);
+}
+
+void asprint_mon_name(char **buf, Mon_handle mon, int article)
+{
+ Mon const *mptr = mon_snap(mon);
+ int i;
+ switch (article)
+ {
+ case 0:
+ i = asprintf(buf, "a%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
+ break;
+ case 1:
+ i = asprintf(buf, "the %s", permons[mptr->pm_ref].name);
+ break;
+ case 2:
+ i = asprintf(buf, "A%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
+ break;
+ case 3:
+ i = asprintf(buf, "The %s", permons[mptr->pm_ref].name);
+ break;
+ default:
+ i = -1;
+ }
+ if (i == -1)
+ {
+ // TODO wibble
+ }
+ return;
+}
+
+/* monsters.cc */
+// vim:cindent
+++ /dev/null
-/*! \file monsters.cc
- * \brief Monster-related functions
- */
-
-/* Copyright 2005-2014 Martin Read
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#define MONSTERS_CC
-#include "obumbrata.hh"
-#include "monsters.hh"
-#include "objects.hh"
-
-const Mon_handle NO_MON = 0u;
-
-std::map<Mon_handle, Mon> monsters;
-static bool reject_mon(int pm);
-
-/*! \brief Summon some monsters
- *
- * \return Number of monsters summoned
- * \param how_many Maximum number of monsters to summon
- */
-int summoning(Coord c, int how_many)
-{
- int i;
- Offset delta;
- Coord testpos;
- int tryct;
- Mon_handle mon;
- int created = 0;
- int pmon;
- for (i = 0; i < how_many; i++)
- {
- for (tryct = 0; tryct < 20; tryct++)
- {
- delta = random_step();
- testpos = c + delta;
- if ((lvl.terrain_at(testpos) == FLOOR) &&
- (lvl.mon_at(testpos) == NO_MON) &&
- (testpos != u.pos))
- {
- pmon = get_random_pmon();
- if (pmon_is_magician(pmon))
- {
- /* Never summon magicians! */
- continue;
- }
- mon = create_mon(NO_PMON, testpos);
- if (mon != NO_MON)
- {
- created++;
- break;
- }
- }
- }
- }
- return created;
-}
-
-int ood(int power, int ratio)
-{
- return (depth - power + ratio - 1) / ratio;
-}
-
-int get_random_pmon(void)
-{
- int tryct;
- int pm;
- for (tryct = 0; tryct < 200; tryct++)
- {
- pm = zero_die(NUM_OF_PERMONS);
- if (reject_mon(pm))
- {
- pm = NO_PMON;
- continue;
- }
- break;
- }
- return pm;
-}
-
-Mon_handle first_free_mon_handle = 1u;
-static Mon_handle get_first_free_mon(void)
-{
- if (first_free_mon_handle == NO_MON)
- {
- return NO_MON;
- }
- return first_free_mon_handle++;
-}
-
-Mon_handle create_mon(int pm_idx, Coord c)
-{
- Mon_handle mon;
- if (lvl.mon_at(c) != NO_MON)
- {
- debug_create_mon_occupied(c);
- return NO_MON;
- }
- if (pm_idx == NO_PMON)
- {
- pm_idx = get_random_pmon();
- if (pm_idx == NO_PMON)
- {
- debug_pmon_select_failed();
- return NO_MON;
- }
- }
- mon = get_first_free_mon();
- if (mon != NO_MON)
- {
- Mon m;
- m.self = mon;
- m.pm_ref = pm_idx;
- m.flags = MF_USED | MF_ALIVE;
- m.pos = c;
- m.ai_lastpos = Nowhere;
- m.hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
- m.hpcur = m.hpmax;
- m.mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
- m.defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
- m.mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
- if (permons[pm_idx].rdam != NO_ATK)
- {
- m.rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
- m.rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
- }
- else
- {
- m.rtohit = NO_ATK;
- m.rdam = NO_ATK;
- }
- monsters[mon] = m;
- lvl.set_mon_at(c, mon);
- if (mon_visible(mon))
- {
- notify_new_mon_at(c, mon);
- }
- return mon;
- }
- return NO_MON;
-}
-
-/* The rules:
- * A monster gets eight entries.
- */
-
-struct Death_drop_entry
-{
- int pm_ref;
- void (*func)(Coord c);
-};
-
-void goblin_death_drop(Coord c)
-{
- if (!zero_die(4))
- {
- create_obj_near(PO_DAGGER, 1, c);
- }
-}
-
-void thug_death_drop(Coord c)
-{
- if (!zero_die(4))
- {
- create_obj_near(PO_MACE, 1, c);
- }
- else if (!zero_die(3))
- {
- create_obj_near(PO_LEATHER_ARMOUR, 1, c);
- }
-}
-
-void hunter_death_drop(Coord c)
-{
- if (!zero_die(6))
- {
- create_obj_near(PO_BOW, 1, c);
- }
-}
-
-void duellist_death_drop(Coord c)
-{
- if (!zero_die(6))
- {
- create_obj_near(PO_LONG_SWORD, 1, c);
- }
-}
-
-void wizard_death_drop(Coord c)
-{
- if (!zero_die(4))
- {
- create_obj_class_near(POCLASS_SCROLL, 1, false, c);
- }
- else if (!zero_die(3))
- {
- create_obj_class_near(POCLASS_POTION, 1, false, c);
- }
-}
-
-void warlord_death_drop(Coord c)
-{
- if (!zero_die(3))
- {
- create_obj_near(PO_RUNESWORD, 1, c);
- }
-}
-
-void demon_death_drop(Coord c)
-{
- if (!zero_die(100))
- {
- create_obj_near(PO_DEVIL_SPLEEN, 1, c);
- }
-}
-
-Death_drop_entry death_drops[] =
-{
- { PM_GOBLIN, goblin_death_drop },
- { PM_THUG, thug_death_drop },
- { PM_GOON, thug_death_drop },
- { PM_HUNTER, hunter_death_drop },
- { PM_DUELLIST, duellist_death_drop },
- { PM_WIZARD, wizard_death_drop },
- { PM_WARLORD, warlord_death_drop },
- { PM_DEMON, demon_death_drop },
- { NO_PMON, nullptr }
-};
-
-void death_drop(Mon_handle mon)
-{
- Mon const *mptr = mon_snap(mon);
- for (int i = 0; death_drops[i].func != nullptr; ++i)
- {
- if (death_drops[i].pm_ref == mptr->pm_ref)
- {
- death_drops[i].func(mptr->pos);
- }
- }
-}
-
-bool Mon::can_pass(Coord c) const
-{
- Terrain terr;
- if (!lvl.in_bounds(c))
- {
- return false;
- }
- if (lvl.mon_at(c) != NO_MON)
- {
- return false;
- }
- if (c == u.pos)
- {
- /* Sanity check! */
- return false;
- }
- if (is_ethereal())
- {
- return true;
- }
- terr = lvl.terrain_at(c);
- if (terrain_blocks_beings(terr))
- {
- /* Let's *not* stuff all the wall types into a switch, eh? */
- return false;
- }
- /* Keep the switch, so that we can maintain a convenient distinction
- * between floor hazards and volumetric hazards. */
- switch (terr)
- {
- case LAVA:
- if (!can_fly() && !resists(DT_FIRE))
- {
- return false;
- }
- break;
- case WATER:
- if (!can_fly() && !resists(DT_DROWNING))
- {
- return false;
- }
- default:
- break;
- }
- return true;
-}
-
-void heal_mon(Mon_handle mon, int amount, int cansee)
-{
- if (amount > (monsters[mon].hpmax - monsters[mon].hpcur))
- {
- amount = monsters[mon].hpmax - monsters[mon].hpcur;
- }
- if (amount > 0)
- {
- if (cansee)
- {
- notify_mon_healed(mon);
- }
- monsters[mon].hpcur += amount;
- }
-}
-
-void unplace_mon(Mon_handle mon)
-{
- lvl.set_mon_at(monsters[mon].pos, NO_MON);
- monsters[mon].flags &= ~MF_USED;
-}
-
-void apply_liquid(int pm, Coord c)
-{
- if (pmon_bleeds(pm))
- {
- lvl.set_decal_at(c, Decal_blood);
- }
- else if (pmon_suppurates(pm))
- {
- lvl.set_decal_at(c, Decal_pus);
- }
-}
-
-/*! \brief Handle the death of a monster
- *
- * \todo Support special effects on monster death
- */
-void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse)
-{
- Mon *mptr = mon_snapv(mon);
- int pm = mptr->pm_ref;
- apply_liquid(pm, mptr->pos);
- if (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse))
- {
- // TODO shower the surroundings with the monster's liquid
- }
- else if (pmon_leaves_corpse(pm))
- {
- create_corpse(pm, mptr->pos);
- }
- death_drop(mon); // phat lewt!
- mptr->flags &= ~MF_ALIVE;
- monsters[mon].hpcur = -1;
- unplace_mon(mon); // cleanup
- if (by_you)
- {
- notify_player_killed_mon(mon);
- gain_experience(permons[monsters[mon].pm_ref].exp);
- }
- else if (mon_visible(mon))
- {
- notify_mon_dies(mon);
- }
-}
-
-void damage_mon(Mon_handle mon, int amount, bool by_you)
-{
- Mon *mptr;
- mptr = mon_snapv(mon);
- if (amount >= mptr->hpcur)
- {
- kill_mon(mon, by_you);
- }
- else
- {
- mptr->hpcur -= amount;
- }
-}
-
-bool reject_mon(int pm)
-{
- if ((permons[pm].power > depth) || (zero_die(100) < permons[pm].rarity))
- {
- return true;
- }
- return false;
-}
-
-Pass_fail teleport_mon_to_you(Mon_handle mon)
-{
- int tryct;
- Offset delta;
- Coord c;
- int success = 0;
- Mon *mptr = mon_snapv(mon);
- Coord oldpos = mptr->pos;
- for (tryct = 0; tryct < 40; tryct++)
- {
- delta = random_step();
- c = u.pos + delta;
- if (mptr->can_pass(c))
- {
- success = 1;
- break;
- }
- }
- if (success)
- {
- reloc_mon(mon, c);
- notify_mon_appears(mon);
- return You_pass;
- }
- return You_fail;
-}
-
-Pass_fail teleport_mon(Mon_handle mon)
-{
- Pass_fail rval = You_fail;
- int cell_try;
- Coord c;
- for (cell_try = 0; cell_try < 200; cell_try++)
- {
- c = lvl.random_point(1);
- if ((lvl.mon_at(c) == NO_MON) && (lvl.terrain_at(c) == FLOOR) && (c != u.pos))
- {
- reloc_mon(mon, c);
- rval = You_pass;
- break;
- }
- }
- return rval;
-}
-
-int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you)
-{
- /* 0 = blocked, 1 = knocked, 2 = killed */
- Mon *mptr = mon_snapv(mon);
- Coord c = mptr->pos + step;
- Coord savedpos = mptr->pos;
- Terrain terr = lvl.terrain_at(c);
-
- if (mptr->resists(DT_KNOCKBACK))
- {
- if (cansee)
- {
- notify_knockback_mon_resisted(mon);
- }
- return 0;
- }
- if (terrain_blocks_beings(terr))
- {
- if (cansee)
- {
- notify_knockback_mon_blocked(mon);
- }
- return 0;
- }
- reloc_mon(mon, c);
- switch (terr)
- {
- case LAVA:
- if (mptr->can_fly())
- {
- if (cansee)
- {
- notify_knockback_mon_hover_lava(mon);
- }
- }
- else
- {
- if (cansee)
- {
- notify_knockback_mon_immersed_lava(mon);
- }
- else
- {
- notify_knockback_mon_lava_offscreen();
- }
- if (!mptr->resists(DT_FIRE))
- {
- kill_mon(mon, by_you);
- return 2;
- }
- }
- break;
- case WATER:
- if (mptr->can_fly())
- {
- if (cansee)
- {
- notify_knockback_mon_hover_water(mon);
- }
- }
- else
- {
- if (cansee)
- {
- notify_knockback_mon_immersed_water(mon);
- }
- else
- {
- notify_knockback_mon_water_offscreen();
- }
- if (!mptr->resists(DT_DROWNING))
- {
- kill_mon(mon, by_you);
- return 2;
- }
- }
- break;
- default:
- break;
- }
- return 1;
-}
-
-void reloc_mon(Mon_handle mon, Coord newpos)
-{
- Mon *mptr = mon_snapv(mon);
- lvl.set_mon_at(mptr->pos, NO_MON);
- notify_new_mon_at(mptr->pos, NO_MON);
- mptr->pos = newpos;
- lvl.set_mon_at(mptr->pos, mon);
- notify_new_mon_at(mptr->pos, mon);
-}
-
-void move_mon(Mon_handle mon, Coord c)
-{
- Mon *mptr;
- mptr = mon_snapv(mon);
- if (!mptr->can_pass(c))
- {
- debug_mon_invalid_move(mon, c);
- return;
- }
- if (lvl.mon_at(mptr->pos) != mon)
- {
- debug_misplaced_monster();
- return;
- }
- reloc_mon(mon, c);
-}
-
-void summon_demon_near(Coord c)
-{
- Coord c2 = c + random_step();
- Mon_handle mon;
- if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) &&
- (c2 != u.pos))
- {
- mon = create_mon(PM_DEMON, c2);
- notify_summon_demon(mon);
- }
-}
-
-bool mon_visible(Mon_handle mon)
-{
- Offset delta;
- Mon const *mptr = mon_snap(mon);
- if (!(mptr->flags & MF_USED))
- {
- return false;
- }
- delta = mptr->pos.delta(u.pos);
- if (delta.len_cheb() <= MAX_FOV_RADIUS)
- {
- return (player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x]) & Visflag_central;
- }
- else
- {
- return false;
- }
-}
-
-void update_mon(Mon_handle mon)
-{
- int cansee;
- Mon *mptr = mon_snapv(mon);
- if (mptr->hpcur < mptr->hpmax)
- {
- cansee = mon_visible(mon);
- // TODO modify regen handling to use flags/fields instead of switching on pm_ref
- switch (mptr->pm_ref)
- {
- case PM_TROLL:
- if (!(game_tick % 10))
- {
- if (cansee)
- {
- notify_mon_regenerates(mon);
- }
- heal_mon(mon, one_die(3) + 3, 0);
- }
- break;
-
- case PM_ZOMBIE:
- /* Zombies don't recover from their injuries. */
- break;
-
- default:
- if (!(game_tick % 20))
- {
- heal_mon(mon, 1, cansee);
- }
- break;
- }
- }
-}
-
-bool Mon::resists(Damtyp dt) const
-{
- switch (dt)
- {
- case DT_COLD:
- return pmon_resists_cold(pm_ref);
- case DT_FIRE:
- return pmon_resists_fire(pm_ref);
- case DT_POISON:
- return pmon_resists_poison(pm_ref);
- case DT_NECRO:
- return pmon_resists_necro(pm_ref);
- case DT_ELEC:
- return pmon_resists_elec(pm_ref);
- case DT_KNOCKBACK:
- return pmon_resists_knockback(pm_ref);
- case DT_DROWNING:
- return pmon_resists_drowning(pm_ref);
- default:
- return false;
- }
-}
-
-bool Mon::is_ethereal(void) const
-{
- return pmon_is_ethereal(pm_ref);
-}
-
-bool Mon::can_fly(void) const
-{
- return pmon_can_fly(pm_ref);
-}
-
-void asprint_mon_name(char **buf, Mon_handle mon, int article)
-{
- Mon const *mptr = mon_snap(mon);
- int i;
- switch (article)
- {
- case 0:
- i = asprintf(buf, "a%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
- break;
- case 1:
- i = asprintf(buf, "the %s", permons[mptr->pm_ref].name);
- break;
- case 2:
- i = asprintf(buf, "A%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
- break;
- case 3:
- i = asprintf(buf, "The %s", permons[mptr->pm_ref].name);
- break;
- default:
- i = -1;
- }
- if (i == -1)
- {
- // TODO wibble
- }
- return;
-}
-
-/* monsters.cc */
-// vim:cindent