Allow tipping container directly into another
This commit is contained in:
@@ -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
|
allow fire-command to automatically use a polearm, if wielding it
|
||||||
make '$' command also count gold carried inside containers
|
make '$' command also count gold carried inside containers
|
||||||
fleeing leprechauns bury their gold after teleporting
|
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
|
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
|
||||||
|
|||||||
274
src/pickup.c
274
src/pickup.c
@@ -40,6 +40,9 @@ static void explain_container_prompt(boolean);
|
|||||||
static int traditional_loot(boolean);
|
static int traditional_loot(boolean);
|
||||||
static int menu_loot(int, boolean);
|
static int menu_loot(int, boolean);
|
||||||
static int tip_ok(struct obj *);
|
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,
|
static char in_or_out_menu(const char *, struct obj *, boolean, boolean,
|
||||||
boolean, boolean);
|
boolean, boolean);
|
||||||
static boolean able_to_loot(int, int, boolean);
|
static boolean able_to_loot(int, int, boolean);
|
||||||
@@ -3354,11 +3357,21 @@ dotip(void)
|
|||||||
return ECMD_OK;
|
return ECMD_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum tipping_check_values {
|
||||||
|
TIPCHECK_OK = 0,
|
||||||
|
TIPCHECK_LOCKED,
|
||||||
|
TIPCHECK_TRAPPED,
|
||||||
|
TIPCHECK_CANNOT,
|
||||||
|
TIPCHECK_EMPTY
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tipcontainer(struct obj *box) /* or bag */
|
tipcontainer(struct obj *box) /* or bag */
|
||||||
{
|
{
|
||||||
xchar ox = u.ux, oy = u.uy; /* #tip only works at hero's location */
|
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
|
/* 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;
|
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))
|
if (get_obj_location(box, &ox, &oy, 0))
|
||||||
box->ox = ox, box->oy = oy;
|
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
|
/* Shop handling: can't rely on the container's own unpaid
|
||||||
or no_charge status because contents might differ with it.
|
or no_charge status because contents might differ with it.
|
||||||
A carried container's contents will be flagged as unpaid
|
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. */
|
to reduce the chance of exhausting shk's billing capacity. */
|
||||||
maybeshopgoods = !carried(box) && costly_spot(box->ox, box->oy);
|
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
|
/* caveat: this assumes that cknown, lknown, olocked, and otrapped
|
||||||
fields haven't been overloaded to mean something special for the
|
fields haven't been overloaded to mean something special for the
|
||||||
non-standard "container" horn of plenty */
|
non-standard "container" horn of plenty */
|
||||||
@@ -3388,8 +3571,11 @@ tipcontainer(struct obj *box) /* or bag */
|
|||||||
if (carried(box))
|
if (carried(box))
|
||||||
update_inventory(); /* jumping the gun slightly; hope that's ok */
|
update_inventory(); /* jumping the gun slightly; hope that's ok */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (box->olocked) {
|
if (box->olocked) {
|
||||||
pline("It's locked.");
|
pline("It's locked.");
|
||||||
|
return TIPCHECK_LOCKED;
|
||||||
|
|
||||||
} else if (box->otrapped) {
|
} else if (box->otrapped) {
|
||||||
/* we're not reaching inside but we're still handling it... */
|
/* we're not reaching inside but we're still handling it... */
|
||||||
(void) chest_trap(box, HAND, FALSE);
|
(void) chest_trap(box, HAND, FALSE);
|
||||||
@@ -3399,9 +3585,16 @@ tipcontainer(struct obj *box) /* or bag */
|
|||||||
g.multi_reason = "tipping a container";
|
g.multi_reason = "tipping a container";
|
||||||
g.nomovemsg = "";
|
g.nomovemsg = "";
|
||||||
}
|
}
|
||||||
|
return TIPCHECK_TRAPPED;
|
||||||
|
|
||||||
} else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) {
|
} else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) {
|
||||||
boolean bag = box->otyp == BAG_OF_TRICKS;
|
boolean bag = box->otyp == BAG_OF_TRICKS;
|
||||||
int old_spe = box->spe, seen = 0;
|
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)
|
if (maybeshopgoods && !box->no_charge)
|
||||||
addtobill(box, FALSE, FALSE, TRUE);
|
addtobill(box, FALSE, FALSE, TRUE);
|
||||||
@@ -3426,8 +3619,11 @@ tipcontainer(struct obj *box) /* or bag */
|
|||||||
}
|
}
|
||||||
if (maybeshopgoods && !box->no_charge)
|
if (maybeshopgoods && !box->no_charge)
|
||||||
subfrombill(box, shop_keeper(*in_rooms(ox, oy, SHOPBASE)));
|
subfrombill(box, shop_keeper(*in_rooms(ox, oy, SHOPBASE)));
|
||||||
|
return TIPCHECK_CANNOT;
|
||||||
|
|
||||||
} else if (SchroedingersBox(box)) {
|
} else if (SchroedingersBox(box)) {
|
||||||
char yourbuf[BUFSZ];
|
char yourbuf[BUFSZ];
|
||||||
|
boolean empty_it = FALSE;
|
||||||
|
|
||||||
observe_quantum_cat(box, TRUE, TRUE);
|
observe_quantum_cat(box, TRUE, TRUE);
|
||||||
if (!Has_contents(box)) /* evidently a live cat came out */
|
if (!Has_contents(box)) /* evidently a live cat came out */
|
||||||
@@ -3436,80 +3632,16 @@ tipcontainer(struct obj *box) /* or bag */
|
|||||||
else /* holds cat corpse */
|
else /* holds cat corpse */
|
||||||
empty_it = TRUE;
|
empty_it = TRUE;
|
||||||
box->cknown = 1;
|
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;
|
box->cknown = 1;
|
||||||
pline("It's empty.");
|
pline("It's empty.");
|
||||||
} else {
|
return TIPCHECK_EMPTY;
|
||||||
empty_it = TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty_it) {
|
return TIPCHECK_OK;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pickup.c*/
|
/*pickup.c*/
|
||||||
|
|||||||
Reference in New Issue
Block a user