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
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 ""
.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.
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
.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.
.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.
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.
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.\\
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}'.
%.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).
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
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.
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
#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
*/
struct instance_globals {
+ struct _cmd_queue *command_queue;
+
/* apply.c */
int jumping_is_magic; /* current jump result of magic */
int polearm_range_min;
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);
#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 */
&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,
} else if (g.multi >= 0) {
nomul(0);
}
+ cmdq_clear();
}
void
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
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))
{
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();
}
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() */
if (!prefix_seen || !help_dir(c1, spkey, "Invalid direction key!"))
Norep("Unknown command '%s'.", expcmd);
+ cmdq_clear();
}
/* didn't move */
g.context.move = FALSE;
};
const struct instance_globals g_init = {
+
+ NULL, /* command_queue */
+
/* apply.c */
0, /* jumping_is_magic */
-1, /* polearm_range_min */
otmp = uleft;
Ring_off(uleft);
dropx(otmp);
+ cmdq_clear();
}
if (rightfall) {
otmp = uright;
Ring_off(uright);
dropx(otmp);
+ cmdq_clear();
}
}
xfl++;
wastwoweap = TRUE;
setuswapwep((struct obj *) 0); /* clears u.twoweap */
+ cmdq_clear();
if (canletgo(otmp, ""))
dropx(otmp);
}
/* xfl++; */
otmp->quan = savequan;
setuwep((struct obj *) 0);
+ cmdq_clear();
if (canletgo(otmp, ""))
dropx(otmp);
}
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 *);
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)
}
}
+ 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;
}
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 */
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 */
g.multi = 0;
if (cantwield(g.youmonst.data)) {
pline("Don't be ridiculous!");
+ cmdq_clear();
return 0;
}
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);
/* 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()
setuqwep((struct obj *) 0);
} else if (wep->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE)) {
You("cannot wield that!");
+ cmdq_clear();
return 0;
}
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;
}