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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
22
src/do.c
22
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;
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
81
src/mon.c
81
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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
20
src/priest.c
20
src/priest.c
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user