]> granicus.if.org Git - nethack/commitdiff
Add assistance to fire-command
authorPasi Kallinen <paxed@alt.org>
Wed, 9 Jun 2021 06:02:31 +0000 (09:02 +0300)
committerPasi Kallinen <paxed@alt.org>
Wed, 16 Jun 2021 10:14:32 +0000 (13:14 +0300)
Allows the fire-command to autowield a launcher; it will now
do either swapweapon or wield an appropriate launcher, if you
have ammo quivered.

This assistance can be turned off with the fireassist boolean option.

Adds a rudimentary command queue, which allows the code to add keys
or extended commands into the queue, and they're executed as if
the user did them.  Time passes normally when doing the queue,
and the queue will get cleared if hero is interrupted.

15 files changed:
doc/Guidebook.mn
doc/Guidebook.tex
doc/fixes37.0
include/decl.h
include/extern.h
include/flag.h
include/optlist.h
src/allmain.c
src/cmd.c
src/decl.c
src/do_wear.c
src/dothrow.c
src/hack.c
src/invent.c
src/wield.c

index b64d37de321108baa1feaed68d9bb0fcf1c50fce..8377f28d6d0114fd494f4d9b7ad28b3d0882c80d 100644 (file)
@@ -797,6 +797,12 @@ If your wielded weapon has the throw-and-return property, your quiver
 is empty, and
 .op autoquiver
 is false, you will throw that wielded weapon instead of filling the quiver.
