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; + 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; + 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; + return GETOBJ_SUGGEST; /* '-', will empty quiver slot if chosen */ - /* exclude when wielded... */ - if ((obj == uwep || (obj == uswapwep && u.twoweap)) - && obj->quan == 1) /* ...unless more than one */ - return GETOBJ_EXCLUDE_INACCESS; + /* downplay when wielded, unless more than one */ + if (obj == uwep || (obj == uswapwep && u.twoweap)) + return (obj->quan == 1) ? GETOBJ_DOWNPLAY : GETOBJ_SUGGEST; - 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 (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