From e49c772f134e3e373e6345e37c5e43146f5ab9f2 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 23 Nov 2022 16:41:12 -0800 Subject: [PATCH] unpaid object sanity checking Handle items in gaps of a wall shared between adjacent shops. Make handling of shop boundaries more explicit: walls, the door, and the "free spot" by the door aren't classified as 'costly' but obj->unpaid and obj->no_charge are valid there. Move unpaid/no_charge checking into its own routine to unclutter objlist_sanity(). Pushing a shop-owned boulder to the free spot or doorway or gap in wall triggers the sanity check for the time being. --- include/extern.h | 2 + src/mkobj.c | 110 ++++++++++++++++++++++++++--------------------- src/shk.c | 46 +++++++++++++++++++- 3 files changed, 109 insertions(+), 49 deletions(-) diff --git a/include/extern.h b/include/extern.h index 2a3a92d28..830a64e03 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2480,6 +2480,7 @@ extern boolean same_price(struct obj *, struct obj *); extern void shopper_financial_report(void); extern int inhishop(struct monst *); extern struct monst *shop_keeper(char); +extern struct monst *find_objowner(struct obj *, coordxy x, coordxy y); extern boolean tended_shop(struct mkroom *); extern boolean onshopbill(struct obj *, struct monst *, boolean); extern boolean is_unpaid(struct obj *); @@ -2518,6 +2519,7 @@ extern boolean is_fshk(struct monst *); extern void shopdig(int); extern void pay_for_damage(const char *, boolean); extern boolean costly_spot(coordxy, coordxy); +extern boolean costly_adjacent(struct monst *, coordxy, coordxy); extern struct obj *shop_object(coordxy, coordxy); extern void price_quote(struct obj *); extern void shk_chat(struct monst *); diff --git a/src/mkobj.c b/src/mkobj.c index 886d16cf8..f7cb712bc 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -13,6 +13,7 @@ static void obj_timer_checks(struct obj *, coordxy, coordxy, int); static void container_weight(struct obj *); static struct obj *save_mtraits(struct obj *, struct monst *); static void objlist_sanity(struct obj *, int, const char *); +static void shop_obj_sanity(struct obj *, const char *); static void mon_obj_sanity(struct monst *, const char *); static void insane_obj_bits(struct obj *, struct monst *); static boolean nomerge_exception(struct obj *); @@ -2722,10 +2723,7 @@ obj_sanity_check(void) static void objlist_sanity(struct obj *objlist, int wheretype, const char *mesg) { - struct obj *obj, *otmp; - struct monst *shkp; - boolean costly; - const char *why; + struct obj *obj; for (obj = objlist; obj; obj = obj->nobj) { if (obj->where != wheretype) @@ -2737,50 +2735,9 @@ objlist_sanity(struct obj *objlist, int wheretype, const char *mesg) mesg, (struct monst *) 0); check_contained(obj, mesg); } - why = (const char *) 0; - if (obj->no_charge && obj->unpaid) { - why = "%s obj both unpaid and no_charge! %s %s: %s"; - } else if (obj->unpaid) { - /* unpaid is only applicable for directly carried objects and - for objects inside carried containers */ - otmp = obj; - while (otmp->where == OBJ_CONTAINED) - otmp = otmp->ocontainer; - if (otmp != obj) - obj->ox = otmp->ox, obj->oy = otmp->oy; - - if (otmp->where != OBJ_INVENT) - why = "%s unpaid obj not carried! %s %s: %s"; - else if ((costly = costly_spot(obj->ox, obj->oy)) == FALSE) - why = "%s unpaid obj not inside shop! %s %s: %s"; - else if ((shkp = shop_keeper(*in_rooms(obj->ox, obj->oy, - SHOPBASE))) == 0) - why = "%s unpaid obj inside untended shop! %s %s: %s"; - else if (!onshopbill(obj, shkp, TRUE)) - why = "%s unpaid obj not on shop bill! %s %s: %s"; - } else if (obj->no_charge) { - /* no_charge is only applicable for floor objects in shops and - for objects inside floor containers in shops */ - otmp = obj; - while (otmp->where == OBJ_CONTAINED) - otmp = otmp->ocontainer; - if (otmp != obj) - (void) get_obj_location(otmp, &obj->ox, &obj->oy, BURIED_TOO); - - if (otmp->where != OBJ_FLOOR) - why = "%s no_charge obj not on floor! %s %s: %s"; - else if ((costly = costly_spot(obj->ox, obj->oy)) == FALSE) - why = "%s no_charge obj not inside shop! %s %s: %s"; - else if ((shkp = shop_keeper(*in_rooms(obj->ox, obj->oy, - SHOPBASE))) == 0) - why = "%s no_charge obj inside untended shop! %s %s: %s"; - else if (onshopbill(obj, shkp, TRUE)) - why = "%s no_charge obj on shop bill! %s %s: %s"; - if (why) - insane_object(obj, why, mesg, (struct monst *) 0); - } /* unpaid and/or no_charge */ - if (why) - insane_object(obj, why, mesg, (struct monst *) 0); + if (obj->unpaid || obj->no_charge) { + shop_obj_sanity(obj, mesg); + } if (obj->owornmask) { char maskbuf[40]; boolean bc_ok = FALSE; @@ -2819,6 +2776,63 @@ objlist_sanity(struct obj *objlist, int wheretype, const char *mesg) } } +/* check obj->unpaid and obj->no_charge for shop sanity; caller has + verified that at least one of them is set */ +static void +shop_obj_sanity(struct obj *obj, const char *mesg) +{ + struct obj *otop; + struct monst *shkp; + const char *why; + boolean costly; + coordxy x = 0, y = 0; + + /* if contained, get top-most container; we needs its location */ + otop = obj; + while (otop->where == OBJ_CONTAINED) + otop = otop->ocontainer; + /* get obj's or its container's location; do not update obj->ox,oy + or otop->ox,oy because that would cause sanity checking to + produce side-effects that won't occur when not sanity checking; + no need for CONTAINED_TOO because we have a top level container */ + (void) get_obj_location(otop, &x, &y, BURIED_TOO); + + /* these will always be needed for the normal case, so don't bother + waiting until we find an insanity to fetch them */ + shkp = find_objowner(obj, x, y); + costly = costly_spot(x, y) || costly_adjacent(shkp, x, y); + + why = (const char *) 0; + if (obj->no_charge && obj->unpaid) { + why = "%s obj both unpaid and no_charge! %s %s: %s"; + } else if (obj->unpaid) { + /* unpaid is only applicable for directly carried objects and + for objects inside carried containers */ + if (otop->where != OBJ_INVENT) + why = "%s unpaid obj not carried! %s %s: %s"; + else if (!costly) + why = "%s unpaid obj not inside tended shop! %s %s: %s"; + else if (!shkp) + why = "%s unpaid obj inside untended shop! %s %s: %s"; + else if (!onshopbill(obj, shkp, TRUE)) + why = "%s unpaid obj not on shop bill! %s %s: %s"; + } else if (obj->no_charge) { + /* no_charge is only applicable for floor objects in shops + and for objects inside floor containers in shops */ + if (otop->where != OBJ_FLOOR) + why = "%s no_charge obj not on floor! %s %s: %s"; + else if (!costly) + why = "%s no_charge obj not inside tended shop! %s %s: %s"; + else if (!shkp) + why = "%s no_charge obj inside untended shop! %s %s: %s"; + else if (onshopbill(obj, shkp, TRUE)) + why = "%s no_charge obj on shop bill! %s %s: %s"; + } + if (why) + insane_object(obj, why, mesg, (struct monst *) 0); + return; +} + /* sanity check for objects carried by all monsters in specified list */ static void mon_obj_sanity(struct monst *monlist, const char *mesg) diff --git a/src/shk.c b/src/shk.c index 49663c586..18c3ea2e7 100644 --- a/src/shk.c +++ b/src/shk.c @@ -862,6 +862,32 @@ shop_keeper(char rmno) return shkp; } +/* find the shopkeeper who owns 'obj'; needed to handle shared shop walls */ +struct monst * +find_objowner( + struct obj *obj, + coordxy x, coordxy y) /* caller passes obj's location since obj->ox,oy + * might be stale; don't update coordinates here + * because if we're called duing sanity checking + * they shouldn't be modified */ +{ + struct monst *shkp, *deflt_shkp = 0; + char *roomindx, *where = in_rooms(x, y, SHOPBASE); + + /* conceptually object could be inside up to four rooms simultaneously; + in practice it will usually be one room but can sometimes be two; + check shk and bill for each room rather than just the first; + fallback to the first shk if obj isn't on the relevant bill(s) */ + for (roomindx = where; *roomindx; ++roomindx) + if ((shkp = shop_keeper(*roomindx)) != 0) { + if (onshopbill(obj, shkp, TRUE)) + return shkp; + if (!deflt_shkp) + deflt_shkp = shkp; + } + return deflt_shkp; +} + boolean tended_shop(struct mkroom *sroom) { @@ -4384,7 +4410,7 @@ pay_for_damage(const char* dmgstr, boolean cant_mollify) /* called in dokick.c when we kick an object that might be in a store */ boolean -costly_spot(register coordxy x, register coordxy y) +costly_spot(coordxy x, coordxy y) { struct monst *shkp; struct eshk *eshkp; @@ -4399,6 +4425,24 @@ costly_spot(register coordxy x, register coordxy y) && !(x == eshkp->shk.x && y == eshkp->shk.y)); } +/* called by sanity checking when an unpaid or no_charge item is not at a + costly_spot; it might still be within the boundary of the shop; if so, + those flags are still valid */ +boolean +costly_adjacent( + struct monst *shkp, + coordxy x, coordxy y) +{ + struct eshk *eshkp; + + if (!shkp || !inhishop(shkp) || !isok(x, y)) + return FALSE; + eshkp = ESHK(shkp); + /* adjacent if is a shop wall spot, including door; + also treat "free spot" one step inside the door as adjacent */ + return (levl[x][y].edge || (x == eshkp->shk.x && y == eshkp->shk.y)); +} + /* called by dotalk(sounds.c) when #chatting; returns obj if location contains shop goods and shopkeeper is willing & able to speak */ struct obj *