fix issue #872 - container-to-container #tip

Reported by k2:  tipping one container's contents directly into
another container allowed transferring a wand of cancellation (not
mentioned:  or a bag of holding or a bag of tricks) into a bag of
holding without blowing it up.

That's now fixed.  There are other issues that this doesn't touch:

I think it's odd that you can transfer stuff from one carried
container to another but not from a carried container to a floor
container nor from one floor container to another one at same spot.

I didn't test shop billing so an not sure what happens when #tip
blows up a bag of holding and there are some unpaid items involved.

Using #tip on horn of plenty treats it like a container, but doing
that when it's carried doesn't offer the chance to tip its contents
directly into a carried container.

Tipping a carried container does not require free hands or even
limbs (for playability) but tipping such into another container
should require at least one free hand.

Fixes #872
This commit is contained in:
PatR
2022-09-12 14:17:22 -07:00
parent 356e3176bc
commit 7ae4efb07c
4 changed files with 37 additions and 18 deletions

View File

@@ -1380,6 +1380,9 @@ command line processing interleaved with options file processing is complex:
when the Astral level got created, any fake player character monsters that
were seen or sensed when created were reported as "<mon> suddenly
appears!" rather than be treated as part of level's initial population
tipping contents of one container directly into another allowed transferring
wands of cancellation and bags of holding or tricks that were inside
a sack, box, or chest into a bag of holding without blowing it up
curses: 'msg_window' option wasn't functional for curses unless the binary
also included tty support

View File

@@ -2105,7 +2105,7 @@ extern int doloot(void);
extern void observe_quantum_cat(struct obj *, boolean, boolean);
extern boolean container_gone(int(*)(struct obj *));
extern boolean u_handsy(void);
extern int use_container(struct obj **, int, boolean);
extern int use_container(struct obj **, boolean, boolean);
extern int loot_mon(struct monst *, int *, boolean *);
extern int dotip(void);
extern struct autopickup_exception *check_autopickup_exceptions(struct obj *);

View File

@@ -4006,7 +4006,7 @@ doapply(void)
case SACK:
case BAG_OF_HOLDING:
case OILSKIN_SACK:
res = use_container(&obj, 1, FALSE);
res = use_container(&obj, TRUE, FALSE);
break;
case BAG_OF_TRICKS:
(void) bagotricks(obj, FALSE, (int *) 0);

View File

