1 #ifdef RCSID
2 static char RCSid[] =
3 "$Header: d:/cvsroot/tads/TADS2/EXECMD.C,v 1.5 1999/07/11 00:46:29 MJRoberts Exp $";
4 #endif
5 
6 /*
7  *   Copyright (c) 1987, 1990 by Michael J. Roberts.  All Rights Reserved.
8  *
9  *   Please see the accompanying license file, LICENSE.TXT, for information
10  *   on using and copying this software.
11  */
12 /*
13 Name
14   execmd     - TADS Interpreter Execute user Command
15 Function
16   Executes a user command after it has been parsed
17 Notes
18   TADS 2.0 version
19 
20   This module contains the implementation of the entire "turn" sequence,
21   which is:
22 
23     preCommand(actor, verb, dobj-list, prep, iobj)
24     verb.verbAction(actor, do, prep, io)
25     actor.actorAction( verb, do, prep, io )
26     actor.location.roomAction( actor, verb, do, prep, io )
27     if ( io )
28     {
29       io.iobjCheck(actor, verb, dobj, prep)
30       if (io does not define verIo<Verb> directly)
31           io.iobjGen(actor, verb, dobj, prep)
32       do.dobjCheck(actor, verb, iobj, prep)
33       if (do does not define do<Verb> directly)
34           do.dobjGen(actor, verb, iobj, prep)
35       io.verIo<Verb>( actor, do )
36       if ( noOutput )
37       {
38         do.verDo<Verb>( actor, io )
39         if ( noOutput ) io.io<Verb>( actor, do )
40       }
41     }
42     else if ( do )
43     {
44       do.dobjCheck(actor, verb, nil, nil)
45       if (do does not define do<Verb> directly)
46           do.dobjGen(actor, verb, nil, nil)
47       do.verDo<Verb>( actor )
48       if ( noOutput )do.do<Verb>( actor )
49     }
50     else
51     {
52       verb.action( actor )
53     }
54     postAction(actor, verb, dobj, prep, iobj, error_code)
55     daemons
56     fuses
57     endCommand(actor, verb, dobj-list, prep, iobj, error_code)
58 
59   If an 'exit' or 'exitobj' is encountered, we skip straight to the
60   daemons.  If an abort is encountered, we skip to endCommand.  If
61   askio, or askdo is encountered, we skip everything remaining.  Under
62   any of these exit scenarios, we return success to our caller.
63 
64   This module also contains code to set and remove fuses and daemons,
65   since they are part of the player turn sequence.
66 Returns
67   0 for success, other for failure.
68 Modified
69   03/25/92 MJRoberts     - TADS 2.0
70   08/13/91 MJRoberts     - add him/her support
71   11/30/90 MJRoberts     - moved main execmd loop here from vocab, moved
72                            fuses/daemon stuff to fuses.c
73   04/23/90 MJRoberts     - clear alarms (notify's) in clrdaemons()
74   07/07/89 MJRoberts     - add fuse/daemon context value
75   06/28/89 MJRoberts     - default message if objects don't handle the verb
76   11/06/88 MJRoberts     - provide error messages in setfuse, setdaemon, etc.
77   11/06/88 MJRoberts     - be careful not to send doX message on ask?o
78   11/05/88 MJRoberts     - save tpldef with "again"
79   10/30/88 MJRoberts     - new "version 6" parser interface
80   12/28/87 MJRoberts     - created
81 */
82 
83 #include <stdio.h>
84 #include <ctype.h>
85 #include <string.h>
86 #include <stdlib.h>
87 
88 #include "os.h"
89 #include "err.h"
90 #include "voc.h"
91 #include "tio.h"
92 #include "mch.h"
93 #include "mcm.h"
94 #include "obj.h"
95 #include "prp.h"
96 #include "run.h"
97 #include "lst.h"
98 #include "bif.h"
99 
100 /* allocate and initialize a fuse/daemon/notifier array */
vocinialo(voccxdef * ctx,vocddef ** what,int cnt)101 void vocinialo(voccxdef *ctx, vocddef **what, int cnt)
102 {
103     vocddef *p;
104 
105     *what = (vocddef *)mchalo(ctx->voccxerr,
106                               (ushort)(cnt * sizeof(vocddef)), "vocinialo");
107 
108     /* set all object/function entries to MCMONINV to indicate not-in-use */
109     for (p = *what ; cnt ; ++p, --cnt)
110         p->vocdfn = MCMONINV;
111 }
112 
113 /* internal service routine to clear one set of fuses/deamons/alerters */
vocdmn1clr(vocddef * dmn,uint cnt)114 static void vocdmn1clr(vocddef *dmn, uint cnt)
115 {
116     for ( ; cnt ; --cnt, ++dmn) dmn->vocdfn = MCMONINV;
117 }
118 
119 /* delete all fuses/daemons/alerters */
vocdmnclr(voccxdef * ctx)120 void vocdmnclr(voccxdef *ctx)
121 {
122     vocdmn1clr(ctx->voccxfus, ctx->voccxfuc);
123     vocdmn1clr(ctx->voccxdmn, ctx->voccxdmc);
124     vocdmn1clr(ctx->voccxalm, ctx->voccxalc);
125 }
126 
127 /* save undo information for a daemon/fuse/notifier */
vocdusav(voccxdef * ctx,vocddef * what)128 static void vocdusav(voccxdef *ctx, vocddef *what)
129 {
130     uchar     *p;
131     objucxdef *uc = ctx->voccxundo;
132     ushort     siz = sizeof(what) + sizeof(*what) + 1;
133 
134     /* if we don't need to save undo, quit now */
135     if (uc == 0 || !objuok(uc))
136         return;
137 
138     /* reserve space for our record */
139     p = objures(uc, OBJUCLI, siz);
140 
141     /* set up our undo record */
142     *p = VOC_UNDO_DAEMON;
143     memcpy(p + 1, &what, (size_t)sizeof(what));
144     memcpy(p + 1 + sizeof(what), what, (size_t)sizeof(*what));
145 
146     uc->objucxhead += siz;
147 }
148 
149 /* apply undo information for a daemon/fuse/notifier */
vocdundo(void * ctx0,uchar * data)150 void vocdundo(void *ctx0, uchar *data)
151 {
152     voccxdef *ctx = (voccxdef *)ctx0;
153     vocddef  *daemon;
154     objnum    objn;
155     ushort    siz;
156     ushort    wrdsiz;
157     uchar    *p;
158     int       sccnt;
159     objnum    sc;
160     int       len1, len2;
161     prpnum    prp;
162     int       flags;
163     uchar    *wrd;
164 
165     switch(*data)
166     {
167     case VOC_UNDO_DAEMON:
168         memcpy(&daemon, data + 1, (size_t)sizeof(daemon));
169         memcpy(daemon, data + 1 + sizeof(daemon), (size_t)sizeof(*daemon));
170         break;
171 
172     case VOC_UNDO_NEWOBJ:
173         /* get the object number */
174         objn = osrp2(data + 1);
175 
176         /* delete the object's inheritance and vocabulary records */
177         vocdel(ctx, objn);
178         vocidel(ctx, objn);
179 
180         /* delete the object */
181         mcmfre(ctx->voccxmem, (mcmon)objn);
182         break;
183 
184     case VOC_UNDO_DELOBJ:
185         /* get the object's number and size */
186         objn = osrp2(data + 1);
187         siz = osrp2(data + 3);
188         wrdsiz = osrp2(data + 5);
189 
190         /* allocate the object with its original number */
191         p = mcmalonum(ctx->voccxmem, siz, (mcmon)objn);
192 
193         /* copy the contents back to the object */
194         memcpy(p, data + 7, (size_t)siz);
195 
196         /* get its superclass if it has one */
197         sccnt = objnsc(p);
198         if (sccnt) sc = osrp2(objsc(p));
199 
200         /* unlock the object, and create its inheritance records */
201         mcmunlck(ctx->voccxmem, (mcmon)objn);
202         vociadd(ctx, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
203 
204         /* restore the words as well */
205         data += 7 + siz;
206         while (wrdsiz)
207         {
208             /* get the lengths from the buffer */
209             len1 = osrp2(data + 2);
210             len2 = osrp2(data + 4);
211 
212             /* add the word */
213             vocadd2(ctx, data[0], objn, data[1], data+6, len1,
214                     data+6+len1, len2);
215 
216             /* remove this object from the word size */
217             wrdsiz -= 6 + len1 + len2;
218             data += 6 + len1 + len2;
219         }
220         break;
221 
222     case VOC_UNDO_ADDVOC:
223     case VOC_UNDO_DELVOC:
224         flags = *(data + 1);
225         prp = *(data + 2);
226         objn = osrp2(data + 3);
227         wrd = data + 5;
228         if (*data == VOC_UNDO_ADDVOC)
229             vocdel1(ctx, objn, (char *)wrd, prp, FALSE, FALSE, FALSE);
230         else
231             vocadd(ctx, prp, objn, flags, (char *)wrd);
232         break;
233 
234     case VOC_UNDO_SETME:
235         ctx->voccxme = osrp2(data + 1);
236         break;
237     }
238 }
239 
240 /* determine size of one of our client undo records */
vocdusz(void * ctx0,uchar * data)241 ushort OS_LOADDS vocdusz(void *ctx0, uchar *data)
242 {
243     VARUSED(ctx0);
244 
245     switch(*data)
246     {
247     case VOC_UNDO_DAEMON:
248         /* it's the size of the structures, plus one for the header */
249         return (ushort)((sizeof(vocddef *) + sizeof(vocddef)) + 1);
250 
251     case VOC_UNDO_NEWOBJ:
252         /* 2 bytes for the objnum plus 1 for the header */
253         return 2 + 1;
254 
255     case VOC_UNDO_DELOBJ:
256         /*
257          *   1 (header) + 2 (objnum) + 2 (size) + 2 (word size) + object
258          *   data size + word size
259          */
260         return osrp2(data+3) + osrp2(data+5) + 1+2+2+2;
261 
262     case VOC_UNDO_ADDVOC:
263     case VOC_UNDO_DELVOC:
264         /* 1 (header) + 2 (objnum) + 1 (flags) + 1 (type) + word size */
265         return osrp2(data + 5) + 5;
266 
267     default:
268         return 0;
269     }
270 }
271 
272 /* save undo for object creation */
vocdusave_newobj(voccxdef * ctx,objnum objn)273 void vocdusave_newobj(voccxdef *ctx, objnum objn)
274 {
275     objucxdef *uc = ctx->voccxundo;
276     uchar     *p;
277 
278     p = objures(uc, OBJUCLI, 3);
279     *p = VOC_UNDO_NEWOBJ;
280     oswp2(p+1, objn);
281 
282     uc->objucxhead += 3;
283 }
284 
285 /* save undo information for a change in the "Me" object */
vocdusave_me(voccxdef * ctx,objnum old_me)286 void vocdusave_me(voccxdef *ctx, objnum old_me)
287 {
288     uchar     *p;
289     objucxdef *uc = ctx->voccxundo;
290 
291     /* if we don't need to save undo, there's nothing to do */
292     if (uc == 0 || !objuok(uc))
293         return;
294 
295     /* reserve space for our record */
296     p = objures(uc, OBJUCLI, 3);
297     *p = VOC_UNDO_SETME;
298     oswp2(p+1, old_me);
299 
300     /* absorb the space */
301     uc->objucxhead += 3;
302 }
303 
304 /* callback context structure */
305 struct delobj_cb_ctx
306 {
307     uchar *p;
308 };
309 
310 /*
311  *   Iteration callback to write vocabulary words for an object being
312  *   deleted to an undo stream, so that they can be restored if the
313  *   deletion is undone.
314  */
delobj_cb(void * ctx0,vocdef * voc,vocwdef * vocw)315 static void delobj_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
316 {
317     struct delobj_cb_ctx *ctx = (struct delobj_cb_ctx *)ctx0;
318     uchar *p = ctx->p;
319 
320     /* write this object's header to the stream */
321     p[0] = vocw->vocwtyp;
322     p[1] = vocw->vocwflg;
323     oswp2(p+2, voc->voclen);
324     oswp2(p+4, voc->vocln2);
325 
326     /* write the words as well */
327     memcpy(p+6, voc->voctxt, (size_t)(voc->voclen + voc->vocln2));
328 
329     /* advance the pointer */
330     ctx->p += 6 + voc->voclen + voc->vocln2;
331 }
332 
333 /* save undo for object deletion */
vocdusave_delobj(voccxdef * ctx,objnum objn)334 void vocdusave_delobj(voccxdef *ctx, objnum objn)
335 {
336     objucxdef *uc = ctx->voccxundo;
337     uchar     *p;
338     uchar     *objp;
339     uint       siz;
340     int        wrdsiz;
341     int        wrdcnt;
342     struct delobj_cb_ctx fnctx;
343 
344     /* figure out how much we need to save */
345     objp = mcmlck(ctx->voccxmem, (mcmon)objn);
346     siz = objfree(objp);
347 
348     /* figure the word size */
349     voc_count(ctx, objn, 0, &wrdcnt, &wrdsiz);
350 
351     /*
352      *   we need to store an additional 6 bytes (2-length1, 2-length2,
353      *   1-type, 1-flags) for each word
354      */
355     wrdsiz += wrdcnt*6;
356 
357     /* set up the undo header */
358     p = objures(uc, OBJUCLI, (ushort)(7 + siz + wrdsiz));
359     *p = VOC_UNDO_DELOBJ;
360     oswp2(p+1, objn);
361     oswp2(p+3, siz);
362     oswp2(p+5, wrdsiz);
363 
364     /* save the object's data */
365     memcpy(p+7, objp, (size_t)siz);
366 
367     /* write the words */
368     fnctx.p = p+7 + siz;
369     voc_iterate(ctx, objn, delobj_cb, &fnctx);
370 
371     /* unlock the object and advance the undo pointer */
372     mcmunlck(ctx->voccxmem, (mcmon)objn);
373     uc->objucxhead += 7 + siz + wrdsiz;
374 }
375 
376 /* save undo for word creation */
vocdusave_addwrd(voccxdef * ctx,objnum objn,prpnum typ,int flags,char * wrd)377 void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
378                       char *wrd)
379 {
380     ushort     wrdsiz;
381     uchar     *p;
382     objucxdef *uc = ctx->voccxundo;
383 
384     /* figure out how much space we need, and reserve it */
385     wrdsiz = osrp2(wrd);
386     p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz));
387 
388     *p = VOC_UNDO_ADDVOC;
389     *(p+1) = flags;
390     *(p+2) = (uchar)typ;
391     oswp2(p+3, objn);
392     memcpy(p+5, wrd, (size_t)wrdsiz);
393 
394     uc->objucxhead += 5 + wrdsiz;
395 }
396 
397 /* save undo for word deletion */
vocdusave_delwrd(voccxdef * ctx,objnum objn,prpnum typ,int flags,char * wrd)398 void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
399                       char *wrd)
400 {
401     ushort     wrdsiz;
402     uchar     *p;
403     objucxdef *uc = ctx->voccxundo;
404 
405     /* figure out how much space we need, and reserve it */
406     wrdsiz = osrp2(wrd);
407     p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz));
408 
409     *p = VOC_UNDO_DELVOC;
410     *(p+1) = flags;
411     *(p+2) = (uchar)typ;
412     oswp2(p+3, objn);
413     memcpy(p+5, wrd, (size_t)wrdsiz);
414 
415     uc->objucxhead += 5 + wrdsiz;
416 }
417 
418 
419 
420 /* set a fuse/daemon/notifier */
vocsetfd(voccxdef * ctx,vocddef * what,objnum func,prpnum prop,uint tm,runsdef * val,int err)421 void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
422               uint tm, runsdef *val, int err)
423 {
424     int      slots;
425 
426     if (what == ctx->voccxdmn)
427         slots = ctx->voccxdmc;
428     else if (what == ctx->voccxalm)
429         slots = ctx->voccxalc;
430     else if (what == ctx->voccxfus)
431         slots = ctx->voccxfuc;
432     else
433         errsig(ctx->voccxerr, ERR_BADSETF);
434 
435     /* find a free slot, and set up our fuse/daemon */
436     for ( ; slots ; ++what, --slots)
437     {
438         if (what->vocdfn == MCMONINV)
439         {
440             /* save an undo record for this slot before changing */
441             vocdusav(ctx, what);
442 
443             /* record the information */
444             what->vocdfn = func;
445             if (val != 0)
446                 OSCPYSTRUCT(what->vocdarg, *val);
447             what->vocdprp = prop;
448             what->vocdtim = tm;
449 
450             /*
451              *   the fuse/notifier/daemon is set - no need to look further
452              *   for an open slot
453              */
454             return;
455         }
456     }
457 
458     /* we didn't find an open slot - signal the appropriate error */
459     errsig(ctx->voccxerr, err);
460 }
461 
462 /* remove a fuse/daemon/notifier */
vocremfd(voccxdef * ctx,vocddef * what,objnum func,prpnum prop,runsdef * val,int err)463 void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
464               runsdef *val, int err)
465 {
466     int      slots;
467 
468     if (what == ctx->voccxdmn) slots = ctx->voccxdmc;
469     else if (what == ctx->voccxalm) slots = ctx->voccxalc;
470     else if (what == ctx->voccxfus) slots = ctx->voccxfuc;
471     else errsig(ctx->voccxerr, ERR_BADREMF);
472 
473     /* find the slot with this same fuse/daemon/notifier, and remove it */
474     for ( ; slots ; ++what, --slots)
475     {
476         if (what->vocdfn == func
477             && what->vocdprp == prop
478             && (!val || (val->runstyp == what->vocdarg.runstyp
479                          && !memcmp(&val->runsv, &what->vocdarg.runsv,
480                                     (size_t)datsiz(val->runstyp,
481                                                    &val->runsv)))))
482         {
483             /* save an undo record for this slot before changing */
484             vocdusav(ctx, what);
485 
486             what->vocdfn = MCMONINV;
487             return;
488         }
489     }
490 
491 /*    errsig(ctx->voccxerr, err); <<<harmless - don't signal it>>> */
492 }
493 
494 /*
495  *   Count one or more turns - burn all fuses down by the given number of
496  *   turns.  Execute any fuses that expire within the given interval, but
497  *   not any that expire at the end of the last turn counted here.  (If
498  *   incrementing by one turn only, no fuses will be executed.)  If the
499  *   do_fuses flag is false, fuses are simply deleted if they burn down
500  *   within the interval.
501  */
vocturn(voccxdef * ctx,int turncnt,int do_fuses)502 void vocturn(voccxdef *ctx, int turncnt, int do_fuses)
503 {
504     vocddef *p;
505     int      i;
506     int      do_exe;
507 
508     while (turncnt--)
509     {
510         /* presume we won't find anything to execute */
511         do_exe = FALSE;
512 
513         /* go through notifiers, looking for fuse-type notifiers */
514         for (i = ctx->voccxalc, p = ctx->voccxalm ; i ; ++p, --i)
515         {
516             if (p->vocdfn != MCMONINV
517                 && p->vocdtim != VOCDTIM_EACH_TURN
518                 && p->vocdtim != 0)
519             {
520                 /* save an undo record for this slot before changing */
521                 vocdusav(ctx, p);
522 
523                 if (--(p->vocdtim) == 0)
524                     do_exe = TRUE;
525             }
526         }
527 
528         /* now go through the fuses */
529         for (i = ctx->voccxfuc, p = ctx->voccxfus ; i ; ++p, --i)
530         {
531             if (p->vocdfn != MCMONINV && p->vocdtim != 0)
532             {
533                 /* save an undo record for this slot before changing */
534                 vocdusav(ctx, p);
535 
536                 if (--(p->vocdtim) == 0)
537                     do_exe = TRUE;
538             }
539         }
540 
541         /*
542          *   if we'll be doing more, and anything burned down, run
543          *   current fuses before going on to the next turn
544          */
545         if ((!do_fuses || turncnt) && do_exe)
546             exefuse(ctx, do_fuses);
547     }
548 }
549 
550 /*
551  *   display a default error message for a verb/dobj/iobj combo.
552  *   The message is "I don't know how to <verb.sdesc> <dobj.thedesc>" if
553  *   the dobj is present, and "I don't know how to <verb.sdesc> anything
554  *   <prep.sdesc> <iobj.thedesc>" if the iobj is present.  Such a message
555  *   is displayed when the objects in the command don't handle the verb
556  *   (i.e., don't have any methods for verification of the verb:  they
557  *   lack verDo<verb> or verIo<verb>).
558  */
exeperr(voccxdef * ctx,objnum verb,objnum dobj,objnum prep,objnum iobj)559 static void exeperr(voccxdef *ctx, objnum verb, objnum dobj,
560                     objnum prep, objnum iobj)
561 {
562     if (ctx->voccxper2 != MCMONINV)
563     {
564         runpobj(ctx->voccxrun, iobj);
565         runpobj(ctx->voccxrun, prep);
566         runpobj(ctx->voccxrun, dobj);
567         runpobj(ctx->voccxrun, verb);
568         runfn(ctx->voccxrun, ctx->voccxper2, 4);
569         return;
570     }
571 
572     vocerr(ctx, VOCERR(110), "I don't know how to ");
573     runppr(ctx->voccxrun, verb, PRP_SDESC, 0);
574 
575     if (dobj != MCMONINV)
576     {
577         vocerr(ctx, VOCERR(111), " ");
578         runppr(ctx->voccxrun, dobj, PRP_THEDESC, 0);
579     }
580     else
581     {
582         vocerr(ctx, VOCERR(112), " anything ");
583         if (prep != MCMONINV)
584             runppr(ctx->voccxrun, prep, PRP_SDESC, 0);
585         else
586             vocerr(ctx, VOCERR(113), "to");
587         vocerr(ctx, VOCERR(114), " ");
588         runppr(ctx->voccxrun, iobj, PRP_THEDESC, 0);
589     }
590     vocerr(ctx, VOCERR(115), ".");
591 }
592 
593 
594 /*
595  *   Execute daemons
596  */
exedaem(voccxdef * ctx)597 void exedaem(voccxdef *ctx)
598 {
599     runcxdef *rcx = ctx->voccxrun;
600     vocddef  *daemon;
601     int       i;
602     runsdef   val;
603     int       err;
604 
605     for (i = ctx->voccxdmc, daemon = ctx->voccxdmn ; i ; ++daemon, --i)
606     {
607         if (daemon->vocdfn != MCMONINV)
608         {
609             objnum thisd = daemon->vocdfn;
610 
611             ERRBEGIN(ctx->voccxerr)
612 
613             OSCPYSTRUCT(val, daemon->vocdarg);
614             runpush(rcx, val.runstyp, &val);
615             runfn(rcx, thisd, 1);
616 
617             ERRCATCH(ctx->voccxerr, err)
618                 if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
619                     errrse(ctx->voccxerr);
620             ERREND(ctx->voccxerr)
621         }
622     }
623     for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i)
624     {
625         if (daemon->vocdfn != MCMONINV
626             && daemon->vocdtim == VOCDTIM_EACH_TURN)
627         {
628             ERRBEGIN(ctx->voccxerr)
629 
630             runppr(rcx, daemon->vocdfn, daemon->vocdprp, 0);
631 
632             ERRCATCH(ctx->voccxerr, err)
633                 if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
634                     errrse(ctx->voccxerr);
635             ERREND(ctx->voccxerr)
636         }
637     }
638 }
639 
640 /*
641  *   Execute any pending fuses.  Return TRUE if any fuses were executed,
642  *   FALSE otherwise.
643  */
exefuse(voccxdef * ctx,int do_run)644 int exefuse(voccxdef *ctx, int do_run)
645 {
646     runcxdef *rcx = ctx->voccxrun;
647     vocddef  *daemon;
648     int       i;
649     int       found = FALSE;
650     runsdef   val;
651     int       err;
652 
653     /* first, execute any expired function-based fuses */
654     for (i = ctx->voccxfuc, daemon = ctx->voccxfus ; i ; ++daemon, --i)
655     {
656         if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0)
657         {
658             objnum thisf = daemon->vocdfn;
659 
660             found = TRUE;
661             ERRBEGIN(ctx->voccxerr)
662 
663             /* save an undo record for this slot before changing */
664             vocdusav(ctx, daemon);
665 
666             /* remove the fuse prior to running  */
667             daemon->vocdfn = MCMONINV;
668 
669             if (do_run)
670             {
671                 OSCPYSTRUCT(val, daemon->vocdarg);
672                 runpush(rcx, val.runstyp, &val);
673                 runfn(rcx, thisf, 1);
674             }
675 
676             ERRCATCH(ctx->voccxerr, err)
677                 if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
678                     errrse(ctx->voccxerr);
679             ERREND(ctx->voccxerr)
680         }
681     }
682 
683     /* next, execute any expired method-based notifier fuses */
684     for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i)
685     {
686         if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0)
687         {
688             objnum thisa = daemon->vocdfn;
689 
690             found = TRUE;
691             ERRBEGIN(ctx->voccxerr)
692 
693             /* save an undo record for this slot before changing */
694             vocdusav(ctx, daemon);
695 
696             /* delete it prior to running it */
697             daemon->vocdfn = MCMONINV;
698 
699             if (do_run)
700                 runppr(rcx, thisa, daemon->vocdprp, 0);
701 
702             ERRCATCH(ctx->voccxerr, err)
703                 if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
704                     errrse(ctx->voccxerr);
705             ERREND(ctx->voccxerr)
706         }
707     }
708 
709     /* return true if we found any expired fuses */
710     return found;
711 }
712 
713 /* ------------------------------------------------------------------------ */
714 /*
715  *   Find the action routine template for a verb.  Fills in *tplofs with
716  *   the offset of the template property within the verb object, and fills
717  *   in actofs with the offset of the "action" property within the verb
718  *   object.  Sets *tplofs to zero if there's no template, and sets
719  *   *actofs to zero if there's no action routine.
720  */
exe_get_tpl(voccxdef * ctx,objnum verb,uint * tplofs,uint * actofs)721 static void exe_get_tpl(voccxdef *ctx, objnum verb,
722                         uint *tplofs, uint *actofs)
723 {
724     /* look up the new-style template first */
725     *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL2, (objnum *)0, FALSE);
726 
727     /* if there's no new-style template, look up the old-style template */
728     if (*tplofs == 0)
729         *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL, (objnum *)0, FALSE);
730 
731     /* also look to see if the verb has an Action method */
732     *actofs = objgetap(ctx->voccxmem, verb, PRP_ACTION, (objnum *)0, FALSE);
733 }
734 
735 
736 /* ------------------------------------------------------------------------ */
737 /*
738  *   Execute fuses and daemons.  Returns zero on success, or ERR_ABORT if
739  *   'abort' was thrown during execution.
740  */
exe_fuses_and_daemons(voccxdef * ctx,int err,int do_fuses,objnum actor,objnum verb,vocoldef * dobj_list,int dobj_cnt,objnum prep,objnum iobj)741 int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses,
742                           objnum actor, objnum verb,
743                           vocoldef *dobj_list, int dobj_cnt,
744                           objnum prep, objnum iobj)
745 {
746     int err2;
747 
748     /* presume no error */
749     err2 = 0;
750 
751     /* execute fuses and daemons if desired - trap any errors that occur */
752     if (do_fuses)
753     {
754         ERRBEGIN(ctx->voccxerr)
755         {
756             /* execute daemons */
757             exedaem(ctx);
758 
759             /* execute fuses */
760             (void)exefuse(ctx, TRUE);
761         }
762         ERRCATCH(ctx->voccxerr, err2)
763         {
764             /*
765              *   if 'abort' was invoked, ignore it, since it's now had the
766              *   desired effect of skipping any remaining fuses and
767              *   daemons; resignal any other error
768              */
769             if (err2 != ERR_RUNABRT)
770                 errrse(ctx->voccxerr);
771 
772             /* replace any previous error with the new error code */
773             err = err2;
774         }
775         ERREND(ctx->voccxerr);
776     }
777 
778     /* execute endCommand if it's defined */
779     if (ctx->voccxendcmd != MCMONINV)
780     {
781         /* push the arguments */
782         runpnum(ctx->voccxrun, err);
783         runpobj(ctx->voccxrun, iobj);
784         runpobj(ctx->voccxrun, prep);
785         voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt);
786         runpobj(ctx->voccxrun, verb);
787         runpobj(ctx->voccxrun, actor);
788 
789         /* call endCommand */
790         runfn(ctx->voccxrun, ctx->voccxendcmd, 6);
791     }
792 
793     /* return the error status */
794     return err;
795 }
796 
797 /* ------------------------------------------------------------------------ */
798 /*
799  *   execute iobjGen/dobjGen methods, if appropriate
800  */
exegen(voccxdef * ctx,objnum obj,prpnum genprop,prpnum verprop,prpnum actprop)801 static int exegen(voccxdef *ctx, objnum obj, prpnum genprop,
802                   prpnum verprop, prpnum actprop)
803 {
804     int     hasgen;                                 /* has xobjGen property */
805     objnum  genobj;                         /* object with xobjGen property */
806     int     hasver;                               /* has verXoVerb property */
807     objnum  verobj;                       /* object with verXoVerb property */
808     int     hasact;                                  /* has xoVerb property */
809     objnum  actobj;                          /* object with xoVerb property */
810 
811     /* ignore it if there's no object here */
812     if (obj == MCMONINV) return(FALSE);
813 
814     /* look up the xobjGen property, and ignore if not present */
815     hasgen = objgetap(ctx->voccxmem, obj, genprop, &genobj, FALSE);
816     if (!hasgen) return(FALSE);
817 
818     /* look up the verXoVerb and xoVerb properties */
819     hasver = objgetap(ctx->voccxmem, obj, verprop, &verobj, FALSE);
820     hasact = objgetap(ctx->voccxmem, obj, actprop, &actobj, FALSE);
821 
822     /* ignore if verXoVerb or xoVerb "overrides" xobjGen */
823     if ((hasver && !bifinh(ctx, vocinh(ctx, genobj), verobj))
824         || (hasact && !bifinh(ctx, vocinh(ctx, genobj), actobj)))
825         return FALSE;
826 
827     /* all conditions are met - execute dobjGen */
828     return TRUE;
829 }
830 
831 /* ------------------------------------------------------------------------ */
832 /*
833  *   Save "again" information for a direct or indirect object
834  */
exe_save_again_obj(vocoldef * againv,const vocoldef * objv,char ** bufp)835 static void exe_save_again_obj(vocoldef *againv, const vocoldef *objv,
836                                char **bufp)
837 {
838     /* if there's an object, save it */
839     if (objv != 0)
840     {
841         /* copy the object information structure */
842         memcpy(againv, objv, sizeof(*againv));
843 
844         /* copy the original command words to the "again" buffer */
845         if (objv->vocolfst != 0 && objv->vocollst != 0)
846         {
847             size_t copylen;
848 
849             /*
850              *   Compute the length of the entire list.  The words are
851              *   arranged consecutively in the buffer, separated by null
852              *   bytes, so we must copy everything from the first word to
853              *   the start of the last word, plus the length of the last
854              *   word, plus the last word's trailing null byte.
855              */
856             copylen = objv->vocollst - objv->vocolfst
857                       + strlen(objv->vocollst) + 1;
858 
859             /* copy the text */
860             memcpy(*bufp, objv->vocolfst, copylen);
861 
862             /*
863              *   set the new structure to point into the copy, not the
864              *   original
865              */
866             againv->vocolfst = *bufp;
867             againv->vocollst = *bufp + (objv->vocollst - objv->vocolfst);
868 
869             /* skip past the space we've consumed in the buffer */
870             *bufp += copylen;
871         }
872     }
873     else
874     {
875         /* there's nothing to save - just set the object ID to invalid */
876         againv->vocolobj = MCMONINV;
877     }
878 }
879 
880 /*
881  *   Restore an "again" object previously saved.  Note that we must copy
882  *   the saved data to our 2-element arrays so that we can insert a
883  *   terminating element after each restored element - other code
884  *   occasionally expects these structures to be stored in the standard
885  *   object list array format.  Returns a pointer to the restored object
886  *   list, which is the same as the first argument.
887  */
exe_restore_again_obj(vocoldef again_array[2],const vocoldef * saved_obj)888 static vocoldef *exe_restore_again_obj(vocoldef again_array[2],
889                                        const vocoldef *saved_obj)
890 {
891     /* copy the saved object into the first array element */
892     memcpy(&again_array[0], saved_obj, sizeof(again_array[0]));
893 
894     /* clear the second element to indicate the end of the object list */
895     again_array[1].vocolobj = MCMONINV;
896     again_array[1].vocolflg = 0;
897 
898     /* return a pointer to the first array element */
899     return &again_array[0];
900 }
901 
902 /* ------------------------------------------------------------------------ */
903 /*
904  *   Execute a single command.  'recursive' indicates whether the routine
905  *   is being called for normal command processing or as a recursive call
906  *   from within the game; if this flag is true, we'll bypass certain
907  *   operations that are only appropriate for normal direct player
908  *   commands: we won't remember the command for "again" processing, we
909  *   won't do end-of-turn processing, and we won't reset the system stack
910  *   before each function invocation.
911  */
exe1cmd(voccxdef * ctx,objnum actor,objnum verb,vocoldef * dobjv,objnum * prepptr,vocoldef * iobjv,int endturn,uchar * tpl,int newstyle,int recursive,int validate_dobj,int validate_iobj,vocoldef * dobj_list,int cur_dobj_idx,int dobj_cnt,int show_multi_prefix,int multi_flags)912 static int exe1cmd(voccxdef *ctx, objnum actor, objnum verb, vocoldef *dobjv,
913                    objnum *prepptr, vocoldef *iobjv, int endturn, uchar *tpl,
914                    int newstyle, int recursive,
915                    int validate_dobj, int validate_iobj,
916                    vocoldef *dobj_list, int cur_dobj_idx, int dobj_cnt,
917                    int show_multi_prefix, int multi_flags)
918 {
919     objnum    loc;
920     int       err;
921     runcxdef *rcx = ctx->voccxrun;
922     objnum    prep = *prepptr;
923     objnum    dobj = (dobjv != 0 ? dobjv->vocolobj : MCMONINV);
924     objnum    iobj = (iobjv != 0 ? iobjv->vocolobj : MCMONINV);
925     int       tplflags;
926     int       dobj_first;
927     objnum    old_tio_actor;
928     vocoldef *old_ctx_dobj;
929     vocoldef *old_ctx_iobj;
930     objnum    old_verb;
931     objnum    old_actor;
932     objnum    old_prep;
933     int       do_fuses;
934     int       do_postact;
935     vocoldef  again_dobj[2];
936     vocoldef  again_iobj[2];
937 
938     /* presume no error will occur */
939     err = 0;
940 
941     /*
942      *   Presume we'll run fuses and daemons if this is the end of the
943      *   turn.  We only do fuses and daemons once per command, even if the
944      *   command contains multiple objects; 'endturn' will be true only
945      *   when this is the last object of the command.
946      */
947     do_fuses = endturn;
948 
949     /* presume we will call postAction */
950     do_postact = TRUE;
951 
952     /* remember the original tio-level actor setting */
953     old_tio_actor = tiogetactor(ctx->voccxtio);
954 
955     /* remember the original command settings (in case this is recursive) */
956     old_actor = ctx->voccxactor;
957     old_verb = ctx->voccxverb;
958     old_prep = ctx->voccxprep;
959     old_ctx_dobj = ctx->voccxdobj;
960     old_ctx_iobj = ctx->voccxiobj;
961 
962     /* the default actor is Me */
963     if (actor == MCMONINV)
964         actor = ctx->voccxme;
965 
966     /* if command is "again", get information from previous command */
967     if (verb == ctx->voccxvag)
968     {
969         /* it's "again" - repeat the last command */
970         actor = ctx->voccxlsa;
971         verb  = ctx->voccxlsv;
972         dobj  = ctx->voccxlsd.vocolobj;
973         iobj  = ctx->voccxlsi.vocolobj;
974         prep  = ctx->voccxlsp;
975         tpl   = ctx->voccxlst;
976         newstyle = ctx->voccxlssty;
977 
978         /*
979          *   If we have a direct or indirect object, restore the full
980          *   object information structure pointers (in particular, this
981          *   restores the word lists).
982          */
983         if (dobj != MCMONINV)
984             dobjv = exe_restore_again_obj(again_dobj, &ctx->voccxlsd);
985         if (iobj != MCMONINV)
986             iobjv = exe_restore_again_obj(again_iobj, &ctx->voccxlsi);
987 
988         /*
989          *   make sure the command is repeatable: there must have been a
990          *   verb, and the objects specified must still be accessible
991          */
992         if (verb == MCMONINV)
993         {
994             /*
995              *   if the last command was lost due to an object deletion,
996              *   show the message "you can't repeat that command";
997              *   otherwise, show the message "there's no command to
998              *   repeat"
999              */
1000             if ((ctx->voccxflg & VOCCXAGAINDEL) != 0)
1001                 vocerr(ctx, VOCERR(27), "You can't repeat that command.");
1002             else
1003                 vocerr(ctx, VOCERR(26), "There's no command to repeat.");
1004 
1005             /* flush the output and return failure */
1006             tioflush(ctx->voccxtio);
1007             return 0;
1008         }
1009         else if ((dobj != MCMONINV &&
1010                   !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb))
1011                  || (iobj != MCMONINV &&
1012                      !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb))
1013                  || !vocchkaccess(ctx, actor, PRP_VALIDACTOR, 0, actor, verb))
1014         {
1015             vocerr(ctx, VOCERR(27), "You can't repeat that command.");
1016             tioflush(ctx->voccxtio);
1017             return(0);
1018         }
1019     }
1020     else
1021     {
1022         /* verify the direct object if present */
1023         if (validate_dobj
1024             && dobj != MCMONINV
1025             && !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb))
1026         {
1027             /* generate an appropriate message */
1028             if (vocchkvis(ctx, dobj, actor))
1029             {
1030                 /* it's visible but not accessible */
1031                 vocnoreach(ctx, &dobj, 1, actor, verb, prep,
1032                            PRP_DODEFAULT, FALSE, 0, 0, 1);
1033             }
1034             else
1035             {
1036                 /* it's not even visible */
1037                 if (recursive)
1038                     vocerr(ctx, VOCERR(39), "You don't see that here.");
1039                 else
1040                     vocerr(ctx, VOCERR(38),
1041                            "You don't see that here any more.");
1042             }
1043 
1044             /* indicate the error */
1045             return ERR_PRS_VAL_DO_FAIL;
1046         }
1047 
1048         /* verify the indirect object if present */
1049         if (validate_iobj
1050             && iobj != MCMONINV
1051             && !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb))
1052         {
1053             /* generate the error message */
1054             if (vocchkvis(ctx, iobj, actor))
1055             {
1056                 /* it's visible but not accessible */
1057                 vocnoreach(ctx, &iobj, 1, actor, verb, prep,
1058                            PRP_IODEFAULT, FALSE, 0, 0, 1);
1059             }
1060             else
1061             {
1062                 /* it's not even visible */
1063                 if (recursive)
1064                     vocerr(ctx, VOCERR(39), "You don't see that here.");
1065                 else
1066                     vocerr(ctx, VOCERR(38),
1067                            "You don't see that here any more.");
1068             }
1069 
1070             /* indicate the error */
1071             return ERR_PRS_VAL_IO_FAIL;
1072         }
1073 
1074         /*
1075          *   save the command, unless this is a recursive call from the
1076          *   game, so that we can repeat this command if the next is
1077          *   "again"
1078          */
1079         if (!recursive)
1080         {
1081             char *dst;
1082 
1083             /* save the command parameters */
1084             ctx->voccxlsa = actor;
1085             ctx->voccxlsv = verb;
1086             ctx->voccxlsp = prep;
1087             ctx->voccxlssty = newstyle;
1088             if (tpl != 0)
1089                 memcpy(ctx->voccxlst, tpl,
1090                        (size_t)(newstyle ? VOCTPL2SIZ : VOCTPLSIZ));
1091 
1092             /* set up to write into the "again" word buffer */
1093             dst = ctx->voccxagainbuf;
1094 
1095             /* save the direct object information */
1096             exe_save_again_obj(&ctx->voccxlsd, dobjv, &dst);
1097 
1098             /* save the indirect object information */
1099             exe_save_again_obj(&ctx->voccxlsi, iobjv, &dst);
1100 
1101             /*
1102              *   clear the flag indicating that "again" was lost due to
1103              *   object deletion, because we obviously have a valid
1104              *   "again" at this point
1105              */
1106             ctx->voccxflg &= ~VOCCXAGAINDEL;
1107         }
1108     }
1109 
1110     /* remember the flags */
1111     tplflags = (tpl != 0 && newstyle ? voctplflg(tpl) : 0);
1112     dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
1113 
1114     /* set up actor for tio subsystem - format strings need to know */
1115     tiosetactor(ctx->voccxtio, actor);
1116 
1117     /* store current dobj and iobj vocoldef's for later reference */
1118     ctx->voccxdobj = dobjv;
1119     ctx->voccxiobj = iobjv;
1120 
1121     /* store the rest of the current command objects for reference */
1122     ctx->voccxactor = actor;
1123     ctx->voccxverb = verb;
1124     ctx->voccxprep = prep;
1125 
1126     ERRBEGIN(ctx->voccxerr)
1127 
1128     /* reset the run-time context if this is a top-level call */
1129     if (!recursive)
1130         runrst(rcx);
1131 
1132     /*
1133      *   if this is the first object, invoke the game's preCommand
1134      *   function, passing the list of all of the direct objects
1135      */
1136     if (cur_dobj_idx == 0 && ctx->voccxprecmd != MCMONINV)
1137     {
1138         /* push the arguments: actor, verb, dobj-list, prep, iobj */
1139         runpobj(rcx, iobj);
1140         runpobj(rcx, prep);
1141         voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt);
1142         runpobj(rcx, verb);
1143         runpobj(rcx, actor);
1144 
1145         /* catch errors specially for preCommand */
1146         ERRBEGIN(ctx->voccxerr)
1147         {
1148             /* invoke preCommand */
1149             runfn(rcx, ctx->voccxprecmd, 5);
1150         }
1151         ERRCATCH(ctx->voccxerr, err)
1152         {
1153             /*
1154              *   if the error was 'exit', translate it to EXITPRECMD so
1155              *   that we handle the outer loop correctly (exiting from
1156              *   preCommand skips execution for all subsequent objects,
1157              *   but doesn't skip fuses and daemons)
1158              */
1159             if (err == ERR_RUNEXIT)
1160                 errsig(ctx->voccxerr, ERR_RUNEXITPRECMD);
1161 
1162             /* no special handling - just resignal the error */
1163             errrse(ctx->voccxerr);
1164         }
1165         ERREND(ctx->voccxerr);
1166     }
1167 
1168     /* show the pre-object prefix if the caller instructed us to do so */
1169     voc_multi_prefix(ctx, dobj, show_multi_prefix, multi_flags,
1170                      cur_dobj_idx, dobj_cnt);
1171 
1172     /*
1173      *   check to see if the verb has verbAction defined - if so, invoke
1174      *   the method
1175      */
1176     if (objgetap(ctx->voccxmem, verb, PRP_VERBACTION, (objnum *)0, FALSE))
1177     {
1178         /* call verb.verbAction(actor, dobj, prep, iobj) */
1179         runpobj(rcx, iobj);
1180         runpobj(rcx, prep);
1181         runpobj(rcx, dobj);
1182         runpobj(rcx, actor);
1183         runppr(rcx, verb, PRP_VERBACTION, 4);
1184     }
1185 
1186     /* invoke cmdActor.actorAction(verb, dobj, prep, iobj) */
1187     runpobj(rcx, iobj);
1188     runpobj(rcx, prep);
1189     runpobj(rcx, dobj);
1190     runpobj(rcx, verb);
1191     runppr(rcx, actor, PRP_ACTORACTION, 4);
1192 
1193     /* reset the run-time context if this is a top-level call */
1194     if (!recursive)
1195         runrst(rcx);
1196 
1197     /* invoke actor.location.roomAction(actor, verb, dobj, prep, iobj) */
1198     runppr(rcx, actor, PRP_LOCATION, 0);
1199     if (runtostyp(rcx) == DAT_OBJECT)
1200     {
1201         loc = runpopobj(rcx);
1202 
1203         /* reset the run-time context if this is a top-level call */
1204         if (!recursive)
1205             runrst(rcx);
1206 
1207         /* call roomAction */
1208         runpobj(rcx, iobj);
1209         runpobj(rcx, prep);
1210         runpobj(rcx, dobj);
1211         runpobj(rcx, verb);
1212         runpobj(rcx, actor);
1213         runppr(rcx, loc, PRP_ROOMACTION, 5);
1214     }
1215     else
1216     {
1217         /* the location isn't an object, so discard it */
1218         rundisc(rcx);
1219     }
1220 
1221     /* if there's an indirect object, execute iobjCheck */
1222     if (iobj != MCMONINV)
1223     {
1224         /* reset the run-time context if this is a top-level call */
1225         if (!recursive)
1226             runrst(rcx);
1227 
1228         /* invoke iobjCheck */
1229         runpobj(rcx, prep);
1230         runpobj(rcx, dobj);
1231         runpobj(rcx, verb);
1232         runpobj(rcx, actor);
1233         runppr(rcx, iobj, PRP_IOBJCHECK, 4);
1234     }
1235 
1236     /*
1237      *   If there's an indirect object, and the indirect object doesn't
1238      *   directly define io<Verb>, call iobj.iobjGen(actor, verb, dobj,
1239      *   prep)
1240      */
1241     if (iobj != MCMONINV
1242         && exegen(ctx, iobj, PRP_IOBJGEN, voctplvi(tpl), voctplio(tpl)))
1243     {
1244         /* reset the run-time context if this is a top-level call */
1245         if (!recursive)
1246             runrst(rcx);
1247 
1248         /* invoke iobjGen */
1249         runpobj(rcx, prep);
1250         runpobj(rcx, dobj);
1251         runpobj(rcx, verb);
1252         runpobj(rcx, actor);
1253         runppr(rcx, iobj, PRP_IOBJGEN, 4);
1254     }
1255 
1256     /* if there's an direct object, execute dobjCheck */
1257     if (dobj != MCMONINV)
1258     {
1259         /* reset the run-time context if this is a top-level call */
1260         if (!recursive)
1261             runrst(rcx);
1262 
1263         /* invoke dobjCheck */
1264         runpobj(rcx, prep);
1265         runpobj(rcx, iobj);
1266         runpobj(rcx, verb);
1267         runpobj(rcx, actor);
1268         runppr(rcx, dobj, PRP_DOBJCHECK, 4);
1269     }
1270 
1271     /*
1272      *   If there's a direct object, and the direct object doesn't
1273      *   directly define do<Verb>, call dobj.dobjGen(actor, verb, iobj,
1274      *   prep)
1275      */
1276     if (dobj != MCMONINV
1277         && exegen(ctx, dobj, PRP_DOBJGEN, voctplvd(tpl), voctpldo(tpl)))
1278     {
1279         /* reset the run-time context if this is a top-level call */
1280         if (!recursive)
1281             runrst(rcx);
1282 
1283         /* invoke dobjGen */
1284         runpobj(rcx, prep);
1285         runpobj(rcx, iobj);
1286         runpobj(rcx, verb);
1287         runpobj(rcx, actor);
1288         runppr(rcx, dobj, PRP_DOBJGEN, 4);
1289     }
1290 
1291     /* reset the hidden-text flag */
1292     tiohide(ctx->voccxtio);
1293     tioshow(ctx->voccxtio);
1294 
1295     /*
1296      *   Now do what needs to be done, depending on the sentence structure:
1297      *
1298      *      No objects ==> cmdVerb.action( cmdActor )
1299      *
1300      *      Direct object only ==> cmdDobj.verDo<Verb>( actor )
1301      *.                            cmdDobj.do<Verb>( actor )
1302      *
1303      *      Indirect + direct ==> cmdDobj.verDo<Verb>( actor, cmdIobj )
1304      *.                           cmdIobj.verIo<Verb>( actor, cmdDobj )
1305      *.                           cmdIobj.io<Verb>( actor, cmdDobj )
1306      */
1307     if (dobj == MCMONINV)
1308     {
1309         /* reset the stack for top-level calls */
1310         if (!recursive)
1311             runrst(rcx);
1312 
1313         /* invoke verb.action */
1314         runpobj(rcx, actor);
1315         runppr(rcx, verb, PRP_ACTION, 1);
1316     }
1317     else if (iobj == MCMONINV)
1318     {
1319         if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE))
1320         {
1321             /* display the error */
1322             exeperr(ctx, verb, dobj, MCMONINV, MCMONINV);
1323 
1324             /* note that verDoVerb failed */
1325             err = ERR_PRS_NO_VERDO;
1326 
1327             /* we're done with this command */
1328             goto skipToFuses;
1329         }
1330 
1331         /* reset the stack for top-level calls */
1332         if (!recursive)
1333             runrst(rcx);
1334 
1335         /* invoke dobj.verDoVerb */
1336         runpobj(rcx, actor);
1337         runppr(rcx, dobj, voctplvd(tpl), 1);
1338 
1339         /* check for an error message from verDoVerb */
1340         if (!tioshow(ctx->voccxtio))
1341         {
1342             /* reset the stack for top-level calls */
1343             if (!recursive)
1344                 runrst(rcx);
1345 
1346             /* dobj.verDoVerb displayed no output - process dobj.doVerb */
1347             runpobj(rcx, actor);
1348             runppr(rcx, dobj, voctpldo(tpl), 1);
1349         }
1350         else
1351         {
1352             /* note that verDoVerb failed */
1353             err = ERR_PRS_VERDO_FAIL;
1354         }
1355     }
1356     else
1357     {
1358         /* check to see if the verDoVerb and verIoVerb methods exist */
1359         if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE))
1360         {
1361             /* no verDoVerb method - show a default message */
1362             exeperr(ctx, verb, dobj, MCMONINV, MCMONINV);
1363 
1364             /* note the error */
1365             err = ERR_PRS_NO_VERDO;
1366 
1367             /* skip to the end of the turn */
1368             goto skipToFuses;
1369         }
1370         else if (!objgetap(ctx->voccxmem, iobj, voctplvi(tpl), (objnum *)0,
1371                            FALSE))
1372         {
1373             /* no verIoVerb method - show a default mesage */
1374             exeperr(ctx, verb, MCMONINV, prep, iobj);
1375 
1376             /* note the error */
1377             err = ERR_PRS_NO_VERIO;
1378 
1379             /* skip to the end of the turn */
1380             goto skipToFuses;
1381         }
1382 
1383         /* reset the stack for top-level calls */
1384         if (!recursive)
1385             runrst(rcx);
1386 
1387         /* call verDoVerb(actor [,iobj]) */
1388         if (!dobj_first)
1389             runpobj(rcx, iobj);
1390         runpobj(rcx, actor);
1391         runppr(rcx, dobj, voctplvd(tpl), (dobj_first ? 1 : 2));
1392 
1393         /* check for error output from verDoVerb */
1394         if (!tioshow(ctx->voccxtio))
1395         {
1396             /* reset the stack for top-level calls */
1397             if (!recursive)
1398                 runrst(rcx);
1399 
1400             /* no error from verDoVerb - call verIoVerb(actor [,dobj]) */
1401             if (dobj_first)
1402                 runpobj(rcx, dobj);
1403             runpobj(rcx, actor);
1404             runppr(rcx, iobj, voctplvi(tpl), (dobj_first ? 2 : 1));
1405 
1406             /* check for error output from verIoVerb */
1407             if (!tioshow(ctx->voccxtio))
1408             {
1409                 /* reset the stack for top-level calls */
1410                 if (!recursive)
1411                     runrst(rcx);
1412 
1413                 /* no error from verDoVerb or verIoVerb - call ioVerb */
1414                 runpobj(rcx, dobj);
1415                 runpobj(rcx, actor);
1416                 runppr(rcx, iobj, voctplio(tpl), 2);
1417             }
1418             else
1419             {
1420                 /* note the error */
1421                 err = ERR_PRS_VERIO_FAIL;
1422             }
1423         }
1424         else
1425         {
1426             /* note the error */
1427             err = ERR_PRS_VERDO_FAIL;
1428         }
1429     }
1430 
1431   skipToFuses:
1432     ERRCATCH(ctx->voccxerr, err)
1433     {
1434         /* if askIo was invoked, get the preposition from the error stack */
1435         if (err == ERR_RUNASKI)
1436             *prepptr = errargint(0);
1437 
1438         /*
1439          *   If we executed 'abort', we'll skip straight to endCommand.
1440          *
1441          *   If we executed askDo or askIo, we won't execute anything
1442          *   more, because the command is being interrupted.
1443          *
1444          *   If 'exit' or 'exitobj' was executed, proceed through
1445          *   postAction and subsequent steps.
1446          *
1447          *   If any error occurred other than 'exit' or 'exitobj' being
1448          *   invoked, resignal the error.
1449          *
1450          *   We don't need to do anything more at this point if 'exit' was
1451          *   invoked, because 'exit' merely skips to the end-of-turn
1452          *   phase, which is where we'll go next from here.
1453          *
1454          *   If 'exitobj' was invoked, we don't want to return an error at
1455          *   all, since we just want to skip the remainder of the normal
1456          *   processing for the current object and proceed to the next
1457          *   object (in a command with multiple direct objects).
1458          */
1459         if (err == ERR_RUNABRT)
1460         {
1461             /*
1462              *   aborting - we're going to call postAction, but we're not
1463              *   going to execute fuses and daemons
1464              */
1465             do_fuses = FALSE;
1466             endturn = TRUE;
1467         }
1468         else if (err == ERR_RUNASKD || err == ERR_RUNASKI)
1469         {
1470             /* we're going to skip all end-of-turn action */
1471             do_fuses = FALSE;
1472             do_postact = FALSE;
1473             endturn = FALSE;
1474         }
1475         else if (err == ERR_RUNEXIT)
1476         {
1477             /*
1478              *   Proceed with the remainder of the processing for this
1479              *   turn, but retain the error code to return to our caller,
1480              *   so they know that the rest of the turn is to be skipped.
1481              *
1482              *   In addition, set 'do_fuses' to true, since we want to go
1483              *   directly to the fuse and daemon processing for this turn,
1484              *   regardless of whether any other objects are present
1485              *   (because we'll skip any remaining objects).
1486              */
1487             endturn = TRUE;
1488             do_fuses = TRUE;
1489         }
1490         else if (err == ERR_RUNEXITPRECMD)
1491         {
1492             /*
1493              *   exited from preCommand - end the turn, but do not run the
1494              *   postAction routine
1495              */
1496             do_fuses = TRUE;
1497             do_postact = FALSE;
1498             endturn = TRUE;
1499         }
1500         else if (err == ERR_RUNEXITOBJ)
1501         {
1502             /*
1503              *   Proceed with the remainder of processing for this turn -
1504              *   we want to proceed to the next object, if any, and
1505              *   process it as normal.  We don't need to update 'endturn'
1506              *   or 'do_fuses', since we want to do all of those in the
1507              *   normal fashion.
1508              */
1509         }
1510         else
1511         {
1512             /*
1513              *   We can't handle any other errors.  Restore the enclosing
1514              *   command context, and resignal the error.
1515              */
1516 
1517             /* restore the previous tio actor setting */
1518             tiosetactor(ctx->voccxtio, old_tio_actor);
1519 
1520             /* restore the original context iobj and dobj settings */
1521             ctx->voccxdobj = old_ctx_dobj;
1522             ctx->voccxiobj = old_ctx_iobj;
1523 
1524             /* restore the original context command objects */
1525             ctx->voccxactor = old_actor;
1526             ctx->voccxverb = old_verb;
1527             ctx->voccxprep = old_prep;
1528 
1529             /* resignal the error */
1530             errrse(ctx->voccxerr);
1531         }
1532     }
1533     ERREND(ctx->voccxerr);
1534 
1535     /*
1536      *   If desired, call postAction(actor, verb, dobj, prep, iobj,
1537      *   error_status).
1538      */
1539     if (do_postact && ctx->voccxpostact != MCMONINV)
1540     {
1541         int err2;
1542 
1543         ERRBEGIN(ctx->voccxerr)
1544         {
1545             /* push the arguments */
1546             runpnum(rcx, err);
1547             runpobj(rcx, iobj);
1548             runpobj(rcx, prep);
1549             runpobj(rcx, dobj);
1550             runpobj(rcx, verb);
1551             runpobj(rcx, actor);
1552 
1553             /* invoke postAction */
1554             runfn(rcx, ctx->voccxpostact, 6);
1555         }
1556         ERRCATCH(ctx->voccxerr, err2)
1557         {
1558             /* remember the new error condition */
1559             err = err2;
1560 
1561             /* if we're aborting, skip fuses and daemons */
1562             if (err == ERR_RUNABRT)
1563             {
1564                 endturn = TRUE;
1565                 do_fuses = FALSE;
1566             }
1567         }
1568         ERREND(ctx->voccxerr);
1569     }
1570 
1571     /* restore the original context iobj and dobj settings */
1572     ctx->voccxdobj = old_ctx_dobj;
1573     ctx->voccxiobj = old_ctx_iobj;
1574 
1575     /* restore the original context command objects */
1576     ctx->voccxverb = old_verb;
1577     ctx->voccxprep = old_prep;
1578 
1579     /* reset the stack for top-level calls */
1580     if (!recursive)
1581         runrst(rcx);
1582 
1583     /*
1584      *   If this is the end of the turn, execute fuses and daemons.  Skip
1585      *   fuses on recursive calls, since we want to count them as part of
1586      *   the enclosing turn.
1587      */
1588     if (endturn && !recursive)
1589     {
1590         /* catch errors so that we can restore the actor globals */
1591         ERRBEGIN(ctx->voccxerr)
1592         {
1593             /* run fuses, daemons, and endCommand */
1594             err = exe_fuses_and_daemons(ctx, err, do_fuses, actor, verb,
1595                                         dobj_list, dobj_cnt, prep, iobj);
1596         }
1597         ERRCLEAN(ctx->voccxerr)
1598         {
1599             /* restore the previous actor globals */
1600             ctx->voccxactor = old_actor;
1601             tiosetactor(ctx->voccxtio, old_tio_actor);
1602         }
1603         ERRENDCLN(ctx->voccxerr);
1604     }
1605 
1606     /* restore the previous actor globals */
1607     ctx->voccxactor = old_actor;
1608     tiosetactor(ctx->voccxtio, old_tio_actor);
1609 
1610     /* success */
1611     return err;
1612 }
1613 
1614 
1615 /*
1616  *   saveit stores the current direct object list in 'it' or 'them'.
1617  */
exesaveit(voccxdef * ctx,vocoldef * dolist)1618 static void exesaveit(voccxdef *ctx, vocoldef *dolist)
1619 {
1620     int       cnt;
1621     int       i;
1622     int       dbg = ctx->voccxflg & VOCCXFDBG;
1623     tiocxdef *tcx = ctx->voccxtio;
1624     runcxdef *rcx = ctx->voccxrun;
1625 
1626     cnt = voclistlen(dolist);
1627     if (cnt == 1)
1628     {
1629         /*
1630          *   check to make sure they're not referring to a number or a
1631          *   string; if so, it doesn't make any sense to save it
1632          */
1633         if (dolist[0].vocolflg == VOCS_STR
1634             || dolist[0].vocolflg == VOCS_NUM)
1635         {
1636             /* save a nil 'it' */
1637             ctx->voccxit = MCMONINV;
1638             if (dbg) tioputs(tcx, ".. setting 'it' to nil (strObj/numObj)\\n");
1639 
1640             /* we're done */
1641             return;
1642         }
1643 
1644         /* save 'it' */
1645         ctx->voccxit = dolist[0].vocolobj;
1646         ctx->voccxthc = 0;
1647 
1648         if (dbg)
1649         {
1650             tioputs(tcx, ".. setting it: ");
1651             runppr(rcx, ctx->voccxit, PRP_SDESC, 0);
1652             tioputs(tcx, "\\n");
1653         }
1654 
1655         /* set "him" if appropriate */
1656         runppr(rcx, ctx->voccxit, PRP_ISHIM, 0);
1657         if (runtostyp(rcx) == DAT_TRUE)
1658         {
1659             ctx->voccxhim = ctx->voccxit;
1660             if (dbg) tioputs(tcx, "... [setting \"him\" to same object]\\n");
1661         }
1662         rundisc(rcx);
1663 
1664         /* set "her" if appropriate */
1665         runppr(rcx, ctx->voccxit, PRP_ISHER, 0);
1666         if (runtostyp(rcx) == DAT_TRUE)
1667         {
1668             ctx->voccxher = ctx->voccxit;
1669             if (dbg) tioputs(tcx, "... [setting \"her\" to same object]\\n");
1670         }
1671         rundisc(rcx);
1672     }
1673     else if (cnt > 1)
1674     {
1675         ctx->voccxthc = cnt;
1676         ctx->voccxit  = MCMONINV;
1677         if (dbg) tioputs(tcx, ".. setting \"them\": [");
1678         for (i = 0 ; i < cnt ; ++i)
1679         {
1680             ctx->voccxthm[i] = dolist[i].vocolobj;
1681             if (dbg)
1682             {
1683                 runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0);
1684                 tioputs(tcx, i+1 < cnt ? ", " : "]\\n");
1685             }
1686         }
1687     }
1688 }
1689 
1690 /* display a multiple-object prefix */
voc_multi_prefix(voccxdef * ctx,objnum objn,int show_prefix,int multi_flags,int cur_index,int count)1691 void voc_multi_prefix(voccxdef *ctx, objnum objn,
1692                       int show_prefix, int multi_flags,
1693                       int cur_index, int count)
1694 {
1695     runcxdef *rcx = ctx->voccxrun;
1696 
1697     /* if the object is invalid, ignore it */
1698     if (objn == MCMONINV)
1699         return;
1700 
1701     /*
1702      *   if there's a prefixdesc method defined, call it rather than the
1703      *   older multisdesc (or even older sdesc) approach
1704      */
1705     if (objgetap(ctx->voccxmem, objn, PRP_PREFIXDESC,
1706                  (objnum *)0, FALSE) != 0)
1707     {
1708         runsdef val;
1709 
1710         /* push the word flags */
1711         runpnum(rcx, multi_flags);
1712 
1713         /*
1714          *   push the object count and the current index (adjusted to a
1715          *   1-based value)
1716          */
1717         runpnum(rcx, count);
1718         runpnum(rcx, cur_index + 1);
1719 
1720         /* push the 'show' flag */
1721         val.runstyp = runclog(show_prefix);
1722         runpush(rcx, val.runstyp, &val);
1723 
1724         /* call the method */
1725         runppr(rcx, objn, PRP_PREFIXDESC, 4);
1726 
1727         /* we're done */
1728         return;
1729     }
1730 
1731     /*
1732      *   if we're not showing the prefix, don't use the multisdesc/sdesc
1733      *   display
1734      */
1735     if (!show_prefix)
1736         return;
1737 
1738     /*
1739      *   use multisdesc if defined (for compatibility with older games,
1740      *   use sdesc if multisdesc doesn't exist for this object)
1741      */
1742     if (objgetap(ctx->voccxmem, objn, PRP_MULTISDESC,
1743                  (objnum *)0, FALSE) == 0)
1744     {
1745         /* there's no multisdesc defined - use the plain sdesc */
1746         runppr(rcx, objn, PRP_SDESC, 0);
1747     }
1748     else
1749     {
1750         /* multisdesc is defined - use it */
1751         runppr(rcx, objn, PRP_MULTISDESC, 0);
1752     }
1753 
1754     /* show the colon */
1755     vocerr_info(ctx, VOCERR(120), ": ");
1756 }
1757 
1758 /* execute command for each object in direct object list */
exeloop(voccxdef * ctx,objnum actor,objnum verb,vocoldef * dolist,objnum * prep,vocoldef * iobj,int multi_flags,uchar * tpl,int newstyle)1759 static int exeloop(voccxdef *ctx, objnum actor, objnum verb,
1760                    vocoldef *dolist, objnum *prep, vocoldef *iobj,
1761                    int multi_flags, uchar *tpl, int newstyle)
1762 {
1763     runcxdef *rcx = ctx->voccxrun;
1764     int       err;
1765     int       i;
1766     int       dobj_cnt;
1767     int       exec_cnt;
1768     vocoldef *dobj;
1769 
1770     /*
1771      *   count the direct objects; we'll iterate over the direct objects,
1772      *   so we execute the command once per direct object
1773      */
1774     exec_cnt = dobj_cnt = (dolist != 0 ? voclistlen(dolist) : 0);
1775 
1776     /*
1777      *   if there are no direct objects, we still must execute the command
1778      *   once
1779      */
1780     if (exec_cnt < 1)
1781         exec_cnt = 1;
1782 
1783     /*
1784      *   If we have multiple direct objects, or we're using "all" with
1785      *   just one direct object, check with the verb to see if multiple
1786      *   words are acceptable: call verb.rejectMultiDobj, and see what it
1787      *   returns; if it returns true, don't allow multiple words, and
1788      *   expect that rejectMultiDobj displayed an error message.
1789      *   Otherwise, proceed.
1790      */
1791     if (((multi_flags & VOCS_ALL) != 0 || dobj_cnt > 1)
1792         && dolist && dolist[0].vocolobj != MCMONINV)
1793     {
1794         int typ;
1795 
1796         ERRBEGIN(ctx->voccxerr)
1797             runrst(rcx);
1798             if (!prep || *prep == MCMONINV)
1799                 runpnil(rcx);
1800             else
1801                 runpobj(rcx, *prep);
1802             runppr(rcx, verb, PRP_REJECTMDO, 1);
1803             typ = runtostyp(rcx);
1804             rundisc(rcx);
1805         ERRCATCH(ctx->voccxerr, err)
1806             if (err == ERR_RUNEXIT || err == ERR_RUNEXITOBJ
1807                 || err == ERR_RUNABRT)
1808                 return err;
1809             else
1810                 errrse(ctx->voccxerr);
1811         ERREND(ctx->voccxerr)
1812 
1813         /* if they returned 'true', don't bother continuing */
1814         if (typ == DAT_TRUE)
1815             return 0;
1816     }
1817 
1818     /*
1819      *   execute the command the required number of times
1820      */
1821     for (i = 0 ; i < exec_cnt ; ++i)
1822     {
1823         int show_multi_prefix;
1824 
1825         /* get the current direct object, if we have one */
1826         dobj = (dolist != 0 ? &dolist[i] : 0);
1827 
1828         /*
1829          *   If we have a number or string, set the current one in
1830          *   numObj/strObj
1831          */
1832         if (dolist != 0)
1833         {
1834             if (dolist[i].vocolflg == VOCS_STR)
1835             {
1836                 /* it's a string - set strObj.value */
1837                 vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
1838                           dolist[i].vocolfst + 1, &dolist[i], &dolist[i]);
1839             }
1840             else if (dolist[i].vocolflg == VOCS_NUM)
1841             {
1842                 long v1, v2;
1843 
1844                 /* it's a number - set numObj.value */
1845                 v1 = atol(dolist[i].vocolfst);
1846                 oswp4(&v2, v1);
1847                 vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, &v2,
1848                           &dolist[i], &dolist[i]);
1849             }
1850         }
1851 
1852         /*
1853          *   For cases where we have a bunch of direct objects (or even
1854          *   one when "all" was used), we want to preface the output from
1855          *   each iteration with the name of the object we're acting on
1856          *   currently.  In other cases, there is no prefix.
1857          */
1858         show_multi_prefix = ((multi_flags != 0 || dobj_cnt > 1) && dobj != 0);
1859 
1860         /*
1861          *   Execute the command for this object.  For every object except
1862          *   the first, re-validate the direct and indirect objects.
1863          *   There's no need to re-validate the objects on the first
1864          *   object in a command, because that will already have been done
1865          *   during object resolution.
1866          */
1867         err = exe1cmd(ctx, actor, verb, dobj, prep, iobj,
1868                       (i + 1 == exec_cnt), tpl, newstyle, FALSE,
1869                       i != 0, i != 0, dolist, i, dobj_cnt,
1870                       show_multi_prefix, multi_flags);
1871 
1872         /* check the error - ignore any verification failures */
1873         switch(err)
1874         {
1875         case ERR_PRS_VERDO_FAIL:
1876         case ERR_PRS_VERIO_FAIL:
1877         case ERR_PRS_NO_VERDO:
1878         case ERR_PRS_NO_VERIO:
1879         case ERR_RUNEXITOBJ:
1880         case ERR_RUNEXIT:
1881             /* ignore the error and continue */
1882             err = 0;
1883             break;
1884 
1885         case ERR_RUNEXITPRECMD:
1886             /*
1887              *   exited from preCommand - skip execution of subsequent
1888              *   objects, but return success
1889              */
1890             return 0;
1891 
1892         case 0:
1893             /* no error; continue */
1894             break;
1895 
1896         default:
1897             /* anything else stops this command */
1898             return err;
1899         }
1900 
1901         /* flush output */
1902         tioflush(ctx->voccxtio);
1903     }
1904 
1905     /* success */
1906     return 0;
1907 }
1908 
1909 /*
1910  *   Execute a command recursively.  Game code can call this routine
1911  *   (indirectly through a built-in function) to execute a command, using
1912  *   all of the same steps that would be applied for the command if the
1913  *   player had typed it.
1914  */
execmd_recurs(voccxdef * ctx,objnum actor,objnum verb,objnum dobj,objnum prep,objnum iobj,int validate_dobj,int validate_iobj)1915 int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb,
1916                   objnum dobj, objnum prep, objnum iobj,
1917                   int validate_dobj, int validate_iobj)
1918 {
1919     int       err;
1920     int       newstyle;
1921     uchar     tpl[VOCTPL2SIZ];
1922     vocoldef  dobjv;
1923     vocoldef  iobjv;
1924     voccxdef  ctx_copy;
1925     runsdef *orig_sp;
1926     runsdef *orig_bp;
1927 
1928     /*
1929      *   Save the stack and base pointers as they are on entry.  Since
1930      *   exe1cmd() is being called recursively, it won't automatically clear
1931      *   the stack after it's done as it would at the top level; this means
1932      *   that an aborted frame can be left on the stack if we throw an
1933      *   'exit' or 'abort' in the course of executing the command.  To make
1934      *   sure we don't leave any aborted frames on the stack before
1935      *   returning to our caller, we simply need to restore the stack and
1936      *   frame pointers on the way out as they were on the way in.
1937      */
1938     orig_sp = ctx->voccxrun->runcxsp;
1939     orig_bp = ctx->voccxrun->runcxbp;
1940 
1941     /* make a copy of the voc context, so that changes aren't permanent */
1942     ctx_copy = *ctx;
1943     ctx = &ctx_copy;
1944 
1945     /*
1946      *   there are no unknown words in the recursive command, since the
1947      *   command was prepared directly from resolved objects
1948      */
1949     ctx->voccxunknown = 0;
1950 
1951     /* set up the vocoldef structure for the direct object, if present */
1952     if (dobj != MCMONINV)
1953     {
1954         dobjv.vocolobj = dobj;
1955         dobjv.vocolfst = dobjv.vocollst = "";
1956         dobjv.vocolflg = 0;
1957     }
1958 
1959     /* set up the vocoldef structure for the indirect object, if present */
1960     if (iobj != MCMONINV)
1961     {
1962         iobjv.vocolobj = iobj;
1963         iobjv.vocolfst = iobjv.vocollst = "";
1964         iobjv.vocolflg = 0;
1965     }
1966 
1967     /* figure out which template we need, based on the objects provided */
1968     if (dobj == MCMONINV)
1969     {
1970         uint actofs;
1971         uint tplofs;
1972 
1973         /*
1974          *   No objects were provided - use the verb's "action" method.
1975          *   Make sure that there is in fact an "action" method.
1976          */
1977         exe_get_tpl(ctx, verb, &tplofs, &actofs);
1978         if (actofs != 0)
1979         {
1980             /* execute the "action" method */
1981             err = exe1cmd(ctx, actor, verb, 0, &prep, 0, FALSE,
1982                           0, FALSE, TRUE, validate_dobj, validate_iobj,
1983                           0, 0, 0, FALSE, 0);
1984         }
1985         else
1986         {
1987             /* indicate that the sentence structure wasn't understood */
1988             err = ERR_PRS_SENT_UNK;
1989         }
1990     }
1991     else if (iobj == MCMONINV)
1992     {
1993         /*
1994          *   No indirect object was provided, but a direct object is
1995          *   present - use the one-object template.  First, look up the
1996          *   template.
1997          */
1998         if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
1999         {
2000             /* execute the command */
2001             err = exe1cmd(ctx, actor, verb, &dobjv, &prep, 0, FALSE,
2002                           tpl, newstyle, TRUE, validate_dobj, validate_iobj,
2003                           &dobjv, 0, 1, FALSE, 0);
2004         }
2005         else
2006         {
2007             /* indicate that the sentence structure wasn't understood */
2008             err = ERR_PRS_SENT_UNK;
2009         }
2010     }
2011     else
2012     {
2013         /*
2014          *   Both a direct and indirect object were provided - find the
2015          *   two-object template for the given preposition.
2016          */
2017         if (voctplfnd(ctx, verb, prep, tpl, &newstyle))
2018         {
2019             /* execute the command */
2020             err = exe1cmd(ctx, actor, verb, &dobjv, &prep, &iobjv, FALSE,
2021                           tpl, newstyle, TRUE, validate_dobj, validate_iobj,
2022                           &dobjv, 0, 1, FALSE, 0);
2023         }
2024         else
2025         {
2026             /* indicate that the sentence structure wasn't understood */
2027             err = ERR_PRS_SENT_UNK;
2028         }
2029     }
2030 
2031     /*
2032      *   if the error was EXITPRECMD, change it to EXIT - EXITPRECMD is a
2033      *   special flag indicating that we exited from a preCommand
2034      *   function, which is different than normal exiting internally but
2035      *   not to the game
2036      */
2037     if (err == ERR_RUNEXITPRECMD)
2038         err = ERR_RUNEXIT;
2039 
2040     /*
2041      *   restore the original stack and base pointers, to ensure that we
2042      *   don't leave any aborted frames on the stack
2043      */
2044     ctx->voccxrun->runcxsp = orig_sp;
2045     ctx->voccxrun->runcxbp = orig_bp;
2046 
2047     /* return the result code */
2048     return err;
2049 }
2050 
2051 
2052 /*
2053  *   Check for ALL, ANY, or THEM in the list - use multi-mode if found,
2054  *   even if we have only one object.  Returns a combination of any of the
2055  *   VOCS_ALL, VOCS_ANY, or VOCS_THEM flags that we find.
2056  */
check_for_multi(vocoldef * dolist)2057 static int check_for_multi(vocoldef *dolist)
2058 {
2059     int dolen;
2060     int i;
2061     int result;
2062 
2063     /* presume we won't find any flags */
2064     result = 0;
2065 
2066     /*
2067      *   scan the list for ALL, ANY, or THEM flags, combining any such
2068      *   flags we find into the result
2069      */
2070     dolen = voclistlen(dolist);
2071     for (i = 0 ; i < dolen ; ++i)
2072         result |= (dolist[i].vocolflg & (VOCS_ALL | VOCS_ANY | VOCS_THEM));
2073 
2074     /* return the result */
2075     return result;
2076 }
2077 
2078 /* ------------------------------------------------------------------------ */
2079 /*
2080  *   Try running the preparseCmd user function.  Returns 0 if the
2081  *   function doesn't exist or returns 'true', ERR_PREPRSCMDCAN if it
2082  *   returns 'nil' (and thus wants to cancel the command), and
2083  *   ERR_PREPRSCMDREDO if it returns a list (and thus wants to redo the
2084  *   command).
2085  */
try_preparse_cmd(voccxdef * ctx,char ** cmd,int wrdcnt,uchar ** preparse_list)2086 int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
2087                      uchar **preparse_list)
2088 {
2089     uchar    listbuf[VOCBUFSIZ + 2 + 3*VOCBUFSIZ];
2090     int      i;
2091     uchar   *p;
2092     size_t   len;
2093     runsdef  val;
2094     int      typ;
2095     int      err;
2096 
2097     /* if there's no preparseCmd, keep processing */
2098     if (ctx->voccxppc == MCMONINV)
2099         return 0;
2100 
2101     /* build a list of the words */
2102     for (p = listbuf + 2, i = 0 ; i < wrdcnt ; ++i)
2103     {
2104         char *src;
2105         int add_quote;
2106 
2107         /* check for strings - they require special handling */
2108         if (cmd[i][0] == '"')
2109         {
2110             /*
2111              *   it's a string - what follows is a run-time style string,
2112              *   with a length prefix followed by the text of the string
2113              */
2114             len = osrp2(cmd[i] + 1) - 2;
2115             src = cmd[i] + 3;
2116 
2117             /* add quotes to the result */
2118             add_quote = TRUE;
2119         }
2120         else
2121         {
2122             /* ordinary word - copy directly */
2123             src = (char *)cmd[i];
2124 
2125             /* it's a null-terminated string */
2126             len = strlen(src);
2127 
2128             /* don't add quotes to the result */
2129             add_quote = FALSE;
2130         }
2131 
2132         /* write the type prefix */
2133         *p++ = DAT_SSTRING;
2134 
2135         /* write the length prefix */
2136         oswp2(p, len + 2 + (add_quote ? 2 : 0));
2137         p += 2;
2138 
2139         /* add an open quote if necessary */
2140         if (add_quote)
2141             *p++ = '"';
2142 
2143         /* copy the text */
2144         memcpy(p, src, len);
2145         p += len;
2146 
2147         /* add the closing quote if necessary */
2148         if (add_quote)
2149             *p++ = '"';
2150     }
2151 
2152     /* set the length of the whole list */
2153     len = p - listbuf;
2154     oswp2(listbuf, len);
2155 
2156     /* push the list as the argument, and call the user's preparseCmd */
2157     val.runstyp = DAT_LIST;
2158     val.runsv.runsvstr = listbuf;
2159     runpush(ctx->voccxrun, DAT_LIST, &val);
2160 
2161     /* presume that no error will occur */
2162     err = 0;
2163 
2164     /* catch errors that occur within preparseCmd */
2165     ERRBEGIN(ctx->voccxerr)
2166     {
2167         /* call preparseCmd */
2168         runfn(ctx->voccxrun, ctx->voccxppc, 1);
2169     }
2170     ERRCATCH(ctx->voccxerr, err)
2171     {
2172         /*
2173          *   if it's abort/exit/exitobj, just return it; for any other
2174          *   errors, just re-throw the same error
2175          */
2176         switch(err)
2177         {
2178         case ERR_RUNABRT:
2179         case ERR_RUNEXIT:
2180         case ERR_RUNEXITOBJ:
2181             /* simply return these errors to the caller */
2182             break;
2183 
2184         default:
2185             /* re-throw anything else */
2186             errrse(ctx->voccxerr);
2187         }
2188     }
2189     ERREND(ctx->voccxerr);
2190 
2191     /* if an error occurred, return the error code */
2192     if (err != 0)
2193         return err;
2194 
2195     /* get the result */
2196     typ = runtostyp(ctx->voccxrun);
2197 
2198     /* if they returned a list, it's a new command to execute */
2199     if (typ == DAT_LIST)
2200     {
2201         /* get the list and give it to the caller */
2202         *preparse_list = runpoplst(ctx->voccxrun);
2203 
2204         /*
2205          *   indicate that the command is to be reparsed with the new word
2206          *   list
2207          */
2208         return ERR_PREPRSCMDREDO;
2209     }
2210 
2211     /* for any other type, we don't need the value, so discard it */
2212     rundisc(ctx->voccxrun);
2213 
2214     /* if the result is nil, don't process this command further */
2215     if (typ == DAT_NIL)
2216         return ERR_PREPRSCMDCAN;
2217     else
2218         return 0;
2219 }
2220 
2221 
2222 /* ------------------------------------------------------------------------ */
2223 /*
2224  *   Call parseAskobjIndirect
2225  */
voc_askobj_indirect(voccxdef * ctx,vocoldef * dolist,objnum actor,objnum verb,objnum prep)2226 static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
2227                                 objnum actor, objnum verb, objnum prep)
2228 {
2229     int cnt;
2230     int i;
2231     size_t len;
2232     uchar *lstp;
2233 
2234     /*
2235      *   Generate the direct object list argument.  This argument is a
2236      *   list of lists.  For each noun phrase, we generate one sublist in
2237      *   the main list.  Each sublist itself consists of three
2238      *   sub-sublists: first, a list of strings giving the words in the
2239      *   noun phrase; second, a list of the objects matching the noun
2240      *   phrase; third, a list of the flags for the matching objects.
2241      *
2242      *   So, if the player typed "put red box and blue ball", we might
2243      *   generate a list something like this:
2244      *
2245      *   [ [ ['red', 'box'], [redBox1, redBox2], [0, 0] ], [ ['blue',
2246      *   'ball'], [blueBall], [0, 0] ] ]
2247      */
2248 
2249     /*
2250      *   First, figure out how much space we need for this list of lists
2251      *   of lists.  Scan the direct object list for distinct noun phrases
2252      *   - we need one sublist for each distinct noun phrase.
2253      */
2254     cnt = voclistlen(dolist);
2255     for (len = 0, i = 0 ; i < cnt ; )
2256     {
2257         char *p;
2258         size_t curlen;
2259         int j;
2260 
2261         /*
2262          *   we need the sublist type prefix (one byte) plus the sublist
2263          *   length prefix (two bytes), plus the type and length prefixes
2264          *   (one plus two bytes) for each of the three sub-sublist
2265          */
2266         len += (1+2) + 3*(1+2);
2267 
2268         /*
2269          *   we need space to store the strings for the words in this noun
2270          *   phrase
2271          */
2272         for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
2273              p += curlen + 1)
2274         {
2275             /*
2276              *   add in the space needed for this string element in the
2277              *   sub-sublist - we need one byte for the type prefix, two
2278              *   bytes for the length prefix, and the bytes for the string
2279              *   itself
2280              */
2281             curlen = strlen(p);
2282             len += (1+2) + curlen;
2283         }
2284 
2285         /*
2286          *   scan each object for this same noun phrase (i.e., for which
2287          *   the vocabulary words are the same)
2288          */
2289         for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
2290              ++j)
2291         {
2292             /*
2293              *   Add in space for this object in the sub-sublist for the
2294              *   current noun phrase.  If this object is nil, we need only
2295              *   one byte for the type; otherwise, we need one byte for
2296              *   the type prefix plus two bytes for the object ID.
2297              */
2298             if (dolist[i].vocolobj == MCMONINV)
2299                 len += 1;
2300             else
2301                 len += (1 + 2);
2302 
2303             /*
2304              *   Add in space for the flags sub-sublist for the current
2305              *   object.  We need one byte for the type and four for the
2306              *   integer value.
2307              */
2308             len += (1 + 4);
2309         }
2310 
2311         /* skip to the next distinct noun phrase */
2312         i = j;
2313     }
2314 
2315     /* allocate the list */
2316     lstp = voc_push_list_siz(ctx, len);
2317 
2318     /*
2319      *   Go through our object array again, and this time actually build
2320      *   the list.
2321      */
2322     for (i = 0 ; i < cnt ; )
2323     {
2324         char *p;
2325         uchar *subp;
2326         uchar *subsubp;
2327         size_t curlen;
2328         int j;
2329 
2330         /* start the sublist with the type prefix */
2331         *lstp++ = DAT_LIST;
2332 
2333         /* leave a placeholder for our length prefix */
2334         subp = lstp;
2335         lstp += 2;
2336 
2337         /* start the sub-sublist with the word strings */
2338         *lstp++ = DAT_LIST;
2339         subsubp = lstp;
2340         lstp += 2;
2341 
2342         /* store the word strings in the sub-sublist */
2343         for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
2344              p += curlen + 1)
2345         {
2346             /* get this string's length */
2347             curlen = strlen(p);
2348 
2349             /* store the type and length prefixes */
2350             *lstp++ = DAT_SSTRING;
2351             oswp2(lstp, curlen + 2);
2352             lstp += 2;
2353 
2354             /* store the string */
2355             memcpy(lstp, p, curlen);
2356             lstp += curlen;
2357         }
2358 
2359         /* fix up the string sub-sublist length */
2360         oswp2(subsubp, lstp - subsubp);
2361 
2362         /* start the second sub-sublist, for the objects */
2363         *lstp++ = DAT_LIST;
2364         subsubp = lstp;
2365         lstp += 2;
2366 
2367         /* write each object */
2368         for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
2369              ++j)
2370         {
2371             /*
2372              *   if this object isn't nil, write it to the sub-sublist;
2373              *   otherwise, just put nil in the sub-sublist
2374              */
2375             if (dolist[j].vocolobj != MCMONINV)
2376             {
2377                 *lstp++ = DAT_OBJECT;
2378                 oswp2(lstp, dolist[j].vocolobj);
2379                 lstp += 2;
2380             }
2381             else
2382             {
2383                 /* no object - just store nil */
2384                 *lstp++ = DAT_NIL;
2385             }
2386         }
2387 
2388         /* fix up the object sub-sublist length */
2389         oswp2(subsubp, lstp - subsubp);
2390 
2391         /* start the third sub-sublist, for the flags */
2392         *lstp++ = DAT_LIST;
2393         subsubp = lstp;
2394         lstp += 2;
2395 
2396         /* write each object's flags */
2397         for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
2398              ++j)
2399         {
2400             /* write the flags */
2401             *lstp++ = DAT_NUMBER;
2402             oswp4(lstp, dolist[j].vocolflg);
2403             lstp += 4;
2404         }
2405 
2406         /* fix up the flag sub-sublist length */
2407         oswp2(subsubp, lstp - subsubp);
2408 
2409         /* skip to the start of the next distinct noun phrase */
2410         i = j;
2411 
2412         /* fix up the sublist length */
2413         oswp2(subp, lstp - subp);
2414     }
2415 
2416     /* push the prep, verb, and actor arguments */
2417     runpobj(ctx->voccxrun, prep);
2418     runpobj(ctx->voccxrun, verb);
2419     runpobj(ctx->voccxrun,
2420             (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
2421 
2422     /* call the function */
2423     runfn(ctx->voccxrun, ctx->voccxpask3, 4);
2424 }
2425 
2426 
2427 /* ------------------------------------------------------------------------ */
2428 /*
2429  *   execmd() - executes a user's command given the verb's verb and
2430  *   preposition words, a list of nouns to be used as indirect objects,
2431  *   and a list to be used for direct objects.  The globals cmdActor and
2432  *   cmdPrep should already be set.  This routine tries to find a template
2433  *   for the verb which matches the player's command.  If no template
2434  *   matches, we try (using default objects and, if that fails, requests
2435  *   to the player for objects) to fill in any missing information in the
2436  *   player's command.  If that still fails, we will say we don't
2437  *   understand the sentence and leave it at that.
2438  */
execmd(voccxdef * ctx,objnum actor,objnum prep,char * vverb,char * vprep,vocoldef * dolist,vocoldef * iolist,char ** cmd,int * typelist,char * cmdbuf,int wrdcnt,uchar ** preparse_list,int * next_word)2439 int execmd(voccxdef *ctx, objnum actor, objnum prep,
2440            char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist,
2441            char **cmd, int *typelist,
2442            char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_word)
2443 {
2444     objnum    verb;
2445     objnum    iobj;
2446     int       multi_flags = 0;
2447     vocwdef  *n;
2448     int       cnt;
2449     vocoldef *newnoun;
2450     int       next;
2451     char     *exenewcmd;
2452     char     *donewcmd;
2453     char     *ionewcmd;
2454     char     *exenewbuf;
2455     char     *donewbuf;
2456     char     *ionewbuf;
2457     char    **exenewlist;
2458     char    **donewlist;
2459     char    **ionewlist;
2460     int      *exenewtype;
2461     int      *donewtype;
2462     int      *ionewtype;
2463     vocoldef *dolist1;
2464     vocoldef *iolist1;
2465     uchar     tpl[VOCTPL2SIZ];
2466     int       foundtpl;        /* used to determine success of tpl searches */
2467     runcxdef *rcx = ctx->voccxrun;
2468     uint      tplofs;                          /* offset of template object */
2469     uint      actofs;                        /* offset of 'action' property */
2470     int       askflags;                /* flag for what we need to ask user */
2471     int       newstyle;   /* flag indicating new-style template definitions */
2472     int       tplflags;
2473     int       err;
2474     uchar    *save_sp;
2475 
2476     /* run preparseCmd */
2477     switch(try_preparse_cmd(ctx, cmd, wrdcnt, preparse_list))
2478     {
2479     case 0:
2480         /* proceed with the command */
2481         break;
2482 
2483     case ERR_PREPRSCMDCAN:
2484         /* command cancelled */
2485         return 0;
2486 
2487     case ERR_RUNEXIT:
2488     case ERR_RUNABRT:
2489     case ERR_RUNEXITOBJ:
2490         /* abort/exit/exitobj - treat this the same as command cancellation */
2491         return 0;
2492 
2493     case ERR_PREPRSCMDREDO:
2494         /* redo the command - so indicate to the caller */
2495         return ERR_PREPRSCMDREDO;
2496     }
2497 
2498     /* look up the verb based on the verb and verb-prep */
2499     n = vocffw(ctx, vverb, (int)strlen(vverb),
2500                vprep, (vprep ? (int)strlen(vprep) : 0), PRP_VERB,
2501                (vocseadef *)0);
2502 
2503     /* if we didn't find a verb template, we can't process the sentence */
2504     if (n == 0)
2505     {
2506         /* try parseUnknownVerb, and show an error if that doesn't handle it */
2507         if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word,
2508                              TRUE, VOCERR(18),
2509                              "I don't understand that sentence."))
2510         {
2511             /* they handled it successfully - end the command with success */
2512             return 0;
2513         }
2514         else
2515         {
2516             /*
2517              *   parseUnknownVerb failed or aborted - end the command with
2518              *   an error
2519              */
2520             return 1;
2521         }
2522     }
2523 
2524     /* get the deepverb object */
2525     verb = n->vocwobj;
2526 
2527     /* default actor is "Me" */
2528     if (actor == MCMONINV)
2529         actor = ctx->voccxme;
2530 
2531     /* set a savepoint, if we're keeping undo information */
2532     if (ctx->voccxundo)
2533         objusav(ctx->voccxundo);
2534 
2535     /*
2536      *   Check that the room will allow this command -- it may not
2537      *   due to darkness or other ailment.  We can find out with the
2538      *   roomCheck(verb) message, sent to the meobj.
2539      */
2540     {
2541         int t;
2542 
2543         /* call roomCheck */
2544         runrst(rcx);
2545         runpobj(rcx, verb);
2546         runppr(rcx, ctx->voccxme, PRP_ROOMCHECK, 1);
2547         t = runpoplog(rcx);
2548 
2549         /* if they returned nil, stop the command, but indicate success */
2550         if (!t)
2551             return 0;
2552     }
2553 
2554     /* look for a new-style template first, then the old-style template */
2555     exe_get_tpl(ctx, verb, &tplofs, &actofs);
2556 
2557     /* make sure we found a verb */
2558     if (tplofs == 0 && actofs == 0 && verb != ctx->voccxvag)
2559     {
2560         /* try parseUnknownVerb, and show an error if that doesn't handle it */
2561         if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word,
2562                              TRUE, VOCERR(23),
2563                  "internal error: verb has no action, doAction, or ioAction"))
2564             return 0;
2565         else
2566             return 1;
2567     }
2568 
2569     /*
2570      *   Check to see if we have an "all" - if we do, we'll need to
2571      *   display the direct object's name even if only one direct object
2572      *   comes of it.
2573      */
2574     multi_flags = check_for_multi(dolist);
2575 
2576     /*
2577      *   set up dobj word list in case objwords is used in doDefault (the
2578      *   game may want to check for "all" and disallow it, for example)
2579      */
2580     ctx->voccxdobj = dolist;
2581 
2582     /* set up our stack allocations, which we may need from now on */
2583     voc_enter(ctx, &save_sp);
2584     VOC_STK_ARRAY(ctx, char,     donewcmd,  VOCBUFSIZ);
2585     VOC_STK_ARRAY(ctx, char,     ionewcmd,  VOCBUFSIZ);
2586     VOC_STK_ARRAY(ctx, char,     donewbuf,  2*VOCBUFSIZ);
2587     VOC_STK_ARRAY(ctx, char,     ionewbuf,  2*VOCBUFSIZ);
2588     VOC_STK_ARRAY(ctx, char *,   donewlist, VOCBUFSIZ);
2589     VOC_STK_ARRAY(ctx, char *,   ionewlist, VOCBUFSIZ);
2590     VOC_MAX_ARRAY(ctx, int,      donewtype);
2591     VOC_MAX_ARRAY(ctx, int,      ionewtype);
2592     VOC_MAX_ARRAY(ctx, vocoldef, dolist1);
2593     VOC_MAX_ARRAY(ctx, vocoldef, iolist1);
2594 
2595     /* keep going until we're done with the sentence */
2596     for ( ;; )
2597     {
2598         askflags = err = 0;
2599 
2600         ERRBEGIN(ctx->voccxerr)
2601 
2602         /*
2603          *   Now see what kind of sentence we have.  If we have no
2604          *   objects and an action, use the action.  If we have a direct
2605          *   object and a doAction, use the doAction.  If we have an
2606          *   indirect object and an ioAction with a matching preposition,
2607          *   use the ioAction.  If we have an indirect object and no
2608          *   matching ioAction, complain.  If we have a direct object and
2609          *   no doAction or ioAction, complain.  If we have fewer objects
2610          *   than we really want, ask the user for more of them.
2611          */
2612         if (voclistlen(dolist) == 0 && voclistlen(iolist) == 0)
2613         {
2614             if (actofs || verb == ctx->voccxvag)
2615             {
2616                 if ((err = exeloop(ctx, actor, verb, (vocoldef *)0, &prep,
2617                                    (vocoldef *)0, multi_flags,
2618                                    (uchar *)0, 0)) != 0)
2619                     goto exit_error;
2620             }
2621             else
2622             {
2623                 /*
2624                  *   The player has not specified any objects, but the
2625                  *   verb seems to require one.  See if there's a unique
2626                  *   default.
2627                  */
2628                 runrst(rcx);
2629                 runpnil(rcx);
2630                 runpobj(rcx, prep);
2631                 runpobj(rcx, actor);
2632                 runppr(rcx, verb, PRP_DODEFAULT, 3);
2633 
2634                 if (runtostyp(rcx) == DAT_LIST)
2635                 {
2636                     uchar   *l = runpoplst(rcx);
2637                     uint     lstsiz;
2638                     objnum   defobj;
2639                     int      objcnt;
2640                     objnum   newprep;
2641                     runsdef  val;
2642                     objnum   o;
2643 
2644                     /* push list back on stack, to keep in heap */
2645                     val.runsv.runsvstr = l;
2646                     val.runstyp = DAT_LIST;
2647                     runrepush(rcx, &val);
2648 
2649                     /* get list size out of list */
2650                     lstsiz = osrp2(l) - 2;
2651                     l += 2;
2652 
2653                     /* find default preposition for verb, if any */
2654                     runppr(rcx, verb, PRP_PREPDEFAULT, 0);
2655                     if (runtostyp(rcx) == DAT_OBJECT)
2656                         newprep = runpopobj(rcx);
2657                     else
2658                     {
2659                         newprep = MCMONINV;
2660                         rundisc(rcx);
2661                     }
2662 
2663                     if (!voctplfnd(ctx, verb, newprep, tpl, &newstyle))
2664                     {
2665                         for (objcnt = 0 ; lstsiz && objcnt < 2
2666                              ; lstadv(&l, &lstsiz))
2667                         {
2668                             if (*l == DAT_OBJECT)
2669                             {
2670                                 ++objcnt;
2671                                 defobj = osrp2(l + 1);
2672                             }
2673                         }
2674                     }
2675                     else
2676                     {
2677                         int dobj_first;
2678 
2679                         /*
2680                          *   Get the template flags.  If we must
2681                          *   disambiguate the direct object first for this
2682                          *   verb, do so now.
2683                          */
2684                         tplflags = (newstyle ? voctplflg(tpl) : 0);
2685                         dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
2686 
2687                         for (objcnt = 0 ; lstsiz && objcnt < 2
2688                              ; lstadv(&l, &lstsiz))
2689                         {
2690                             if (*l == DAT_OBJECT)
2691                             {
2692                                 o = osrp2(l + 1);
2693                                 if (!objgetap(ctx->voccxmem, o, voctplvd(tpl),
2694                                               (objnum *)0, FALSE))
2695                                     continue;
2696 
2697                                 tiohide(ctx->voccxtio);
2698                                 if (newprep != MCMONINV && !dobj_first)
2699                                     runpnil(rcx);
2700                                 runpobj(rcx, actor);
2701                                 runppr(rcx, o, voctplvd(tpl),
2702                                        ((newprep != MCMONINV && !dobj_first)
2703                                         ? 2 : 1));
2704 
2705                                 if (!tioshow(ctx->voccxtio))
2706                                 {
2707                                     ++objcnt;
2708                                     defobj = o;
2709                                 }
2710                             }
2711                         }
2712 
2713                         /* no longer need list in heap, so discard it */
2714                         rundisc(rcx);
2715 
2716                         /* use default object if there's exactly one */
2717                         if (objcnt == 1)
2718                         {
2719                             dolist[0].vocolobj = defobj;
2720                             dolist[0].vocolflg = 0;
2721                             dolist[0].vocolfst = dolist[0].vocollst = 0;
2722                             dolist[1].vocolobj = MCMONINV;
2723                             dolist[1].vocolflg = 0;
2724                             dolist[1].vocolfst = dolist[1].vocollst = 0;
2725 
2726                             runrst(rcx);
2727                             if (ctx->voccxpdef2 != MCMONINV)
2728                             {
2729                                 runpnil(rcx);
2730                                 runpobj(rcx, defobj);
2731                                 runpobj(rcx, verb);
2732                                 runpobj(rcx, actor);
2733                                 runfn(rcx, ctx->voccxpdef2, 4);
2734                             }
2735                             else if (ctx->voccxpdef != MCMONINV)
2736                             {
2737                                 runpnil(rcx);
2738                                 runpobj(rcx, defobj);
2739                                 runfn(rcx, ctx->voccxpdef, 2);
2740                             }
2741                             else
2742                             {
2743                                 /* tell the player what we're doing */
2744                                 vocerr_info(ctx, VOCERR(130), "(");
2745                                 runppr(rcx, defobj, PRP_THEDESC, 0);
2746                                 vocerr_info(ctx, VOCERR(131), ")");
2747                                 tioflush(ctx->voccxtio);
2748                             }
2749                             err = -2;                         /* "continue" */
2750                             goto exit_error;
2751                         }
2752                     }
2753                 }
2754                 else
2755                     rundisc(rcx);
2756 
2757                 /*
2758                  *   No unique default; ask the player for a direct
2759                  *   object, and try the command again if he is kind
2760                  *   enough to provide one.
2761                  */
2762                 askflags = ERR_RUNASKD;
2763             }
2764         }
2765         else if (voclistlen(iolist) == 0)
2766         {
2767             /* direct object(s), but no indirect object -- find doAction */
2768             if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
2769             {
2770                 /* disambiguate the direct object list, now that we can */
2771                 if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
2772                                 PRP_VALIDDO, voctplvd(tpl), cmd, MCMONINV,
2773                                 actor, verb, prep, cmdbuf, FALSE))
2774                 {
2775                     err = -1;
2776                     goto exit_error;
2777                 }
2778                 iobj = MCMONINV;
2779 
2780                 /*
2781                  *   save the disambiguated direct object list, in case
2782                  *   we hit an askio in the course of processing it
2783                  */
2784                 memcpy(dolist, dolist1,
2785                        (size_t)(voclistlen(dolist1) + 1)*sizeof(dolist[0]));
2786 
2787                 /* re-check for multi-mode */
2788                 if (multi_flags == 0)
2789                     multi_flags = check_for_multi(dolist1);
2790 
2791                 /* save it/them/him/her, and execute the command */
2792                 exesaveit(ctx, dolist1);
2793                 if ((err = exeloop(ctx, actor, verb, dolist1, &prep,
2794                                    (vocoldef *)0, multi_flags,
2795                                    tpl, newstyle)) != 0)
2796                     goto exit_error;
2797             }
2798             else
2799             {
2800                 /* no doAction - we'll need to find an indirect object */
2801                 runrst(rcx);
2802                 runppr(rcx, verb, PRP_PREPDEFAULT, 0);
2803                 if (runtostyp(rcx) != DAT_OBJECT)
2804                 {
2805                     /* discard the result */
2806                     rundisc(rcx);
2807 
2808                     /* call parseUnknownVerb to handle it */
2809                     if (try_unknown_verb(ctx, actor, cmd, typelist,
2810                                          wrdcnt, next_word, TRUE, VOCERR(24),
2811                                          "I don't recognize that sentence."))
2812                     {
2813                         /* handled - end the command successfully */
2814                         err = 0;
2815                     }
2816                     else
2817                     {
2818                         /* not handled - indicate failure */
2819                         err = -1;
2820                     }
2821                     goto exit_error;
2822                 }
2823                 prep = runpopobj(rcx);
2824 
2825                 runrst(rcx);
2826                 runpobj(rcx, prep);
2827                 runpobj(rcx, actor);
2828                 runppr(rcx, verb, PRP_IODEFAULT, 2);
2829 
2830                 if (runtostyp(rcx) == DAT_LIST)
2831                 {
2832                     uchar   *l = runpoplst(rcx);
2833                     uint     lstsiz;
2834                     objnum   defobj;
2835                     int      objcnt;
2836                     runsdef  val;
2837                     objnum   o;
2838 
2839                     /* push list back on stack, to keep in heap */
2840                     val.runsv.runsvstr = l;
2841                     val.runstyp = DAT_LIST;
2842                     runrepush(rcx, &val);
2843 
2844                     /* get list size out of list */
2845                     lstsiz = osrp2(l) - 2;
2846                     l += 2;
2847 
2848                     if (!voctplfnd(ctx, verb, prep, tpl, &newstyle))
2849                     {
2850                         for (objcnt = 0 ; lstsiz && objcnt < 2
2851                              ; lstadv(&l, &lstsiz))
2852                         {
2853                             if (*l == DAT_OBJECT)
2854                             {
2855                                 objcnt++;
2856                                 defobj = osrp2(l + 1);
2857                             }
2858                         }
2859                     }
2860                     else
2861                     {
2862                         int dobj_first;
2863 
2864                         /*
2865                          *   Get the template flags.  If we must
2866                          *   disambiguate the direct object first for this
2867                          *   verb, do so now.
2868                          */
2869                         tplflags = (newstyle ? voctplflg(tpl) : 0);
2870                         dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
2871                         if (dobj_first)
2872                         {
2873                             if (vocdisambig(ctx, dolist1, dolist,
2874                                             PRP_DODEFAULT, PRP_VALIDDO,
2875                                             voctplvd(tpl), cmd, MCMONINV,
2876                                             actor, verb, prep, cmdbuf,
2877                                             FALSE))
2878                             {
2879                                 err = -1;
2880                                 goto exit_error;
2881                             }
2882 
2883                             /* only one direct object is allowed here */
2884                             if (voclistlen(dolist1) > 1)
2885                             {
2886                                 vocerr(ctx, VOCERR(28),
2887                           "You can't use multiple objects with this command.");
2888                                 err = -1;
2889                                 goto exit_error;
2890                             }
2891 
2892                             /* save the object in the original list */
2893                             memcpy(dolist, dolist1,
2894                                    (size_t)(2 * sizeof(dolist[0])));
2895                         }
2896 
2897                         for (objcnt = 0 ; lstsiz && objcnt < 2
2898                              ; lstadv(&l, &lstsiz))
2899                         {
2900                             if (*l == DAT_OBJECT)
2901                             {
2902                                 o = osrp2(l + 1);
2903                                 if (!objgetap(ctx->voccxmem, o, voctplvi(tpl),
2904                                               (objnum *)0, FALSE))
2905                                     continue;
2906 
2907                                 tiohide(ctx->voccxtio);
2908                                 if (dobj_first)
2909                                     runpobj(rcx, dolist[0].vocolobj);
2910                                 runpobj(rcx, actor);
2911                                 runppr(rcx, o, voctplvi(tpl),
2912                                        (dobj_first ? 2 : 1));
2913                                 if (!tioshow(ctx->voccxtio))
2914                                 {
2915                                     objcnt++;
2916                                     defobj = o;
2917                                 }
2918                             }
2919                         }
2920                     }
2921 
2922                     /* no longer need list in heap, so discard it */
2923                     rundisc(rcx);
2924 
2925                     /* if there's exactly one default object, use it */
2926                     if (objcnt == 1)
2927                     {
2928                         iolist[0].vocolobj = defobj;
2929                         iolist[0].vocolflg = 0;
2930                         iolist[0].vocolfst = iolist[0].vocollst = 0;
2931                         iolist[1].vocolobj = MCMONINV;
2932                         iolist[1].vocolflg = 0;
2933                         iolist[1].vocolfst = iolist[1].vocollst = 0;
2934 
2935                         /* tell the user what we're assuming */
2936                         runrst(rcx);
2937                         if (ctx->voccxpdef2 != MCMONINV)
2938                         {
2939                             runpobj(rcx, prep);
2940                             runpobj(rcx, defobj);
2941                             runpobj(rcx, verb);
2942                             runpobj(rcx, actor);
2943                             runfn(rcx, ctx->voccxpdef2, 4);
2944                         }
2945                         else if (ctx->voccxpdef != MCMONINV)
2946                         {
2947                             runpobj(rcx, prep);
2948                             runpobj(rcx, defobj);
2949                             runfn(rcx, ctx->voccxpdef, 2);
2950                         }
2951                         else
2952                         {
2953                             vocerr_info(ctx, VOCERR(130), "(");
2954                             runppr(rcx, prep, PRP_SDESC, 0);
2955                             vocerr_info(ctx, VOCERR(132), " ");
2956                             runppr(rcx, defobj, PRP_THEDESC, 0);
2957                             vocerr_info(ctx, VOCERR(131), ")");
2958                         }
2959                         tioflush(ctx->voccxtio);
2960                         err = -2;                             /* "continue" */
2961                         goto exit_error;
2962                     }
2963                 }
2964                 else
2965                     rundisc(rcx);
2966 
2967                 /*
2968                  *   We didn't get a unique default indirect object, so
2969                  *   we should ask the player for an indirct object, and
2970                  *   repeat the command should he provide one.
2971                  */
2972                 askflags = ERR_RUNASKI;
2973             }
2974         }
2975         else
2976         {
2977             objnum otherobj;
2978 
2979             /* find the template for this verb/prep combination */
2980             if (!voctplfnd(ctx, verb, prep, tpl, &newstyle))
2981             {
2982                 /* call parseUnknownVerb to handle the error */
2983                 if (try_unknown_verb(ctx, actor, cmd, typelist,
2984                                      wrdcnt, next_word, TRUE,
2985                                      VOCERR(24),
2986                                      "I don't recognize that sentence."))
2987                 {
2988                     /* they handled it - terminate command successfully */
2989                     err = 0;
2990                 }
2991                 else
2992                 {
2993                     /* that failed - terminate the command with an error */
2994                     err = -1;
2995                 }
2996 
2997                 /* terminate the command */
2998                 goto exit_error;
2999             }
3000 
3001             /*
3002              *   We have both direct and indirect objects.  If we don't
3003              *   yet have the direct object, go ask for it
3004              */
3005             if (voclistlen(dolist) == 0)
3006             {
3007                 askflags = ERR_RUNASKD;
3008                 goto exit_error;
3009             }
3010 
3011             /* get the flags (if old-style, flags are always zero) */
3012             tplflags = (newstyle ? voctplflg(tpl) : 0);
3013 
3014             /*
3015              *   the "other" object (dobj if doing iobj, iobj if doing
3016              *   dobj) is not known when the first object is disambiguated
3017              */
3018             otherobj = MCMONINV;
3019 
3020             /* disambiguate the objects in the proper order */
3021             if (tplflags & VOCTPLFLG_DOBJ_FIRST)
3022             {
3023                 /* disambiguate the direct object list */
3024                 if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
3025                                 PRP_VALIDDO, voctplvd(tpl), cmd, otherobj,
3026                                 actor, verb, prep, cmdbuf, FALSE))
3027                 {
3028                     err = -1;
3029                     goto exit_error;
3030                 }
3031 
3032                 /*
3033                  *   only one direct object is allowed if it's
3034                  *   disambiguated first
3035                  */
3036                 if (voclistlen(dolist1) > 1)
3037                 {
3038                     vocerr(ctx, VOCERR(28),
3039                          "You can't use multiple objects with this command.");
3040                     err = -1;
3041                     goto exit_error;
3042                 }
3043 
3044                 /* the other object is now known for iboj disambiguation */
3045                 otherobj = dolist1[0].vocolobj;
3046             }
3047 
3048             /* disambiguate the indirect object list */
3049             if (vocdisambig(ctx, iolist1, iolist, PRP_IODEFAULT,
3050                             PRP_VALIDIO, voctplvi(tpl), cmd, otherobj,
3051                             actor, verb, prep, cmdbuf, FALSE))
3052             {
3053                 err = -1;
3054                 goto exit_error;
3055             }
3056 
3057             /* only one indirect object is allowed */
3058             if (voclistlen(iolist1) > 1)
3059             {
3060                 vocerr(ctx, VOCERR(25),
3061                        "You can't use multiple indirect objects.");
3062                 err = -1;
3063                 goto exit_error;
3064             }
3065             otherobj = iobj = iolist1[0].vocolobj;
3066 
3067             /*
3068              *   disambiguate the direct object list if we haven't
3069              *   already done so (we might have disambiguated it first due
3070              *   to the DisambigDobjFirst flag being set in the template)
3071              */
3072             if (!(tplflags & VOCTPLFLG_DOBJ_FIRST)
3073                 && vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
3074                                PRP_VALIDDO, voctplvd(tpl), cmd, otherobj,
3075                                actor, verb, prep, cmdbuf, FALSE))
3076             {
3077                 err = -1;
3078                 goto exit_error;
3079             }
3080 
3081             /* re-check for multi-mode */
3082             if (multi_flags == 0)
3083                 multi_flags = check_for_multi(dolist1);
3084 
3085             /* save it/them/him/her, and execute the command */
3086             exesaveit(ctx, dolist1);
3087             if ((err = exeloop(ctx, actor, verb, dolist1, &prep, iolist1,
3088                                multi_flags, tpl, newstyle)) != 0)
3089                 goto exit_error;
3090         }
3091 
3092     exit_error: ;
3093 
3094         ERRCATCH(ctx->voccxerr, err)
3095             if (err == ERR_RUNASKI) prep = errargint(0);
3096             if (err != ERR_RUNASKD && err != ERR_RUNASKI)
3097                 errrse(ctx->voccxerr);
3098         ERREND(ctx->voccxerr)
3099 
3100         switch(err)
3101         {
3102         case 0:
3103             break;
3104 
3105         case ERR_RUNABRT:
3106             /* "abort" executed - return the ABORT code */
3107             VOC_RETVAL(ctx, save_sp, err);
3108 
3109         case ERR_RUNEXIT:
3110             /*
3111              *   "exit" executed - terminate the command, but return
3112              *   success, since we want to process any additional commands
3113              */
3114             VOC_RETVAL(ctx, save_sp, 0);
3115 
3116         case ERR_RUNEXITOBJ:
3117             /*
3118              *   "exitobj" executed - indicate success, since this merely
3119              *   indicates that the game decided it was done processing an
3120              *   object early
3121              */
3122             VOC_RETVAL(ctx, save_sp, 0);
3123 
3124         case ERR_RUNASKI:
3125         case ERR_RUNASKD:
3126             askflags = err;
3127             break;
3128 
3129         case -2:                   /* special code: continue with main loop */
3130             continue;
3131 
3132         case -1:                           /* special code: return an error */
3133         default:
3134             VOC_RETVAL(ctx, save_sp, 1);
3135         }
3136 
3137         /*
3138          *   If we got this far, we probably want more information.  The
3139          *   askflags can tell us what to do from here.
3140          */
3141         if (askflags)
3142         {
3143             int old_unknown;
3144             int exenewpos;
3145 
3146             /*
3147              *   if we had unknown words, don't ask for more information
3148              *   at this point; simply give up and report the unknown word
3149              */
3150             if (ctx->voccxunknown != 0)
3151             {
3152                 VOC_RETVAL(ctx, save_sp, 1);
3153             }
3154 
3155             /* find new template indicated by the additional object */
3156             foundtpl = voctplfnd(ctx, verb, prep, tpl, &newstyle);
3157             tplflags = (newstyle ? voctplflg(tpl) : 0);
3158 
3159             /* find a default object of the type requested */
3160             runrst(rcx);
3161             if (askflags == ERR_RUNASKD) runpnil(rcx);
3162             runpobj(rcx, prep);
3163             runpobj(rcx, actor);
3164             runppr(rcx, verb,
3165                    (prpnum)(askflags == ERR_RUNASKD
3166                             ? PRP_DODEFAULT : PRP_IODEFAULT),
3167                    (askflags == ERR_RUNASKD ? 3 : 2));
3168 
3169             /*
3170              *   If we got a list back from ?oDefault, and we have a new
3171              *   template for the command, process the list normally with
3172              *   the object verification routine for this template.  If we
3173              *   end up with exactly one object, we will assume it is the
3174              *   object to be used; otherwise, make no assumption and ask
3175              *   the user for guidance.
3176              */
3177             if (runtostyp(rcx) == DAT_LIST && foundtpl)
3178             {
3179                 uchar   *l = runpoplst(rcx);
3180                 uint     lstsiz;
3181                 int      objcnt;
3182                 objnum   defobj;
3183                 objnum   o;
3184                 runsdef  val;
3185 
3186                 /* push list back on stack, to keep it in the heap */
3187                 val.runsv.runsvstr = l;
3188                 val.runstyp = DAT_LIST;
3189                 runrepush(rcx, &val);
3190 
3191                 /* get list size out of list */
3192                 lstsiz = osrp2(l) - 2;
3193                 l += 2;
3194 
3195                 for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz))
3196                 {
3197                     if (*l == DAT_OBJECT)
3198                     {
3199                         prpnum verprop;
3200                         int argc = 1;
3201 
3202                         o = osrp2(l + 1);
3203                         verprop = (askflags == ERR_RUNASKD ? voctplvd(tpl)
3204                                                         : voctplvi(tpl));
3205 
3206                         if (!objgetap(ctx->voccxmem, o, verprop,
3207                                       (objnum *)0, FALSE))
3208                             continue;
3209 
3210                         tiohide(ctx->voccxtio);
3211 
3212                         /*
3213                          *   In the unlikely event that we have an
3214                          *   indirect object but no direct object, push
3215                          *   the iobj.  This can happen when the player
3216                          *   types a sentence such as "verb prep iobj".
3217                          */
3218                         if (voclistlen(iolist) != 0
3219                             && askflags == ERR_RUNASKD
3220                             && !(tplflags & VOCTPLFLG_DOBJ_FIRST))
3221                         {
3222                             /* push the indirect object */
3223                             runpobj(rcx, iolist[0].vocolobj);
3224 
3225                             /* note the second argument */
3226                             argc = 2;
3227                         }
3228 
3229                         /*
3230                          *   If this is a disambigDobjFirst verb, and
3231                          *   we're validating an indirect object list,
3232                          *   then we must push the direct object argument
3233                          *   to the indirect object validation routine.
3234                          */
3235                         if (askflags == ERR_RUNASKI
3236                             && (tplflags & VOCTPLFLG_DOBJ_FIRST) != 0)
3237                         {
3238                             /* push the diret object */
3239                             runpobj(rcx, dolist[0].vocolobj);
3240 
3241                             /* note the second argument */
3242                             argc = 2;
3243                         }
3244 
3245                         /* push the actor and call the verXoVerb routine */
3246                         runpobj(rcx, actor);
3247                         runppr(rcx, o, verprop, argc);
3248                         if (!tioshow(ctx->voccxtio))
3249                         {
3250                             ++objcnt;
3251                             defobj = o;
3252                         }
3253 
3254                     }
3255                 }
3256 
3257                 /* no longer need list in heap, so discard it */
3258                 rundisc(rcx);
3259 
3260                 /* if we found exactly one object, it's the default */
3261                 if (objcnt == 1)
3262                 {
3263                     if (askflags == ERR_RUNASKD)
3264                     {
3265                         dolist[0].vocolobj = defobj;
3266                         dolist[0].vocolflg = 0;
3267                         dolist[0].vocolfst = dolist[0].vocollst = 0;
3268                         dolist[1].vocolobj = MCMONINV;
3269                         dolist[1].vocolflg = 0;
3270                         dolist[1].vocolfst = dolist[1].vocollst = 0;
3271                     }
3272                     else
3273                     {
3274                         iolist[0].vocolobj = defobj;
3275                         iolist[0].vocolflg = 0;
3276                         iolist[0].vocolfst = iolist[0].vocollst = 0;
3277                         iolist[1].vocolobj = MCMONINV;
3278                         iolist[1].vocolflg = 0;
3279                         iolist[1].vocolfst = iolist[1].vocollst = 0;
3280                     }
3281 
3282                     /* tell the user what we're assuming */
3283                     if (ctx->voccxpdef2 != MCMONINV)
3284                     {
3285                         if (askflags == ERR_RUNASKI)
3286                             runpobj(rcx, prep);
3287                         else
3288                             runpnil(rcx);
3289                         runpobj(rcx, defobj);
3290                         runpobj(rcx, verb);
3291                         runpobj(rcx, actor);
3292                         runfn(rcx, ctx->voccxpdef2, 4);
3293                     }
3294                     else if (ctx->voccxpdef != MCMONINV)
3295                     {
3296                         if (askflags == ERR_RUNASKI)
3297                             runpobj(rcx, prep);
3298                         else
3299                             runpnil(rcx);
3300                         runpobj(rcx, defobj);
3301                         runfn(rcx, ctx->voccxpdef, 2);
3302                     }
3303                     else
3304                     {
3305                         vocerr_info(ctx, VOCERR(130), "(");
3306                         if (askflags == ERR_RUNASKI)
3307                         {
3308                             runppr(rcx, prep, PRP_SDESC, 0);
3309                             vocerr_info(ctx, VOCERR(132), " ");
3310                         }
3311                         runppr(rcx, defobj, PRP_THEDESC, 0);
3312                         vocerr_info(ctx, VOCERR(131), ")");
3313                     }
3314                     tioflush(ctx->voccxtio);
3315                     continue;                      /* try the command again */
3316                 }
3317             }
3318             else
3319                 rundisc(rcx);
3320 
3321             /* make sure output capturing is off for the prompt */
3322             tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
3323             tioclrcapture(ctx->voccxtio);
3324 
3325             /*
3326              *   If we're asking for an indirect object, and we have a
3327              *   list of direct objects, and parseAskobjIndirect is
3328              *   defined, call it.  Otherwise, if there's a
3329              *   parseAskobjActor routine, call it.  Otherwise, if there's
3330              *   a parseAskobj routine, use that.  Finally, if none of
3331              *   those are defined, generate the default phrasing.
3332              */
3333             if (ctx->voccxpask3 != MCMONINV
3334                 && askflags == ERR_RUNASKI
3335                 && voclistlen(dolist) != 0)
3336             {
3337                 /* call parseAskobjIndirect */
3338                 voc_askobj_indirect(ctx, dolist, actor, verb, prep);
3339             }
3340             else if (ctx->voccxpask2 != MCMONINV)
3341             {
3342                 if (askflags == ERR_RUNASKI)
3343                     runpobj(ctx->voccxrun, prep);
3344                 runpobj(ctx->voccxrun, verb);
3345                 runpobj(ctx->voccxrun,
3346                         (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
3347                 runfn(ctx->voccxrun, ctx->voccxpask2,
3348                       askflags == ERR_RUNASKI ? 3 : 2);
3349             }
3350             else if (ctx->voccxpask != MCMONINV)
3351             {
3352                 if (askflags == ERR_RUNASKI)
3353                     runpobj(ctx->voccxrun, prep);
3354                 runpobj(ctx->voccxrun, verb);
3355                 runfn(ctx->voccxrun, ctx->voccxpask,
3356                       askflags == ERR_RUNASKI ? 2 : 1);
3357             }
3358             else
3359             {
3360                 /*
3361                  *   Phrase the question: askDo: "What do you want
3362                  *   <actor> to <verb>?"  askIo: "What do you want <actor>
3363                  *   to <verb> it <prep>?"  If the actor is Me, leave the
3364                  *   actor out of it.
3365                  */
3366                 if (actor != MCMONINV && actor != ctx->voccxme)
3367                 {
3368                     vocerr_info(ctx, VOCERR(148), "What do you want ");
3369                     runppr(rcx, actor, PRP_THEDESC, 0);
3370                     vocerr_info(ctx, VOCERR(149), " to ");
3371                 }
3372                 else
3373                 {
3374                     /* no actor - don't mention one */
3375                     vocerr_info(ctx, VOCERR(140), "What do you want to ");
3376                 }
3377 
3378                 /* add the verb */
3379                 runppr(rcx, verb, PRP_SDESC, 0);
3380 
3381                 /*
3382                  *   add an appropriate pronoun for the direct object,
3383                  *   and the preposition, if we're asking for an indirect
3384                  *   object
3385                  */
3386                 if (askflags == ERR_RUNASKI)
3387                 {
3388                     int   i;
3389                     int   cnt;
3390                     int   distinct;
3391                     char *lastfst;
3392 
3393                     /*
3394                      *   If possible, tailor the pronoun to the situation
3395                      *   rather than using "it"; if we have multiple
3396                      *   objects, use "them", and if we have agreement
3397                      *   with the possible single objects about "him" or
3398                      *   "her", use that.  Otherwise, use "it".  If "all"
3399                      *   was specified for any word, automatically assume
3400                      *   multiple distinct objects were specified.
3401                      */
3402                     cnt = voclistlen(dolist);
3403                     for (distinct = 0, i = 0, lastfst = 0 ; i < cnt ; ++i)
3404                     {
3405                         /* if the first word is different here, note it */
3406                         if (lastfst != dolist[i].vocolfst)
3407                         {
3408                             /* this is a different word - count it */
3409                             ++distinct;
3410                             lastfst = dolist[i].vocolfst;
3411                         }
3412 
3413                         /* always assume multiple distinct objects on "all" */
3414                         if (dolist[i].vocolflg & VOCS_ALL)
3415                         {
3416                             distinct = 2;
3417                             break;
3418                         }
3419                     }
3420 
3421                     /*
3422                      *   If we have multiple words, use "them";
3423                      *   otherwise, see if we can find agreement about
3424                      *   using "him" or "her".
3425                      */
3426                     if (distinct > 1)
3427                     {
3428                         /* multiple words specified by user - use "them" */
3429                         vocerr_info(ctx, VOCERR(144), " them ");
3430                     }
3431                     else
3432                     {
3433                         int is_him;
3434                         int is_her;
3435                         int is_them;
3436 
3437                         /* run through the objects and check him/her */
3438                         for (i = 0 ; i < cnt ; ++i)
3439                         {
3440                             int him1, her1, them1;
3441 
3442                             /* if it's special (number, string), use "it" */
3443                             if (dolist[i].vocolobj == MCMONINV)
3444                             {
3445                                 him1 = FALSE;
3446                                 her1 = FALSE;
3447                                 them1 = FALSE;
3448                             }
3449                             else
3450                             {
3451                                 /* check for "him" */
3452                                 runppr(rcx, dolist[i].vocolobj, PRP_ISHIM, 0);
3453                                 him1 = (runtostyp(rcx) == DAT_TRUE);
3454                                 rundisc(rcx);
3455 
3456                                 /* check for "her" */
3457                                 runppr(rcx, dolist[i].vocolobj, PRP_ISHER, 0);
3458                                 her1 = (runtostyp(rcx) == DAT_TRUE);
3459                                 rundisc(rcx);
3460 
3461                                 /* check for "them" */
3462                                 runppr(rcx, dolist[i].vocolobj,
3463                                        PRP_ISTHEM, 0);
3464                                 them1 = (runtostyp(rcx) == DAT_TRUE);
3465                                 rundisc(rcx);
3466                             }
3467 
3468                             /*
3469                              *   if this is the first object, it
3470                              *   definitely agrees; otherwise, keep going
3471                              *   only if it agrees with what we found on
3472                              *   the last pass
3473                              */
3474                             if (i == 0)
3475                             {
3476                                 is_him = him1;
3477                                 is_her = her1;
3478                                 is_them = them1;
3479                             }
3480                             else
3481                             {
3482                                 /* turn off either that is no longer true */
3483                                 if (!him1) is_him = FALSE;
3484                                 if (!her1) is_her = FALSE;
3485                                 if (!them1) is_them = FALSE;
3486                             }
3487 
3488                             /* if all are false, stop now */
3489                             if (!is_him && !is_her && !is_them)
3490                                 break;
3491                         }
3492 
3493                         /*
3494                          *   If we could agree on "him", "her", or "them",
3495                          *   use that pronoun; otherwise, use "it".  If we
3496                          *   found both "him" and "her" are acceptable for
3497                          *   all objects, use "them".
3498                          */
3499                         if ((is_him && is_her) || is_them)
3500                             vocerr_info(ctx, VOCERR(147), " them ");
3501                         else if (is_him)
3502                             vocerr_info(ctx, VOCERR(145), " him ");
3503                         else if (is_her)
3504                             vocerr_info(ctx, VOCERR(146), " her ");
3505                         else
3506                             vocerr_info(ctx, VOCERR(141), " it ");
3507                     }
3508 
3509                     /* finish off the question with the prep and a "?" */
3510                     if (prep != MCMONINV)
3511                         runppr(rcx, prep, PRP_SDESC, 0);
3512                     else
3513                         vocerr_info(ctx, VOCERR(142), "to");
3514                 }
3515                 vocerr_info(ctx, VOCERR(143), "?");
3516             }
3517             tioflush(ctx->voccxtio);
3518 
3519             /*
3520              *   Get a new command line.  If the player gives us
3521              *   something that looks like a noun list, and nothing more,
3522              *   he anwered our question; otherwise, he's typing a new
3523              *   command, so we must return to the caller with the reparse
3524              *   flag set.
3525              */
3526             if (askflags == ERR_RUNASKD)
3527             {
3528                 exenewbuf = donewbuf;
3529                 exenewcmd = donewcmd;
3530                 exenewlist = donewlist;
3531                 exenewtype = donewtype;
3532             }
3533             else
3534             {
3535                 exenewbuf = ionewbuf;
3536                 exenewcmd = ionewcmd;
3537                 exenewlist = ionewlist;
3538                 exenewtype = ionewtype;
3539             }
3540 
3541             /* read the new command */
3542             if (vocread(ctx, actor, verb, exenewcmd, VOCBUFSIZ,
3543                         askflags == ERR_RUNASKD ? 3 : 4) == VOCREAD_REDO)
3544             {
3545                 /*
3546                  *   we got an input line, but we want to treat it as a brand
3547                  *   new command line - copy the new text to the command
3548                  *   buffer, set the 'redo' flag, and give up
3549                  */
3550                 strcpy(cmdbuf, exenewcmd);
3551                 ctx->voccxredo = TRUE;
3552                 VOC_RETVAL(ctx, save_sp, 1);
3553             }
3554 
3555             if (!(cnt = voctok(ctx, exenewcmd, exenewbuf, exenewlist,
3556                                TRUE, FALSE, TRUE)))
3557             {
3558                 runrst(rcx);
3559                 runfn(rcx, ctx->voccxprd, 0);
3560                 VOC_RETVAL(ctx, save_sp, 1);
3561             }
3562             if (cnt < 0)
3563             {
3564                 ctx->voccxunknown = 0;
3565                 VOC_RETVAL(ctx, save_sp, 1);
3566             }
3567 
3568             /*
3569              *   Save the unknown word count while getting types, and set
3570              *   the count to a non-zero value - this will force the type
3571              *   checker to generate an error on an unknown word.  This
3572              *   removes a little control from the game (since
3573              *   parseUnknownXobj won't be called), but there's not much
3574              *   else we can do here.
3575              */
3576             old_unknown = ctx->voccxunknown;
3577             ctx->voccxunknown = 1;
3578 
3579             /* get the types */
3580             exenewlist[cnt] = 0;
3581             if (vocgtyp(ctx, exenewlist, exenewtype, cmdbuf))
3582             {
3583                 /*
3584                  *   clear the unknown word count so that we fail with
3585                  *   this error rather than trying to deal with unknown
3586                  *   words
3587                  */
3588                 ctx->voccxunknown = 0;
3589 
3590                 /* return failure */
3591                 VOC_RETVAL(ctx, save_sp, 1);
3592             }
3593 
3594             /* restore the unknown word count */
3595             ctx->voccxunknown = old_unknown;
3596 
3597             /* start at the first word */
3598             exenewpos = 0;
3599 
3600             /*
3601              *   if we're asking for an indirect object, and the first
3602              *   word is a preposition, and matches the preposition that
3603              *   we supplied to precede the indirect object, skip the
3604              *   preposition
3605              */
3606             if (askflags == ERR_RUNASKI
3607                 && prep != MCMONINV
3608                 && (exenewtype[0] & VOCT_PREP) != 0)
3609             {
3610                 vocwdef *vp;
3611 
3612                 /* get the preposition */
3613                 vp = vocffw(ctx, exenewlist[0], (int)strlen(exenewlist[0]),
3614                             (char *)0, 0, PRP_PREP, (vocseadef *)0);
3615                 if (vp != 0 && vp->vocwobj == prep)
3616                     ++exenewpos;
3617             }
3618 
3619             /* check for a noun */
3620             newnoun = (askflags == ERR_RUNASKD ? dolist : iolist);
3621             cnt = vocchknoun(ctx, exenewlist, exenewtype, exenewpos, &next,
3622                              newnoun, FALSE);
3623 
3624             if (cnt < 0) { VOC_RETVAL(ctx, save_sp, 1); } /* invalid syntax */
3625             if (cnt == 0
3626                 || (exenewlist[next] && !vocspec(exenewlist[next], VOCW_THEN)
3627                     && *exenewlist[next] != '\0'))
3628             {
3629                 strcpy(cmdbuf, exenewcmd);
3630                 ctx->voccxredo = TRUE;
3631                 VOC_RETVAL(ctx, save_sp, 1);
3632             }
3633 
3634             /* re-check the 'multi' flags */
3635             multi_flags = check_for_multi(newnoun);
3636 
3637             /* give it another go by going back to the top of the loop */
3638         }
3639         else
3640         {
3641             /* normal exit flags - return success */
3642             VOC_RETVAL(ctx, save_sp, 0);
3643         }
3644     }
3645 }
3646 
3647