Allow tipping container directly into another

This commit is contained in:
Pasi Kallinen
2022-01-01 16:15:29 +02:00
parent edf0e3e673
commit 0f132470c7
2 changed files with 204 additions and 71 deletions

View File

@@ -728,6 +728,7 @@ if a giant carrying a boulder was on ice that melted, it could be killed
allow fire-command to automatically use a polearm, if wielding it
make '$' command also count gold carried inside containers
fleeing leprechauns bury their gold after teleporting
allow #tipping container contents directly into another container
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository

View File

@@ -40,6 +40,9 @@ static void explain_container_prompt(boolean);
static int traditional_loot(boolean);
static int menu_loot(int, boolean);
static int tip_ok(struct obj *);
static int count_containers(struct obj *);
static struct obj *tipcontainer_gettarget(struct obj *, boolean *);
static int tipcontainer_checks(struct obj *, boolean);
static char in_or_out_menu(const char *, struct obj *, boolean, boolean,
boolean, boolean);
static boolean able_to_loot(int, int, boolean);
@@ -3354,11 +3357,21 @@ dotip(void)
return ECMD_OK;
}
enum tipping_check_values {
TIPCHECK_OK = 0,
TIPCHECK_LOCKED,
TIPCHECK_TRAPPED,
TIPCHECK_CANNOT,
TIPCHECK_EMPTY
};
static void
tipcontainer(struct obj *box) /* or bag */
{
xchar ox = u.ux, oy = u.uy; /* #tip only works at hero's location */
boolean empty_it = FALSE, maybeshopgoods;
boolean empty_it = TRUE, maybeshopgoods;
struct obj *targetbox = (struct obj *) 0;
boolean cancelled;
/* box is either held or on floor at hero's spot; no need to check for
nesting; when held, we need to update its location to match hero's;
@@ -3366,6 +3379,10 @@ tipcontainer(struct obj *box) /* or bag */
if (get_obj_location(box, &ox, &oy, 0))
box->ox = ox, box->oy = oy;
targetbox = tipcontainer_gettarget(box, &cancelled);
if (cancelled)
return;
/* Shop handling: can't rely on the container's own unpaid
or no_charge status because contents might differ with it.
A carried container's contents will be flagged as unpaid
@@ -3380,6 +3397,172 @@ tipcontainer(struct obj *box) /* or bag */
to reduce the chance of exhausting shk's billing capacity. */
maybeshopgoods = !carried(box) && costly_spot(box->ox, box->oy);
if (tipcontainer_checks(box, FALSE) != TIPCHECK_OK)
return;
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;
if (u.uswallow)
highdrop = altarizing = FALSE;
terse = !(highdrop || altarizing || costly_spot(box->ox, box->oy));
box->cknown = 1;
/* Terse formatting is
* "Objects spill out: obj1, obj2, obj3, ..., objN."
* If any other messages intervene between objects, we revert to
* "ObjK drops to the floor.", "ObjL drops to the floor.", &c.
*/
if (targetbox)
pline("%s into %s.",
box->cobj->nobj ? "Objects tumble" : "Object tumbles",
the(xname(targetbox)));
else
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);
otmp->ox = box->ox, otmp->oy = box->oy;
if (box->otyp == ICE_BOX) {
removed_from_icebox(otmp); /* resume rotting for corpse */
} else if (cursed_mbag && is_boh_item_gone()) {
loss += mbag_item_gone(held, otmp, FALSE);
/* abbreviated drop format is no longer appropriate */
terse = FALSE;
continue;
}
if (maybeshopgoods) {
addtobill(otmp, FALSE, FALSE, TRUE);
iflags.suppress_price++; /* doname formatting */
}
if (targetbox) {
(void) add_to_container(targetbox, otmp);
} else if (highdrop) {
/* might break or fall down stairs; handles altars itself */
hitfloor(otmp, TRUE);
} else {
if (altarizing) {
doaltarobj(otmp);
} else if (!terse) {
pline("%s %s to the %s.", Doname2(otmp),
otense(otmp, "drop"), surface(ox, oy));
} else {
pline("%s%c", doname(otmp), nobj ? ',' : '.');
iflags.last_msg = PLNMSG_OBJNAM_ONLY;
}
dropy(otmp);
if (iflags.last_msg != PLNMSG_OBJNAM_ONLY)
terse = FALSE; /* terse formatting has been interrupted */
}
if (maybeshopgoods)
iflags.suppress_price--; /* reset */
}
if (loss) /* magic bag lost some shop goods */
You("owe %ld %s for lost merchandise.", loss, currency(loss));
box->owt = weight(box); /* mbag_item_gone() doesn't update this */
if (targetbox)
targetbox->owt = weight(targetbox);
if (held)
(void) encumber_msg();
}
if (carried(box) || (targetbox && carried(targetbox)))
update_inventory();
}
/* Returns number of containers in object chain,
does not recurse into containers */
static int
count_containers(struct obj *otmp)
{
int ret = 0;
while (otmp) {
if (Is_container(otmp))
ret++;
otmp = otmp->nobj;
}
return ret;
}
/* ask user for a carried container where they want box to be emptied
cancelled is TRUE if user cancelled the menu pick. */
static struct obj *
tipcontainer_gettarget(struct obj *box, boolean *cancelled)
{
int n;
winid win;
anything any;
char buf[BUFSZ];
menu_item *pick_list = (menu_item *) 0;
struct obj dummyobj, *otmp;
int n_conts = count_containers(g.invent);
/* we're carrying the box, don't count it as possible target */
if (box->where == OBJ_INVENT)
n_conts--;
if (n_conts < 1) {
if (cancelled)
*cancelled = FALSE;
return (struct obj *) 0;
}
win = create_nhwindow(NHW_MENU);
start_menu(win, MENU_BEHAVE_STANDARD);
any = cg.zeroany;
any.a_obj = &dummyobj;
add_menu(win, &nul_glyphinfo, &any, '-', 0, ATR_NONE,
"on the floor", MENU_ITEMFLAGS_SELECTED);
any = cg.zeroany;
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
"", MENU_ITEMFLAGS_NONE);
for (otmp = g.invent; otmp; otmp = otmp->nobj)
if (Is_container(otmp) && (otmp != box)) {
any = cg.zeroany;
any.a_obj = otmp;
add_menu(win, &nul_glyphinfo, &any, otmp->invlet, 0,
ATR_NONE, doname(otmp), MENU_ITEMFLAGS_NONE);
}
Sprintf(buf, "Where to tip the contents of %s", doname(box));
end_menu(win, buf);
n = select_menu(win, PICK_ONE, &pick_list);
destroy_nhwindow(win);
otmp = (n <= 0) ? (struct obj *) 0 : pick_list[0].item.a_obj;
if (n > 1 && otmp == &dummyobj)
otmp = pick_list[1].item.a_obj;
if (pick_list)
free((genericptr_t) pick_list);
if (cancelled)
*cancelled = (n == -1);
if (otmp && otmp != &dummyobj)
return otmp;
return (struct obj *) 0;
}
/* Perform check on box if we can tip it.
Returns one of TIPCHECK_foo values.
If allowempty if TRUE, return TIPCHECK_OK instead of TIPCHECK_EMPTY. */
static int
tipcontainer_checks(struct obj *box, boolean allowempty)
{
/* caveat: this assumes that cknown, lknown, olocked, and otrapped
fields haven't been overloaded to mean something special for the
non-standard "container" horn of plenty */
@@ -3388,8 +3571,11 @@ tipcontainer(struct obj *box) /* or bag */
if (carried(box))
update_inventory(); /* jumping the gun slightly; hope that's ok */
}
if (box->olocked) {
pline("It's locked.");
return TIPCHECK_LOCKED;
} else if (box->otrapped) {
/* we're not reaching inside but we're still handling it... */
(void) chest_trap(box, HAND, FALSE);
@@ -3399,9 +3585,16 @@ tipcontainer(struct obj *box) /* or bag */
g.multi_reason = "tipping a container";
g.nomovemsg = "";
}
return TIPCHECK_TRAPPED;
} else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) {
boolean bag = box->otyp == BAG_OF_TRICKS;
int old_spe = box->spe, seen = 0;
boolean maybeshopgoods = !carried(box) && costly_spot(box->ox, box->oy);
xchar ox = u.ux, oy = u.uy;
if (get_obj_location(box, &ox, &oy, 0))
box->ox = ox, box->oy = oy;
if (maybeshopgoods && !box->no_charge)
addtobill(box, FALSE, FALSE, TRUE);
@@ -3426,8 +3619,11 @@ tipcontainer(struct obj *box) /* or bag */
}
if (maybeshopgoods && !box->no_charge)
subfrombill(box, shop_keeper(*in_rooms(ox, oy, SHOPBASE)));
return TIPCHECK_CANNOT;
} else if (SchroedingersBox(box)) {
char yourbuf[BUFSZ];
boolean empty_it = FALSE;
observe_quantum_cat(box, TRUE, TRUE);
if (!Has_contents(box)) /* evidently a live cat came out */
@@ -3436,80 +3632,16 @@ tipcontainer(struct obj *box) /* or bag */
else /* holds cat corpse */
empty_it = TRUE;
box->cknown = 1;
} else if (!Has_contents(box)) {
return (empty_it || allowempty) ? TIPCHECK_OK : TIPCHECK_EMPTY;
} else if (!allowempty && !Has_contents(box)) {
box->cknown = 1;
pline("It's empty.");
} else {
empty_it = TRUE;
return TIPCHECK_EMPTY;
}
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);
long loss = 0L;
if (u.uswallow)
highdrop = altarizing = FALSE;
terse = !(highdrop || altarizing || costly_spot(box->ox, box->oy));
box->cknown = 1;
/* Terse formatting is
* "Objects spill out: obj1, obj2, obj3, ..., objN."
* If any other messages intervene between objects, we revert to
* "ObjK drops to the floor.", "ObjL drops to the floor.", &c.
*/
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);
otmp->ox = box->ox, otmp->oy = box->oy;
if (box->otyp == ICE_BOX) {
removed_from_icebox(otmp); /* resume rotting for corpse */
} else if (cursed_mbag && is_boh_item_gone()) {
loss += mbag_item_gone(held, otmp, FALSE);
/* abbreviated drop format is no longer appropriate */
terse = FALSE;
continue;
}
if (maybeshopgoods) {
addtobill(otmp, FALSE, FALSE, TRUE);
iflags.suppress_price++; /* doname formatting */
}
if (highdrop) {
/* might break or fall down stairs; handles altars itself */
hitfloor(otmp, TRUE);
} else {
if (altarizing) {
doaltarobj(otmp);
} else if (!terse) {
pline("%s %s to the %s.", Doname2(otmp),
otense(otmp, "drop"), surface(ox, oy));
} else {
pline("%s%c", doname(otmp), nobj ? ',' : '.');
iflags.last_msg = PLNMSG_OBJNAM_ONLY;
}
dropy(otmp);
if (iflags.last_msg != PLNMSG_OBJNAM_ONLY)
terse = FALSE; /* terse formatting has been interrupted */
}
if (maybeshopgoods)
iflags.suppress_price--; /* reset */
}
if (loss) /* magic bag lost some shop goods */
You("owe %ld %s for lost merchandise.", loss, currency(loss));
box->owt = weight(box); /* mbag_item_gone() doesn't update this */
if (held)
(void) encumber_msg();
}
if (carried(box)) /* box is now empty with cknown set */
update_inventory();
return TIPCHECK_OK;
}
/*pickup.c*/