]> granicus.if.org Git - nethack/commitdiff
'f'ire revamp
authorPatR <rankin@nethack.org>
Sat, 23 Apr 2022 09:12:21 +0000 (02:12 -0700)
committerPatR <rankin@nethack.org>
Sat, 23 Apr 2022 09:12:21 +0000 (02:12 -0700)
The fire command could claim that time passed when it hadn't (fill
quiver with ammo, which takes no time, then queue commands to switch
to matching launcher, which should also take no time while queueing,
only during subsequent execution).

If quiver is empty or has ammo in it, give wielded thrown-and-return
weapon (aklys) priority over filling quiver or switching to ammo's
launcher.  Don't do that if quiver has non-ammo in it, otherwise
players running Valks who wield Mjollnir with super strength but
want to throw quivered daggers would complain.

When player is being asked what to fill the quiver with, use the
\#quiver command to do that.  Using it honors a count to split a
stack, handles switching uwep or uswapwep to uquiver, and gives
feedback.  This is actually a fairly substantial change.

For 'fireassist', when switching to a launcher that isn't already
uswapwep pick one known to be blessed or uncursed over one having
unknown BUC status.  But use the latter as last resort.

include/extern.h
src/dothrow.c
src/invent.c
src/wield.c

index c5cd0e839ad951965b2fe523ed063a532336d43e..255dcef04746431b4acb5ca1ffc43a66ae544d83 100644 (file)
@@ -3134,6 +3134,7 @@ extern void setuswapwep(struct obj *);
 extern int dowield(void);
 extern int doswapweapon(void);
 extern int dowieldquiver(void);
+extern int doquiver_core(const char *);
 extern boolean wield_tool(struct obj *, const char *);
 extern int can_twoweapon(void);
 extern void drop_uswapwep(void);
index 46b511f20aee509417b2dc761c9d225cd1b46861..cc3fb9b670bc4af505cdfa2e3b129ec87911c8a8 100644 (file)
@@ -408,16 +408,22 @@ autoquiver(void)
 static struct obj *
 find_launcher(struct obj *ammo)
 {
-    struct obj *otmp;
+    struct obj *otmp, *oX;
 
     if (!ammo)
-        return (struct obj *)0;
-
-    for (otmp = g.invent; otmp; otmp = otmp->nobj)
-        if (ammo_and_launcher(ammo, otmp) && !(otmp->cursed && otmp->bknown))
-            return otmp;
-
-    return (struct obj *)0;
+        return (struct obj *) 0;
+
+    for (oX = 0, otmp = g.invent; otmp; otmp = otmp->nobj) {
+        if (otmp->cursed && otmp->bknown)
+            continue; /* known to be cursed, so skip */
+        if (ammo_and_launcher(ammo, otmp)) {
+            if (otmp->bknown)
+                return otmp; /* known-B or known-U (known-C won't get here) */
+            if (!oX)
+                oX = otmp; /* unknown-BUC; used if no known-BU item found */
+        }
+    }
+    return oX;
 }
 
 /* the #fire command -- throw from the quiver or use wielded polearm */
