cfad47cfa3/tads2/execmd.c

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
 */
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 */
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 */
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 */
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 */
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 */
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
 */
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
 */
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
 */
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
 */
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
 */
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
 */
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
 */
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
 */
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
 */
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
 */
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
 */
1618
static void exesaveit(voccxdef *ctx, vocoldef *dolist)
1619
{
1620
    int       cnt;
1621
    int       i;
1622
    int       dbg = ctx->voccxflg & VOCCXFDBG;
1623
    runcxdef *rcx = ctx->voccxrun;
1624
1625
    cnt = voclistlen(dolist);
1626
    if (cnt == 1)
1627
    {
1628
        /*
1629
         *   check to make sure they're not referring to a number or a
1630
         *   string; if so, it doesn't make any sense to save it 
1631
         */
1632
        if (dolist[0].vocolflg == VOCS_STR
1633
            || dolist[0].vocolflg == VOCS_NUM)
1634
        {
1635
            /* 
1636
             *   As of 2.5.11, don't clear 'it' on a number or string;
1637
             *   rather, just leave it as it was from the prior command.
1638
             *   Players will almost never expect a number or string to have
1639
             *   anything to do with pronoun antecedents, and in fact some
1640
             *   players reported finding it confusing to have the antecedant
1641
             *   implied by the second-most-recent command disappear when the
1642
             *   most recent command used a number of string.  
1643
             */
1644
#if 0
1645
            /* save a nil 'it' */
1646
            ctx->voccxit = MCMONINV;
1647
            if (dbg)
1648
                tioputs(ctx->voccxtio,
1649
                        ".. setting 'it' to nil (strObj/numObj)\\n");
1650
#endif
1651
1652
            /* we're done */
1653
            return;
1654
        }
1655
1656
        /* save 'it' */
1657
        ctx->voccxit = dolist[0].vocolobj;
1658
        ctx->voccxthc = 0;
1659
1660
        if (dbg)
1661
        {
1662
            tioputs(ctx->voccxtio, ".. setting it: ");
1663
            runppr(rcx, ctx->voccxit, PRP_SDESC, 0);
1664
            tioputs(ctx->voccxtio, "\\n");
1665
        }
1666
1667
        /* set "him" if appropriate */
1668
        runppr(rcx, ctx->voccxit, PRP_ISHIM, 0);
1669
        if (runtostyp(rcx) == DAT_TRUE)
1670
        {
1671
            ctx->voccxhim = ctx->voccxit;
1672
            if (dbg)
1673
                tioputs(ctx->voccxtio,
1674
                        "... [setting \"him\" to same object]\\n");
1675
        }
1676
        rundisc(rcx);
1677
1678
        /* set "her" if appropriate */
1679
        runppr(rcx, ctx->voccxit, PRP_ISHER, 0);
1680
        if (runtostyp(rcx) == DAT_TRUE)
1681
        {
1682
            ctx->voccxher = ctx->voccxit;
1683
            if (dbg)
1684
                tioputs(ctx->voccxtio,
1685
                        "... [setting \"her\" to same object]\\n");
1686
        }
1687
        rundisc(rcx);
1688
    }
1689
    else if (cnt > 1)
1690
    {
1691
        ctx->voccxthc = cnt;
1692
        ctx->voccxit  = MCMONINV;
1693
        if (dbg) tioputs(ctx->voccxtio, ".. setting \"them\": [");
1694
        for (i = 0 ; i < cnt ; ++i)
1695
        {
1696
            ctx->voccxthm[i] = dolist[i].vocolobj;
1697
            if (dbg)
1698
            {
1699
                runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0);
1700
                tioputs(ctx->voccxtio, i+1 < cnt ? ", " : "]\\n");
1701
            }
1702
        }
1703
    }
1704
}
1705
1706
/* display a multiple-object prefix */
1707
void voc_multi_prefix(voccxdef *ctx, objnum objn,
1708
                      int show_prefix, int multi_flags,
1709
                      int cur_index, int count)
1710
{
1711
    runcxdef *rcx = ctx->voccxrun;
1712
1713
    /* if the object is invalid, ignore it */
1714
    if (objn == MCMONINV)
1715
        return;
1716
1717
    /* 
1718
     *   if there's a prefixdesc method defined, call it rather than the
1719
     *   older multisdesc (or even older sdesc) approach 
1720
     */
1721
    if (objgetap(ctx->voccxmem, objn, PRP_PREFIXDESC,
1722
                 (objnum *)0, FALSE) != 0)
1723
    {
1724
        runsdef val;
1725
1726
        /* push the word flags */
1727
        runpnum(rcx, multi_flags);
1728
1729
        /* 
1730
         *   push the object count and the current index (adjusted to a
1731
         *   1-based value) 
1732
         */
1733
        runpnum(rcx, count);
1734
        runpnum(rcx, cur_index + 1);
1735
        
1736
        /* push the 'show' flag */
1737
        val.runstyp = runclog(show_prefix);
1738
        runpush(rcx, val.runstyp, &val);
1739
1740
        /* call the method */
1741
        runppr(rcx, objn, PRP_PREFIXDESC, 4);
1742
1743
        /* we're done */
1744
        return;
1745
    }
1746
1747
    /* 
1748
     *   if we're not showing the prefix, don't use the multisdesc/sdesc
1749
     *   display 
1750
     */
1751
    if (!show_prefix)
1752
        return;
1753
    
1754
    /*
1755
     *   use multisdesc if defined (for compatibility with older games,
1756
     *   use sdesc if multisdesc doesn't exist for this object) 
1757
     */
1758
    if (objgetap(ctx->voccxmem, objn, PRP_MULTISDESC,
1759
                 (objnum *)0, FALSE) == 0)
1760
    {
1761
        /* there's no multisdesc defined - use the plain sdesc */
1762
        runppr(rcx, objn, PRP_SDESC, 0);
1763
    }
1764
    else
1765
    {
1766
        /* multisdesc is defined - use it */
1767
        runppr(rcx, objn, PRP_MULTISDESC, 0);
1768
    }
1769
1770
    /* show the colon */
1771
    vocerr_info(ctx, VOCERR(120), ": ");
1772
}
1773
1774
/* execute command for each object in direct object list */
1775
static int exeloop(voccxdef *ctx, objnum actor, objnum verb,
1776
                   vocoldef *dolist, objnum *prep, vocoldef *iobj,
1777
                   int multi_flags, uchar *tpl, int newstyle)
1778
{
1779
    runcxdef *rcx = ctx->voccxrun;
1780
    int       err;
1781
    int       i;
1782
    int       dobj_cnt;
1783
    int       exec_cnt;
1784
    vocoldef *dobj;
1785
1786
    /* 
1787
     *   count the direct objects; we'll iterate over the direct objects,
1788
     *   so we execute the command once per direct object 
1789
     */
1790
    exec_cnt = dobj_cnt = (dolist != 0 ? voclistlen(dolist) : 0);
1791
1792
    /* 
1793
     *   if there are no direct objects, we still must execute the command
1794
     *   once 
1795
     */
1796
    if (exec_cnt < 1)
1797
        exec_cnt = 1;
1798
1799
    /*
1800
     *   If we have multiple direct objects, or we're using "all" with
1801
     *   just one direct object, check with the verb to see if multiple
1802
     *   words are acceptable: call verb.rejectMultiDobj, and see what it
1803
     *   returns; if it returns true, don't allow multiple words, and
1804
     *   expect that rejectMultiDobj displayed an error message.
1805
     *   Otherwise, proceed.  
1806
     */
1807
    if (((multi_flags & VOCS_ALL) != 0 || dobj_cnt > 1)
1808
        && dolist && dolist[0].vocolobj != MCMONINV)
1809
    {
1810
        int typ;
1811
1812
        ERRBEGIN(ctx->voccxerr)
1813
            runrst(rcx);
1814
            if (!prep || *prep == MCMONINV)
1815
                runpnil(rcx);
1816
            else
1817
                runpobj(rcx, *prep);
1818
            runppr(rcx, verb, PRP_REJECTMDO, 1);
1819
            typ = runtostyp(rcx);
1820
            rundisc(rcx);
1821
        ERRCATCH(ctx->voccxerr, err)
1822
            if (err == ERR_RUNEXIT || err == ERR_RUNEXITOBJ
1823
                || err == ERR_RUNABRT)
1824
                return err;
1825
            else
1826
                errrse(ctx->voccxerr);
1827
        ERREND(ctx->voccxerr)
1828
1829
        /* if they returned 'true', don't bother continuing */
1830
        if (typ == DAT_TRUE)
1831
            return 0;
1832
    }
1833
1834
    /* 
1835
     *   execute the command the required number of times 
1836
     */
1837
    for (i = 0 ; i < exec_cnt ; ++i)
1838
    {
1839
        int show_multi_prefix;
1840
1841
        /* get the current direct object, if we have one */
1842
        dobj = (dolist != 0 ? &dolist[i] : 0);
1843
1844
        /*
1845
         *   If we have a number or string, set the current one in
1846
         *   numObj/strObj 
1847
         */
1848
        if (dolist != 0)
1849
        {
1850
            if (dolist[i].vocolflg == VOCS_STR)
1851
            {
1852
                /* it's a string - set strObj.value */
1853
                vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
1854
                          dolist[i].vocolfst + 1, &dolist[i], &dolist[i]);
1855
            }
1856
            else if (dolist[i].vocolflg == VOCS_NUM)
1857
            {
1858
                long v1, v2;
1859
1860
                /* it's a number - set numObj.value */
1861
                v1 = atol(dolist[i].vocolfst);
1862
                oswp4(&v2, v1);
1863
                vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, &v2,
1864
                          &dolist[i], &dolist[i]);
1865
            }
1866
        }
1867
1868
        /*
1869
         *   For cases where we have a bunch of direct objects (or even
1870
         *   one when "all" was used), we want to preface the output from
1871
         *   each iteration with the name of the object we're acting on
1872
         *   currently.  In other cases, there is no prefix.  
1873
         */
