]> granicus.if.org Git - nethack/commitdiff
Mild Zombie Apocalypse
authorPasi Kallinen <paxed@alt.org>
Wed, 21 Oct 2020 18:04:03 +0000 (21:04 +0300)
committerPasi Kallinen <paxed@alt.org>
Fri, 23 Oct 2020 16:47:10 +0000 (19:47 +0300)
When a zombie (or lich) kills a monster in melee without a weapon,
the monster can rise few turns later as a zombie.

The only creatures that can be zombified are ones that actually have
a zombie counterpart monster. A zombie cannot turn a jackal into
a zombie, for instance. But it could turn a shopkeeper into a human
zombie, or a dwarf king into a dwarf zombie.

Zombies will fight with monsters that can be turned into zombies.

Originally this was a SliceHack feature, but this is based on xNetHack
version of it, with some modifications.

13 files changed:
doc/fixes37.0
include/decl.h
include/extern.h
include/timeout.h
src/decl.c
src/do.c
src/end.c
src/mhitm.c
src/mkobj.c
src/mon.c
src/monmove.c
src/priest.c
src/timeout.c

index c728191188590f89f007ef8d6e3ef47bdbe1b3d8..163b1c38fc56f4360d06c03a85964042cc1669a0 100644 (file)
@@ -569,6 +569,7 @@ add section marker [] support to run-time config file; CHOOSE section1,section2
 render the color names in the corresponding color when using the pick-a-color
        menu for adding status highlights or menu colors via 'O'
 reading blessed scroll of teleportation confers one-shot teleport control
+mild zombie apocalypse
 
 
 Platform- and/or Interface-Specific New Features