@@ -426,16 +432,25 @@ dofire(void)
 {
     int shotlimit;
     struct obj *obj;
+    /* AutoReturn() verifies Valkyrie if weapon is Mjollnir, but it relies
+       on its caller to make sure hero is strong enough to throw that */
+    boolean uwep_Throw_and_Return = (uwep && AutoReturn(uwep, uwep->owornmask)
+                                     && (uwep->oartifact != ART_MJOLLNIR
+                                         || ACURR(A_STR) >= STR19(25)));
+    int altres, res = ECMD_OK;
 
     /*
      * Same as dothrow(), except we use quivered missile instead
-     * of asking what to throw/shoot.
+     * of asking what to throw/shoot.  [Note: with the advent of
+     * fireassist that is no longer accurate...]
      *
-     * If quiver is empty, we use autoquiver to fill it when the
-     * corresponding option is on.  If the option is off and hero
-     * is wielding a thrown-and-return weapon, use the wielded
-     * weapon.  If option is off and not wielding such a weapon or
-     * if autoquiver doesn't select anything, we ask what to throw.
+     * If hero is wielding a thrown-and-return weapon and quiver
+     * is empty or contains ammo, use the wielded weapon (won't
+     * have any ammo's launcher wielded due to the weapon).
+     * If quiver is empty, use autoquiver to fill it when the
+     * corresponding option is on.
+     * If option is off or autoquiver doesn't select anything,
+     * we ask what to throw.
      * Then we put the chosen item into the quiver slot unless
      * it is already in another slot.  [Matters most if it is a
      * stack but also matters for single item if this throw gets
@@ -444,78 +459,87 @@ dofire(void)
     if (!ok_to_throw(&shotlimit))
         return ECMD_OK;
 
-    if ((obj = uquiver) == 0) {
+    obj = uquiver;
+    /* if wielding a throw-and-return weapon, throw it if quiver is empty
+       or has ammo rather than missiles [since the throw/return weapon is
+       wielded, the ammo's launcher isn't; the ammo-only policy avoids
+       throwing Mjollnir if quiver contains daggers] */
+    if (uwep_Throw_and_Return && (!obj || is_ammo(obj))) {
+        obj = uwep;
+
+    } else if (!obj) {
         if (!flags.autoquiver) {
-            if (uwep && AutoReturn(uwep, uwep->owornmask))
-                obj = uwep;
-            else {
-                /* if we're wielding a polearm, apply it */
-                if (uwep && is_pole(uwep))
-                    return use_pole(uwep, TRUE);
-                /* if we're wielding a bullwhip, apply it */
-                else if (uwep && uwep->otyp == BULLWHIP)
-                    return use_whip(uwep);
-                else if (iflags.fireassist
-                         && uswapwep && is_pole(uswapwep)
-                         && !(uswapwep->cursed && uswapwep->bknown)) {
-                    /* we have a known not-cursed polearm as swap weapon.
-                       swap to it and retry */
-                    cmdq_add_ec(doswapweapon);
-                    cmdq_add_ec(dofire);
-                    return ECMD_TIME;
-                } else
-                    You("have no ammunition readied.");
+            /* if we're wielding a polearm, apply it */
+            if (uwep && is_pole(uwep)) {
+                return use_pole(uwep, TRUE);
+            /* if we're wielding a bullwhip, apply it */
+            } else if (uwep && uwep->otyp == BULLWHIP) {
+                return use_whip(uwep);
+            } else if (iflags.fireassist
+                       && uswapwep && is_pole(uswapwep)
+                       && !(uswapwep->cursed && uswapwep->bknown)) {
+                /* we have a known not-cursed polearm as swap weapon.
+                   swap to it and retry */
+                cmdq_add_ec(doswapweapon);
+                cmdq_add_ec(dofire);
+                return ECMD_OK; /* haven't taken any time yet */
+            } else {
+                You("have no ammunition readied.");
             }
         } else {
             autoquiver();
-            if ((obj = uquiver) == 0)
+            obj = uquiver;
+            if (obj) {
+                /* give feedback if quiver has now been filled */
+                uquiver->owornmask &= ~W_QUIVER; /* less verbose */
+                prinv("You ready:", obj, 0L);
+                uquiver->owornmask |= W_QUIVER;
+            } else {
                 You("have nothing appropriate for your quiver.");
+            }
         }
-        /* if autoquiver is disabled or has failed, prompt for missile;
-           fill quiver with it if it's not wielded or worn */
-        if (!obj) {
-            /* in case we're using ^A to repeat prior 'f' command, don't
-               use direction of previous throw as getobj()'s choice here */
-            g.in_doagain = 0;
-            /* choose something from inventory, then usually quiver it */
-            obj = getobj("throw", throw_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
-            /* Q command doesn't allow gold in quiver */
-            if (!obj)
-                return ECMD_CANCEL;
-            if (obj && !obj->owornmask && obj->oclass != COIN_CLASS)
-                setuqwep(obj); /* demi-autoquiver */
-        }
-        /* give feedback if quiver has now been filled */
-        if (uquiver) {
-            uquiver->owornmask &= ~W_QUIVER; /* less verbose */
-            prinv("You ready:", uquiver, 0L);
-            uquiver->owornmask |= W_QUIVER;
-        }
     }
 
-    if (uquiver && iflags.fireassist) {
+    /* if autoquiver is disabled or has failed, prompt for missile */
+    if (!obj) {
+        /* in case we're using ^A to repeat prior 'f' command, don't
+           use direction of previous throw as getobj()'s choice here */
+        g.in_doagain = 0;
+
+        /* this gives its own feedback about populating the quiver slot */
+        res = doquiver_core("fire");
+        if (res != ECMD_OK && res != ECMD_TIME)
+            return res;
+
+        obj = uquiver;
+    }
+
+    if (uquiver && is_ammo(uquiver) && iflags.fireassist) {
         struct obj *olauncher;
 
         /* Try to find a launcher */
         if (ammo_and_launcher(uquiver, uwep)) {
-            /* Do nothing, already wielding a launcher */
+            obj = uquiver;
         } else if (ammo_and_launcher(uquiver, uswapwep)) {
             /* swap weapons and retry fire */
             cmdq_add_ec(doswapweapon);
             cmdq_add_ec(dofire);
-            return ECMD_TIME;
-        } else if ((olauncher = find_launcher(obj)) != 0) {
+            return res;
+        } else if ((olauncher = find_launcher(uquiver)) != 0) {
             /* wield launcher, retry fire */
             if (uwep && !flags.pushweapon)
                 cmdq_add_ec(doswapweapon);
             cmdq_add_ec(dowield);
             cmdq_add_key(olauncher->invlet);
             cmdq_add_ec(dofire);
-            return ECMD_TIME;
+            return res;
         }
     }
 
-    return obj ? throw_obj(obj, shotlimit) : ECMD_OK;
+    altres = obj ? throw_obj(obj, shotlimit) : ECMD_CANCEL;
+    /* fire can take time by filling quiver (if that causes something which
+       was wielded to be unwielded) even if the throw itself gets cancelled */
+    return (res == ECMD_TIME) ? res : altres;
 }
 
 /* if in midst of multishot shooting/throwing, stop early */
index d139254093ba01321501c06310b36ba257ad0e65..b40edb5a3ddcbfdd4274da194c5cd6fed93ccba8 100644 (file)
@@ -1751,9 +1751,10 @@ getobj(
             }
         }
         if (cntgiven && !strcmp(word, "throw")) {
-            /* permit counts for throwing gold, but don't accept
-             * counts for other things since the throw code will
-             * split off a single item anyway */
+            /* permit counts for throwing gold, but don't accept counts
+               for other things since the throw code will split off a
+               single item anyway; if populating quiver, 'word' will be
+               "ready" or "fire" and this restriction doesn't apply */
             if (cnt == 0)
                 return (struct obj *) 0;
             if (cnt > 1 && (ilet != def_oc_syms[COIN_CLASS].sym
index 3c021b39966849bcb86cadd8146dbf71b89cd89f..3c763a45e310f43c8d32da1e46616330c27af310 100644 (file)
@@ -265,29 +265,40 @@ setuswapwep(struct obj *obj)
     return;
 }
 
-/* getobj callback for object to ready for throwing/shooting */
+/* getobj callback for object to ready for throwing/shooting;
+   this filter lets worn items through so that caller can reject them */
 static int
 ready_ok(struct obj *obj)
 {
     if (!obj)
-        return GETOBJ_SUGGEST;
-
-    /* exclude when wielded... */
-    if ((obj == uwep || (obj == uswapwep && u.twoweap))
-        && obj->quan == 1) /* ...unless more than one */
-        return GETOBJ_EXCLUDE_INACCESS;
-
-    if (obj->oclass == WEAPON_CLASS || obj->oclass == COIN_CLASS)
-        return GETOBJ_SUGGEST;
-    /* Possible extension: exclude weapons that make no sense to throw, such as
-     * whips, bows, slings, rubber hoses. */
+        return GETOBJ_SUGGEST; /* '-', will empty quiver slot if chosen */
+
+    /* downplay when wielded, unless more than one */
+    if (obj == uwep || (obj == uswapwep && u.twoweap))
+        return (obj->quan == 1) ? GETOBJ_DOWNPLAY : GETOBJ_SUGGEST;
+
+    if (is_ammo(obj)) {
+        return ((uwep && ammo_and_launcher(obj, uwep))
+                || (uswapwep && ammo_and_launcher(obj, uswapwep)))
+                ? GETOBJ_SUGGEST
+                : GETOBJ_DOWNPLAY;
+    } else if (is_launcher(obj)) { /* part of 'possible extension' below */
+        return GETOBJ_DOWNPLAY;
+    } else {
+        if (obj->oclass == WEAPON_CLASS || obj->oclass == COIN_CLASS)
+            return GETOBJ_SUGGEST;
+        /* Possible extension: exclude weapons that make no sense to throw,
+           such as whips, bows, slings, rubber hoses. */
+    }
 
+#if 0   /* superseded by ammo_and_launcher handling above */
     /* Include gems/stones as likely candidates if either primary
        or secondary weapon is a sling. */
     if (obj->oclass == GEM_CLASS
         && (uslinging()
             || (uswapwep && objects[uswapwep->otyp].oc_skill == P_SLING)))
         return GETOBJ_SUGGEST;
+#endif
 
     return GETOBJ_DOWNPLAY;
 }
@@ -467,6 +478,13 @@ doswapweapon(void)
 /* the #quiver command */
 int
 dowieldquiver(void)
+{
+    return doquiver_core("ready");
+}
+
+/* guts of #quiver command; also used by #fire when refilling empty quiver */
+int
+doquiver_core(const char *verb) /* "ready" or "fire" */
 {
     char qbuf[QBUFSZ];
     struct obj *newquiver;
@@ -480,8 +498,8 @@ dowieldquiver(void)
     /* forget last splitobj() before calling getobj() with GETOBJ_ALLOWCNT */
     clear_splitobjs();
 
-    /* Prompt for a new quiver: "What do you want to ready?" */
-    newquiver = getobj("ready", ready_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
+    /* Prompt for a new quiver: "What do you want to {ready|fire}?" */
+    newquiver = getobj(verb, ready_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
 
     if (!newquiver) {
         /* Cancelled */
@@ -515,7 +533,7 @@ dowieldquiver(void)
         pline("That ammunition is already readied!");
         return ECMD_OK;
     } else if (newquiver->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE)) {
-        You("cannot ready that!");
+        You("cannot %s that!", verb);
         return ECMD_OK;
     } else if (newquiver == uwep) {
         int weld_res = !uwep->bknown;
@@ -607,10 +625,18 @@ dowieldquiver(void)
         addinv(newquiver);
         newquiver->nomerge = 0;
     }
-    /* place item in quiver before printing so that inventory feedback
-       includes "(at the ready)" */
-    setuqwep(newquiver);
-    prinv((char *) 0, newquiver, 0L);
+
+    if (!strcmp(verb, "ready")) {
+        /* place item in quiver before printing so that inventory feedback
+           includes "(at the ready)" */
+        setuqwep(newquiver);
+        prinv((char *) 0, newquiver, 0L);
+    } else { /* verb=="fire", manually refilling quiver during 'f'ire */
+        /* prefix item with description of action, so don't want that to
+           include "(at the ready)" */
+        prinv("You ready:", newquiver, 0L);
+        setuqwep(newquiver);
+    }
 
     /* quiver is a convenience slot and manipulating it ordinarily
        consumes no time, but unwielding primary or secondary weapon