1874
        show_multi_prefix = ((multi_flags != 0 || dobj_cnt > 1) && dobj != 0);
1875
1876
        /* 
1877
         *   Execute the command for this object.  For every object except
1878
         *   the first, re-validate the direct and indirect objects.
1879
         *   There's no need to re-validate the objects on the first
1880
         *   object in a command, because that will already have been done
1881
         *   during object resolution. 
1882
         */
1883
        err = exe1cmd(ctx, actor, verb, dobj, prep, iobj,
1884
                      (i + 1 == exec_cnt), tpl, newstyle, FALSE,
1885
                      i != 0, i != 0, dolist, i, dobj_cnt,
1886
                      show_multi_prefix, multi_flags);
1887
1888
        /* check the error - ignore any verification failures */
1889
        switch(err)
1890
        {
1891
        case ERR_PRS_VERDO_FAIL:
1892
        case ERR_PRS_VERIO_FAIL:
1893
        case ERR_PRS_NO_VERDO:
1894
        case ERR_PRS_NO_VERIO:
1895
        case ERR_RUNEXITOBJ:
1896
        case ERR_RUNEXIT:
1897
            /* ignore the error and continue */
1898
            err = 0;
1899
            break;
1900
1901
        case ERR_RUNEXITPRECMD:
1902
            /* 
1903
             *   exited from preCommand - skip execution of subsequent
1904
             *   objects, but return success 
1905
             */
1906
            return 0;
1907
1908
        case 0:
1909
            /* no error; continue */
1910
            break;
1911
1912
        default:
1913
            /* anything else stops this command */
1914
            return err;
1915
        }
1916
1917
        /* flush output */
1918
        tioflush(ctx->voccxtio);
1919
    }
1920
1921
    /* success */
1922
    return 0;
1923
}
1924
1925
/*
1926
 *   Execute a command recursively.  Game code can call this routine
1927
 *   (indirectly through a built-in function) to execute a command, using
1928
 *   all of the same steps that would be applied for the command if the
1929
 *   player had typed it.
1930
 */
1931
int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb,
1932
                  objnum dobj, objnum prep, objnum iobj,
1933
                  int validate_dobj, int validate_iobj)
1934
{
1935
    int       err;
1936
    int       newstyle;
1937
    uchar     tpl[VOCTPL2SIZ];
1938
    vocoldef  dobjv;
1939
    vocoldef  iobjv;
1940
    voccxdef  ctx_copy;
1941
    runsdef *orig_sp;
1942
    runsdef *orig_bp;
1943
1944
    /*
1945
     *   Save the stack and base pointers as they are on entry.  Since
1946
     *   exe1cmd() is being called recursively, it won't automatically clear
1947
     *   the stack after it's done as it would at the top level; this means
1948
     *   that an aborted frame can be left on the stack if we throw an
1949
     *   'exit' or 'abort' in the course of executing the command.  To make
1950
     *   sure we don't leave any aborted frames on the stack before
1951
     *   returning to our caller, we simply need to restore the stack and
1952
     *   frame pointers on the way out as they were on the way in.  
1953
     */
1954
    orig_sp = ctx->voccxrun->runcxsp;
1955
    orig_bp = ctx->voccxrun->runcxbp;
1956
1957
    /* make a copy of the voc context, so that changes aren't permanent */
1958
    ctx_copy = *ctx;
1959
    ctx = &ctx_copy;
1960
1961
    /* 
1962
     *   there are no unknown words in the recursive command, since the
1963
     *   command was prepared directly from resolved objects 
1964
     */
1965
    ctx->voccxunknown = 0;
1966
1967
    /* set up the vocoldef structure for the direct object, if present */
1968
    if (dobj != MCMONINV)
1969
    {
1970
        dobjv.vocolobj = dobj;
1971
        dobjv.vocolfst = dobjv.vocollst = "";
1972
        dobjv.vocolflg = 0;
1973
    }
1974
1975
    /* set up the vocoldef structure for the indirect object, if present */
1976
    if (iobj != MCMONINV)
1977
    {
1978
        iobjv.vocolobj = iobj;
1979
        iobjv.vocolfst = iobjv.vocollst = "";
1980
        iobjv.vocolflg = 0;
1981
    }
1982
    
1983
    /* figure out which template we need, based on the objects provided */
1984
    if (dobj == MCMONINV)
1985
    {
1986
        uint actofs;
1987
        uint tplofs;
1988
        
1989
        /* 
1990
         *   No objects were provided - use the verb's "action" method.
1991
         *   Make sure that there is in fact an "action" method. 
1992
         */
1993
        exe_get_tpl(ctx, verb, &tplofs, &actofs);
1994
        if (actofs != 0)
1995
        {
1996
            /* execute the "action" method */
1997
            err = exe1cmd(ctx, actor, verb, 0, &prep, 0, FALSE,
1998
                          0, FALSE, TRUE, validate_dobj, validate_iobj,
1999
                          0, 0, 0, FALSE, 0);
2000
        }
2001
        else
2002
        {
2003
            /* indicate that the sentence structure wasn't understood */
2004
            err = ERR_PRS_SENT_UNK;
2005
        }
2006
    }
2007
    else if (iobj == MCMONINV)
2008
    {
2009
        /* 
2010
         *   No indirect object was provided, but a direct object is
2011
         *   present - use the one-object template.  First, look up the
2012
         *   template.  
2013
         */
2014
        if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
2015
        {
2016
            /* execute the command */
2017
            err = exe1cmd(ctx, actor, verb, &dobjv, &prep, 0, FALSE,
2018
                          tpl, newstyle, TRUE, validate_dobj, validate_iobj,
2019
                          &dobjv, 0, 1, FALSE, 0);
2020
        }
2021
        else
2022
        {
2023
            /* indicate that the sentence structure wasn't understood */
2024
            err = ERR_PRS_SENT_UNK;
2025
        }
2026
    }
2027
    else
2028
    {
2029
        /* 
2030
         *   Both a direct and indirect object were provided - find the
2031
         *   two-object template for the given preposition.
2032
         */
2033
        if (voctplfnd(ctx, verb, prep, tpl, &newstyle))
2034
        {
2035
            /* execute the command */
2036
            err = exe1cmd(ctx, actor, verb, &dobjv, &prep, &iobjv, FALSE,
2037
                          tpl, newstyle, TRUE, validate_dobj, validate_iobj,
2038
                          &dobjv, 0, 1, FALSE, 0);
2039
        }
2040
        else
2041
        {
2042
            /* indicate that the sentence structure wasn't understood */
2043
            err = ERR_PRS_SENT_UNK;
2044
        }
2045
    }
2046
2047
    /* 
2048
     *   if the error was EXITPRECMD, change it to EXIT - EXITPRECMD is a
2049
     *   special flag indicating that we exited from a preCommand
2050
     *   function, which is different than normal exiting internally but
2051
     *   not to the game 
2052
     */
2053
    if (err == ERR_RUNEXITPRECMD)
2054
        err = ERR_RUNEXIT;
2055
2056
    /*
2057
     *   restore the original stack and base pointers, to ensure that we
2058
     *   don't leave any aborted frames on the stack 
2059
     */
2060
    ctx->voccxrun->runcxsp = orig_sp;
2061
    ctx->voccxrun->runcxbp = orig_bp;
2062
2063
    /* return the result code */
2064
    return err;
2065
}
2066
2067
2068
/*
2069
 *   Check for ALL, ANY, or THEM in the list - use multi-mode if found,
2070
 *   even if we have only one object.  Returns a combination of any of the
2071
 *   VOCS_ALL, VOCS_ANY, or VOCS_THEM flags that we find.  
2072
 */
2073
static int check_for_multi(vocoldef *dolist)
2074
{
2075
    int dolen;
2076
    int i;
2077
    int result;
2078
2079
    /* presume we won't find any flags */
2080
    result = 0;
2081
2082
    /* 
2083
     *   scan the list for ALL, ANY, or THEM flags, combining any such
2084
     *   flags we find into the result 
2085
     */
2086
    dolen = voclistlen(dolist);
2087
    for (i = 0 ; i < dolen ; ++i)
2088
        result |= (dolist[i].vocolflg & (VOCS_ALL | VOCS_ANY | VOCS_THEM));
2089
2090
    /* return the result */
2091
    return result;
2092
}
2093
2094
/* ------------------------------------------------------------------------ */
2095
/*
2096
 *   Try running the preparseCmd user function.  Returns 0 if the
2097
 *   function doesn't exist or returns 'true', ERR_PREPRSCMDCAN if it
2098
 *   returns 'nil' (and thus wants to cancel the command), and
2099
 *   ERR_PREPRSCMDREDO if it returns a list (and thus wants to redo the
2100
 *   command). 
2101
 */
2102
int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
2103
                     uchar **preparse_list)
