From e2a3c98aacae9591096a5575129b05ae31df0d88 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 9 Feb 2024 23:32:16 -0800 Subject: [PATCH] worn/wielded pointer sanity checks Check the various uarm, uwep, and so forth pointers to make sure that they point to items in hero's inventory and that those items have the corresponding W_ARM, W_WEP, &c bit set in their owornmask field. Also check whether any other items in inventory have the same bit set. [Some of this is already handled by sanity_check_worn() in mkobj.c.] Also validate two-weapon combat mode. I don't recall ever seeing any problems reported about it though. Does not validate ball and chain. Those should have their own sanity checks that validate a bunch of other stuff besides just worn slots. They already get some checking by the normal object tests. This works ok with 'sanity_check' set and items worn and wielded normally. The only insane situation tested was by reverting the confused-looting-with-quivered-gold fix from earlier today. I haven't used a debugger to force other such problems so this isn't very thoroughly tested. --- include/extern.h | 3 +- src/cmd.c | 3 +- src/worn.c | 124 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 111 insertions(+), 19 deletions(-) diff --git a/include/extern.h b/include/extern.h index cd0613e01..982ba5ebf 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1,4 +1,4 @@ -/* NetHack 3.7 extern.h $NHDT-Date: 1706213788 2024/01/25 20:16:28 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1373 $ */ +/* NetHack 3.7 extern.h $NHDT-Date: 1707547708 2024/02/10 06:48:28 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1381 $ */ /* Copyright (c) Steve Creps, 1988. */ /* NetHack may be freely redistributed. See license for details. */ @@ -3686,6 +3686,7 @@ extern void setnotworn(struct obj *) NO_NNARGS; /* has tests for obj */ extern void allunworn(void); extern struct obj *wearmask_to_obj(long); extern long wearslot(struct obj *) NONNULLARG1; +extern void check_wornmask_slots(void); extern void mon_set_minvis(struct monst *) NONNULLARG1; extern void mon_adjust_speed(struct monst *, int, struct obj *) NONNULLARG1; extern void update_mon_extrinsics(struct monst *, struct obj *, boolean, diff --git a/src/cmd.c b/src/cmd.c index a337922e3..0940eb93b 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 cmd.c $NHDT-Date: 1704225560 2024/01/02 19:59:20 $ $NHDT-Branch: keni-luabits2 $:$NHDT-Revision: 1.699 $ */ +/* NetHack 3.7 cmd.c $NHDT-Date: 1707547723 2024/02/10 06:48:43 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.704 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -4255,6 +4255,7 @@ you_sanity_check(void) impossible("sanity_check: you over monster"); } + check_wornmask_slots(); (void) check_invent_gold("invent"); } diff --git a/src/worn.c b/src/worn.c index aa8f44c41..695a7d705 100644 --- a/src/worn.c +++ b/src/worn.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 worn.c $NHDT-Date: 1652577035 2022/05/15 01:10:35 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.84 $ */ +/* NetHack 3.7 worn.c $NHDT-Date: 1707547726 2024/02/10 06:48:46 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.100 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -13,22 +13,23 @@ static int extra_pref(struct monst *, struct obj *) NONNULLARG1; const struct worn { long w_mask; struct obj **w_obj; -} worn[] = { { W_ARM, &uarm }, - { W_ARMC, &uarmc }, - { W_ARMH, &uarmh }, - { W_ARMS, &uarms }, - { W_ARMG, &uarmg }, - { W_ARMF, &uarmf }, - { W_ARMU, &uarmu }, - { W_RINGL, &uleft }, - { W_RINGR, &uright }, - { W_WEP, &uwep }, - { W_SWAPWEP, &uswapwep }, - { W_QUIVER, &uquiver }, - { W_AMUL, &uamul }, - { W_TOOL, &ublindf }, - { W_BALL, &uball }, - { W_CHAIN, &uchain }, + const char *w_what; /* for failing sanity check's feedback */ +} worn[] = { { W_ARM, &uarm, "suit" }, + { W_ARMC, &uarmc, "cloak" }, + { W_ARMH, &uarmh, "helmet" }, + { W_ARMS, &uarms, "shield" }, + { W_ARMG, &uarmg, "gloves" }, + { W_ARMF, &uarmf, "boots" }, + { W_ARMU, &uarmu, "shirt" }, + { W_RINGL, &uleft, "left ring" }, + { W_RINGR, &uright, "right ring" }, + { W_WEP, &uwep, "weapon" }, + { W_SWAPWEP, &uswapwep, "alternate weapon" }, + { W_QUIVER, &uquiver, "quiver" }, + { W_AMUL, &uamul, "amulet" }, + { W_TOOL, &ublindf, "facewear" }, /* blindfold|towel|lenses */ + { W_BALL, &uball, "chained ball" }, + { W_CHAIN, &uchain, "attached chain" }, { 0, 0 } }; @@ -251,6 +252,95 @@ wearslot(struct obj *obj) return res; } +/* for 'sanity_check' option, called by you_sanity_check() */ +void +check_wornmask_slots(void) +{ + /* we'll skip ball and chain here--they warrant separate sanity check; + at present, we also skip 'uskin' because it isn't included in worn[] */ +#define IGNORE_SLOTS (W_ART | W_ARTI | W_SADDLE | W_BALL| W_CHAIN) + char whybuf[BUFSZ]; + const struct worn *wp; + struct obj *o, *otmp; + long m; + + for (wp = worn; wp->w_mask; wp++) { + o = *wp->w_obj; + if (!o) + continue; + m = wp->w_mask; + if ((m & IGNORE_SLOTS) != 0L && (m & ~IGNORE_SLOTS) == 0L) + continue; + if ((o = *wp->w_obj) != 0) { + whybuf[0] = '\0'; + /* slot pointer (uarm, uwep, &c) is populated; check that object + is in inventory and has the relevant owornmask bit set */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp == o) + break; + if (!otmp) + Sprintf(whybuf, "%s (%s) not found in invent", + wp->w_what, fmt_ptr(o)); + else if ((o->owornmask & m) == 0L) + Sprintf(whybuf, "%s bit not set in owornmask [0x%08lx]", + wp->w_what, o->owornmask); + else if ((o->owornmask & ~(m | IGNORE_SLOTS)) != 0L) + Sprintf(whybuf, "%s wrong bit set in owornmask [0x%08lx]", + wp->w_what, o->owornmask); + if (whybuf[0]) + impossible("Worn-slot insanity: %s.", whybuf); + } /* o != NULL */ + + /* check whether any item other than the one in the slot pointer + claims to be worn/wielded in this slot; make this test whether + 'o' is Null or not; [sanity_check_worn(mkobj.c) for object by + object checking will most likely have already caught this] */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + if (otmp != o && (otmp->owornmask & wp->w_mask) != 0L) { + Sprintf(whybuf, "%s has owornmask 0x%08lx [0x%08lx bit set]", + wp->w_what, otmp->owornmask, m); + impossible("Worn-slot insanity: %s.", whybuf); + } + } + } + +#ifdef EXTRA_SANITY_CHECKS + /* dual wielding: not a slot but lots of things to verify */ + if (u.twoweap) { + const char *why = NULL; + + if (!uwep || !uswapwep) { + Sprintf(whybuf, "without %s%s%s", + !uwep ? "uwep" : "", + (!uwep && !uswapwep) ? " and without " : "", + !uswapwep ? "uswapwep" : ""); + why = whybuf; + } else if (uarms) + why = "while wearing shield"; + else if (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep)) + why = "uwep is not a weapon"; + else if (is_launcher(uwep) || is_ammo(uwep) || is_missile(uwep)) + why = "uwep is not a melee weapon"; + else if (bimanual(uwep)) + why = "uwep is two-handed"; + else if (uswapwep->oclass != WEAPON_CLASS && !is_weptool(uswapwep)) + why = "uswapwep is not a weapon"; + else if (is_launcher(uswapwep) || is_ammo(uswapwep) + || is_missile(uswapwep)) + why = "uswapwep is not a melee weapon"; + else if (bimanual(uswapwep)) + why = "uswapwep is two-handed"; + else if (!could_twoweap(gy.youmonst.data)) + why = "without two weapon attacks"; + + if (why) + impossible("Two-weapon insanity: %s.", why); + } +#endif /* EXTRA_SANITY_CHECKS */ + return; +#undef IGNORE_SLOTS +} /* check_wornmask_slots() */ + void mon_set_minvis(struct monst *mon) {