index be1d583ac1fea05fa16b8063094915d8f84ca7d7..c72ab57bffbdf4f8d4e4dc0d9af5a7d5186c6f11 100644 (file)
@@ -972,6 +972,7 @@ struct instance_globals {
     /* mon.c */
     boolean vamp_rise_msg;
     boolean disintegested;
+    boolean zombify;
     short *animal_list; /* list of PM values for animal monsters */
     int animal_list_count;
 
index b743db20686bf30333d2951d3a63b2254772f0b2..a8151c0343166cd1a65923d04adb780837560d7a 100644 (file)
@@ -425,6 +425,7 @@ E void FDECL(schedule_goto, (d_level *, BOOLEAN_P, BOOLEAN_P, int,
 E void NDECL(deferred_goto);
 E boolean FDECL(revive_corpse, (struct obj *));
 E void FDECL(revive_mon, (ANY_P *, long));
+E void FDECL(zombify_mon, (ANY_P *, long));
 E boolean FDECL(cmd_safety_prevention, (const char *, const char *, int *));
 E int NDECL(donull);
 E int NDECL(dowipe);
@@ -1454,6 +1455,8 @@ E int FDECL(cmap_to_type, (int));
 /* ### mon.c ### */
 
 E void NDECL(mon_sanity_check);
+E boolean FDECL(zombie_maker, (struct permonst *));
+E int FDECL(zombie_form, (struct permonst *));
 E int FDECL(m_poisongas_ok, (struct monst *));
 E int FDECL(undead_to_corpse, (int));
 E int FDECL(genus, (int, int));
@@ -1583,6 +1586,7 @@ E void FDECL(mon_yells, (struct monst *, const char *));
 E int FDECL(dochug, (struct monst *));
 E boolean FDECL(m_digweapon_check, (struct monst *, XCHAR_P, XCHAR_P));
 E int FDECL(m_move, (struct monst *, int));
+E int FDECL(m_move_aggress, (struct monst *, XCHAR_P, XCHAR_P));
 E void FDECL(dissolve_bars, (int, int));
 E boolean FDECL(closed_door, (int, int));
 E boolean FDECL(accessible, (int, int));
index 5cad4388af6e898993b8ab5faecc62f65f07da39..3853c80d48c991dee49e48649054d59d34bc99f8 100644 (file)
@@ -29,6 +29,7 @@ enum timeout_types {
     ROT_ORGANIC = 0, /* for buried organics */
     ROT_CORPSE,
     REVIVE_MON,
+    ZOMBIFY_MON,
     BURN_OBJECT,
     HATCH_EGG,
     FIG_TRANSFORM,
index 4c836bbdeff17c8abe3e02e42ac60201ebad3c6d..5f44d64e15ae869d28a16b8d165d9648aea5e4bf 100644 (file)
@@ -498,6 +498,7 @@ const struct instance_globals g_init = {
     /* mon.c */
     UNDEFINED_VALUE, /* vamp_rise_msg */
     UNDEFINED_VALUE, /* disintegested */
+    UNDEFINED_VALUE, /* zombify */
     NULL, /* animal_list */
     UNDEFINED_VALUE, /* animal_list_count */
 
index c891a94d46645f40e5e9419119d617b3f665025c..ee2c3c14187254161d3ac37cae94297df95826fc 100644 (file)
--- a/src/do.c
+++ b/src/do.c
@@ -1982,6 +1982,28 @@ long timeout UNUSED;
     }
 }
 
+/* Timeout callback. Revive the corpse as a zombie. */
+/*ARGSUSED*/
+void
+zombify_mon(arg, timeout)
+anything *arg;
+long timeout UNUSED;
+{
+    struct obj *body = arg->a_obj;
+    int zmon = zombie_form(&mons[body->corpsenm]);
+
+    if (zmon != NON_PM) {
+
+        if (has_omid(body))
+            free_omid(body);
+        if (has_omonst(body))
+            free_omonst(body);
+
+        body->corpsenm = zmon;
+        revive_mon(arg, timeout);
+    }
+}
+
 boolean
 cmd_safety_prevention(cmddesc, act, flagcounter)
 const char *cmddesc;
index e25657c842f405c50d86616cff97a4d78959bde2..d8187a9e8b4504e005a961f2fe539dd0ff6a23a7 100644 (file)
--- a/src/end.c
+++ b/src/end.c
@@ -491,6 +491,8 @@ int how;
         u.ugrave_arise = PM_WRAITH;
     else if (mptr->mlet == S_MUMMY && g.urace.mummynum != NON_PM)
         u.ugrave_arise = g.urace.mummynum;
+    else if (zombie_maker(mptr) && zombie_form(g.youmonst.data) != NON_PM)
+        u.ugrave_arise = zombie_form(g.youmonst.data);
     else if (mptr->mlet == S_VAMPIRE && Race_if(PM_HUMAN))
         u.ugrave_arise = PM_VAMPIRE;
     else if (mptr == &mons[PM_GHOUL])
index e08ce35ba7736996af9f53ccb2dcf9c13dbbf4e3..fba87b7e4f942fa198385cdd6701c4edae95c3ef 100644 (file)
@@ -1485,7 +1485,13 @@ int dieroll;
             place_monster(mdef, mdef->mx, mdef->my);
             mdef->mhp = 0;
         }
+        g.zombify = !mwep && zombie_maker(magr->data)
+            && ((mattk->aatyp == AT_TUCH
+                 || mattk->aatyp == AT_CLAW
+                 || mattk->aatyp == AT_BITE)
+                && zombie_form(mdef->data) != NON_PM);
         monkilled(mdef, "", (int) mattk->adtyp);
+        g.zombify = FALSE; /* reset */
         if (!DEADMONSTER(mdef))
             return res; /* mdef lifesaved */
         else if (res == MM_AGR_DIED)
index d894ae920ffbf24e9cc20ae6739d3113131a9da4..7f97bae9646775601be2fc1fd66260925474a66b 100644 (file)
@@ -1198,6 +1198,10 @@ struct obj *body;
                 when = age;
                 break;
             }
+    } else if (!no_revival && g.zombify
+               && zombie_form(&mons[body->corpsenm]) != NON_PM) {
+        action = ZOMBIFY_MON;
+        when = 5 + rn2(15);
     }
 
     (void) start_timer(when, TIMER_OBJECT, action, obj_to_any(body));