2104
{
2105
    uchar    listbuf[VOCBUFSIZ + 2 + 3*VOCBUFSIZ];
2106
    int      i;
2107
    uchar   *p;
2108
    size_t   len;
2109
    runsdef  val;
2110
    int      typ;
2111
    int      err;
2112
2113
    /* if there's no preparseCmd, keep processing */
2114
    if (ctx->voccxppc == MCMONINV)
2115
        return 0;
2116
    
2117
    /* build a list of the words */
2118
    for (p = listbuf + 2, i = 0 ; i < wrdcnt ; ++i)
2119
    {
2120
        char *src;
2121
        int add_quote;
2122
        
2123
        /* check for strings - they require special handling */
2124
        if (cmd[i][0] == '"')
2125
        {
2126
            /* 
2127
             *   it's a string - what follows is a run-time style string,
2128
             *   with a length prefix followed by the text of the string 
2129
             */
2130
            len = osrp2(cmd[i] + 1) - 2;
2131
            src = cmd[i] + 3;
2132
2133
            /* add quotes to the result */
2134
            add_quote = TRUE;
2135
        }
2136
        else
2137
        {
2138
            /* ordinary word - copy directly */
2139
            src = (char *)cmd[i];
2140
2141
            /* it's a null-terminated string */
2142
            len = strlen(src);
2143
2144
            /* don't add quotes to the result */
2145
            add_quote = FALSE;
2146
        }
2147
2148
        /* write the type prefix */
2149
        *p++ = DAT_SSTRING;
2150
2151
        /* write the length prefix */
2152
        oswp2(p, len + 2 + (add_quote ? 2 : 0));
2153
        p += 2;
2154
2155
        /* add an open quote if necessary */
2156
        if (add_quote)
2157
            *p++ = '"';
2158
2159
        /* copy the text */
2160
        memcpy(p, src, len);
2161
        p += len;
2162
2163
        /* add the closing quote if necessary */
2164
        if (add_quote)
2165
            *p++ = '"';
2166
    }
2167
    
2168
    /* set the length of the whole list */
2169
    len = p - listbuf;
2170
    oswp2(listbuf, len);
2171
    
2172
    /* push the list as the argument, and call the user's preparseCmd */
2173
    val.runstyp = DAT_LIST;
2174
    val.runsv.runsvstr = listbuf;
2175
    runpush(ctx->voccxrun, DAT_LIST, &val);
2176
2177
    /* presume that no error will occur */
2178
    err = 0;
2179
2180
    /* catch errors that occur within preparseCmd */
2181
    ERRBEGIN(ctx->voccxerr)
2182
    {
2183
        /* call preparseCmd */
2184
        runfn(ctx->voccxrun, ctx->voccxppc, 1);
2185
    }
2186
    ERRCATCH(ctx->voccxerr, err)
2187
    {
2188
        /* 
2189
         *   if it's abort/exit/exitobj, just return it; for any other
2190
         *   errors, just re-throw the same error 
2191
         */
2192
        switch(err)
2193
        {
2194
        case ERR_RUNABRT:
2195
        case ERR_RUNEXIT:
2196
        case ERR_RUNEXITOBJ:
2197
            /* simply return these errors to the caller */
2198
            break;
2199
2200
        default:
2201
            /* re-throw anything else */
2202
            errrse(ctx->voccxerr);
2203
        }
2204
    }
2205
    ERREND(ctx->voccxerr);
2206
2207
    /* if an error occurred, return the error code */
2208
    if (err != 0)
2209
        return err;
2210
2211
    /* get the result */
2212
    typ = runtostyp(ctx->voccxrun);
2213
    
2214
    /* if they returned a list, it's a new command to execute */
2215
    if (typ == DAT_LIST)
2216
    {
2217
        /* get the list and give it to the caller */
2218
        *preparse_list = runpoplst(ctx->voccxrun);
2219
        
2220
        /* 
2221
         *   indicate that the command is to be reparsed with the new word
2222
         *   list 
2223
         */
2224
        return ERR_PREPRSCMDREDO;
2225
    }
2226
2227
    /* for any other type, we don't need the value, so discard it */
2228
    rundisc(ctx->voccxrun);
2229
2230
    /* if the result is nil, don't process this command further */
2231
    if (typ == DAT_NIL)
2232
        return ERR_PREPRSCMDCAN;
2233
    else
2234
        return 0;
2235
}
2236
2237
2238
/* ------------------------------------------------------------------------ */
2239
/*
2240
 *   Call parseAskobjIndirect 
2241
 */
2242
static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
2243
                                objnum actor, objnum verb, objnum prep)
2244
{
2245
    int cnt;
2246
    int i;
2247
    size_t len;
2248
    uchar *lstp;
2249
    
2250
    /*
2251
     *   Generate the direct object list argument.  This argument is a
2252
     *   list of lists.  For each noun phrase, we generate one sublist in
2253
     *   the main list.  Each sublist itself consists of three
2254
     *   sub-sublists: first, a list of strings giving the words in the
2255
     *   noun phrase; second, a list of the objects matching the noun
2256
     *   phrase; third, a list of the flags for the matching objects.
2257
     *   
2258
     *   So, if the player typed "put red box and blue ball", we might
2259
     *   generate a list something like this:
2260
     *   
2261
     *   [ [ ['red', 'box'], [redBox1, redBox2], [0, 0] ], [ ['blue',
2262
     *   'ball'], [blueBall], [0, 0] ] ] 
2263
     */
2264
2265
    /*
2266
     *   First, figure out how much space we need for this list of lists
2267
     *   of lists.  Scan the direct object list for distinct noun phrases
2268
     *   - we need one sublist for each distinct noun phrase.
2269
     */
2270
    cnt = voclistlen(dolist);
2271
    for (len = 0, i = 0 ; i < cnt ; )
2272
    {
2273
        char *p;
2274
        size_t curlen;
2275
        int j;
2276
            
2277
        /* 
2278
         *   we need the sublist type prefix (one byte) plus the sublist
2279
         *   length prefix (two bytes), plus the type and length prefixes
2280
         *   (one plus two bytes) for each of the three sub-sublist 
2281
         */
2282
        len += (1+2) + 3*(1+2);
2283
2284
        /* 
2285
         *   we need space to store the strings for the words in this noun
2286
         *   phrase 
2287
         */
2288
        for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
2289
             p += curlen + 1)
2290
        {
2291
            /* 
2292
             *   add in the space needed for this string element in the
2293
             *   sub-sublist - we need one byte for the type prefix, two
2294
             *   bytes for the length prefix, and the bytes for the string
2295
             *   itself 
2296
             */
2297
            curlen = strlen(p);
2298
            len += (1+2) + curlen;
2299
        }
2300
2301
        /* 
2302
         *   scan each object for this same noun phrase (i.e., for which
2303
         *   the vocabulary words are the same) 
2304
         */
2305
        for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
2306
             ++j)
2307
        {
2308
            /* 
2309
             *   Add in space for this object in the sub-sublist for the
2310
             *   current noun phrase.  If this object is nil, we need only
2311
             *   one byte for the type; otherwise, we need one byte for
2312
             *   the type prefix plus two bytes for the object ID.  
2313
             */
2314
            if (dolist[i].vocolobj == MCMONINV)
2315
                len += 1;
2316
            else
2317
                len += (1 + 2);
2318
            
2319
            /*
2320
             *   Add in space for the flags sub-sublist for the current
2321
             *   object.  We need one byte for the type and four for the
2322
             *   integer value.  
2323
             */
2324
            len += (1 + 4);
2325
        }
2326
2327
        /* skip to the next distinct noun phrase */
2328
        i = j;
2329
    }
2330
2331
    /* allocate the list */
2332
    lstp = voc_push_list_siz(ctx, len);
2333
2334
    /* 
2335
     *   Go through our object array again, and this time actually build
2336
     *   the list.  
2337
     */
2338
    for (i = 0 ; i < cnt ; )
2339
    {
2340
        char *p;
2341
        uchar *subp;
2342
        uchar *subsubp;
2343
        size_t curlen;
2344
        int j;
2345
2346
        /* start the sublist with the type prefix */
2347
        *lstp++ = DAT_LIST;
2348
2349
        /* leave a placeholder for our length prefix */
2350
        subp = lstp;
2351
        lstp += 2;
2352
2353
        /* start the sub-sublist with the word strings */
2354
        *lstp++ = DAT_LIST;
2355
        subsubp = lstp;
2356
        lstp += 2;
2357
2358
        /* store the word strings in the sub-sublist */
2359
        for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
2360
             p += curlen + 1)
2361
        {
2362
            /* get this string's length */
2363
            curlen = strlen(p);
2364
2365
            /* store the type and length prefixes */
2366
            *lstp++ = DAT_SSTRING;
2367
            oswp2(lstp, curlen + 2);
2368
            lstp += 2;
2369
2370
            /* store the string */
2371
            memcpy(lstp, p, curlen);
2372
            lstp += curlen;
2373
        }
2374
2375
        /* fix up the string sub-sublist length */
2376
        oswp2(subsubp, lstp - subsubp);
2377
2378
        /* start the second sub-sublist, for the objects */
2379
        *lstp++ = DAT_LIST;
2380
        subsubp = lstp;
2381
        lstp += 2;
2382
2383
        /* write each object */
2384
        for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
2385
             ++j)
2386
        {
2387
            /* 
2388
             *   if this object isn't nil, write it to the sub-sublist;
2389
             *   otherwise, just put nil in the sub-sublist 
2390
             */
2391
            if (dolist[j].vocolobj != MCMONINV)
2392
            {
2393
                *lstp++ = DAT_OBJECT;
2394
                oswp2(lstp, dolist[j].vocolobj);
2395
                lstp += 2;
2396
            }
2397
            else
2398
            {
2399
                /* no object - just store nil */
2400
                *lstp++ = DAT_NIL;
2401
            }
2402
        }
2403
2404
        /* fix up the object sub-sublist length */
2405
        oswp2(subsubp, lstp - subsubp);
2406
2407
        /* start the third sub-sublist, for the flags */
2408
        *lstp++ = DAT_LIST;
2409
        subsubp = lstp;
2410
        lstp += 2;
2411
2412
        /* write each object's flags */
2413
        for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
2414
             ++j)
2415
        {
2416
            /* write the flags */
2417
            *lstp++ = DAT_NUMBER;
2418
            oswp4(lstp, dolist[j].vocolflg);
2419
            lstp += 4;
2420
        }
2421
2422
        /* fix up the flag sub-sublist length */
2423
        oswp2(subsubp, lstp - subsubp);
2424
2425
        /* skip to the start of the next distinct noun phrase */
2426
        i = j;
2427
2428
        /* fix up the sublist length */
2429
        oswp2(subp, lstp - subp);
2430
    }
2431
2432
    /* push the prep, verb, and actor arguments */
