more #tip, mostly container-to-container

Require a free hand when tipping a container into another container.
Presumeably you need to open the destination container and possibly
keep holding it open.

If you try to tip a carried container into an unknown bag of tricks,
apply the bag (once) instead of performing the tip.  (To 'open' the
destination as above.)  Possibly slightly confusing if bag is empty.

When tipping a container, always ask for the destination instead of
doing that only when carrying other containers.  Confirming floor
as destination can be annoying but having to do that sometimes and
skipping that sometimes is aggravating because it is error prone.
And floor is preselected so can be chosen with space or return.
(I wanted to change the selector letter for floor from '-' to '.'
and then keep '-' as an unseen group accelerator, but the latter
doesn't work for PICK_ONE so I've left '-' as-is.)

Don't display "monsters appear" after tipping a bag of tricks.
Monster creation gives feedback these days.  (Comparable to recent
"summon nasties" fix.)
This commit is contained in:
PatR
2022-09-15 12:30:44 -07:00
parent 48bd67a25f
commit 5b0f4a2e6d
2 changed files with 89 additions and 56 deletions

View File

@@ -2371,9 +2371,10 @@ set_mimic_sym(register struct monst *mtmp)
/* release monster from bag of tricks; return number of monsters created */ /* release monster from bag of tricks; return number of monsters created */
int int
bagotricks(struct obj *bag, bagotricks(
boolean tipping, /* caller emptying entire contents; affects shop handling */ struct obj *bag,
int *seencount) /* secondary output */ boolean tipping, /* caller emptying entirely; affects shop handling */
int *seencount) /* secondary output */
{ {
int moncount = 0; int moncount = 0;

View File

@@ -40,7 +40,6 @@ 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 struct obj *tipcontainer_gettarget(struct obj *, boolean *);
static int tipcontainer_checks(struct obj *, struct obj *, boolean); static int tipcontainer_checks(struct obj *, 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,
@@ -3445,6 +3444,11 @@ 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;
/*
* TODO?
* if 'box' is known to be empty or known to be locked, give up
* before choosing 'targetbox'.
*/
targetbox = tipcontainer_gettarget(box, &cancelled); targetbox = tipcontainer_gettarget(box, &cancelled);
if (cancelled) if (cancelled)
return; return;
@@ -3562,78 +3566,93 @@ tipcontainer(struct obj *box) /* or bag */
update_inventory(); update_inventory();
} }
/* Returns number of containers in object chain, #if 0
does not recurse into containers */ static int count_target_containers(struct obj *, struct obj *);
/* returns number of containers in object chain; does not recurse into
containers; skips bags of tricks when they're known */
static int static int
count_containers(struct obj *otmp) count_target_containers(
struct obj *olist, /* list of objects (invent) */
struct obj *excludo) /* particular object to exclude if found in list */
{ {
int ret = 0; int ret = 0;
while (otmp) { while (olist) {
if (Is_container(otmp)) if (olist != excludo && Is_container(olist)
/* include bag of tricks when not known to be such */
&& (box->otyp != BAG_OF_TRICKS || !box->dknown
|| !objects[box->otyp].oc_name_known))
ret++; ret++;
otmp = otmp->nobj; olist = olist->nobj;
} }
return ret; return ret;
} }
#endif
/* ask user for a carried container where they want box to be emptied /* ask user for a carried container where they want box to be emptied;
cancelled is TRUE if user cancelled the menu pick. */ cancelled is TRUE if user cancelled the menu pick; hands aren't required
when tipping to the floor but are when tipping into another container */
static struct obj * static struct obj *
tipcontainer_gettarget(struct obj *box, boolean *cancelled) tipcontainer_gettarget(
struct obj *box,
boolean *cancelled)
{ {
int n; int n, n_conts;
winid win; winid win;
anything any; anything any;
char buf[BUFSZ]; char buf[BUFSZ];
menu_item *pick_list = (menu_item *) 0; menu_item *pick_list = (menu_item *) 0;
struct obj dummyobj, *otmp; struct obj dummyobj, *otmp;
int n_conts; boolean hands_available = TRUE, exclude_it;
int clr = 0; int clr = 0;
/* if tipping a known bag of tricks, don't prompt for destination */ #if 0 /* [skip potential early return so that menu response is needed
if (box->otyp == BAG_OF_TRICKS && box->dknown * regardless of whether other containers are being carried] */
&& objects[box->otyp].oc_name_known) int n_conts = count_target_containers(g.invent, box);
return (struct obj *) 0;
n_conts = count_containers(g.invent); if (n_conts < 1 || !u_handsy()) {
/* when we're carrying the box, don't count it as possible target; if (n_conts >= 1)
note: 'box' might be a horn so not be included in the count */ pline("Tipping contents to floor only...");
if (carried(box) && Is_container(box))
n_conts--;
if (n_conts < 1) {
*cancelled = FALSE; *cancelled = FALSE;
return (struct obj *) 0; return (struct obj *) 0;
} }
#endif
win = create_nhwindow(NHW_MENU); win = create_nhwindow(NHW_MENU);
start_menu(win, MENU_BEHAVE_STANDARD); start_menu(win, MENU_BEHAVE_STANDARD);
dummyobj = cg.zeroobj; /* lint suppression; only its address matters */
any = cg.zeroany; any = cg.zeroany;
any.a_obj = &dummyobj; any.a_obj = &dummyobj;
add_menu(win, &nul_glyphinfo, &any, '-', 0, ATR_NONE, /* tip to floor does not require free hands */
clr, "on the floor", MENU_ITEMFLAGS_SELECTED); add_menu(win, &nul_glyphinfo, &any, '-', 0, ATR_NONE, clr,
/* [TODO? vary destination string depending on surface()] */
"on the floor", MENU_ITEMFLAGS_SELECTED);
any = cg.zeroany; any = cg.zeroany;
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr,
clr, "", MENU_ITEMFLAGS_NONE); "", MENU_ITEMFLAGS_NONE);
n_conts = 0;
for (otmp = g.invent; otmp; otmp = otmp->nobj) { for (otmp = g.invent; otmp; otmp = otmp->nobj) {
if (otmp == box) if (otmp == box)
continue; continue;
/* bag of tricks passes Is_container() test; don't include it if /* skip non-containers; bag of tricks passes Is_container() test,
it is known to be a bag of tricks */ only include it if it isn't known to be a bag of tricks */
if (otmp->otyp == BAG_OF_TRICKS && otmp->dknown if (!Is_container(otmp)
&& objects[otmp->otyp].oc_name_known) || (otmp->otyp == BAG_OF_TRICKS && otmp->dknown
&& objects[otmp->otyp].oc_name_known))
continue; continue;
/* include any container unless it's known to be locked */ if (!n_conts++)
if (Is_container(otmp) && !(otmp->olocked && otmp->lknown)) { hands_available = u_handsy(); /* might issue message */
any = cg.zeroany; /* container-to-container tip requires free hands;
any.a_obj = otmp; exclude container as possible target when known to be locked */
add_menu(win, &nul_glyphinfo, &any, otmp->invlet, 0, exclude_it = !hands_available || (otmp->olocked && otmp->lknown);
ATR_NONE, clr, doname(otmp), MENU_ITEMFLAGS_NONE); any = cg.zeroany;
} any.a_obj = !exclude_it ? otmp : 0;
Sprintf(buf, "%s%s", !exclude_it ? "" : " ", doname(otmp));
add_menu(win, &nul_glyphinfo, &any, !exclude_it ? otmp->invlet : 0, 0,
ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE);
} }
Sprintf(buf, "Where to tip the contents of %s", doname(box)); Sprintf(buf, "Where to tip the contents of %s", doname(box));
@@ -3641,16 +3660,19 @@ tipcontainer_gettarget(struct obj *box, boolean *cancelled)
n = select_menu(win, PICK_ONE, &pick_list); n = select_menu(win, PICK_ONE, &pick_list);
destroy_nhwindow(win); destroy_nhwindow(win);
otmp = (n <= 0) ? (struct obj *) 0 : pick_list[0].item.a_obj; otmp = 0;
if (n > 1 && otmp == &dummyobj) if (pick_list) {
otmp = pick_list[1].item.a_obj; otmp = pick_list[0].item.a_obj;
if (pick_list) /* PICK_ONE with a preselected item might return 2;
if so, choose the one that wasn't preselected */
if (n > 1 && otmp == &dummyobj)
otmp = pick_list[1].item.a_obj;
if (otmp == &dummyobj)
otmp = 0;
free((genericptr_t) pick_list); free((genericptr_t) pick_list);
}
*cancelled = (boolean) (n == -1); *cancelled = (boolean) (n == -1);
if (otmp && otmp != &dummyobj) return otmp;
return otmp;
return (struct obj *) 0;
} }
/* Perform check on box if we can tip it. /* Perform check on box if we can tip it.
@@ -3662,6 +3684,16 @@ tipcontainer_checks(
struct obj *targetbox, /* destination (used here for horn of plenty) */ struct obj *targetbox, /* destination (used here for horn of plenty) */
boolean allowempty) /* affects result when box is empty */ boolean allowempty) /* affects result when box is empty */
{ {
/* undiscovered bag of tricks is acceptable as a container-to-container
destination but it can't receive items; it has to be opened in
preparation so apply it once before even trying to tip source box */
if (targetbox && targetbox->otyp == BAG_OF_TRICKS) {
int seencount = 0;
bagotricks(targetbox, FALSE, &seencount);
return TIPCHECK_CANNOT;
}
/* 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 */
@@ -3689,7 +3721,7 @@ tipcontainer_checks(
} else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) { } else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) {
int res = TIPCHECK_OK; int res = TIPCHECK_OK;
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, totseen;
boolean maybeshopgoods = (!carried(box) boolean maybeshopgoods = (!carried(box)
&& costly_spot(box->ox, box->oy)); && costly_spot(box->ox, box->oy));
coordxy ox = u.ux, oy = u.uy; coordxy ox = u.ux, oy = u.uy;
@@ -3706,17 +3738,17 @@ tipcontainer_checks(
addtobill(box, FALSE, FALSE, TRUE); addtobill(box, FALSE, FALSE, TRUE);
/* apply this bag/horn until empty or monster/object creation fails /* apply this bag/horn until empty or monster/object creation fails
(if the latter occurs, force the former...) */ (if the latter occurs, force the former...) */
seen = totseen = 0;
do { do {
if (!(bag ? bagotricks(box, TRUE, &seen) if (!(bag ? bagotricks(box, TRUE, &seen)
: hornoplenty(box, TRUE, targetbox))) : hornoplenty(box, TRUE, targetbox)))
break; break;
totseen += seen;
} while (box->spe > 0); } while (box->spe > 0);
if (box->spe < old_spe) { if (box->spe < old_spe) {
if (bag) if (bag && !totseen)
pline((seen == 0) ? "Nothing seems to happen." pline("Nothing seems to happen.");
: (seen == 1) ? "A monster appears."
: "Monsters appear!");
/* check_unpaid wants to see a non-zero charge count */ /* check_unpaid wants to see a non-zero charge count */
box->spe = old_spe; box->spe = old_spe;
check_unpaid_usage(box, TRUE); check_unpaid_usage(box, TRUE);
@@ -3742,7 +3774,7 @@ tipcontainer_checks(
} else if (!allowempty && !Has_contents(box)) { } else if (!allowempty && !Has_contents(box)) {
box->cknown = 1; box->cknown = 1;
pline("It's empty."); pline("%s is empty.", upstart(thesimpleoname(box)));
return TIPCHECK_EMPTY; return TIPCHECK_EMPTY;
} }