]> granicus.if.org Git - nethack/commitdiff
'm' prefix for drinking and dipping
authorPatR <rankin@nethack.org>
Wed, 13 Apr 2022 10:14:39 +0000 (03:14 -0700)
committerPatR <rankin@nethack.org>
Wed, 13 Apr 2022 10:14:39 +0000 (03:14 -0700)
Allow the player to precede q/#quaff or M-d/#dip with the 'm' prefix
to skip asking about fountains, sinks, or pools if one of those
happens to be present, similar to how using it for e/#eat skips food
on the floor and goes straight to inventory.

If you use it and don't have any potions, you'll get "you don't have
anything to drink" or "you don't have anything to dip into", same as
when there is no suitable dungeon feature present combined with no
potions.  However, if an applicable dungeon feature is present and
you don't use the prefix but answer 'no' to drink from fountain,&c
and you don't have any potions, "else" will be inserted into the
message: "you don't have anything else to drink".

A big part of the diff is just a change in indentation level for
code that is now inside 'if (!iflags.menu_requested) {' ... '}'.

doc/Guidebook.mn
doc/Guidebook.tex
doc/fixes3-7-0.txt
include/hack.h
src/cmd.c
src/invent.c
src/potion.c

index 053d205b72891189aeef6d0c9e9aaff5ddc3b868..ee8640dec9ad65dbe03af6ea82d3ed7bcb6d7eec 100644 (file)
@@ -651,7 +651,8 @@ a menu of several sorting alternatives (which sets a new value for the
 .op sortdiscoveries
 option).
 .lp ""