2433
    runpobj(ctx->voccxrun, prep);
2434
    runpobj(ctx->voccxrun, verb);
2435
    runpobj(ctx->voccxrun,
2436
            (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
2437
2438
    /* call the function */
2439
    runfn(ctx->voccxrun, ctx->voccxpask3, 4);
2440
}
2441
2442
2443
/* ------------------------------------------------------------------------ */
2444
/*
2445
 *   execmd() - executes a user's command given the verb's verb and
2446
 *   preposition words, a list of nouns to be used as indirect objects,
2447
 *   and a list to be used for direct objects.  The globals cmdActor and
2448
 *   cmdPrep should already be set.  This routine tries to find a template
2449
 *   for the verb which matches the player's command.  If no template
2450
 *   matches, we try (using default objects and, if that fails, requests
2451
 *   to the player for objects) to fill in any missing information in the
2452
 *   player's command.  If that still fails, we will say we don't
2453
 *   understand the sentence and leave it at that.  
2454
 */
2455
int execmd(voccxdef *ctx, objnum actor, objnum prep,
2456
           char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist,
2457
           char **cmd, int *typelist,
2458
           char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_word)
2459
{
2460
    objnum    verb;
2461
    objnum    iobj;
2462
    int       multi_flags = 0;
2463
    vocwdef  *n;
2464
    int       cnt;
2465
    vocoldef *newnoun;
2466
    int       next;
2467
    char     *exenewcmd;
2468
    char     *donewcmd;
2469
    char     *ionewcmd;
2470
    char     *exenewbuf;
2471
    char     *donewbuf;
2472
    char     *ionewbuf;
2473
    char    **exenewlist;
2474
    char    **donewlist;
2475
    char    **ionewlist;
2476
    int      *exenewtype;
2477
    int      *donewtype;
2478
    int      *ionewtype;
2479
    vocoldef *dolist1;
2480
    vocoldef *iolist1;
2481
    uchar     tpl[VOCTPL2SIZ];
2482
    int       foundtpl;        /* used to determine success of tpl searches */
2483
    runcxdef *rcx = ctx->voccxrun;
2484
    uint      tplofs;                          /* offset of template object */
2485
    uint      actofs;                        /* offset of 'action' property */
2486
    int       askflags;                /* flag for what we need to ask user */
2487
    int       newstyle;   /* flag indicating new-style template definitions */
2488
    int       tplflags;
2489
    int       err;
2490
    uchar    *save_sp;
2491
2492
    /* run preparseCmd */
2493
    switch(try_preparse_cmd(ctx, cmd, wrdcnt, preparse_list))
2494
    {
2495
    case 0:
2496
        /* proceed with the command */
2497
        break;
2498
2499
    case ERR_PREPRSCMDCAN:
2500
        /* command cancelled */
2501
        return 0;
2502
2503
    case ERR_RUNEXIT:
2504
    case ERR_RUNABRT:
2505
    case ERR_RUNEXITOBJ:
2506
        /* abort/exit/exitobj - treat this the same as command cancellation */
2507
        return 0;
2508
2509
    case ERR_PREPRSCMDREDO:
2510
        /* redo the command - so indicate to the caller */
2511
        return ERR_PREPRSCMDREDO;
2512
    }
2513
2514
    /* look up the verb based on the verb and verb-prep */
2515
    n = vocffw(ctx, vverb, (int)strlen(vverb),
2516
               vprep, (vprep ? (int)strlen(vprep) : 0), PRP_VERB,
2517
               (vocseadef *)0);
2518
2519
    /* if we didn't find a verb template, we can't process the sentence */
2520
    if (n == 0)
2521
    {
2522
        /* try parseUnknownVerb, and show an error if that doesn't handle it */
2523
        if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word,
2524
                             TRUE, VOCERR(18),
2525
                             "I don't understand that sentence."))
2526
        {
2527
            /* they handled it successfully - end the command with success */
2528
            return 0;
2529
        }
2530
        else
2531
        {
2532
            /* 
2533
             *   parseUnknownVerb failed or aborted - end the command with
2534
             *   an error 
2535
             */
2536
            return 1;
2537
        }
2538
    }
2539
2540
    /* get the deepverb object */
2541
    verb = n->vocwobj;
2542
2543
    /* default actor is "Me" */
2544
    if (actor == MCMONINV)
2545
        actor = ctx->voccxme;
2546
    
2547
    /* set a savepoint, if we're keeping undo information */
2548
    if (ctx->voccxundo)
2549
        objusav(ctx->voccxundo);
2550
2551
    /*
2552
     *   Check that the room will allow this command -- it may not
2553
     *   due to darkness or other ailment.  We can find out with the
2554
     *   roomCheck(verb) message, sent to the meobj.  
2555
     */
2556
    {
2557
        int t;
2558
2559
        /* call roomCheck */
2560
        runrst(rcx);
2561
        runpobj(rcx, verb);
2562
        runppr(rcx, ctx->voccxme, PRP_ROOMCHECK, 1);
2563
        t = runpoplog(rcx);
2564
2565
        /* if they returned nil, stop the command, but indicate success */
2566
        if (!t)
2567
            return 0;
2568
    }
2569
2570
    /* look for a new-style template first, then the old-style template */
2571
    exe_get_tpl(ctx, verb, &tplofs, &actofs);
2572
    
2573
    /* make sure we found a verb */
2574
    if (tplofs == 0 && actofs == 0 && verb != ctx->voccxvag)
2575
    {
2576
        /* try parseUnknownVerb, and show an error if that doesn't handle it */
2577
        if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word,
2578
                             TRUE, VOCERR(23),
2579
                 "internal error: verb has no action, doAction, or ioAction"))
2580
            return 0;
2581
        else
2582
            return 1;
2583
    }
2584
2585
    /*
2586
     *   Check to see if we have an "all" - if we do, we'll need to
2587
     *   display the direct object's name even if only one direct object
2588
     *   comes of it.  
2589
     */
2590
    multi_flags = check_for_multi(dolist);
2591
2592
    /* 
2593
     *   set up dobj word list in case objwords is used in doDefault (the
2594
     *   game may want to check for "all" and disallow it, for example)
2595
     */
2596
    ctx->voccxdobj = dolist;
2597
2598
    /* set up our stack allocations, which we may need from now on */
2599
    voc_enter(ctx, &save_sp);
2600
    VOC_STK_ARRAY(ctx, char,     donewcmd,  VOCBUFSIZ);
2601
    VOC_STK_ARRAY(ctx, char,     ionewcmd,  VOCBUFSIZ);
2602
    VOC_STK_ARRAY(ctx, char,     donewbuf,  2*VOCBUFSIZ);
2603
    VOC_STK_ARRAY(ctx, char,     ionewbuf,  2*VOCBUFSIZ);
2604
    VOC_STK_ARRAY(ctx, char *,   donewlist, VOCBUFSIZ);
2605
    VOC_STK_ARRAY(ctx, char *,   ionewlist, VOCBUFSIZ);
2606
    VOC_MAX_ARRAY(ctx, int,      donewtype);
2607
    VOC_MAX_ARRAY(ctx, int,      ionewtype);
2608
    VOC_MAX_ARRAY(ctx, vocoldef, dolist1);
2609
    VOC_MAX_ARRAY(ctx, vocoldef, iolist1);
2610
2611
    /* keep going until we're done with the sentence */
2612
    for ( ;; )
