From: Pasi Kallinen Date: Wed, 21 Oct 2020 18:04:03 +0000 (+0300) Subject: Mild Zombie Apocalypse X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=aeb0ea65e3ea173ccab744964fa7a0bb8425dc0c;p=nethack 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. --- diff --git a/doc/fixes37.0 b/doc/fixes37.0 index c72819118..163b1c38f 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 diff --git a/include/decl.h b/include/decl.h index be1d583ac..c72ab57bf 100644 --- a/include/decl.h +++ b/include/decl.h @@ -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; diff --git a/include/extern.h b/include/extern.h index b743db206..a8151c034 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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)); diff --git a/include/timeout.h b/include/timeout.h index 5cad4388a..3853c80d4 100644 --- a/include/timeout.h +++ b/include/timeout.h @@ -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, diff --git a/src/decl.c b/src/decl.c index 4c836bbde..5f44d64e1 100644 --- a/src/decl.c +++ b/src/decl.c @@ -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 */ diff --git a/src/do.c b/src/do.c index c891a94d4..ee2c3c141 100644 --- 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; diff --git a/src/end.c b/src/end.c index e25657c84..d8187a9e8 100644 --- 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]) diff --git a/src/mhitm.c b/src/mhitm.c index e08ce35ba..fba87b7e4 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -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) diff --git a/src/mkobj.c b/src/mkobj.c index d894ae920..7f97bae96 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -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); diff --git a/src/mon.c b/src/mon.c index 85279ae2e..d6e9e24a0 100644 --- 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))); diff --git a/src/monmove.c b/src/monmove.c index a16336ff0..b367096e8 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -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; diff --git a/src/priest.c b/src/priest.c index 67e8c3114..3f9a1199b 100644 --- a/src/priest.c +++ b/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); diff --git a/src/timeout.c b/src/timeout.c index ca24e94b9..80e1f10ea 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -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"),