From: PatR Date: Sat, 23 Apr 2022 09:12:21 +0000 (-0700) Subject: 'f'ire revamp X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=40d0caa1570570d76a7abe918cfcdc965cc5207a;p=nethack 'f'ire revamp 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. --- diff --git a/include/extern.h b/include/extern.h index c5cd0e839..255dcef04 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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); diff --git a/src/dothrow.c b/src/dothrow.c index 46b511f20..cc3fb9b67 100644 --- a/src/dothrow.c +++ b/src/dothrow.c @@ -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 */ diff --git a/src/invent.c b/src/invent.c index d13925409..b40edb5a3 100644 --- a/src/invent.c +++ b/src/invent.c @@ -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 diff --git a/src/wield.c b/src/wield.c index 3c021b399..3c763a45e 100644 --- a/src/wield.c +++ b/src/wield.c @@ -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