2613
    {
2614
        askflags = err = 0;
2615
        
2616
        ERRBEGIN(ctx->voccxerr)
2617
        
2618
        /*
2619
         *   Now see what kind of sentence we have.  If we have no
2620
         *   objects and an action, use the action.  If we have a direct
2621
         *   object and a doAction, use the doAction.  If we have an
2622
         *   indirect object and an ioAction with a matching preposition,
2623
         *   use the ioAction.  If we have an indirect object and no
2624
         *   matching ioAction, complain.  If we have a direct object and
2625
         *   no doAction or ioAction, complain.  If we have fewer objects
2626
         *   than we really want, ask the user for more of them.  
2627
         */
2628
        if (voclistlen(dolist) == 0 && voclistlen(iolist) == 0)
2629
        {
2630
            if (actofs || verb == ctx->voccxvag)
2631
            {
2632
                if ((err = exeloop(ctx, actor, verb, (vocoldef *)0, &prep,
2633
                                   (vocoldef *)0, multi_flags,
2634
                                   (uchar *)0, 0)) != 0)
2635
                    goto exit_error;
2636
            }
2637
            else
2638
            {
2639
                /*
2640
                 *   The player has not specified any objects, but the
2641
                 *   verb seems to require one.  See if there's a unique
2642
                 *   default.  
2643
                 */
2644
                runrst(rcx);
2645
                runpnil(rcx);
2646
                runpobj(rcx, prep);
2647
                runpobj(rcx, actor);
2648
                runppr(rcx, verb, PRP_DODEFAULT, 3);
2649
                
2650
                if (runtostyp(rcx) == DAT_LIST)
2651
                {
2652
                    uchar   *l = runpoplst(rcx);
2653
                    uint     lstsiz;
2654
                    objnum   defobj;
2655
                    int      objcnt;
2656
                    objnum   newprep;
2657
                    runsdef  val;
2658
                    objnum   o;
2659
                    
2660
                    /* push list back on stack, to keep in heap */
2661
                    val.runsv.runsvstr = l;
2662
                    val.runstyp = DAT_LIST;
2663
                    runrepush(rcx, &val);
2664
                    
2665
                    /* get list size out of list */
2666
                    lstsiz = osrp2(l) - 2;
2667
                    l += 2;
2668
2669
                    /* find default preposition for verb, if any */
2670
                    runppr(rcx, verb, PRP_PREPDEFAULT, 0);
2671
                    if (runtostyp(rcx) == DAT_OBJECT)
2672
                        newprep = runpopobj(rcx);
2673
                    else
2674
                    {
2675
                        newprep = MCMONINV;
2676
                        rundisc(rcx);
2677
                    }
2678
                    
2679
                    if (!voctplfnd(ctx, verb, newprep, tpl, &newstyle))
2680
                    {
2681
                        for (objcnt = 0 ; lstsiz && objcnt < 2
2682
                             ; lstadv(&l, &lstsiz))
2683
                        {
2684
                            if (*l == DAT_OBJECT)
2685
                            {
2686
                                ++objcnt;
2687
                                defobj = osrp2(l + 1);
2688
                            }
2689
                        }
2690
                    }
2691
                    else
2692
                    {
2693
                        int dobj_first;
2694
                        
2695
                        /*
2696
                         *   Get the template flags.  If we must
2697
                         *   disambiguate the direct object first for this
2698
                         *   verb, do so now. 
2699
                         */
2700
                        tplflags = (newstyle ? voctplflg(tpl) : 0);
2701
                        dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
2702
2703
                        for (objcnt = 0 ; lstsiz && objcnt < 2
2704
                             ; lstadv(&l, &lstsiz))
2705
                        {
2706
                            if (*l == DAT_OBJECT)
2707
                            {
2708
                                o = osrp2(l + 1);
2709
                                if (!objgetap(ctx->voccxmem, o, voctplvd(tpl),
2710
                                              (objnum *)0, FALSE))
2711
                                    continue;
2712
                                
2713
                                tiohide(ctx->voccxtio);
2714
                                if (newprep != MCMONINV && !dobj_first)
2715
                                    runpnil(rcx);
2716
                                runpobj(rcx, actor);
2717
                                runppr(rcx, o, voctplvd(tpl),
2718
                                       ((newprep != MCMONINV && !dobj_first)
2719
                                        ? 2 : 1));
2720
                                
2721
                                if (!tioshow(ctx->voccxtio))
2722
                                {
2723
                                    ++objcnt;
2724
                                    defobj = o;
2725
                                }
2726
                            }
2727
                        }
2728
                        
2729
                        /* no longer need list in heap, so discard it */
2730
                        rundisc(rcx);
2731
                
2732
                        /* use default object if there's exactly one */
2733
                        if (objcnt == 1)
2734
                        {
2735
                            dolist[0].vocolobj = defobj;
2736
                            dolist[0].vocolflg = 0;
2737
                            dolist[0].vocolfst = dolist[0].vocollst = 0;
2738
                            dolist[1].vocolobj = MCMONINV;
2739
                            dolist[1].vocolflg = 0;
2740
                            dolist[1].vocolfst = dolist[1].vocollst = 0;
2741
2742
                            runrst(rcx);
2743
                            if (ctx->voccxpdef2 != MCMONINV)
2744
                            {
2745
                                runpnil(rcx);
2746
                                runpobj(rcx, defobj);
2747
                                runpobj(rcx, verb);
2748
                                runpobj(rcx, actor);
2749
                                runfn(rcx, ctx->voccxpdef2, 4);
2750
                            }
2751
                            else if (ctx->voccxpdef != MCMONINV)
2752
                            {
2753
                                runpnil(rcx);
2754
                                runpobj(rcx, defobj);
2755
                                runfn(rcx, ctx->voccxpdef, 2);
2756
                            }
2757
                            else
2758
                            {
2759
                                /* tell the player what we're doing */
2760
                                vocerr_info(ctx, VOCERR(130), "(");
2761
                                runppr(rcx, defobj, PRP_THEDESC, 0);
2762
                                vocerr_info(ctx, VOCERR(131), ")");
2763
                                tioflush(ctx->voccxtio);
2764
                            }
2765
                            err = -2;                         /* "continue" */
2766
                            goto exit_error;
2767
                        }
2768
                    }
2769
                }
2770
                else
2771
                    rundisc(rcx);
2772
            
2773
                /*
2774
                 *   No unique default; ask the player for a direct
2775
                 *   object, and try the command again if he is kind
2776
                 *   enough to provide one.  
2777
                 */
2778
                askflags = ERR_RUNASKD;
2779
            }
2780
        }
2781
        else if (voclistlen(iolist) == 0)
2782
        {
2783
            /* direct object(s), but no indirect object -- find doAction */
2784
            if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
2785
            {
2786
                /* disambiguate the direct object list, now that we can */
2787
                if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
2788
                                PRP_VALIDDO, voctplvd(tpl), cmd, MCMONINV,
2789
                                actor, verb, prep, cmdbuf, FALSE))
2790
                {
2791
                    err = -1;
2792
                    goto exit_error;
2793
                }
2794
                iobj = MCMONINV;
2795
2796
                /*
2797
                 *   save the disambiguated direct object list, in case
2798
                 *   we hit an askio in the course of processing it 
2799
                 */
2800
                memcpy(dolist, dolist1,
2801
                       (size_t)(voclistlen(dolist1) + 1)*sizeof(dolist[0]));
2802
2803
                /* re-check for multi-mode */
2804
                if (multi_flags == 0)
2805
                    multi_flags = check_for_multi(dolist1);
2806
                
2807
                /* save it/them/him/her, and execute the command */
2808
                exesaveit(ctx, dolist1);
2809
                if ((err = exeloop(ctx, actor, verb, dolist1, &prep,
2810
                                   (vocoldef *)0, multi_flags,
2811
                                   tpl, newstyle)) != 0)
2812
                    goto exit_error;
2813
            }
2814
            else
2815
            {
2816
                /* no doAction - we'll need to find an indirect object */
2817
                runrst(rcx);
2818
                runppr(rcx, verb, PRP_PREPDEFAULT, 0);
2819
                if (runtostyp(rcx) != DAT_OBJECT)
2820
                {
2821
                    /* discard the result */
2822
                    rundisc(rcx);
2823
2824
                    /* call parseUnknownVerb to handle it */
2825
                    if (try_unknown_verb(ctx, actor, cmd, typelist,
2826
                                         wrdcnt, next_word, TRUE, VOCERR(24),
2827
                                         "I don't recognize that sentence."))
2828
                    {
2829
                        /* handled - end the command successfully */
2830
                        err = 0;
2831
                    }
2832
                    else
2833
                    {
2834
                        /* not handled - indicate failure */
2835
                        err = -1;
2836
                    }
2837
                    goto exit_error;
2838
                }
2839
                prep = runpopobj(rcx);
2840
            
2841
                runrst(rcx);
2842
                runpobj(rcx, prep);
2843
                runpobj(rcx, actor);
2844
                runppr(rcx, verb, PRP_IODEFAULT, 2);
2845
                
2846
                if (runtostyp(rcx) == DAT_LIST)
2847
                {
2848
                    uchar   *l = runpoplst(rcx);
2849
                    uint     lstsiz;
2850
                    objnum   defobj;
2851
                    int      objcnt;
2852
                    runsdef  val;
2853
                    objnum   o;
2854
                    
2855
                    /* push list back on stack, to keep in heap */
2856
                    val.runsv.runsvstr = l;
2857
                    val.runstyp = DAT_LIST;
2858
                    runrepush(rcx, &val);
2859
                    
2860
                    /* get list size out of list */
2861
                    lstsiz = osrp2(l) - 2;
2862
                    l += 2;
2863
                    
2864
                    if (!voctplfnd(ctx, verb, prep, tpl, &newstyle))
2865
                    {
2866
                        for (objcnt = 0 ; lstsiz && objcnt < 2
2867
                             ; lstadv(&l, &lstsiz))
2868
                        {
2869
                            if (*l == DAT_OBJECT)
2870
                            {
2871
                                objcnt++;
2872
                                defobj = osrp2(l + 1);
2873
                            }
2874
                        }
2875
                    }
2876
                    else
2877
                    {
2878
                        int dobj_first;
2879
                        
2880
                        /*
2881
                         *   Get the template flags.  If we must
2882
                         *   disambiguate the direct object first for this
2883
                         *   verb, do so now. 
2884
                         */
2885
                        tplflags = (newstyle ? voctplflg(tpl) : 0);
2886
                        dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
2887
                        if (dobj_first)
2888
                        {
2889
                            if (vocdisambig(ctx, dolist1, dolist,
2890
                                            PRP_DODEFAULT, PRP_VALIDDO,
2891
                                            voctplvd(tpl), cmd, MCMONINV,
2892
                                            actor, verb, prep, cmdbuf,
2893
                                            FALSE))
2894
                            {
2895
                                err = -1;
2896
                                goto exit_error;
2897
                            }
2898
2899
                            /* only one direct object is allowed here */
2900
                            if (voclistlen(dolist1) > 1)
2901
                            {
2902
                                vocerr(ctx, VOCERR(28),
2903
                          "You can't use multiple objects with this command.");
2904
                                err = -1;
2905
                                goto exit_error;
2906
                            }
2907
2908
                            /* save the object in the original list */
2909
                            memcpy(dolist, dolist1,
2910
                                   (size_t)(2 * sizeof(dolist[0])));
2911
                        }
2912
                        
2913
                        for (objcnt = 0 ; lstsiz && objcnt < 2
2914
                             ; lstadv(&l, &lstsiz))
2915
                        {
2916
                            if (*l == DAT_OBJECT)
2917
                            {
2918
                                o = osrp2(l + 1);
2919
                                if (!objgetap(ctx->voccxmem, o, voctplvi(tpl),
2920
                                              (objnum *)0, FALSE))
2921
                                    continue;
2922
                                
2923
                                tiohide(ctx->voccxtio);
2924
                                if (dobj_first)
2925
                                    runpobj(rcx, dolist[0].vocolobj);
2926
                                runpobj(rcx, actor);
2927
                                runppr(rcx, o, voctplvi(tpl),
2928
                                       (dobj_first ? 2 : 1));
2929
                                if (!tioshow(ctx->voccxtio))
2930
                                {
2931
                                    objcnt++;
2932
                                    defobj = o;
2933
                                }
2934
                            }
2935
                        }
2936
                    }
2937
                    
2938
                    /* no longer need list in heap, so discard it */
2939
                    rundisc(rcx);
2940
2941
                    /* if there's exactly one default object, use it */
2942
                    if (objcnt == 1)
2943
                    {
2944
                        iolist[0].vocolobj = defobj;
2945
                        iolist[0].vocolflg = 0;
2946
                        iolist[0].vocolfst = iolist[0].vocollst = 0;
2947
                        iolist[1].vocolobj = MCMONINV;
2948
                        iolist[1].vocolflg = 0;
2949
                        iolist[1].vocolfst = iolist[1].vocollst = 0;
2950
                    
2951
                        /* tell the user what we're assuming */
2952
                        runrst(rcx);
2953
                        if (ctx->voccxpdef2 != MCMONINV)
2954
                        {
2955
                            runpobj(rcx, prep);
2956
                            runpobj(rcx, defobj);
2957
                            runpobj(rcx, verb);
2958
                            runpobj(rcx, actor);
2959
                            runfn(rcx, ctx->voccxpdef2, 4);
2960
                        }
2961
                        else if (ctx->voccxpdef != MCMONINV)
2962
                        {
2963
                            runpobj(rcx, prep);
2964
                            runpobj(rcx, defobj);
2965
                            runfn(rcx, ctx->voccxpdef, 2);
2966
                        }
2967
                        else
2968
                        {
2969
                            vocerr_info(ctx, VOCERR(130), "(");
2970
                            runppr(rcx, prep, PRP_SDESC, 0);
2971
                            vocerr_info(ctx, VOCERR(132), " ");
2972
                            runppr(rcx, defobj, PRP_THEDESC, 0);
2973
                            vocerr_info(ctx, VOCERR(131), ")");
2974
                        }
2975
                        tioflush(ctx->voccxtio);
2976
                        err = -2;                             /* "continue" */
2977
                        goto exit_error;
2978
                    }
2979
                }
2980
                else
2981
                    rundisc(rcx);
2982
            
2983
                /*
2984
                 *   We didn't get a unique default indirect object, so
2985
                 *   we should ask the player for an indirct object, and
2986
                 *   repeat the command should he provide one.  
2987
                 */
2988
                askflags = ERR_RUNASKI;
2989
            }