@@ -31,10 +31,10 @@ static int lift_object(struct obj *, struct obj *, long *, boolean);
static boolean mbag_explodes(struct obj *, int);
static boolean is_boh_item_gone(void);
static void do_boh_explosion(struct obj *, boolean);
static long boh_loss(struct obj *, int);
static long boh_loss(struct obj *, boolean);
static int in_container(struct obj *);
static int out_container(struct obj *);
static long mbag_item_gone(int, struct obj *, boolean);
static long mbag_item_gone(boolean, struct obj *, boolean);
static int stash_ok(struct obj *);
static void explain_container_prompt(boolean);
static int traditional_loot(boolean);
@@ -1958,7 +1958,7 @@ do_loot_cont(
g.abort_looting = TRUE;
return ECMD_TIME;
}
return use_container(cobjp, 0, (boolean) (cindex < ccount));
return use_container(cobjp, FALSE, (boolean) (cindex < ccount));
}
/* #loot extended command */
@@ -2308,9 +2308,8 @@ is_boh_item_gone(void)
return (boolean) (!rn2(13));
}
/* Scatter most of Bag of holding contents around.
Some items will be destroyed with the same chance as looting a cursed bag.
*/
/* Scatter most of Bag of holding contents around. Some items will be
destroyed with the same chance as looting a cursed bag. */
static void
do_boh_explosion(struct obj *boh, boolean on_floor)
{
@@ -2329,7 +2328,7 @@ do_boh_explosion(struct obj *boh, boolean on_floor)
}
static long
boh_loss(struct obj *container, int held)
boh_loss(struct obj *container, boolean held)
{
/* sometimes toss objects if a cursed magic bag */
if (Is_mbag(container) && container->cursed && Has_contents(container)) {
@@ -2598,7 +2597,7 @@ removed_from_icebox(struct obj *obj)
/* an object inside a cursed bag of holding is being destroyed */
static long
mbag_item_gone(int held, struct obj *item, boolean silent)
mbag_item_gone(boolean held, struct obj *item, boolean silent)
{
struct monst *shkp;
long loss = 0L;
@@ -2755,7 +2754,7 @@ stash_ok(struct obj *obj)
int
use_container(
struct obj **objp,
int held,
boolean held,
boolean more_containers) /* True iff #loot multiple and this isn't last */
{
struct obj *otmp, *obj = *objp;
@@ -3436,7 +3435,7 @@ static void
tipcontainer(struct obj *box) /* or bag */
{
coordxy ox = u.ux, oy = u.uy; /* #tip only works at hero's location */
boolean empty_it = TRUE, maybeshopgoods;
boolean held = FALSE, maybeshopgoods;
struct obj *targetbox = (struct obj *) 0;
boolean cancelled = FALSE;
@@ -3469,14 +3468,14 @@ tipcontainer(struct obj *box) /* or bag */
if (targetbox && tipcontainer_checks(targetbox, TRUE) != TIPCHECK_OK)
return;
if (empty_it) {
{
struct obj *otmp, *nobj;
boolean terse, highdrop = !can_reach_floor(TRUE),
altarizing = IS_ALTAR(levl[ox][oy].typ),
cursed_mbag = (Is_mbag(box) && box->cursed);
int held = carried(box) || (targetbox && carried(targetbox));
long loss = 0L;
held = carried(box) || (targetbox && carried(targetbox));
if (u.uswallow)
highdrop = altarizing = FALSE;
terse = !(highdrop || altarizing || costly_spot(box->ox, box->oy));
@@ -3494,6 +3493,7 @@ tipcontainer(struct obj *box) /* or bag */
pline("%s out%c",
box->cobj->nobj ? "Objects spill" : "An object spills",
terse ? ':' : '.');
for (otmp = box->cobj; otmp; otmp = nobj) {
nobj = otmp->nobj;
obj_extract_self(otmp);
@@ -3507,14 +3507,27 @@ tipcontainer(struct obj *box) /* or bag */
terse = FALSE;
continue;
}
if (maybeshopgoods) {
addtobill(otmp, FALSE, FALSE, TRUE);
iflags.suppress_price++; /* doname formatting */
}
if (targetbox) {
(void) add_to_container(targetbox, otmp);
if (Is_mbag(targetbox) && mbag_explodes(otmp, 0)) {
/* explicitly mention what item is triggering explosion */
urgent_pline(
"As %s %s inside, you are blasted by a magical explosion!",
doname(otmp), otense(otmp, "tumble"));
do_boh_explosion(targetbox, held);
nobj = 0; /* stop tipping; want loop to exit 'normally' */
if (!held)
useup(targetbox);
else
useupf(targetbox, targetbox->quan);
targetbox = 0; /* it's gone */
} else {
(void) add_to_container(targetbox, otmp);
}
} else if (highdrop) {
/* might break or fall down stairs; handles altars itself */
hitfloor(otmp, TRUE);
@@ -3544,7 +3557,8 @@ tipcontainer(struct obj *box) /* or bag */
if (held)
(void) encumber_msg();
}
if (carried(box) || (targetbox && carried(targetbox)))
if (held)
update_inventory();
}
@@ -3600,7 +3614,9 @@ tipcontainer_gettarget(struct obj *box, boolean *cancelled)
clr, "", MENU_ITEMFLAGS_NONE);
for (otmp = g.invent; otmp; otmp = otmp->nobj)
if (Is_container(otmp) && (otmp != box)) {
if (Is_container(otmp) && otmp != box
/* don't include any container that's known to be locked */
&& (!otmp->olocked || !otmp->lknown)) {
any = cg.zeroany;
any.a_obj = otmp;
add_menu(win, &nul_glyphinfo, &any, otmp->invlet, 0,