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
/* mon.c */
boolean vamp_rise_msg;
boolean disintegested;
+ boolean zombify;
short *animal_list; /* list of PM values for animal monsters */
int animal_list_count;
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);
/* ### 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));
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));
ROT_ORGANIC = 0, /* for buried organics */
ROT_CORPSE,
REVIVE_MON,
+ ZOMBIFY_MON,
BURN_OBJECT,
HATCH_EGG,
FIG_TRANSFORM,
/* mon.c */
UNDEFINED_VALUE, /* vamp_rise_msg */
UNDEFINED_VALUE, /* disintegested */
+ UNDEFINED_VALUE, /* zombify */
NULL, /* animal_list */
UNDEFINED_VALUE, /* animal_list_count */
}
}
+/* 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;
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])
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)
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));
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);
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);
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)
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
{
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)
/* 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 */
}
/* 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)));
* 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;
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;
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;
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];
}
}
}
}
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);
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"),