unpaid object: sanity check, teleporting, 'I u'

It turns out that there are some objects marked unpaid that aren't
carried by the hero, so the recent sanity check for unpaid/no_charge
could complain.  Unpaid items dropped on the shop boundary (gap in
shop wall, doorway, shk's free spot) stayed unpaid when dropped onto
the floor, similar to recent change for pushed shop-owned boulders.
Don't give sanity complaints for those.  They could be all the way
inside a shop too, where unpaid items in a gap in the shop wall got
pushed into the shop when the wall was repaired.  (Possibly those
should come off the bill instead of remaining unpaid.)

Teleporting items out of a shop was marking them unpaid instead of
treating that as robbery.  That's a bug caught by the sanity check.
rloco() was also marking shop items which got teleported from one
spot inside the shop to another spot inside the same shop as unpaid.
Fix both of those things.  Also, if an unpaid item on the boundary
gets teleported all the way inside, take it off the bill.

Change 'I u' to mention whether there are additional unpaid items on
the floor somewhere since they won't be part of unpaid inventory and
they're not on the used-up bill either.  It might occasionally help
the player figure out why the shopkeeper won't let the hero out of
the shop.
This commit is contained in:
PatR
2022-11-29 13:55:42 -08:00
parent 8836b32128
commit e64ed2859d
6 changed files with 84 additions and 27 deletions

View File

@@ -1058,6 +1058,8 @@ when hero hears an unseen monster reading a scroll, only describe the monster
acccurately if hero is not hallucinating and monster is same species
as hero's current form
don't allow monsters to disarm hero with bullwhip if hero is engulfed
teleporting an object out of a shop put it on the shop bill instead of dealing
with robbery
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
@@ -1883,6 +1885,8 @@ very large humanoids can wear mummy wrappings
for ranger characters, shooting any type of arrow while wielding the Longbow
of Diana gets an extra +1 to multi-shot
change the #vanquished command from debug-only to general user command
have 'I u' mention whether there are any unpaid items on the floor (unusual
but not impossible); it doesn't itemize them or show shop price
Platform- and/or Interface-Specific New Features

View File

@@ -87,8 +87,10 @@ struct obj {
Bitfield(cursed, 1); /* uncursed when neither cursed nor blessed */
Bitfield(blessed, 1);
Bitfield(unpaid, 1); /* owned by shop; valid for objects in hero's
* inventory or inside containers there;
* not used for items on the floor */
* inventory or inside containers there; also,
* used for items on the floor only on the shop
* boundary (including "free spot") or if moved
* from there to inside by wall repairs */
Bitfield(no_charge, 1); /* if shk shouldn't charge for this; valid for
* items on shop floor or in containers there;
* implicit for items at any other location

View File

@@ -395,8 +395,10 @@ ghitm(register struct monst *mtmp, register struct obj *gold)
/* container is kicked, dropped, thrown or otherwise impacted by player.
* Assumes container is on floor. Checks contents for possible damage. */
void
container_impact_dmg(struct obj *obj, coordxy x,
coordxy y) /* coordinates where object was before the impact, not after */
container_impact_dmg(
struct obj *obj,
coordxy x, /* coordinates where object was */
coordxy y) /* before the impact, not after */
{
struct monst *shkp;
struct obj *otmp, *otmp2;
@@ -504,7 +506,7 @@ really_kick_object(coordxy x, coordxy y)
return 1;
}
if (trap->ttyp == STATUE_TRAP) {
activate_statue_trap(trap, x,y, FALSE);
activate_statue_trap(trap, x, y, FALSE);
return 1;
}
}

View File

@@ -30,7 +30,7 @@ static char display_pickinv(const char *, const char *, const char *,
boolean, long *);
static char display_used_invlets(char);
static boolean this_type_only(struct obj *);
static void dounpaid(void);
static void dounpaid(int);
static struct obj *find_unpaid(struct obj *, struct obj **);
static void menu_identify(int);
static boolean tool_being_used(struct obj *);
@@ -3732,7 +3732,7 @@ count_contents(
}
static void
dounpaid(void)
dounpaid(int floorcount)
{
winid win;
struct obj *otmp, *marker, *contnr;
@@ -3744,7 +3744,7 @@ dounpaid(void)
count = count_unpaid(g.invent);
otmp = marker = contnr = (struct obj *) 0;
if (count == 1) {
if (count == 1 && !floorcount) {
otmp = find_unpaid(g.invent, &marker);
contnr = unknwn_contnr_contents(otmp);
}
@@ -3825,10 +3825,30 @@ dounpaid(void)
}
}
putstr(win, 0, "");
putstr(win, 0,
xprname((struct obj *) 0, "Total:", '*', FALSE, totcost, 0L));
display_nhwindow(win, FALSE);
if (count > 0) {
putstr(win, 0, "");
putstr(win, 0,
xprname((struct obj *) 0, "Total:", '*', FALSE, totcost, 0L));
}
if (floorcount > 0) {
char buf[BUFSZ];
const char *floorverb = (floorcount > 1) ? "are" : "is";
if (!count) {
You(
"aren't carrying any unpaid items but there %s %d on the floor.",
floorverb, floorcount);
} else {
putstr(win, 0, "");
Sprintf(buf, "(There %s %d more unpaid object%s on the floor.)",
floorverb, floorcount, plur(floorcount));
putstr(win, 0, buf);
}
}
if (count > 0)
display_nhwindow(win, FALSE);
destroy_nhwindow(win);
}
@@ -3989,8 +4009,10 @@ dotypeinv(void)
goto doI_done;
}
if (c == 'u' || (c == 'U' && unpaid_count && !ucnt)) {
if (unpaid_count)
dounpaid();
int floorcount = count_unpaid(fobj);
if (unpaid_count || floorcount)
dounpaid(floorcount);
else
You("are not carrying any unpaid objects.");
goto doI_done;

View File

@@ -2784,7 +2784,7 @@ shop_obj_sanity(struct obj *obj, const char *mesg)
struct obj *otop;
struct monst *shkp;
const char *why;
boolean costly;
boolean costly, costlytoo;
coordxy x = 0, y = 0;
/* if contained, get top-most container; we needs its location */
@@ -2800,17 +2800,22 @@ shop_obj_sanity(struct obj *obj, const char *mesg)
/* 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);
costly = costly_spot(x, y);
costlytoo = 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)
/* unpaid is only applicable for directly carried objects, for
objects inside carried containers, and for floor items outside
the shop proper but within the shop boundary (walls, door, "free
spot") and for objects moved from such spots into the shop proper
by repair of shop walls */
if (otop->where != OBJ_INVENT
&& (otop->where != OBJ_FLOOR || (!costly && !costlytoo)))
why = "%s unpaid obj not carried! %s %s: %s";
else if (!costly)
else if (!costly && !costlytoo)
why = "%s unpaid obj not inside tended shop! %s %s: %s";
else if (!shkp)
why = "%s unpaid obj inside untended shop! %s %s: %s";

View File

@@ -1655,15 +1655,37 @@ rloco(register struct obj* obj)
} else if (otx == 0 && oty == 0) {
; /* fell through a trap door; no update of old loc needed */
} else {
if (costly_spot(otx, oty)
&& (!costly_spot(tx, ty)
|| !strchr(in_rooms(tx, ty, 0), *in_rooms(otx, oty, 0)))) {
if (costly_spot(u.ux, u.uy)
&& strchr(u.urooms, *in_rooms(otx, oty, 0)))
addtobill(obj, FALSE, FALSE, FALSE);
else
struct monst *shkp = find_objowner(obj, otx, oty);
boolean objinshop = shkp && costly_spot(otx, oty),
onboundary = shkp && costly_adjacent(shkp, otx, oty);
/*
* If object starts inside shop or is unpaid and on shop boundary:
* if hero is outside the shop, treat this as theft;
* otherwise, if it arrives inside same shop, remove it from bill;
* otherwise, if it arrives on the boundary, add it to bill;
* if it arrives outside the shop, treat this as a theft.
* Billing routines deal with obj->no_charge.
*/
if (objinshop || (obj->unpaid && onboundary)) {
char h = *in_rooms(u.ux, u.uy, SHOPBASE),
oo = *in_rooms(otx, oty, 0);
boolean hinshop = h && strchr(in_rooms(shkp->mx, shkp->my, 0), h);
if (hinshop && costly_spot(tx, ty)
/* verify that it's the same shop */
&& oo && strchr(in_rooms(tx, ty, 0), oo)) {
if (obj->unpaid)
subfrombill(obj, shkp);
} else if (hinshop && costly_adjacent(shkp, tx, ty)
&& oo && strchr(in_rooms(tx, ty, 0), oo)) {
if (!obj->unpaid)
addtobill(obj, FALSE, FALSE, FALSE);
} else {
(void) stolen_value(obj, otx, oty, FALSE, FALSE);
}
}
newsym(otx, oty); /* update old location */
}
place_object(obj, tx, ty);