From: Pasi Kallinen Date: Sat, 1 Jan 2022 14:15:29 +0000 (+0200) Subject: Allow tipping container directly into another X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0f132470c7eaaabce0c9ff8eb53b5e9685f9d57e;p=nethack Allow tipping container directly into another --- diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 0d0b2005b..7e5a24e18 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 diff --git a/src/pickup.c b/src/pickup.c index 18cc4bfd1..545212d6c 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -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,75 +3397,17 @@ 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); - /* 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 */ - if (!box->lknown) { - box->lknown = 1; - if (carried(box)) - update_inventory(); /* jumping the gun slightly; hope that's ok */ - } - if (box->olocked) { - pline("It's locked."); - } else if (box->otrapped) { - /* we're not reaching inside but we're still handling it... */ - (void) chest_trap(box, HAND, FALSE); - /* even if the trap fails, you've used up this turn */ - if (g.multi >= 0) { /* in case we didn't become paralyzed */ - nomul(-1); - g.multi_reason = "tipping a container"; - g.nomovemsg = ""; - } - } 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; - - if (maybeshopgoods && !box->no_charge) - addtobill(box, FALSE, FALSE, TRUE); - /* apply this bag/horn until empty or monster/object creation fails - (if the latter occurs, force the former...) */ - do { - if (!(bag ? bagotricks(box, TRUE, &seen) - : hornoplenty(box, TRUE))) - break; - } while (box->spe > 0); - - if (box->spe < old_spe) { - if (bag) - pline((seen == 0) ? "Nothing seems to happen." - : (seen == 1) ? "A monster appears." - : "Monsters appear!"); - /* check_unpaid wants to see a non-zero charge count */ - box->spe = old_spe; - check_unpaid_usage(box, TRUE); - box->spe = 0; /* empty */ - box->cknown = 1; - } - if (maybeshopgoods && !box->no_charge) - subfrombill(box, shop_keeper(*in_rooms(ox, oy, SHOPBASE))); - } else if (SchroedingersBox(box)) { - char yourbuf[BUFSZ]; - - observe_quantum_cat(box, TRUE, TRUE); - if (!Has_contents(box)) /* evidently a live cat came out */ - /* container type of "large box" is inferred */ - pline("%sbox is now empty.", Shk_Your(yourbuf, box)); - else /* holds cat corpse */ - empty_it = TRUE; - box->cknown = 1; - } else if (!Has_contents(box)) { - box->cknown = 1; - pline("It's empty."); - } else { - empty_it = TRUE; - } + 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); + int held = carried(box) || (targetbox && carried(targetbox)); long loss = 0L; if (u.uswallow) @@ -3460,7 +3419,12 @@ tipcontainer(struct obj *box) /* or bag */ * 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", + 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) { @@ -3482,7 +3446,9 @@ tipcontainer(struct obj *box) /* or bag */ iflags.suppress_price++; /* doname formatting */ } - if (highdrop) { + if (targetbox) { + (void) add_to_container(targetbox, otmp); + } else if (highdrop) { /* might break or fall down stairs; handles altars itself */ hitfloor(otmp, TRUE); } else { @@ -3499,17 +3465,183 @@ tipcontainer(struct obj *box) /* or bag */ 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)) /* box is now empty with cknown set */ + 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 */ + if (!box->lknown) { + box->lknown = 1; + 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); + /* even if the trap fails, you've used up this turn */ + if (g.multi >= 0) { /* in case we didn't become paralyzed */ + nomul(-1); + 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); + /* apply this bag/horn until empty or monster/object creation fails + (if the latter occurs, force the former...) */ + do { + if (!(bag ? bagotricks(box, TRUE, &seen) + : hornoplenty(box, TRUE))) + break; + } while (box->spe > 0); + + if (box->spe < old_spe) { + if (bag) + pline((seen == 0) ? "Nothing seems to happen." + : (seen == 1) ? "A monster appears." + : "Monsters appear!"); + /* check_unpaid wants to see a non-zero charge count */ + box->spe = old_spe; + check_unpaid_usage(box, TRUE); + box->spe = 0; /* empty */ + box->cknown = 1; + } + 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 */ + /* container type of "large box" is inferred */ + pline("%sbox is now empty.", Shk_Your(yourbuf, box)); + else /* holds cat corpse */ + empty_it = TRUE; + box->cknown = 1; + return (empty_it || allowempty) ? TIPCHECK_OK : TIPCHECK_EMPTY; + + } else if (!allowempty && !Has_contents(box)) { + box->cknown = 1; + pline("It's empty."); + return TIPCHECK_EMPTY; + + } + + return TIPCHECK_OK; +} + /*pickup.c*/