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

@@ -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,