@@ -1541,7 +1545,7 @@ unsigned corpstatflags;
 
         otmp->corpsenm = monsndx(ptr);
         otmp->owt = weight(otmp);
-        if (otmp->otyp == CORPSE && (special_corpse(old_corpsenm)
+        if (otmp->otyp == CORPSE && (g.zombify || special_corpse(old_corpsenm)
                                      || special_corpse(otmp->corpsenm))) {
             obj_stop_timers(otmp);
             start_corpse_timeout(otmp);
index 85279ae2e490729485490b2f577d8edb9f622abb..d6e9e24a0d24916fac66c35fa14e1e95dfa86965 100644 (file)
--- a/src/mon.c
+++ b/src/mon.c
@@ -10,6 +10,7 @@
 static void FDECL(sanity_check_single_mon, (struct monst *, BOOLEAN_P,
                                                 const char *));
 static boolean FDECL(restrap, (struct monst *));
+static long FDECL(mm_2way_aggression, (struct monst *, struct monst *));
 static long FDECL(mm_aggression, (struct monst *, struct monst *));
 static long FDECL(mm_displacement, (struct monst *, struct monst *));
 static int NDECL(pick_animal);
@@ -191,6 +192,59 @@ struct monst *mtmp;
     return M_POISONGAS_BAD;
 }
 
+/* Return TRUE if this monster is capable of converting other monsters into
+ * zombies. */
+boolean
+zombie_maker(pm)
+struct permonst *pm;
+{
+    switch(pm->mlet) {
+    case S_ZOMBIE:
+        /* Z-class monsters that aren't actually zombies go here */
+        if (pm == &mons[PM_GHOUL] || pm == &mons[PM_SKELETON])
+            return FALSE;
+        return TRUE;
+    case S_LICH:
+        /* all liches will create zombies as well */
+        return TRUE;
+    }
+    return FALSE;
+}
+
+/* return the monster index of the zombie monster which this monster could be
+ * turned into, or NON_PM if it doesn't have a direct counterpart. Sort of the
+ * zombie-specific inverse of undead_to_corpse.
+ * If a zombie gets passed to this function, it should return NON_PM, not the
+ * same monster again. */
+int
+zombie_form(pm)
+struct permonst *pm;
+{
+    switch(pm->mlet) {
+    case S_KOBOLD:
+        return PM_KOBOLD_ZOMBIE;
+    case S_ORC:
+        return PM_ORC_ZOMBIE;
+    case S_GIANT:
+        if (pm == &mons[PM_ETTIN])
+            return PM_ETTIN_ZOMBIE;
+        return PM_GIANT_ZOMBIE;
+    case S_HUMAN:
+    case S_KOP:
+        if (is_elf(pm))
+            return PM_ELF_ZOMBIE;
+        return PM_HUMAN_ZOMBIE;
+    case S_HUMANOID:
+        if (is_dwarf(pm))
+            return PM_DWARF_ZOMBIE;
+        else
+            break;
+    case S_GNOME:
+        return PM_GNOME_ZOMBIE;
+    }
+    return NON_PM;
+}
+
 /* convert the monster index of an undead to its living counterpart */
 int
 undead_to_corpse(mndx)
@@ -1710,6 +1764,23 @@ long flag;
     return cnt;
 }
 
+/* Part of mm_aggression that represents two-way aggression. To avoid having to
+ * code each case twice, this function contains those cases that ought to
+ * happen twice, and mm_aggression will call it twice. */
+static long
+mm_2way_aggression(magr, mdef)
+struct monst *magr, *mdef;
+{
+    struct permonst *ma = magr->data;
+    struct permonst *md = mdef->data;
+
+    /* zombies vs things that can be zombified */
+    if (zombie_maker(ma) && zombie_form(md) != NON_PM)
+        return ALLOW_M|ALLOW_TM;
+
+    return 0;
+}
+
 /* Monster against monster special attacks; for the specified monster
    combinations, this allows one monster to attack another adjacent one
    in the absence of Conflict.  There is no provision for targetting
@@ -1722,6 +1793,10 @@ struct monst *magr, /* monster that is currently deciding where to move */
 {
     int mndx = monsndx(magr->data);
 
+    /* don't allow pets to fight each other */
+    if (magr->mtame && mdef->mtame)
+        return 0;
+
     /* supposedly purple worms are attracted to shrieking because they
        like to eat shriekers, so attack the latter when feasible */
     if ((mndx == PM_PURPLE_WORM || mndx == PM_BABY_PURPLE_WORM)
@@ -1730,7 +1805,7 @@ struct monst *magr, /* monster that is currently deciding where to move */
     /* Various other combinations such as dog vs cat, cat vs rat, and
        elf vs orc have been suggested.  For the time being we don't
        support those. */
-    return 0L;
+    return (mm_2way_aggression(magr, mdef) | mm_2way_aggression(mdef, magr));
 }
 
 /* Monster displacing another monster out of the way */
@@ -2665,8 +2740,12 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */
         }
         /* corpse--none if hero was inside the monster */
         if (!wasinside && corpse_chance(mtmp, (struct monst *) 0, FALSE)) {
+            g.zombify = (!g.thrownobj && !g.stoned && !uwep
+                         && zombie_maker(g.youmonst.data)
+                         && zombie_form(mtmp->data) != NON_PM);
             cadaver = make_corpse(mtmp, burycorpse ? CORPSTAT_BURIED
                                                    : CORPSTAT_NONE);
+            g.zombify = FALSE; /* reset */
             if (burycorpse && cadaver && cansee(x, y) && !mtmp->minvis
                 && cadaver->where == OBJ_BURIED && !nomsg) {
                 pline("%s corpse ends up buried.", s_suffix(Monnam(mtmp)));
index a16336ff0a37705967e59fb9d7cd3d2de211d039..b367096e81cc0673c8a7107014afb010c419caa9 100644 (file)
@@ -1293,29 +1293,8 @@ register int after;
          * Pets get taken care of above and shouldn't reach this code.
          * Conflict gets handled even farther away (movemon()).
          */
-        if ((info[chi] & ALLOW_M) || (nix == mtmp->mux && niy == mtmp->muy)) {
-            struct monst *mtmp2;
-            int mstatus;
-
-            mtmp2 = m_at(nix, niy);
-
-            g.notonhead = mtmp2 && (nix != mtmp2->mx || niy != mtmp2->my);
-            /* note: mstatus returns 0 if mtmp2 is nonexistent */
-            mstatus = mattackm(mtmp, mtmp2);
-
-            if (mstatus & MM_AGR_DIED) /* aggressor died */
-                return 2;
-
-            if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) && rn2(4)
-                && mtmp2->movement >= NORMAL_SPEED) {
-                mtmp2->movement -= NORMAL_SPEED;
-                g.notonhead = 0;
-                mstatus = mattackm(mtmp2, mtmp); /* return attack */
-                if (mstatus & MM_DEF_DIED)
-                    return 2;
-            }
-            return 3;
-        }
+        if ((info[chi] & ALLOW_M) || (nix == mtmp->mux && niy == mtmp->muy))
+            return m_move_aggress(mtmp, nix, niy);
 
         if ((info[chi] & ALLOW_MDISP)) {
             struct monst *mtmp2;
@@ -1607,6 +1586,44 @@ register int after;
     return mmoved;
 }
 
+/* The part of m_move that deals with a monster attacking another monster (and
+ * that monster possibly retaliating).
+ * Extracted into its own function so that it can be called with monsters that
+ * have special move patterns (shopkeepers, priests, etc) that want to attack
+ * other monsters but aren't just roaming freely around the level (so allowing
+ * m_move to run fully for them could select an invalid move).
+ * x and y are the coordinates mtmp wants to attack.
+ * Return values are the same as for m_move, but this function only return 2
+ * (mtmp died) or 3 (mtmp made its move).
+ */
+int
+m_move_aggress(mtmp, x, y)
+struct monst * mtmp;
+xchar x, y;
+{
+    struct monst *mtmp2;
+    int mstatus;
+
+    mtmp2 = m_at(x, y);
+
+    g.notonhead = mtmp2 && (x != mtmp2->mx || y != mtmp2->my);
+    /* note: mstatus returns 0 if mtmp2 is nonexistent */
+    mstatus = mattackm(mtmp, mtmp2);
+
+    if (mstatus & MM_AGR_DIED) /* aggressor died */
+        return 2;
+
+    if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) && rn2(4)
+        && mtmp2->movement >= NORMAL_SPEED) {
+        mtmp2->movement -= NORMAL_SPEED;
+        g.notonhead = 0;
+        mstatus = mattackm(mtmp2, mtmp); /* return attack */
+        if (mstatus & MM_DEF_DIED)
+            return 2;
+    }
+    return 3;
+}
+
 void
 dissolve_bars(x, y)
 register int x, y;