2990
        }
2991
        else
2992
        {
2993
            objnum otherobj;
2994
            
2995
            /* find the template for this verb/prep combination */
2996
            if (!voctplfnd(ctx, verb, prep, tpl, &newstyle))
2997
            {
2998
                vocoldef *np1;
2999
                
3000
                /* 
3001
                 *   If we could have used the preposition in the first noun
3002
                 *   phrase rather than in the verb, and this would have
3003
                 *   yielded a valid verb phrase, the error is "I don't see
3004
                 *   any <noun phrase> here".
3005
                 *   
3006
                 *   Otherwise, it's a verb phrasing error.  In this case,
3007
                 *   call parseUnknownVerb to handle the error; the default
3008
                 *   error is "I don't recognize that sentence".  
3009
                 */
3010
                np1 = dolist[0].vocolfst < iolist[0].vocolfst
3011
                      ? dolist : iolist;
3012
                if ((np1->vocolflg & VOCS_TRIMPREP) != 0)
3013
                {
3014
                    char namebuf[VOCBUFSIZ];
3015
                    
3016
                    /* 
3017
                     *   it's a trimmed prep phrase, so we actually have an
3018
                     *   unmatched object - report the error 
3019
                     */
3020
                    voc_make_obj_name_from_list(
3021
                        ctx, namebuf, cmd, np1->vocolfst, np1->vocolhlst);
3022
                    vocerr(ctx, VOCERR(9), "I don't see any %s here.",
3023
                           namebuf);
3024
3025
                    /* terminate the command with an error */
3026
                    err = -1;
3027
                }
3028
                else if (try_unknown_verb(ctx, actor, cmd, typelist,
3029
                                     wrdcnt, next_word, TRUE,
3030
                                     VOCERR(24),
3031
                                     "I don't recognize that sentence."))
3032
                {
3033
                    /* they handled it - terminate command successfully */
3034
                    err = 0;
3035
                }
3036
                else
3037
                {
3038
                    /* that failed - terminate the command with an error */
3039
                    err = -1;
3040
                }
3041
3042
                /* terminate the command */
3043
                goto exit_error;
3044
            }
3045
3046
            /*
3047
             *   We have both direct and indirect objects.  If we don't
3048
             *   yet have the direct object, go ask for it 
3049
             */
3050
            if (voclistlen(dolist) == 0)
3051
            {
3052
                askflags = ERR_RUNASKD;
3053
                goto exit_error;
3054
            }
3055
3056
            /* get the flags (if old-style, flags are always zero) */
3057
            tplflags = (newstyle ? voctplflg(tpl) : 0);
3058
3059
            /*
3060
             *   the "other" object (dobj if doing iobj, iobj if doing
3061
             *   dobj) is not known when the first object is disambiguated
3062
             */
3063
            otherobj = MCMONINV;
3064
3065
            /* disambiguate the objects in the proper order */
3066
            if (tplflags & VOCTPLFLG_DOBJ_FIRST)
3067
            {
3068
                /* disambiguate the direct object list */
3069
                if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
3070
                                PRP_VALIDDO, voctplvd(tpl), cmd, otherobj,
3071
                                actor, verb, prep, cmdbuf, FALSE))
3072
                {
3073
                    err = -1;
3074
                    goto exit_error;
3075
                }
3076
3077
                /*
3078
                 *   only one direct object is allowed if it's
3079
                 *   disambiguated first 
3080
                 */
3081
                if (voclistlen(dolist1) > 1)
3082
                {
3083
                    vocerr(ctx, VOCERR(28),
3084
                         "You can't use multiple objects with this command.");
3085
                    err = -1;
3086
                    goto exit_error;
3087
                }
3088
3089
                /* the other object is now known for iboj disambiguation */
3090
                otherobj = dolist1[0].vocolobj;
3091
            }
3092
3093
            /* disambiguate the indirect object list */
3094
            if (vocdisambig(ctx, iolist1, iolist, PRP_IODEFAULT,
3095
                            PRP_VALIDIO, voctplvi(tpl), cmd, otherobj,
3096
                            actor, verb, prep, cmdbuf, FALSE))
3097
            {
3098
                err = -1;
3099
                goto exit_error;
3100
            }
3101
3102
            /* only one indirect object is allowed */
3103
            if (voclistlen(iolist1) > 1)
3104
            {
3105
                vocerr(ctx, VOCERR(25),
3106
                       "You can't use multiple indirect objects.");
3107
                err = -1;
3108
                goto exit_error;
3109
            }
3110
            otherobj = iobj = iolist1[0].vocolobj;
3111
3112
            /*
3113
             *   disambiguate the direct object list if we haven't
3114
             *   already done so (we might have disambiguated it first due
3115
             *   to the DisambigDobjFirst flag being set in the template)
3116
             */
3117
            if (!(tplflags & VOCTPLFLG_DOBJ_FIRST)
3118
                && vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
3119
                               PRP_VALIDDO, voctplvd(tpl), cmd, otherobj,
3120
                               actor, verb, prep, cmdbuf, FALSE))
3121
            {
3122
                err = -1;
3123
                goto exit_error;
3124
            }
3125
                
3126
            /* re-check for multi-mode */
3127
            if (multi_flags == 0)
3128
                multi_flags = check_for_multi(dolist1);
3129
            
3130
            /* save it/them/him/her, and execute the command */
3131
            exesaveit(ctx, dolist1);
3132
            if ((err = exeloop(ctx, actor, verb, dolist1, &prep, iolist1,
3133
                               multi_flags, tpl, newstyle)) != 0)
3134
                goto exit_error;
3135
        }
3136
        
3137
    exit_error: ;
3138
        
3139
        ERRCATCH(ctx->voccxerr, err)
3140
            if (err == ERR_RUNASKI) prep = errargint(0);
3141
            if (err != ERR_RUNASKD && err != ERR_RUNASKI)
3142
                errrse(ctx->voccxerr);
3143
        ERREND(ctx->voccxerr)
3144
3145
        switch(err)
3146
        {
3147
        case 0:
3148
            break;
3149
            
3150
        case ERR_RUNABRT:
3151
            /* "abort" executed - return the ABORT code */
3152
            VOC_RETVAL(ctx, save_sp, err);
3153
            
3154
        case ERR_RUNEXIT:
3155
            /* 
3156
             *   "exit" executed - terminate the command, but return
3157
             *   success, since we want to process any additional commands 
3158
             */
3159
            VOC_RETVAL(ctx, save_sp, 0);
3160
3161
        case ERR_RUNEXITOBJ:
3162
            /* 
3163
             *   "exitobj" executed - indicate success, since this merely
3164
             *   indicates that the game decided it was done processing an
3165
             *   object early 
3166
             */
3167
            VOC_RETVAL(ctx, save_sp, 0);
3168
3169
        case ERR_RUNASKI:
3170
        case ERR_RUNASKD:
3171
            askflags = err;
3172
            break;
3173
            
3174
        case -2:                   /* special code: continue with main loop */
3175
            continue;
3176
3177
        case -1:                           /* special code: return an error */
3178
        default:
3179
            VOC_RETVAL(ctx, save_sp, 1);
3180
        }
3181
    
3182
        /*
3183
         *   If we got this far, we probably want more information.  The
3184
         *   askflags can tell us what to do from here.  
3185
         */
3186
        if (askflags)
