diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 053d205b7..ee8640dec 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -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. diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 90a993dba..8f7ad1bde 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -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.\\ diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 82a419754..f543c78e6 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -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 diff --git a/include/hack.h b/include/hack.h index 38c436e90..95edc50ee 100644 --- a/include/hack.h +++ b/include/hack.h @@ -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 diff --git a/src/cmd.c b/src/cmd.c index efe3288c4..b4b606482 100644 --- 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 }, diff --git a/src/invent.c b/src/invent.c index d622ae4df..70a101a79 100644 --- a/src/invent.c +++ b/src/invent.c @@ -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"); } diff --git a/src/potion.c b/src/potion.c index 90ecd6263..fc125ccc7 100644 --- a/src/potion.c +++ b/src/potion.c @@ -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 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 into? [xyz or ?*] " + */ + Strcpy(obuf, short_oname(obj, doname, thesimpleoname, /* 128 - (24 + 54 + 1) leaves 49 for */ - 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 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 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 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 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 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;