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