3187
        {
3188
            int old_unknown;
3189
            int exenewpos;
3190
            
3191
            /* 
3192
             *   if we had unknown words, don't ask for more information
3193
             *   at this point; simply give up and report the unknown word 
3194
             */
3195
            if (ctx->voccxunknown != 0)
3196
            {
3197
                VOC_RETVAL(ctx, save_sp, 1);
3198
            }
3199
            
3200
            /* find new template indicated by the additional object */
3201
            foundtpl = voctplfnd(ctx, verb, prep, tpl, &newstyle);
3202
            tplflags = (newstyle ? voctplflg(tpl) : 0);
3203
        
3204
            /* find a default object of the type requested */
3205
            runrst(rcx);
3206
            if (askflags == ERR_RUNASKD) runpnil(rcx);
3207
            runpobj(rcx, prep);
3208
            runpobj(rcx, actor);
3209
            runppr(rcx, verb,
3210
                   (prpnum)(askflags == ERR_RUNASKD
3211
                            ? PRP_DODEFAULT : PRP_IODEFAULT),
3212
                   (askflags == ERR_RUNASKD ? 3 : 2));
3213
            
3214
            /*
3215
             *   If we got a list back from ?oDefault, and we have a new
3216
             *   template for the command, process the list normally with
3217
             *   the object verification routine for this template.  If we
3218
             *   end up with exactly one object, we will assume it is the
3219
             *   object to be used; otherwise, make no assumption and ask
3220
             *   the user for guidance.  
3221
             */
3222
            if (runtostyp(rcx) == DAT_LIST && foundtpl)
3223
            {
3224
                uchar   *l = runpoplst(rcx);
3225
                uint     lstsiz;
3226
                int      objcnt;
3227
                objnum   defobj;
3228
                objnum   o;
3229
                runsdef  val;
3230
                
3231
                /* push list back on stack, to keep it in the heap */
3232
                val.runsv.runsvstr = l;
3233
                val.runstyp = DAT_LIST;
3234
                runrepush(rcx, &val);
3235
                
3236
                /* get list size out of list */
3237
                lstsiz = osrp2(l) - 2;
3238
                l += 2;
3239
                
3240
                for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz))
3241
                {
3242
                    if (*l == DAT_OBJECT)
3243
                    {
3244
                        prpnum verprop;
3245
                        int argc = 1;
3246
                    
3247
                        o = osrp2(l + 1);
3248
                        verprop = (askflags == ERR_RUNASKD ? voctplvd(tpl)
3249
                                                        : voctplvi(tpl));
3250
                
3251
                        if (!objgetap(ctx->voccxmem, o, verprop,
3252
                                      (objnum *)0, FALSE))
3253
                            continue;
3254
3255
                        tiohide(ctx->voccxtio);
3256
3257
                        /*
3258
                         *   In the unlikely event that we have an
3259
                         *   indirect object but no direct object, push
3260
                         *   the iobj.  This can happen when the player
3261
                         *   types a sentence such as "verb prep iobj".  
3262
                         */
3263
                        if (voclistlen(iolist) != 0
3264
                            && askflags == ERR_RUNASKD
3265
                            && !(tplflags & VOCTPLFLG_DOBJ_FIRST))
3266
                        {
3267
                            /* push the indirect object */
3268
                            runpobj(rcx, iolist[0].vocolobj);
3269
3270
                            /* note the second argument */
3271
                            argc = 2;
3272
                        }
3273
3274
                        /*
3275
                         *   If this is a disambigDobjFirst verb, and
3276
                         *   we're validating an indirect object list,
3277
                         *   then we must push the direct object argument
3278
                         *   to the indirect object validation routine.  
3279
                         */
3280
                        if (askflags == ERR_RUNASKI
3281
                            && (tplflags & VOCTPLFLG_DOBJ_FIRST) != 0)
3282
                        {
3283
                            /* push the diret object */
3284
                            runpobj(rcx, dolist[0].vocolobj);
3285
3286
                            /* note the second argument */
3287
                            argc = 2;
3288
                        }
3289
3290
                        /* push the actor and call the verXoVerb routine */
3291
                        runpobj(rcx, actor);
3292
                        runppr(rcx, o, verprop, argc);
3293
                        if (!tioshow(ctx->voccxtio))
3294
                        {
3295
                            ++objcnt;
3296
                            defobj = o;
3297
                        }
3298
3299
                    }
3300
                }
3301
                
3302
                /* no longer need list in heap, so discard it */
3303
                rundisc(rcx);
3304
                
3305
                /* if we found exactly one object, it's the default */
3306
                if (objcnt == 1)
3307
                {
3308
                    if (askflags == ERR_RUNASKD)
3309
                    {
3310
                        dolist[0].vocolobj = defobj;
3311
                        dolist[0].vocolflg = 0;
3312
                        dolist[0].vocolfst = dolist[0].vocollst = 0;
3313
                        dolist[1].vocolobj = MCMONINV;
3314
                        dolist[1].vocolflg = 0;
3315
                        dolist[1].vocolfst = dolist[1].vocollst = 0;
3316
                    }
3317
                    else
3318
                    {
3319
                        iolist[0].vocolobj = defobj;
3320
                        iolist[0].vocolflg = 0;
3321
                        iolist[0].vocolfst = iolist[0].vocollst = 0;
3322
                        iolist[1].vocolobj = MCMONINV;
3323
                        iolist[1].vocolflg = 0;
3324
                        iolist[1].vocolfst = iolist[1].vocollst = 0;
3325
                    }
3326
                    
3327
                    /* tell the user what we're assuming */
3328
                    if (ctx->voccxpdef2 != MCMONINV)
3329
                    {
3330
                        if (askflags == ERR_RUNASKI)
3331
                            runpobj(rcx, prep);
3332
                        else
3333
                            runpnil(rcx);
3334
                        runpobj(rcx, defobj);
3335
                        runpobj(rcx, verb);
3336
                        runpobj(rcx, actor);
3337
                        runfn(rcx, ctx->voccxpdef2, 4);
3338
                    }
3339
                    else if (ctx->voccxpdef != MCMONINV)
3340
                    {
3341
                        if (askflags == ERR_RUNASKI)
3342
                            runpobj(rcx, prep);
3343
                        else
3344
                            runpnil(rcx);
3345
                        runpobj(rcx, defobj);
3346
                        runfn(rcx, ctx->voccxpdef, 2);
3347
                    }
3348
                    else
3349
                    {
3350
                        vocerr_info(ctx, VOCERR(130), "(");
3351
                        if (askflags == ERR_RUNASKI)
3352
                        {
3353
                            runppr(rcx, prep, PRP_SDESC, 0);
3354
                            vocerr_info(ctx, VOCERR(132), " ");
3355
                        }
3356
                        runppr(rcx, defobj, PRP_THEDESC, 0);
3357
                        vocerr_info(ctx, VOCERR(131), ")");
3358
                    }
3359
                    tioflush(ctx->voccxtio);
3360
                    continue;                      /* try the command again */
3361
                }
3362
            }
3363
            else
3364
                rundisc(rcx);
3365
3366
            /* make sure output capturing is off for the prompt */
3367
            tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
3368
            tioclrcapture(ctx->voccxtio);
3369
            
3370
            /*
3371
             *   If we're asking for an indirect object, and we have a
3372
             *   list of direct objects, and parseAskobjIndirect is
3373
             *   defined, call it.  Otherwise, if there's a
3374
             *   parseAskobjActor routine, call it.  Otherwise, if there's
3375
             *   a parseAskobj routine, use that.  Finally, if none of
3376
             *   those are defined, generate the default phrasing.  
3377
             */
3378
            if (ctx->voccxpask3 != MCMONINV
3379
                && askflags == ERR_RUNASKI
3380
                && voclistlen(dolist) != 0)
3381
            {
3382
                /* call parseAskobjIndirect */
3383
                voc_askobj_indirect(ctx, dolist, actor, verb, prep);
3384
            }
3385
            else if (ctx->voccxpask2 != MCMONINV)