+If
+.op fireassist
+is true, firing will automatically try to wield a launcher (for example,
+a bow or a sling) matching the ammo in the quiver; this might take multiple
+turns, and get interrupted by a monster.
+Remember to swap back to your main melee weapon afterwards.
 .lp ""
 See also \(oqt\(cq (throw) for more general throwing and shooting.
 .lp i
@@ -924,7 +930,7 @@ If you throw an arrow while not wielding a bow, you are throwing
 it by hand and it will generally be less effective than when shot.
 .lp ""
 See also \(oqf\(cq (fire) for throwing or shooting an item pre-selected
-via the \(oqQ\(cq (quiver) command.
+via the \(oqQ\(cq (quiver) command, with some extra assistance.
 .lp T
 Take off armor.
 .lp ""
@@ -1234,7 +1240,7 @@ You can set the
 .op paranoid_confirmation:quit
 option to require a response of \f(CRyes\fP instead.
 .lp "#fire    "
-Fire ammunition from quiver.
+Fire ammunition from quiver, possibly autowielding a launcher.
 Default key is \(oqf\(cq.
 .lp "#force   "
 Force a lock.
@@ -2416,7 +2422,7 @@ Some of the more obscure weapons (such as the \fIaklys\fP,
 appendix to \fIUnearthed Arcana\fP, an AD&D supplement.
 .pg
 The commands to use weapons are \(oqw\(cq (wield), \(oqt\(cq (throw),
-\(oqf\(cq (fire, an alternate way of throwing), \(oqQ\(cq (quiver),
+\(oqf\(cq (fire), \(oqQ\(cq (quiver),
 \(oqx\(cq (exchange), \(oqX\(cq (twoweapon), and \(lq#enhance\(rq
 (see below).
 .hn 3
@@ -2458,6 +2464,9 @@ If your quiver is empty,
 .op autoquiver
 is false, and you are wielding a weapon which returns when thrown,
 you will throw that weapon instead of filling the quiver.
+The fire command also has extra assistance, if
+.op fireassist
+is on it will try to wield a launcher matching the ammo in the quiver.
 .pg
 Some characters have the ability to throw or shoot a volley of multiple
 items (from the same stack) in a single action.
@@ -3686,6 +3695,10 @@ extended ones (off).
 .lp female
 An obsolete synonym for \(lqgender:female\(rq.
 Cannot be set with the \(oqO\(cq command.
+.lp fireassist
+This option controls what happens when you attempt the \(oqf\(cq (fire)
+and don't have an appropriate launcher, such as a bow or a sling, wielded.
+If on, you will automatically wield the launcher. Default is on.
 .lp fixinv
 An object's inventory letter sticks to it when it's dropped (default on).
 If this is off, dropping an object shifts all the remaining inventory letters.
index b0c630a870a0ede96ba2fc2dd49b3bcbf8d0447b..9845a8e8f7ef735baf309682357a181904e0bddc 100644 (file)
@@ -879,6 +879,10 @@ computer pick something appropriate if {\it autoquiver\/} is true.
 If your wielded weapon has the throw-and-return property, your quiver
 is empty, and {\it autoquiver\/}
 is false, you will throw that wielded weapon instead of filling the quiver.
+If {\it fireassist\/} is true, firing will automatically try to wield a launcher
+(for example, a bow or a sling) matching the ammo in the quiver; this might
+take multiple turns, and get interrupted by a monster.
+Remember to swap back to your main melee weapon afterwards.
 %.lp ""
 \\
 See also `{\tt t}' (throw) for more general throwing and shooting.
@@ -1011,7 +1015,7 @@ If you ``throw'' an arrow while not wielding a bow, you are throwing
 it by hand and it will generally be less effective than when shot.\\
 %.lp ""
 See also `{\tt f}' (fire) for throwing or shooting an item pre-selected
-via the `{\tt Q}' (quiver) command.
+via the `{\tt Q}' (quiver) command, with some extra assistance.
 %.lp
 \item[\tb{T}]
 Take off armor.\\
@@ -1326,7 +1330,8 @@ You can set the
 option to require a response of ``{\tt yes}'' instead.
 %.lp
 \item[\tb{\#fire}]
-Fire ammunition from quiver. Default key is `{\tt f}'.
+Fire ammunition from quiver, possibly autowielding a launcher.
+Default key is `{\tt f}'.
 %.lp
 \item[\tb{\#force}]
 Force a lock. Autocompletes. Default key is `{\tt M-f}'.
@@ -2630,7 +2635,7 @@ in an appendix to {\it Unearthed Arcana}, an AD\&D supplement.
 
 %.pg
 The commands to use weapons are `{\tt w}' (wield), `{\tt t}' (throw),
-`{\tt f}' (fire, an alternate way of throwing), `{\tt Q}' (quiver),
+`{\tt f}' (fire), `{\tt Q}' (quiver),
 `{\tt x}' (exchange), `{\tt X}' (twoweapon), and ``{\tt \#enhance}''
 (see below).
 
@@ -2672,6 +2677,8 @@ for `{\tt Q}' runs out.
 If your quiver is empty, {\it autoquiver\/}
 is false, and you are wielding a weapon which returns when thrown,
 you will throw that weapon instead of filling the quiver.
+The fire command also has extra assistance, if {\it fireassist\/}
+is on it will try to wield a launcher matching the ammo in the quiver.
 
 %.pg
 Some characters have the ability to throw or shoot a volley of multiple
@@ -3981,6 +3988,11 @@ extended ones (off).
 An obsolete synonym for ``{\tt gender:female}''.  Cannot be set with the
 `{\tt O}' command.
 %.lp
+\item[\ib{fireassist}]
+This option controls what happens when you attempt the `{\tt f}' (fire)
+and don't have an appropriate launcher, such as a bow or a sling, wielded.
+If on, you will automatically wield the launcher. Default is on.
+%.lp
 \item[\ib{fixinv}]
 An object's inventory letter sticks to it when it's dropped (default on).
 If this is off, dropping an object shifts all the remaining inventory letters.
index 569d577268d7c04768b35debd3ac50911cd3abdf..d8b271e1dd1cb7d2b61473226701f4c2030db664 100644 (file)
@@ -545,6 +545,8 @@ resurrected corpse of mon could end up with different gender from original mon
 using a bullwhip to snatch a wielded cockatrice corpse from a monster when not
        wearing gloves and without life-saving could trigger "obj_is_local"
        panic during final cleanup
+make fire-command autowield an appropriate launcher and add fireassist boolean
+       option to toggle the assistance off
 
 
 Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
index 8614bdf2d4ea25e5494ee2df118e74fd53069f29..b2bb4967cb4f66215412af2b07643f2950f52b2c 100644 (file)
@@ -644,6 +644,25 @@ struct _create_particular_data {
 #define LUA_VER_BUFSIZ 20
 #define LUA_COPYRIGHT_BUFSIZ 120
 
+/*
+ * Rudimentary command queue.
+ * Allows the code to put keys and extended commands into the queue,
+ * and they're executed just as if the user did them.  Time passes
+ * normally when doing queued actions.  The queue will get cleared
+ * if hero is interrupted.
+ */
+enum cmdq_cmdtypes {
+    CMDQ_KEY = 0, /* a literal character, cmdq_add_key() */
+    CMDQ_EXTCMD,  /* extended command, cmdq_add_ec() */
+};
+
+struct _cmd_queue {
+    int typ;
+    char key;
+    const struct ext_func_tab *ec_entry;
+    struct _cmd_queue *next;
+};
+
 /*
  * 'g' -- instance_globals holds engine state that does not need to be
  * persisted upon game exit.  The initialization state is well defined
@@ -656,6 +675,8 @@ struct _create_particular_data {
  */
 struct instance_globals {
 
+    struct _cmd_queue *command_queue;
+
     /* apply.c */
     int jumping_is_magic; /* current jump result of magic */
     int polearm_range_min;
index 66bcccaaa90ef58fa86c25baf2e28907e9d5fbb1..92ce774e8285e677e4137a12aba580e3c8bcd6e7 100644 (file)
@@ -199,12 +199,17 @@ extern char randomkey(void);
 extern void random_response(char *, int);
 extern int rnd_extcmd_idx(void);
 extern int domonability(void);
+extern const struct ext_func_tab *ext_func_tab_from_func(int(*)(void));
 extern char cmd_from_func(int(*)(void));
 extern const char *cmdname_from_func(int(*)(void), char *, boolean);
 extern boolean redraw_cmd(char);
 extern const char *levltyp_to_name(int);
 extern void reset_occupations(void);
 extern void set_occupation(int(*)(void), const char *, int);
+extern void cmdq_add_ec(int(*)(void));
+extern void cmdq_add_key(char);
+extern struct _cmd_queue *cmdq_pop(void);
+extern void cmdq_clear(void);
 extern char pgetchar(void);
 extern void pushch(char);
 extern void savech(char);
index 98c4bc0d144241eb87971cdf15b6f4d4bab427ce..1e75ffe7ed69d8fca0ec6a3612d0747fd4cd9931 100644 (file)
@@ -293,6 +293,7 @@ struct instance_flags {
 #endif
     boolean clicklook;       /* allow right-clicking for look */
     boolean cmdassist;       /* provide detailed assistance for some comnds */
+    boolean fireassist;      /* autowield launcher when using fire-command */
     boolean time_botl;       /* context.botl for 'time' (moves) only */
     boolean wizweight;       /* display weight of everything in wizard mode */
     boolean wizmgender;      /* test gender info from core in window port */
index 416eb63d21a11fc325e15b68ce1a3a28af44c450..225ab5d65e425c46612da693d4c86c531b9ffe92 100644 (file)
@@ -170,6 +170,8 @@ opt_##a,
                 &iflags.extmenu)
     NHOPTB(female, 0, opt_in, set_in_config, Off, Yes, No, No, "male",
                 &flags.female)
+    NHOPTB(fireassist, 0, opt_out, set_in_game, On, Yes, No, No, NoAlias,
+                &iflags.fireassist)
     NHOPTB(fixinv, 0, opt_out, set_in_game, On, Yes, No, No, NoAlias,
                 &flags.invlet_constant)
     NHOPTC(font_map, 40, opt_in, set_gameview, Yes, Yes, Yes, No, NoAlias,
index a3af3976ab1bdae1acb4c0ba8d0a778d06d79171..d1fd65351d141c6ce66f8ddc1618354d36abb72c 100644 (file)
@@ -568,6 +568,7 @@ stop_occupation(void)
     } else if (g.multi >= 0) {
         nomul(0);
     }
+    cmdq_clear();
 }
 
 void
index 4f2a8c0d0ee85fc536072cebc660668d25d03a89..00277ccf96a63d658378629a4da215d36522c6b1 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -228,6 +228,76 @@ set_occupation(int (*fn)(void), const char *txt, int xtime)
     return;
 }
 
+/* add extended command function to the command queue */
+void
+cmdq_add_ec(int (*fn)(void))
+{
+    struct _cmd_queue *tmp = (struct _cmd_queue *)alloc(sizeof(struct _cmd_queue));
+    struct _cmd_queue *cq = g.command_queue;
+
+    tmp->typ = CMDQ_EXTCMD;
+    tmp->ec_entry = ext_func_tab_from_func(fn);
+    tmp->next = NULL;
+
+    while (cq && cq->next)
+        cq = cq->next;
+
+    if (cq)
+        cq->next = tmp;
+    else
+        g.command_queue = tmp;
+}
+
+/* add a key to the command queue */
+void
+cmdq_add_key(char key)
+{
+    struct _cmd_queue *tmp = (struct _cmd_queue *)alloc(sizeof(struct _cmd_queue));
+    struct _cmd_queue *cq = g.command_queue;
+
+    tmp->typ = CMDQ_KEY;
+    tmp->key = key;
+    tmp->next = NULL;
+
+    while (cq && cq->next)
+        cq = cq->next;
+
+    if (cq)
+        cq->next = tmp;
+    else
+        g.command_queue = tmp;
+}
+
+/* pop off the topmost command from the command queue.
+ * caller is responsible for freeing the returned _cmd_queue.
+ */
+struct _cmd_queue *
+cmdq_pop(void)
+{
+    struct _cmd_queue *tmp = g.command_queue;
+
+    if (tmp) {
+        g.command_queue = tmp->next;
+        tmp->next = NULL;
+    }
+    return tmp;
+}
+
+/* clear all commands from the command queue */
+void
+cmdq_clear(void)
+{
+    struct _cmd_queue *tmp = g.command_queue;
+    struct _cmd_queue *tmp2;
+
+    while (tmp) {
+        tmp2 = tmp->next;
+        free(tmp);
+        tmp = tmp2;
+    }
+    g.command_queue = NULL;
+}
+
 static char popch(void);
 
 static char
@@ -2482,6 +2552,18 @@ dokeylist(void)
     destroy_nhwindow(datawin);
 }
 
+const struct ext_func_tab *
+ext_func_tab_from_func(int (*fn)(void))
+{
+    const struct ext_func_tab *extcmd;
+
+    for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd)
+        if (extcmd->ef_funct == fn)
+            return extcmd;
+
+    return NULL;
+}
+
 char
 cmd_from_func(int (*fn)(void))
 {
@@ -3350,13 +3432,30 @@ rhack(char *cmd)
     int spkey;
     boolean prefix_seen, bad_command,
         firsttime = (cmd == 0);
+    struct _cmd_queue *cmdq = NULL;
+    const struct ext_func_tab *cmdq_ec = NULL;
 
     iflags.menu_requested = FALSE;
 #ifdef SAFERHANGUP
     if (g.program_state.done_hup)
         end_of_input();
 #endif
-    if (firsttime) {
+    if ((cmdq = cmdq_pop()) != 0) {
+        /* doing queued commands */
+        if (cmdq->typ == CMDQ_KEY) {
+            static char commandline[2];
+
+            if (!cmd)
+                cmd = commandline;
+            cmd[0] = cmdq->key;
+            cmd[1] = '\0';
+        } else if (cmdq->typ == CMDQ_EXTCMD) {
+            cmdq_ec = cmdq->ec_entry;
+        }
+        free(cmdq);
+        if (cmdq_ec)
+            goto do_cmdq_extcmd;
+    } else if (firsttime) {
         g.context.nopick = 0;
         cmd = parse();
     }
@@ -3539,14 +3638,22 @@ rhack(char *cmd)
         register const struct ext_func_tab *tlist;
         int res, (*func)(void);
 
+do_cmdq_extcmd:
+        if (cmdq_ec)
+            tlist = cmdq_ec;
+        else
+            tlist = g.Cmd.commands[*cmd & 0xff];
+
         /* current - use *cmd to directly index cmdlist array */
-        if ((tlist = g.Cmd.commands[*cmd & 0xff]) != 0) {
+        if (tlist != 0) {
             if (!wizard && (tlist->flags & WIZMODECMD)) {
                 You_cant("do that!");
                 res = 0;
+                cmdq_clear();
             } else if (u.uburied && !(tlist->flags & IFBURIED)) {
                 You_cant("do that while you are buried!");
                 res = 0;
+                cmdq_clear();
             } else {
                 /* we discard 'const' because some compilers seem to have
                    trouble with the pointer passed to set_occupation() */
@@ -3575,6 +3682,7 @@ rhack(char *cmd)
 
         if (!prefix_seen || !help_dir(c1, spkey, "Invalid direction key!"))
             Norep("Unknown command '%s'.", expcmd);
+        cmdq_clear();
     }
     /* didn't move */
     g.context.move = FALSE;
index e8a8b818ece56409c0e8d4b206f43b4a24aec9c7..696011e9dfcdb400cbadf4826477ad3dfe57c415 100644 (file)
@@ -207,6 +207,9 @@ const struct Race urace_init_data = {
 };
 
 const struct instance_globals g_init = {
+
+    NULL, /* command_queue */
+
     /* apply.c */
     0,  /* jumping_is_magic */
     -1, /* polearm_range_min */
index 2695b3bf57bed0a160e1b6b4538d9353361592c5..d80c67220a93dc54cb5ec82afdf4bf6109b1d9f8 100644 (file)
@@ -2228,11 +2228,13 @@ glibr(void)
             otmp = uleft;
             Ring_off(uleft);
             dropx(otmp);
+            cmdq_clear();
         }
         if (rightfall) {
             otmp = uright;
             Ring_off(uright);
             dropx(otmp);
+            cmdq_clear();
         }
     }
 
@@ -2253,6 +2255,7 @@ glibr(void)
         xfl++;
         wastwoweap = TRUE;
         setuswapwep((struct obj *) 0); /* clears u.twoweap */
+        cmdq_clear();
         if (canletgo(otmp, ""))
             dropx(otmp);
     }
@@ -2288,6 +2291,7 @@ glibr(void)
         /* xfl++; */
         otmp->quan = savequan;
         setuwep((struct obj *) 0);
+        cmdq_clear();
         if (canletgo(otmp, ""))
             dropx(otmp);
     }
index 681815ab85f0c91300979266c0653cab878db102..e91fa8582b0c206f34c6008b0af397b1ae84c096 100644 (file)
@@ -11,6 +11,7 @@ static int throw_obj(struct obj *, int);
 static boolean ok_to_throw(int *);
 static int throw_ok(struct obj *);
 static void autoquiver(void);
+static struct obj *find_launcher(struct obj *);
 static int gem_accept(struct monst *, struct obj *);
 static void tmiss(struct obj *, struct monst *, boolean);
 static int throw_gold(struct obj *);
@@ -399,6 +400,23 @@ autoquiver(void)
     return;
 }
 
+/* look through hero inventory for launcher matching ammo,
+   avoiding known cursed items. Returns NULL if no match. */
+static struct obj *
+find_launcher(struct obj *ammo)
+{
+    struct obj *otmp;
+
+    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;
+}
+
 /* f command -- fire: throw from the quiver */
 int
 dofire(void)
@@ -454,6 +472,28 @@ dofire(void)
         }
     }
 
+    if (uquiver && iflags.fireassist) {
+        struct obj *olauncher;
+
+        /* Try to find a launcher */
+        if (ammo_and_launcher(uquiver, uwep)) {
+            /* Do nothing, already wielding a launcher */
+        } else if (ammo_and_launcher(uquiver, uswapwep)) {
+            /* swap weapons and retry fire */
+            cmdq_add_ec(doswapweapon);
+            cmdq_add_ec(dofire);
+            return 0;
+        } else if ((olauncher = find_launcher(obj)) != 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 0;
+        }
+    }
+
     return obj ? throw_obj(obj, shotlimit) : 0;
 }
 
index 8c9bd61489e625fa06cfe97107898630d0105528..68ecccc29b3ed6074f6b475823ea9d01206e1fc6 100644 (file)
@@ -3028,6 +3028,7 @@ nomul(int nval)
     if (nval == 0)
         g.multi_reason = NULL, g.multireasonbuf[0] = '\0';
     end_running(TRUE);
+    cmdq_clear();
 }
 
 /* called when a non-movement, multi-turn action has completed */
index 53567c069ccbe99a4ea8b9175059add78a7f0cde..5e6bcaece59e1815d92559523979562b3e7dfcfa 100644 (file)
@@ -1488,6 +1488,31 @@ getobj(const char *word,
     boolean oneloop = FALSE;
     Loot *sortedinvent, *srtinv;
 
+    struct _cmd_queue *cmdq = cmdq_pop();
+
+    if (cmdq) {
+        /* it's not a key, abort */
+        if (cmdq->typ != CMDQ_KEY) {
+            free(cmdq);
+            return (struct obj *)0;
+        }
+
+        for (otmp = g.invent; otmp; otmp = otmp->nobj)
+            if (otmp->invlet == cmdq->key) {
+                int v = (*obj_ok)(otmp);
+
+                if (v == GETOBJ_SUGGEST || v == GETOBJ_DOWNPLAY) {
+                    free(cmdq);
+                    return otmp;
+                }
+            }
+
+        /* did not find the object, abort */
+        free(cmdq);
+        cmdq_clear();
+        return (struct obj *)0;
+    }
+
     /* is "hands"/"self" a valid thing to do this action on? */
     switch ((*obj_ok)((struct obj *) 0)) {
     case GETOBJ_SUGGEST: /* treat as likely candidate */
index 44bae5eac2e4b91e6f10d5def081c3542db30782..4a9e3551cadc194ead0a53ac04c9367e11d72f15 100644 (file)
@@ -320,6 +320,7 @@ dowield(void)
     g.multi = 0;
     if (cantwield(g.youmonst.data)) {
         pline("Don't be ridiculous!");
+        cmdq_clear();
         return 0;
     }
 
@@ -327,12 +328,14 @@ dowield(void)
     clear_splitobjs();
     if (!(wep = getobj("wield", wield_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT))) {
         /* Cancelled */
+        cmdq_clear();
         return 0;
     } else if (wep == uwep) {
  already_wielded:
         You("are already wielding that!");
         if (is_weptool(wep) || is_wet_towel(wep))
             g.unweapon = FALSE; /* [see setuwep()] */
+        cmdq_clear();
         return 0;
     } else if (welded(uwep)) {
         weldmsg(uwep);
@@ -341,6 +344,7 @@ dowield(void)
         /* if player chose a partial stack but can't wield it, undo split */
         if (wep->o_id && wep->o_id == g.context.objsplit.child_oid)
             unsplitobj(wep);
+        cmdq_clear();
         return 0;
     } else if (wep->o_id && wep->o_id == g.context.objsplit.child_oid) {
         /* if wep is the result of supplying a count to getobj()
@@ -396,6 +400,7 @@ dowield(void)
         setuqwep((struct obj *) 0);
     } else if (wep->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE)) {
         You("cannot wield that!");
+        cmdq_clear();
         return 0;
     }
 
@@ -428,10 +433,12 @@ doswapweapon(void)
     g.multi = 0;
     if (cantwield(g.youmonst.data)) {
         pline("Don't be ridiculous!");
+        cmdq_clear();
         return 0;
     }
     if (welded(uwep)) {
         weldmsg(uwep);
+        cmdq_clear();
         return 0;
     }