index 67e8c3114bb1a52f12934ea065aac61101310c6f..3f9a1199b553e4b16cbf15b3364ed253cb8393fe 100644 (file)
@@ -52,6 +52,7 @@ register xchar omx, omy, gx, gy;
     schar chcnt, cnt;
     coord poss[9];
     long info[9];
+    long ninfo;
     long allowflags;
 #if 0 /* dead code; see below */
     struct obj *ib = (struct obj *) 0;
@@ -100,12 +101,14 @@ pick_move:
         ny = poss[i].y;
         if (IS_ROOM(levl[nx][ny].typ)
             || (mtmp->isshk && (!in_his_shop || ESHK(mtmp)->following))) {
-            if (avoid && (info[i] & NOTONL))
+            if (avoid && (info[i] & NOTONL) && !(info[i] & ALLOW_M))
                 continue;
             if ((!appr && !rn2(++chcnt))
-                || (appr && GDIST(nx, ny) < GDIST(nix, niy))) {
+                || (appr && GDIST(nx, ny) < GDIST(nix, niy))
+                || (info[i] & ALLOW_M)) {
                 nix = nx;
                 niy = ny;
+                ninfo = info[i];
             }
         }
     }
@@ -118,6 +121,19 @@ pick_move:
     }
 
     if (nix != omx || niy != omy) {
+
+        if (ninfo & ALLOW_M) {
+            /* mtmp is deciding it would like to attack this turn.
+             * Returns from m_move_aggress don't correspond to the same things
+             * as this function should return, so we need to translate. */
+            switch (m_move_aggress(mtmp, nix, niy)) {
+            case 2:
+                return -2; /* died making the attack */
+            case 3:
+                return 1; /* attacked and spent this move */
+            }
+        }
+
         if (MON_AT(nix, niy))
             return 0;
         remove_monster(omx, omy);
index ca24e94b98b6012dc6d7d3e245a57e139d745409..80e1f10ea120693872b7d94fb2c9dce58a53cb4f 100644 (file)
@@ -1745,6 +1745,7 @@ static const ttable timeout_funcs[NUM_TIME_FUNCS] = {
     TTAB(rot_organic, (timeout_proc) 0, "rot_organic"),
     TTAB(rot_corpse, (timeout_proc) 0, "rot_corpse"),
     TTAB(revive_mon, (timeout_proc) 0, "revive_mon"),
+    TTAB(zombify_mon, (timeout_proc) 0, "zombify_mon"),
     TTAB(burn_object, cleanup_burn, "burn_object"),
     TTAB(hatch_egg, (timeout_proc) 0, "hatch_egg"),
     TTAB(fig_transform, (timeout_proc) 0, "fig_transform"),