Mild Zombie Apocalypse

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.
This commit is contained in:
Pasi Kallinen
2020-10-21 21:04:03 +03:00
parent 1cc26106bf
commit aeb0ea65e3
13 changed files with 182 additions and 27 deletions

View 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 render the color names in the corresponding color when using the pick-a-color
menu for adding status highlights or menu colors via 'O' menu for adding status highlights or menu colors via 'O'
reading blessed scroll of teleportation confers one-shot teleport control reading blessed scroll of teleportation confers one-shot teleport control
mild zombie apocalypse
Platform- and/or Interface-Specific New Features Platform- and/or Interface-Specific New Features

View File

@@ -972,6 +972,7 @@ struct instance_globals {
/* mon.c */ /* mon.c */
boolean vamp_rise_msg; boolean vamp_rise_msg;
boolean disintegested; boolean disintegested;
boolean zombify;
short *animal_list; /* list of PM values for animal monsters */ short *animal_list; /* list of PM values for animal monsters */
int animal_list_count; int animal_list_count;

View File

@@ -425,6 +425,7 @@ E void FDECL(schedule_goto, (d_level *, BOOLEAN_P, BOOLEAN_P, int,
E void NDECL(deferred_goto); E void NDECL(deferred_goto);
E boolean FDECL(revive_corpse, (struct obj *)); E boolean FDECL(revive_corpse, (struct obj *));
E void FDECL(revive_mon, (ANY_P *, long)); 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 boolean FDECL(cmd_safety_prevention, (const char *, const char *, int *));
E int NDECL(donull); E int NDECL(donull);
E int NDECL(dowipe); E int NDECL(dowipe);
@@ -1454,6 +1455,8 @@ E int FDECL(cmap_to_type, (int));
/* ### mon.c ### */ /* ### mon.c ### */
E void NDECL(mon_sanity_check); 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(m_poisongas_ok, (struct monst *));
E int FDECL(undead_to_corpse, (int)); E int FDECL(undead_to_corpse, (int));
E int FDECL(genus, (int, 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 int FDECL(dochug, (struct monst *));
E boolean FDECL(m_digweapon_check, (struct monst *, XCHAR_P, XCHAR_P)); 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, (struct monst *, int));
E int FDECL(m_move_aggress, (struct monst *, XCHAR_P, XCHAR_P));
E void FDECL(dissolve_bars, (int, int)); E void FDECL(dissolve_bars, (int, int));
E boolean FDECL(closed_door, (int, int)); E boolean FDECL(closed_door, (int, int));
E boolean FDECL(accessible, (int, int)); E boolean FDECL(accessible, (int, int));

View File

@@ -29,6 +29,7 @@ enum timeout_types {
ROT_ORGANIC = 0, /* for buried organics */ ROT_ORGANIC = 0, /* for buried organics */
ROT_CORPSE, ROT_CORPSE,
REVIVE_MON, REVIVE_MON,
ZOMBIFY_MON,
BURN_OBJECT, BURN_OBJECT,
HATCH_EGG, HATCH_EGG,
FIG_TRANSFORM, FIG_TRANSFORM,

View File

@@ -498,6 +498,7 @@ const struct instance_globals g_init = {
/* mon.c */ /* mon.c */
UNDEFINED_VALUE, /* vamp_rise_msg */ UNDEFINED_VALUE, /* vamp_rise_msg */
UNDEFINED_VALUE, /* disintegested */ UNDEFINED_VALUE, /* disintegested */
UNDEFINED_VALUE, /* zombify */
NULL, /* animal_list */ NULL, /* animal_list */
UNDEFINED_VALUE, /* animal_list_count */ UNDEFINED_VALUE, /* animal_list_count */

View File

@@ -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 boolean
cmd_safety_prevention(cmddesc, act, flagcounter) cmd_safety_prevention(cmddesc, act, flagcounter)
const char *cmddesc; const char *cmddesc;

View File

@@ -491,6 +491,8 @@ int how;
u.ugrave_arise = PM_WRAITH; u.ugrave_arise = PM_WRAITH;
else if (mptr->mlet == S_MUMMY && g.urace.mummynum != NON_PM) else if (mptr->mlet == S_MUMMY && g.urace.mummynum != NON_PM)
u.ugrave_arise = g.urace.mummynum; 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)) else if (mptr->mlet == S_VAMPIRE && Race_if(PM_HUMAN))
u.ugrave_arise = PM_VAMPIRE; u.ugrave_arise = PM_VAMPIRE;
else if (mptr == &mons[PM_GHOUL]) else if (mptr == &mons[PM_GHOUL])

View File

@@ -1485,7 +1485,13 @@ int dieroll;
place_monster(mdef, mdef->mx, mdef->my); place_monster(mdef, mdef->mx, mdef->my);
mdef->mhp = 0; 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); monkilled(mdef, "", (int) mattk->adtyp);
g.zombify = FALSE; /* reset */
if (!DEADMONSTER(mdef)) if (!DEADMONSTER(mdef))
return res; /* mdef lifesaved */ return res; /* mdef lifesaved */
else if (res == MM_AGR_DIED) else if (res == MM_AGR_DIED)

View File

@@ -1198,6 +1198,10 @@ struct obj *body;
when = age; when = age;
break; 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)); (void) start_timer(when, TIMER_OBJECT, action, obj_to_any(body));
@@ -1541,7 +1545,7 @@ unsigned corpstatflags;
otmp->corpsenm = monsndx(ptr); otmp->corpsenm = monsndx(ptr);
otmp->owt = weight(otmp); 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))) { || special_corpse(otmp->corpsenm))) {
obj_stop_timers(otmp); obj_stop_timers(otmp);
start_corpse_timeout(otmp); start_corpse_timeout(otmp);

View File

@@ -10,6 +10,7 @@
static void FDECL(sanity_check_single_mon, (struct monst *, BOOLEAN_P, static void FDECL(sanity_check_single_mon, (struct monst *, BOOLEAN_P,
const char *)); const char *));
static boolean FDECL(restrap, (struct monst *)); 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_aggression, (struct monst *, struct monst *));
static long FDECL(mm_displacement, (struct monst *, struct monst *)); static long FDECL(mm_displacement, (struct monst *, struct monst *));
static int NDECL(pick_animal); static int NDECL(pick_animal);
@@ -191,6 +192,59 @@ struct monst *mtmp;
return M_POISONGAS_BAD; 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 */ /* convert the monster index of an undead to its living counterpart */
int int
undead_to_corpse(mndx) undead_to_corpse(mndx)
@@ -1710,6 +1764,23 @@ long flag;
return cnt; 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 /* Monster against monster special attacks; for the specified monster
combinations, this allows one monster to attack another adjacent one combinations, this allows one monster to attack another adjacent one
in the absence of Conflict. There is no provision for targetting 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); 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 /* supposedly purple worms are attracted to shrieking because they
like to eat shriekers, so attack the latter when feasible */ like to eat shriekers, so attack the latter when feasible */
if ((mndx == PM_PURPLE_WORM || mndx == PM_BABY_PURPLE_WORM) 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 /* 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 elf vs orc have been suggested. For the time being we don't
support those. */ support those. */
return 0L; return (mm_2way_aggression(magr, mdef) | mm_2way_aggression(mdef, magr));
} }
/* Monster displacing another monster out of the way */ /* 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 */ /* corpse--none if hero was inside the monster */
if (!wasinside && corpse_chance(mtmp, (struct monst *) 0, FALSE)) { 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 cadaver = make_corpse(mtmp, burycorpse ? CORPSTAT_BURIED
: CORPSTAT_NONE); : CORPSTAT_NONE);
g.zombify = FALSE; /* reset */
if (burycorpse && cadaver && cansee(x, y) && !mtmp->minvis if (burycorpse && cadaver && cansee(x, y) && !mtmp->minvis
&& cadaver->where == OBJ_BURIED && !nomsg) { && cadaver->where == OBJ_BURIED && !nomsg) {
pline("%s corpse ends up buried.", s_suffix(Monnam(mtmp))); pline("%s corpse ends up buried.", s_suffix(Monnam(mtmp)));

View File

@@ -1293,29 +1293,8 @@ register int after;
* Pets get taken care of above and shouldn't reach this code. * Pets get taken care of above and shouldn't reach this code.
* Conflict gets handled even farther away (movemon()). * Conflict gets handled even farther away (movemon()).
*/ */
if ((info[chi] & ALLOW_M) || (nix == mtmp->mux && niy == mtmp->muy)) { if ((info[chi] & ALLOW_M) || (nix == mtmp->mux && niy == mtmp->muy))
struct monst *mtmp2; return m_move_aggress(mtmp, nix, niy);
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_MDISP)) { if ((info[chi] & ALLOW_MDISP)) {
struct monst *mtmp2; struct monst *mtmp2;
@@ -1607,6 +1586,44 @@ register int after;
return mmoved; 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 void
dissolve_bars(x, y) dissolve_bars(x, y)
register int x, y; register int x, y;

View File

@@ -52,6 +52,7 @@ register xchar omx, omy, gx, gy;
schar chcnt, cnt; schar chcnt, cnt;
coord poss[9]; coord poss[9];
long info[9]; long info[9];
long ninfo;
long allowflags; long allowflags;
#if 0 /* dead code; see below */ #if 0 /* dead code; see below */
struct obj *ib = (struct obj *) 0; struct obj *ib = (struct obj *) 0;
@@ -100,12 +101,14 @@ pick_move:
ny = poss[i].y; ny = poss[i].y;
if (IS_ROOM(levl[nx][ny].typ) if (IS_ROOM(levl[nx][ny].typ)
|| (mtmp->isshk && (!in_his_shop || ESHK(mtmp)->following))) { || (mtmp->isshk && (!in_his_shop || ESHK(mtmp)->following))) {
if (avoid && (info[i] & NOTONL)) if (avoid && (info[i] & NOTONL) && !(info[i] & ALLOW_M))
continue; continue;
if ((!appr && !rn2(++chcnt)) if ((!appr && !rn2(++chcnt))
|| (appr && GDIST(nx, ny) < GDIST(nix, niy))) { || (appr && GDIST(nx, ny) < GDIST(nix, niy))
|| (info[i] & ALLOW_M)) {
nix = nx; nix = nx;
niy = ny; niy = ny;
ninfo = info[i];
} }
} }
} }
@@ -118,6 +121,19 @@ pick_move:
} }
if (nix != omx || niy != omy) { 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)) if (MON_AT(nix, niy))
return 0; return 0;
remove_monster(omx, omy); remove_monster(omx, omy);

View File

@@ -1745,6 +1745,7 @@ static const ttable timeout_funcs[NUM_TIME_FUNCS] = {
TTAB(rot_organic, (timeout_proc) 0, "rot_organic"), TTAB(rot_organic, (timeout_proc) 0, "rot_organic"),
TTAB(rot_corpse, (timeout_proc) 0, "rot_corpse"), TTAB(rot_corpse, (timeout_proc) 0, "rot_corpse"),
TTAB(revive_mon, (timeout_proc) 0, "revive_mon"), TTAB(revive_mon, (timeout_proc) 0, "revive_mon"),
TTAB(zombify_mon, (timeout_proc) 0, "zombify_mon"),
TTAB(burn_object, cleanup_burn, "burn_object"), TTAB(burn_object, cleanup_burn, "burn_object"),
TTAB(hatch_egg, (timeout_proc) 0, "hatch_egg"), TTAB(hatch_egg, (timeout_proc) 0, "hatch_egg"),
TTAB(fig_transform, (timeout_proc) 0, "fig_transform"), TTAB(fig_transform, (timeout_proc) 0, "fig_transform"),