3386
            {
3387
                if (askflags == ERR_RUNASKI)
3388
                    runpobj(ctx->voccxrun, prep);
3389
                runpobj(ctx->voccxrun, verb);
3390
                runpobj(ctx->voccxrun,
3391
                        (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
3392
                runfn(ctx->voccxrun, ctx->voccxpask2,
3393
                      askflags == ERR_RUNASKI ? 3 : 2);
3394
            }
3395
            else if (ctx->voccxpask != MCMONINV)
3396
            {
3397
                if (askflags == ERR_RUNASKI)
3398
                    runpobj(ctx->voccxrun, prep);
3399
                runpobj(ctx->voccxrun, verb);
3400
                runfn(ctx->voccxrun, ctx->voccxpask,
3401
                      askflags == ERR_RUNASKI ? 2 : 1);
3402
            }
3403
            else
3404
            {
3405
                /*
3406
                 *   Phrase the question: askDo: "What do you want
3407
                 *   <actor> to <verb>?"  askIo: "What do you want <actor>
3408
                 *   to <verb> it <prep>?"  If the actor is Me, leave the
3409
                 *   actor out of it.  
3410
                 */
3411
                if (actor != MCMONINV && actor != ctx->voccxme)
3412
                {
3413
                    vocerr_info(ctx, VOCERR(148), "What do you want ");
3414
                    runppr(rcx, actor, PRP_THEDESC, 0);
3415
                    vocerr_info(ctx, VOCERR(149), " to ");
3416
                }
3417
                else
3418
                {
3419
                    /* no actor - don't mention one */
3420
                    vocerr_info(ctx, VOCERR(140), "What do you want to ");
3421
                }
3422
3423
                /* add the verb */
3424
                runppr(rcx, verb, PRP_SDESC, 0);
3425
3426
                /*
3427
                 *   add an appropriate pronoun for the direct object,
3428
                 *   and the preposition, if we're asking for an indirect
3429
                 *   object 
3430
                 */
3431
                if (askflags == ERR_RUNASKI)
3432
                {
3433
                    int   i;
3434
                    int   cnt;
3435
                    int   distinct;
3436
                    char *lastfst;
3437
3438
                    /*
3439
                     *   If possible, tailor the pronoun to the situation
3440
                     *   rather than using "it"; if we have multiple
3441
                     *   objects, use "them", and if we have agreement
3442
                     *   with the possible single objects about "him" or
3443
                     *   "her", use that.  Otherwise, use "it".  If "all"
3444
                     *   was specified for any word, automatically assume
3445
                     *   multiple distinct objects were specified.  
3446
                     */
3447
                    cnt = voclistlen(dolist);
3448
                    for (distinct = 0, i = 0, lastfst = 0 ; i < cnt ; ++i)
3449
                    {
3450
                        /* if the first word is different here, note it */
3451
                        if (lastfst != dolist[i].vocolfst)
3452
                        {
3453
                            /* this is a different word - count it */
3454
                            ++distinct;
3455
                            lastfst = dolist[i].vocolfst;
3456
                        }
3457
3458
                        /* always assume multiple distinct objects on "all" */
3459
                        if (dolist[i].vocolflg & VOCS_ALL)
3460
                        {
3461
                            distinct = 2;
3462
                            break;
3463
                        }
3464
                    }
3465
3466
                    /*
3467
                     *   If we have multiple words, use "them";
3468
                     *   otherwise, see if we can find agreement about
3469
                     *   using "him" or "her". 
3470
                     */
3471
                    if (distinct > 1)
3472
                    {
3473
                        /* multiple words specified by user - use "them" */
3474
                        vocerr_info(ctx, VOCERR(144), " them ");
3475
                    }
3476
                    else
3477
                    {
3478
                        int is_him;
3479
                        int is_her;
3480
                        int is_them;
3481
3482
                        /* run through the objects and check him/her */
3483
                        for (i = 0 ; i < cnt ; ++i)
3484
                        {
3485
                            int him1, her1, them1;
3486
3487
                            /* if it's special (number, string), use "it" */
3488
                            if (dolist[i].vocolobj == MCMONINV)
3489
                            {
3490
                                him1 = FALSE;
3491
                                her1 = FALSE;
3492
                                them1 = FALSE;
3493
                            }
3494
                            else
3495
                            {
3496
                                /* check for "him" */
3497
                                runppr(rcx, dolist[i].vocolobj, PRP_ISHIM, 0);
3498
                                him1 = (runtostyp(rcx) == DAT_TRUE);
3499
                                rundisc(rcx);
3500
                                
3501
                                /* check for "her" */
3502
                                runppr(rcx, dolist[i].vocolobj, PRP_ISHER, 0);
3503
                                her1 = (runtostyp(rcx) == DAT_TRUE);
3504
                                rundisc(rcx);
3505
3506
                                /* check for "them" */
3507
                                runppr(rcx, dolist[i].vocolobj,
3508
                                       PRP_ISTHEM, 0);
3509
                                them1 = (runtostyp(rcx) == DAT_TRUE);
3510
                                rundisc(rcx);
3511
                            }
3512
3513
                            /*
3514
                             *   if this is the first object, it
3515
                             *   definitely agrees; otherwise, keep going
3516
                             *   only if it agrees with what we found on
3517
                             *   the last pass 
3518
                             */
3519
                            if (i == 0)
3520
                            {
3521
                                is_him = him1;
3522
                                is_her = her1;
3523
                                is_them = them1;
3524
                            }
3525
                            else
3526
                            {
3527
                                /* turn off either that is no longer true */
3528
                                if (!him1) is_him = FALSE;
3529
                                if (!her1) is_her = FALSE;
3530
                                if (!them1) is_them = FALSE;
3531
                            }
3532
3533
                            /* if all are false, stop now */
3534
                            if (!is_him && !is_her && !is_them)
3535
                                break;
3536
                        }
3537
3538
                        /*
3539
                         *   If we could agree on "him", "her", or "them",
3540
                         *   use that pronoun; otherwise, use "it".  If we
3541
                         *   found both "him" and "her" are acceptable for
3542
                         *   all objects, use "them".  
3543
                         */
3544
                        if ((is_him && is_her) || is_them)
3545
                            vocerr_info(ctx, VOCERR(147), " them ");
3546
                        else if (is_him)
3547
                            vocerr_info(ctx, VOCERR(145), " him ");
3548
                        else if (is_her)
3549
                            vocerr_info(ctx, VOCERR(146), " her ");
3550
                        else
3551
                            vocerr_info(ctx, VOCERR(141), " it ");
3552
                    }
3553
3554
                    /* finish off the question with the prep and a "?" */
3555
                    if (prep != MCMONINV)
3556
                        runppr(rcx, prep, PRP_SDESC, 0);
3557
                    else
3558
                        vocerr_info(ctx, VOCERR(142), "to");
3559
                }
3560
                vocerr_info(ctx, VOCERR(143), "?");
3561
            }
3562
            tioflush(ctx->voccxtio);
3563
                
3564
            /*
3565
             *   Get a new command line.  If the player gives us
3566
             *   something that looks like a noun list, and nothing more,
3567
             *   he anwered our question; otherwise, he's typing a new
3568
             *   command, so we must return to the caller with the reparse
3569
             *   flag set.  
3570
             */
3571
            if (askflags == ERR_RUNASKD)
3572
            {
3573
                exenewbuf = donewbuf;
3574
                exenewcmd = donewcmd;
3575
                exenewlist = donewlist;
3576
                exenewtype = donewtype;
3577
            }
3578
            else
3579
            {
3580
                exenewbuf = ionewbuf;
3581
                exenewcmd = ionewcmd;
3582
                exenewlist = ionewlist;
3583
                exenewtype = ionewtype;
3584
            }
3585
3586
            /* read the new command */
3587
            if (vocread(ctx, actor, verb, exenewcmd, VOCBUFSIZ,
3588
                        askflags == ERR_RUNASKD ? 3 : 4) == VOCREAD_REDO)
3589
            {
3590
                /* 
3591
                 *   we got an input line, but we want to treat it as a brand
3592
                 *   new command line - copy the new text to the command
3593
                 *   buffer, set the 'redo' flag, and give up 
3594
                 */
3595
                strcpy(cmdbuf, exenewcmd);
3596
                ctx->voccxredo = TRUE;
3597
                VOC_RETVAL(ctx, save_sp, 1);
3598
            }
3599
            
3600
            if (!(cnt = voctok(ctx, exenewcmd, exenewbuf, exenewlist,
3601
                               TRUE, FALSE, TRUE)))
3602
            {
3603
                runrst(rcx);
3604
                runfn(rcx, ctx->voccxprd, 0);
3605
                VOC_RETVAL(ctx, save_sp, 1);
3606
            }
3607
            if (cnt < 0)
3608
            {
3609
                ctx->voccxunknown = 0;
3610
                VOC_RETVAL(ctx, save_sp, 1);
3611
            }
3612
3613
            /* 
3614
             *   Save the unknown word count while getting types, and set
3615
             *   the count to a non-zero value - this will force the type
3616
             *   checker to generate an error on an unknown word.  This
3617
             *   removes a little control from the game (since
3618
             *   parseUnknownXobj won't be called), but there's not much
3619
             *   else we can do here. 
3620
             */
3621
            old_unknown = ctx->voccxunknown;
3622
            ctx->voccxunknown = 1;
3623
3624
            /* get the types */
3625
            exenewlist[cnt] = 0;
3626
            if (vocgtyp(ctx, exenewlist, exenewtype, cmdbuf))
3627
            {
3628
                /* 
3629
                 *   clear the unknown word count so that we fail with
3630
                 *   this error rather than trying to deal with unknown
3631
                 *   words 
3632
                 */
3633
                ctx->voccxunknown = 0;
3634
3635
                /* return failure */
3636
                VOC_RETVAL(ctx, save_sp, 1);
3637
            }
3638
3639
            /* restore the unknown word count */
3640
            ctx->voccxunknown = old_unknown;
3641
3642
            /* start at the first word */
3643
            exenewpos = 0;
3644
3645
            /* 
3646
             *   if we're asking for an indirect object, and the first
3647
             *   word is a preposition, and matches the preposition that
3648
             *   we supplied to precede the indirect object, skip the
3649
             *   preposition 
3650
             */
3651
            if (askflags == ERR_RUNASKI
3652
                && prep != MCMONINV
3653
                && (exenewtype[0] & VOCT_PREP) != 0)
3654
            {
3655
                vocwdef *vp;
3656
                
3657
                /* get the preposition */
3658
                vp = vocffw(ctx, exenewlist[0], (int)strlen(exenewlist[0]),
3659
                            (char *)0, 0, PRP_PREP, (vocseadef *)0);
3660
                if (vp != 0 && vp->vocwobj == prep)
3661
                    ++exenewpos;
3662
            }
3663
3664
            /* check for a noun */
3665
            newnoun = (askflags == ERR_RUNASKD ? dolist : iolist);
3666
            cnt = vocchknoun(ctx, exenewlist, exenewtype, exenewpos, &next,
3667
                             newnoun, FALSE);
3668
            
3669
            if (cnt < 0) { VOC_RETVAL(ctx, save_sp, 1); } /* invalid syntax */
3670
            if (cnt == 0
3671
                || (exenewlist[next] && !vocspec(exenewlist[next], VOCW_THEN)
3672
                    && *exenewlist[next] != '\0'))
3673
            {
3674
                strcpy(cmdbuf, exenewcmd);
3675
                ctx->voccxredo = TRUE;
3676
                VOC_RETVAL(ctx, save_sp, 1);
3677
            }
3678
3679
            /* re-check the 'multi' flags */
3680
            multi_flags = check_for_multi(newnoun);
3681
            
3682
            /* give it another go by going back to the top of the loop */
3683
        }
3684
        else
3685
        {
3686
            /* normal exit flags - return success */
3687
            VOC_RETVAL(ctx, save_sp, 0);
3688
        }
3689
    }
3690
}
3691