]> granicus.if.org Git - nethack/commitdiff
Allow tipping container directly into another
authorPasi Kallinen <paxed@alt.org>
Sat, 1 Jan 2022 14:15:29 +0000 (16:15 +0200)
committerPasi Kallinen <paxed@alt.org>
Sat, 1 Jan 2022 14:15:29 +0000 (16:15 +0200)
doc/fixes37.0
src/pickup.c

index 0d0b2005b6e1bffe31e5f2b9a10e768980136058..7e5a24e18b1d40792cc91970af5ad4f96dce30d4 100644 (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
index 18cc4bfd11a115f827bd919c82f971788a40b07c..545212d6c144672465fa18172c581351d7120c24 100644 (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,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*/