-A few other commands (eat food, offer sacrifice, apply tinning-kit) use
+A few other commands (eat food, offer sacrifice, apply tinning-kit,
+drink/quaff, dip) use
 the \(oq\f(CRm\fP\(cq prefix to skip checking for applicable objects on
 the floor and go straight to checking inventory,
 or (for \(lq#loot\(rq to remove a saddle),
@@ -878,6 +879,13 @@ For some interfaces, the behavior can be varied via the
 option.
 .lp q
 Quaff (drink) something (potion, water, etc).
+.lp ""
+When there is a fountain or sink present, it asks whether to drink
+from that.
+If that is declined, then it offers a chance to choose a potion from
+inventory.
+Precede \(oqq\(cq with the \(oqm\(cq prefix to skip asking about
+drinking from a fountain or sink.
 .lp Q
 Select an object for your quiver, quiver sack, or just generally at
 the ready (only one of these is available at a time).
@@ -1216,6 +1224,9 @@ See the section below entitled \(lqConduct\(rq for details.
 Dip an object into something.
 Autocompletes.
 Default key is \(oqM-d\(cq.
+.lp ""
+The \(oqm\(cq prefix skips dipping into a fountain or pool if there
+is one at your location.
 .lp "#down    "
 Go down a staircase.
 Default key is \(oq>\(cq.
@@ -1419,6 +1430,9 @@ Default key is \(oqP\(cq.
 .lp "#quaff   "
 Quaff (drink) something.
 Default key is \(oqq\(cq.
+.lp ""
+The \(oqm\(cq prefix skips drinking from a fountain or sink if there
+is one at your location.
 .lp "#quit    "
 Quit the program without saving your game.
 Autocompletes.
index 90a993dba0d2fbdd8e9ea2003a1e3b35af88ce5f..8f7ad1bded17071ff882afbb692b893bf5b86053 100644 (file)
@@ -744,7 +744,8 @@ a menu of several sorting alternatives (which sets a new value for the
 {\it sortdiscoveries\/} option).
 \\
 %.lp ""
-A few other commands (eat food, offer sacrifice, apply tinning-kit) use
+A few other commands (eat food, offer sacrifice, apply tinning-kit,
+drink/quaff, dip) use
 the `{\tt m}' prefix to skip checking for applicable objects on
 the floor and go straight to checking inventory,
 or (for ``{\tt \#loot}'' to remove a saddle),
@@ -972,7 +973,14 @@ For some interfaces, the behavior can be varied via the
 {\it msg\verb+_+window\/} option.
 %.lp
 \item[\tb{q}]
-Quaff (drink) something (potion, water, etc).
+Quaff (drink) something (potion, water, etc).\\
+%.lp ""
+When there is a fountain or sink present, it asks whether to drink
+from that.
+If that is declined, then it offers a chance to choose a potion from
+inventory.
+Precede {\tt q} with the {\tt m} prefix to skip asking about
+drinking from a fountain or sink.
 %.lp
 \item[\tb{Q}]
 Select an object for your quiver, quiver sack, or just generally at
@@ -1317,7 +1325,10 @@ Default key is `{\tt M-C}'.\\
 See the section below entitled ``Conduct'' for details.
 %.lp
 \item[\tb{\#dip}]
-Dip an object into something. Autocompletes. Default key is `{\tt M-d}'.
+Dip an object into something. Autocompletes. Default key is `{\tt M-d}'.\\
+%.lp ""
+The {\tt m} prefix skips dipping into a fountain or pool if there
+is one at your location.
 %.lp
 \item[\tb{\#down}]
 Go down a staircase. Default key is `{\tt >}'.
@@ -1525,7 +1536,10 @@ Show previously displayed game messages. Default key is `{\tt \^{}P}'.
 Put on an accessory (ring, amulet, etc). Default key is `{\tt P}'.
 %.lp
 \item[\tb{\#quaff}]
-Quaff (drink) something. Default key is `{\tt q}'.
+Quaff (drink) something. Default key is `{\tt q}'.\\
+%.lp ""
+The {\tt m} prefix skips drinking from a fountain or sink if there
+is one at your location.
 %.lp
 \item[\tb{\#quit}]
 Quit the program without saving your game. Autocompletes.\\
index 82a419754b8f8214edbc171804c0f972b5c3ae52..f543c78e6b7f757684b16027e12b7c798aa29aa7 100644 (file)
@@ -880,6 +880,8 @@ don't try to catch up for lost time for shop damage repair in restdamage()
 putting objects into a container with menustyle=traditional and then taking
        them back out with #tip would result in complaints about obj bypass
        bit being set if sanity_check was On
+when drinking or dipping, allow the 'm' prefix to be used to skip asking
+       about fountains and pools
 
 
 Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
index 38c436e905caa732589cfa806de9ae3e205ee93c..95edc50eebb38bd975a126628d6f5ae23889dc44 100644 (file)
@@ -583,7 +583,11 @@ enum getobj_callback_returns {
     /* generally invalid - can't be used for this purpose. will give a "silly
      * thing" message if the player tries to pick it, unless a more specific
      * failure message is in getobj itself - e.g. "You cannot foo gold". */
-    GETOBJ_EXCLUDE = -2,
+    GETOBJ_EXCLUDE = -3,
+    /* invalid because it is not in inventory; used when the hands/self
+     * possibility is queried and the player passed up something on the
+     * floor before getobj. */
+    GETOBJ_EXCLUDE_NONINVENT = -2,
     /* invalid because it is an inaccessible or unwanted piece of gear, but
      * psuedo-valid for the purposes of allowing the player to select it and
      * getobj to return it if there is a prompt instead of getting "silly
index efe3288c41592691221f1a200703d2a66125d548..b4b6064824aee9fb53622d5189b93c515912471a 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -2244,7 +2244,7 @@ struct ext_func_tab extcmdlist[] = {
     { M('C'), "conduct", "list voluntary challenges you have maintained",
               doconduct, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL },
     { M('d'), "dip", "dip an object into something",
-              dodip, AUTOCOMPLETE, NULL },
+              dodip, AUTOCOMPLETE | CMD_M_PREFIX, NULL },
     { '>',    "down", "go down a staircase",
               /* allows 'm' prefix (for move without autopickup) but not the
                  g/G/F movement modifiers; not flagged as MOVEMENTCMD because
@@ -2336,7 +2336,7 @@ struct ext_func_tab extcmdlist[] = {
     { 'P',    "puton", "put on an accessory (ring, amulet, etc)",
               doputon, 0, NULL },
     { 'q',    "quaff", "quaff (drink) something",
-              dodrink, 0, NULL },
+              dodrink, CMD_M_PREFIX, NULL },
     { '\0', "quit", "exit without saving current game",
               done2, IFBURIED | AUTOCOMPLETE | GENERALCMD | NOFUZZERCMD,
               NULL },
index d622ae4df57400a06098db9b3f94c3e29831add3..70a101a79024353ee12854b4f5a575e919f5f100 100644 (file)
@@ -1552,6 +1552,12 @@ getobj(const char *word,
         allownone = TRUE;
         *ap++ = HANDS_SYM;
         break;
+    case GETOBJ_EXCLUDE_NONINVENT: /* player skipped some alternative that's
+                                    * not in inventory, now the hands/self
+                                    * possibility is telling us so */
+        forceprompt = FALSE;
+        inaccess++;
+        break;
     default:
         break;
     }
@@ -1595,6 +1601,7 @@ getobj(const char *word,
             break;
         case GETOBJ_SUGGEST:
             break; /* adding otmp->invlet is all that's needed */
+        case GETOBJ_EXCLUDE_NONINVENT: /* not applicable for invent items */
         default:
             impossible("bad return from getobj callback");
         }
index 90ecd626331387591a4b95438406a33cd56216d8..fc125ccc7b2757022d6476c3922cceea5a595f88 100644 (file)
@@ -42,6 +42,11 @@ static void hold_potion(struct obj *, const char *, const char *,
                         const char *);
 static int potion_dip(struct obj *obj, struct obj *potion);
 
+/* note: (*potn_test)() is for use by drink_ok() which is used to validate
+   potion to drink and also for potion to dip into [reinitialized every time
+   it's used so does not need to be placed in struct instance_globals g] */
+static boolean (*potn_test)(void) = (boolean (*)(void)) 0;
+
 /* force `val' to be within valid range for intrinsic timeout value */
 static long
 itimeout(long val)
@@ -489,6 +494,27 @@ ghost_from_bottle(void)
     g.nomovemsg = "You regain your composure.";
 }
 
+/* for drink_ok() when called for dodrink(); called thru (*potn_test)  */
+static boolean
+could_have_drunk(void)
+{
+    /* caveat: relies on knowing details of dodrink() */
+    return (((IS_FOUNTAIN(levl[u.ux][u.uy].typ)
+              || IS_SINK(levl[u.ux][u.uy].typ))
+             && can_reach_floor(FALSE))
+            || (Underwater && !u.uswallow));
+}
+
+/* for drink_ok() when called for dodip(); called thru (*potn_test)  */
+static boolean
+could_have_dipped(void)
+{
+    /* caveat: relies on knowing details of dodip() */
+    return ((IS_FOUNTAIN(levl[u.ux][u.uy].typ)
+             || is_pool(u.ux, u.uy))
+            && can_reach_floor(FALSE));
+}
+
 /* getobj callback for object to drink from, which also does double duty as
    the callback for dipping into (both just allow potions). */
 static int
@@ -497,6 +523,15 @@ drink_ok(struct obj *obj)
     if (obj && obj->oclass == POTION_CLASS)
         return GETOBJ_SUGGEST;
 
+    /* getobj()'s callback to test whether hands/self is a valid "item" to
+       pick is used here to communicate the fact that player has already
+       passed up an opportunity to perform the action (drink or dip) on a
+       non-inventory dungeon feature, so if there are no potions in invent
+       the message will be "you have nothing /else/ to {drink | dip into}";
+       skip "else" if player used 'm' prefix to bypass dungeon features */
+    if (!obj && !iflags.menu_requested && potn_test && (*potn_test)())
+        return GETOBJ_EXCLUDE_NONINVENT;
+
     return GETOBJ_EXCLUDE;
 }
 
@@ -505,39 +540,47 @@ drink_ok(struct obj *obj)
 int
 dodrink(void)
 {
-    register struct obj *otmp;
+    struct obj *otmp;
 
     if (Strangled) {
         pline("If you can't breathe air, how can you drink liquid?");
         return ECMD_OK;
     }
-    /* Is there a fountain to drink from here? */
-    if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)
-        /* not as low as floor level but similar restrictions apply */
-        && can_reach_floor(FALSE)) {
-        if (yn("Drink from the fountain?") == 'y') {
-            drinkfountain();
-            return ECMD_TIME;
+
+    /* preceding 'q'/#quaff with 'm' skips the possibility of drinking
+       from fountains, sinks, and surrounding water plus the prompting
+       which those entail */
+    if (!iflags.menu_requested) {
+        /* Is there a fountain to drink from here? */
+        if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)
+            /* not as low as floor level but similar restrictions apply */
+            && can_reach_floor(FALSE)) {
+            if (yn("Drink from the fountain?") == 'y') {
+                drinkfountain();
+                return ECMD_TIME;
+            }
         }
-    }
-    /* Or a kitchen sink? */
-    if (IS_SINK(levl[u.ux][u.uy].typ)
-        /* not as low as floor level but similar restrictions apply */
-        && can_reach_floor(FALSE)) {
-        if (yn("Drink from the sink?") == 'y') {
-            drinksink();
-            return ECMD_TIME;
+        /* Or a kitchen sink? */
+        if (IS_SINK(levl[u.ux][u.uy].typ)
+            /* not as low as floor level but similar restrictions apply */
+            && can_reach_floor(FALSE)) {
+            if (yn("Drink from the sink?") == 'y') {
+                drinksink();
+                return ECMD_TIME;
+            }
         }
-    }
-    /* Or are you surrounded by water? */
-    if (Underwater && !u.uswallow) {
-        if (yn("Drink the water around you?") == 'y') {
-            pline("Do you know what lives in this water?");
-            return ECMD_TIME;
+        /* Or are you surrounded by water? */
+        if (Underwater && !u.uswallow) {
+            if (yn("Drink the water around you?") == 'y') {
+                pline("Do you know what lives in this water?");
+                return ECMD_TIME;
+            }
         }
     }
 
+    potn_test = could_have_drunk;
     otmp = getobj("drink", drink_ok, GETOBJ_NOFLAGS);
+    potn_test = (boolean (*)(void)) 0;
     if (!otmp)
         return ECMD_CANCEL;
 
@@ -2186,66 +2229,74 @@ dodip(void)
     if (inaccessible_equipment(obj, "dip", FALSE))
         return ECMD_OK;
 
-    shortestname = (is_plural(obj) || pair_of(obj)) ? "them" : "it";
-    /*
-     * Bypass safe_qbuf() since it doesn't handle varying suffix without
-     * an awful lot of support work.  Format the object once, even though
-     * the fountain and pool prompts offer a lot more room for it.
-     * 3.6.0 used thesimpleoname() unconditionally, which posed no risk
-     * of buffer overflow but drew bug reports because it omits user-
-     * supplied type name.
-     * getobj: "What do you want to dip <the object> into? [xyz or ?*] "
-     */
-    Strcpy(obuf, short_oname(obj, doname, thesimpleoname,
+    /* preceding #dip with 'm' skips the possibility of dipping into
+       fountains and pools plus the prompting which those entail */
+    if (!iflags.menu_requested) {
+        shortestname = (is_plural(obj) || pair_of(obj)) ? "them" : "it";
+        /*
+         * Bypass safe_qbuf() since it doesn't handle varying suffix without
+         * an awful lot of support work.  Format the object once, even though
+         * the fountain and pool prompts offer a lot more room for it.
+         * 3.6.0 used thesimpleoname() unconditionally, which posed no risk
+         * of buffer overflow but drew bug reports because it omits user-
+         * supplied type name.
+         * getobj: "What do you want to dip <the object> into? [xyz or ?*] "
+         */
+        Strcpy(obuf, short_oname(obj, doname, thesimpleoname,
                              /* 128 - (24 + 54 + 1) leaves 49 for <object> */
-                             QBUFSZ - sizeof "What do you want to dip \
+                                 QBUFSZ - sizeof "What do you want to dip \
  into? [abdeghjkmnpqstvwyzBCEFHIKLNOQRTUWXZ#-# or ?*] "));
 
-    here = levl[u.ux][u.uy].typ;
-    /* Is there a fountain to dip into here? */
-    if (IS_FOUNTAIN(here)) {
-        Snprintf(qbuf, sizeof(qbuf), "%s%s into the fountain?", Dip_,
-                 flags.verbose ? obuf : shortestname);
-        /* "Dip <the object> into the fountain?" */
-        if (yn(qbuf) == 'y') {
-            obj->pickup_prev = 0;
-            dipfountain(obj);
-            return ECMD_TIME;
-        }
-    } else if (is_pool(u.ux, u.uy)) {
-        const char *pooltype = waterbody_name(u.ux, u.uy);
-
-        Snprintf(qbuf, sizeof(qbuf), "%s%s into the %s?", Dip_,
-                 flags.verbose ? obuf : shortestname, pooltype);
-        /* "Dip <the object> into the {pool, moat, &c}?" */
-        if (yn(qbuf) == 'y') {
-            if (Levitation) {
-                floating_above(pooltype);
-            } else if (u.usteed && !is_swimmer(u.usteed->data)
-                       && P_SKILL(P_RIDING) < P_BASIC) {
-                rider_cant_reach(); /* not skilled enough to reach */
-            } else {
+        here = levl[u.ux][u.uy].typ;
+        /* Is there a fountain to dip into here? */
+        if (!can_reach_floor(FALSE)) {
+            ; /* can't dip something into fountain or pool if can't reach */
+        } else if (IS_FOUNTAIN(here)) {
+            Snprintf(qbuf, sizeof(qbuf), "%s%s into the fountain?", Dip_,
+                     flags.verbose ? obuf : shortestname);
+            /* "Dip <the object> into the fountain?" */
+            if (yn(qbuf) == 'y') {
                 obj->pickup_prev = 0;
-                if (obj->otyp == POT_ACID)
-                    obj->in_use = 1;
-                if (water_damage(obj, 0, TRUE) != ER_DESTROYED && obj->in_use)
-                    useup(obj);
+                dipfountain(obj);
+                return ECMD_TIME;
+            }
+        } else if (is_pool(u.ux, u.uy)) {
+            const char *pooltype = waterbody_name(u.ux, u.uy);
+
+            Snprintf(qbuf, sizeof(qbuf), "%s%s into the %s?", Dip_,
+                     flags.verbose ? obuf : shortestname, pooltype);
+            /* "Dip <the object> into the {pool, moat, &c}?" */
+            if (yn(qbuf) == 'y') {
+                if (Levitation) {
+                    floating_above(pooltype);
+                } else if (u.usteed && !is_swimmer(u.usteed->data)
+                           && P_SKILL(P_RIDING) < P_BASIC) {
+                    rider_cant_reach(); /* not skilled enough to reach */
+                } else {
+                    obj->pickup_prev = 0;
+                    if (obj->otyp == POT_ACID)
+                        obj->in_use = 1;
+                    if (water_damage(obj, 0, TRUE) != ER_DESTROYED
+                        && obj->in_use)
+                        useup(obj);
+                }
+                return ECMD_TIME;
             }
-            return ECMD_TIME;
         }
     }
-
+    potn_test = could_have_dipped; /* augment drink_ok() */
     /* "What do you want to dip <the object> into? [xyz or ?*] " */
     Snprintf(qbuf, sizeof qbuf, "dip %s into",
              flags.verbose ? obuf : shortestname);
     potion = getobj(qbuf, drink_ok, GETOBJ_NOFLAGS);
+    potn_test = (boolean (*)(void)) 0;
     if (!potion)
         return ECMD_CANCEL;
     return potion_dip(obj, potion);
 }
 
 /* #altdip - for context-sensitive inventory item-action;
-   potion already selected */
+   potion already selected and pending in cmdq */
 int
 dip_into(void)
 {
@@ -2254,6 +2305,9 @@ dip_into(void)
 
     if (!cmdq_peek())
         panic("dip_into: where is potion?");
+    potn_test = (boolean (*)(void)) 0; /* not needed for this drink_ok() */
+    /* note: drink_ok() callback for quaffing is also used to validate
+       a potion to dip into */
     potion = getobj("dip", drink_ok, GETOBJ_NOFLAGS);
     if (!potion || potion->oclass != POTION_CLASS)
         return ECMD_CANCEL;