/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * TADS Interpreter Execute user Command * * Function * Executes a user command after it has been parsed * Notes * TADS 2.0 version * * This module contains the implementation of the entire "turn" sequence, * which is: * * preCommand(actor, verb, dobj-list, prep, iobj) * verb.verbAction(actor, do, prep, io) * actor.actorAction( verb, do, prep, io ) * actor.location.roomAction( actor, verb, do, prep, io ) * if ( io ) * { * io.iobjCheck(actor, verb, dobj, prep) * if (io does not define verIo directly) * io.iobjGen(actor, verb, dobj, prep) * do.dobjCheck(actor, verb, iobj, prep) * if (do does not define do directly) * do.dobjGen(actor, verb, iobj, prep) * io.verIo( actor, do ) * if ( noOutput ) * { * do.verDo( actor, io ) * if ( noOutput ) io.io( actor, do ) * } * } * else if ( do ) * { * do.dobjCheck(actor, verb, nil, nil) * if (do does not define do directly) * do.dobjGen(actor, verb, nil, nil) * do.verDo( actor ) * if ( noOutput )do.do( actor ) * } * else * { * verb.action( actor ) * } * postAction(actor, verb, dobj, prep, iobj, error_code) * daemons * fuses * endCommand(actor, verb, dobj-list, prep, iobj, error_code) * * If an 'exit' or 'exitobj' is encountered, we skip straight to the * daemons. If an abort is encountered, we skip to endCommand. If * askio, or askdo is encountered, we skip everything remaining. Under * any of these exit scenarios, we return success to our caller. * * This module also contains code to set and remove fuses and daemons, * since they are part of the player turn sequence. * Returns * 0 for success, other for failure. */ #include "glk/tads/tads2/built_in.h" #include "glk/tads/tads2/error.h" #include "glk/tads/tads2/memory_cache_heap.h" #include "glk/tads/tads2/run.h" #include "glk/tads/tads2/vocabulary.h" #include "glk/tads/os_glk.h" namespace Glk { namespace TADS { namespace TADS2 { /* allocate and initialize a fuse/daemon/notifier array */ void vocinialo(voccxdef *ctx, vocddef **what, int cnt) { vocddef *p; *what = (vocddef *)mchalo(ctx->voccxerr, (cnt * sizeof(vocddef)), "vocinialo"); /* set all object/function entries to MCMONINV to indicate not-in-use */ for (p = *what ; cnt ; ++p, --cnt) p->vocdfn = MCMONINV; } /* internal service routine to clear one set of fuses/deamons/alerters */ static void vocdmn1clr(vocddef *dmn, uint cnt) { for ( ; cnt ; --cnt, ++dmn) dmn->vocdfn = MCMONINV; } /* delete all fuses/daemons/alerters */ void vocdmnclr(voccxdef *ctx) { vocdmn1clr(ctx->voccxfus, ctx->voccxfuc); vocdmn1clr(ctx->voccxdmn, ctx->voccxdmc); vocdmn1clr(ctx->voccxalm, ctx->voccxalc); } /* save undo information for a daemon/fuse/notifier */ static void vocdusav(voccxdef *ctx, vocddef *what) { uchar *p; objucxdef *uc = ctx->voccxundo; ushort siz = sizeof(what) + sizeof(*what) + 1; /* if we don't need to save undo, quit now */ if (uc == 0 || !objuok(uc)) return; /* reserve space for our record */ p = objures(uc, OBJUCLI, siz); /* set up our undo record */ *p = VOC_UNDO_DAEMON; memcpy(p + 1, &what, (size_t)sizeof(what)); memcpy(p + 1 + sizeof(what), what, (size_t)sizeof(*what)); uc->objucxhead += siz; } /* apply undo information for a daemon/fuse/notifier */ void vocdundo(void *ctx0, uchar *data) { voccxdef *ctx = (voccxdef *)ctx0; vocddef *daemon; objnum objn; ushort siz; ushort wrdsiz; uchar *p; int sccnt; objnum sc; int len1, len2; prpnum prp; int flags; uchar *wrd; switch(*data) { case VOC_UNDO_DAEMON: memcpy(&daemon, data + 1, (size_t)sizeof(daemon)); memcpy(daemon, data + 1 + sizeof(daemon), (size_t)sizeof(*daemon)); break; case VOC_UNDO_NEWOBJ: /* get the object number */ objn = osrp2(data + 1); /* delete the object's inheritance and vocabulary records */ vocdel(ctx, objn); vocidel(ctx, objn); /* delete the object */ mcmfre(ctx->voccxmem, (mcmon)objn); break; case VOC_UNDO_DELOBJ: /* get the object's number and size */ objn = osrp2(data + 1); siz = osrp2(data + 3); wrdsiz = osrp2(data + 5); /* allocate the object with its original number */ p = mcmalonum(ctx->voccxmem, siz, (mcmon)objn); /* copy the contents back to the object */ memcpy(p, data + 7, (size_t)siz); /* get its superclass if it has one */ sccnt = objnsc(p); if (sccnt) sc = osrp2(objsc(p)); /* unlock the object, and create its inheritance records */ mcmunlck(ctx->voccxmem, (mcmon)objn); vociadd(ctx, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC); /* restore the words as well */ data += 7 + siz; while (wrdsiz) { /* get the lengths from the buffer */ len1 = osrp2(data + 2); len2 = osrp2(data + 4); /* add the word */ vocadd2(ctx, data[0], objn, data[1], data+6, len1, data+6+len1, len2); /* remove this object from the word size */ wrdsiz -= 6 + len1 + len2; data += 6 + len1 + len2; } break; case VOC_UNDO_ADDVOC: case VOC_UNDO_DELVOC: flags = *(data + 1); prp = *(data + 2); objn = osrp2(data + 3); wrd = data + 5; if (*data == VOC_UNDO_ADDVOC) vocdel1(ctx, objn, (char *)wrd, prp, FALSE, FALSE, FALSE); else vocadd(ctx, prp, objn, flags, (char *)wrd); break; case VOC_UNDO_SETME: ctx->voccxme = osrp2(data + 1); break; } } /* determine size of one of our client undo records */ ushort OS_LOADDS vocdusz(void *ctx0, uchar *data) { VARUSED(ctx0); switch(*data) { case VOC_UNDO_DAEMON: /* it's the size of the structures, plus one for the header */ return (ushort)((sizeof(vocddef *) + sizeof(vocddef)) + 1); case VOC_UNDO_NEWOBJ: /* 2 bytes for the objnum plus 1 for the header */ return 2 + 1; case VOC_UNDO_DELOBJ: /* * 1 (header) + 2 (objnum) + 2 (size) + 2 (word size) + object * data size + word size */ return osrp2(data+3) + osrp2(data+5) + 1+2+2+2; case VOC_UNDO_ADDVOC: case VOC_UNDO_DELVOC: /* 1 (header) + 2 (objnum) + 1 (flags) + 1 (type) + word size */ return osrp2(data + 5) + 5; default: return 0; } } /* save undo for object creation */ void vocdusave_newobj(voccxdef *ctx, objnum objn) { objucxdef *uc = ctx->voccxundo; uchar *p; p = objures(uc, OBJUCLI, 3); *p = VOC_UNDO_NEWOBJ; oswp2(p+1, objn); uc->objucxhead += 3; } /* save undo information for a change in the "Me" object */ void vocdusave_me(voccxdef *ctx, objnum old_me) { uchar *p; objucxdef *uc = ctx->voccxundo; /* if we don't need to save undo, there's nothing to do */ if (uc == 0 || !objuok(uc)) return; /* reserve space for our record */ p = objures(uc, OBJUCLI, 3); *p = VOC_UNDO_SETME; oswp2(p+1, old_me); /* absorb the space */ uc->objucxhead += 3; } /* callback context structure */ struct delobj_cb_ctx { uchar *p; }; /* * Iteration callback to write vocabulary words for an object being * deleted to an undo stream, so that they can be restored if the * deletion is undone. */ static void delobj_cb(void *ctx0, vocdef *voc, vocwdef *vocw) { struct delobj_cb_ctx *ctx = (struct delobj_cb_ctx *)ctx0; uchar *p = ctx->p; /* write this object's header to the stream */ p[0] = vocw->vocwtyp; p[1] = vocw->vocwflg; oswp2(p+2, voc->voclen); oswp2(p+4, voc->vocln2); /* write the words as well */ memcpy(p+6, voc->voctxt, (size_t)(voc->voclen + voc->vocln2)); /* advance the pointer */ ctx->p += 6 + voc->voclen + voc->vocln2; } /* save undo for object deletion */ void vocdusave_delobj(voccxdef *ctx, objnum objn) { objucxdef *uc = ctx->voccxundo; uchar *p; uchar *objp; uint siz; int wrdsiz; int wrdcnt; struct delobj_cb_ctx fnctx; /* figure out how much we need to save */ objp = mcmlck(ctx->voccxmem, (mcmon)objn); siz = objfree(objp); /* figure the word size */ voc_count(ctx, objn, 0, &wrdcnt, &wrdsiz); /* * we need to store an additional 6 bytes (2-length1, 2-length2, * 1-type, 1-flags) for each word */ wrdsiz += wrdcnt*6; /* set up the undo header */ p = objures(uc, OBJUCLI, (ushort)(7 + siz + wrdsiz)); *p = VOC_UNDO_DELOBJ; oswp2(p+1, objn); oswp2(p+3, siz); oswp2(p+5, wrdsiz); /* save the object's data */ memcpy(p+7, objp, (size_t)siz); /* write the words */ fnctx.p = p+7 + siz; voc_iterate(ctx, objn, delobj_cb, &fnctx); /* unlock the object and advance the undo pointer */ mcmunlck(ctx->voccxmem, (mcmon)objn); uc->objucxhead += 7 + siz + wrdsiz; } /* save undo for word creation */ void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags, char *wrd) { ushort wrdsiz; uchar *p; objucxdef *uc = ctx->voccxundo; /* figure out how much space we need, and reserve it */ wrdsiz = osrp2(wrd); p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz)); *p = VOC_UNDO_ADDVOC; *(p+1) = flags; *(p+2) = (uchar)typ; oswp2(p+3, objn); memcpy(p+5, wrd, (size_t)wrdsiz); uc->objucxhead += 5 + wrdsiz; } /* save undo for word deletion */ void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags, char *wrd) { ushort wrdsiz; uchar *p; objucxdef *uc = ctx->voccxundo; /* figure out how much space we need, and reserve it */ wrdsiz = osrp2(wrd); p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz)); *p = VOC_UNDO_DELVOC; *(p+1) = flags; *(p+2) = (uchar)typ; oswp2(p+3, objn); memcpy(p+5, wrd, (size_t)wrdsiz); uc->objucxhead += 5 + wrdsiz; } /* set a fuse/daemon/notifier */ void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop, uint tm, runsdef *val, int err) { int slots = 0; if (what == ctx->voccxdmn) slots = ctx->voccxdmc; else if (what == ctx->voccxalm) slots = ctx->voccxalc; else if (what == ctx->voccxfus) slots = ctx->voccxfuc; else errsig(ctx->voccxerr, ERR_BADSETF); /* find a free slot, and set up our fuse/daemon */ for ( ; slots ; ++what, --slots) { if (what->vocdfn == MCMONINV) { /* save an undo record for this slot before changing */ vocdusav(ctx, what); /* record the information */ what->vocdfn = func; if (val != 0) OSCPYSTRUCT(what->vocdarg, *val); what->vocdprp = prop; what->vocdtim = tm; /* * the fuse/notifier/daemon is set - no need to look further * for an open slot */ return; } } /* we didn't find an open slot - signal the appropriate error */ errsig(ctx->voccxerr, err); } /* remove a fuse/daemon/notifier */ void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop, runsdef *val, int err) { int slots = 0; if (what == ctx->voccxdmn) slots = ctx->voccxdmc; else if (what == ctx->voccxalm) slots = ctx->voccxalc; else if (what == ctx->voccxfus) slots = ctx->voccxfuc; else errsig(ctx->voccxerr, ERR_BADREMF); /* find the slot with this same fuse/daemon/notifier, and remove it */ for ( ; slots ; ++what, --slots) { if (what->vocdfn == func && what->vocdprp == prop && (!val || (val->runstyp == what->vocdarg.runstyp && !memcmp(&val->runsv, &what->vocdarg.runsv, (size_t)datsiz(val->runstyp, &val->runsv))))) { /* save an undo record for this slot before changing */ vocdusav(ctx, what); what->vocdfn = MCMONINV; return; } } /* errsig(ctx->voccxerr, err); <<>> */ } /* * Count one or more turns - burn all fuses down by the given number of * turns. Execute any fuses that expire within the given interval, but * not any that expire at the end of the last turn counted here. (If * incrementing by one turn only, no fuses will be executed.) If the * do_fuses flag is false, fuses are simply deleted if they burn down * within the interval. */ void vocturn(voccxdef *ctx, int turncnt, int do_fuses) { vocddef *p; int i; int do_exe; while (turncnt--) { /* presume we won't find anything to execute */ do_exe = FALSE; /* go through notifiers, looking for fuse-type notifiers */ for (i = ctx->voccxalc, p = ctx->voccxalm ; i ; ++p, --i) { if (p->vocdfn != MCMONINV && p->vocdtim != VOCDTIM_EACH_TURN && p->vocdtim != 0) { /* save an undo record for this slot before changing */ vocdusav(ctx, p); if (--(p->vocdtim) == 0) do_exe = TRUE; } } /* now go through the fuses */ for (i = ctx->voccxfuc, p = ctx->voccxfus ; i ; ++p, --i) { if (p->vocdfn != MCMONINV && p->vocdtim != 0) { /* save an undo record for this slot before changing */ vocdusav(ctx, p); if (--(p->vocdtim) == 0) do_exe = TRUE; } } /* * if we'll be doing more, and anything burned down, run * current fuses before going on to the next turn */ if ((!do_fuses || turncnt) && do_exe) exefuse(ctx, do_fuses); } } /* * display a default error message for a verb/dobj/iobj combo. * The message is "I don't know how to " if * the dobj is present, and "I don't know how to anything * " if the iobj is present. Such a message * is displayed when the objects in the command don't handle the verb * (i.e., don't have any methods for verification of the verb: they * lack verDo or verIo). */ static void exeperr(voccxdef *ctx, objnum verb, objnum dobj, objnum prep, objnum iobj) { if (ctx->voccxper2 != MCMONINV) { runpobj(ctx->voccxrun, iobj); runpobj(ctx->voccxrun, prep); runpobj(ctx->voccxrun, dobj); runpobj(ctx->voccxrun, verb); runfn(ctx->voccxrun, ctx->voccxper2, 4); return; } vocerr(ctx, VOCERR(110), "I don't know how to "); runppr(ctx->voccxrun, verb, PRP_SDESC, 0); if (dobj != MCMONINV) { vocerr(ctx, VOCERR(111), " "); runppr(ctx->voccxrun, dobj, PRP_THEDESC, 0); } else { vocerr(ctx, VOCERR(112), " anything "); if (prep != MCMONINV) runppr(ctx->voccxrun, prep, PRP_SDESC, 0); else vocerr(ctx, VOCERR(113), "to"); vocerr(ctx, VOCERR(114), " "); runppr(ctx->voccxrun, iobj, PRP_THEDESC, 0); } vocerr(ctx, VOCERR(115), "."); } /* * Execute daemons */ void exedaem(voccxdef *ctx) { runcxdef *rcx = ctx->voccxrun; vocddef *daemon; int i; runsdef val; int err; for (i = ctx->voccxdmc, daemon = ctx->voccxdmn ; i ; ++daemon, --i) { if (daemon->vocdfn != MCMONINV) { objnum thisd = daemon->vocdfn; ERRBEGIN(ctx->voccxerr) OSCPYSTRUCT(val, daemon->vocdarg); runpush(rcx, val.runstyp, &val); runfn(rcx, thisd, 1); ERRCATCH(ctx->voccxerr, err) if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ) errrse(ctx->voccxerr); ERREND(ctx->voccxerr) } } for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i) { if (daemon->vocdfn != MCMONINV && daemon->vocdtim == VOCDTIM_EACH_TURN) { ERRBEGIN(ctx->voccxerr) runppr(rcx, daemon->vocdfn, daemon->vocdprp, 0); ERRCATCH(ctx->voccxerr, err) if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ) errrse(ctx->voccxerr); ERREND(ctx->voccxerr) } } } /* * Execute any pending fuses. Return TRUE if any fuses were executed, * FALSE otherwise. */ int exefuse(voccxdef *ctx, int do_run) { runcxdef *rcx = ctx->voccxrun; vocddef *daemon; int i; int found = FALSE; runsdef val; int err; /* first, execute any expired function-based fuses */ for (i = ctx->voccxfuc, daemon = ctx->voccxfus ; i ; ++daemon, --i) { if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0) { objnum thisf = daemon->vocdfn; found = TRUE; ERRBEGIN(ctx->voccxerr) /* save an undo record for this slot before changing */ vocdusav(ctx, daemon); /* remove the fuse prior to running */ daemon->vocdfn = MCMONINV; if (do_run) { OSCPYSTRUCT(val, daemon->vocdarg); runpush(rcx, val.runstyp, &val); runfn(rcx, thisf, 1); } ERRCATCH(ctx->voccxerr, err) if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ) errrse(ctx->voccxerr); ERREND(ctx->voccxerr) } } /* next, execute any expired method-based notifier fuses */ for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i) { if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0) { objnum thisa = daemon->vocdfn; found = TRUE; ERRBEGIN(ctx->voccxerr) /* save an undo record for this slot before changing */ vocdusav(ctx, daemon); /* delete it prior to running it */ daemon->vocdfn = MCMONINV; if (do_run) runppr(rcx, thisa, daemon->vocdprp, 0); ERRCATCH(ctx->voccxerr, err) if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ) errrse(ctx->voccxerr); ERREND(ctx->voccxerr) } } /* return true if we found any expired fuses */ return found; } /* ------------------------------------------------------------------------ */ /* * Find the action routine template for a verb. Fills in *tplofs with * the offset of the template property within the verb object, and fills * in actofs with the offset of the "action" property within the verb * object. Sets *tplofs to zero if there's no template, and sets * *actofs to zero if there's no action routine. */ static void exe_get_tpl(voccxdef *ctx, objnum verb, uint *tplofs, uint *actofs) { /* look up the new-style template first */ *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL2, (objnum *)0, FALSE); /* if there's no new-style template, look up the old-style template */ if (*tplofs == 0) *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL, (objnum *)0, FALSE); /* also look to see if the verb has an Action method */ *actofs = objgetap(ctx->voccxmem, verb, PRP_ACTION, (objnum *)0, FALSE); } /* ------------------------------------------------------------------------ */ /* * Execute fuses and daemons. Returns zero on success, or ERR_ABORT if * 'abort' was thrown during execution. */ int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses, objnum actor, objnum verb, vocoldef *dobj_list, int dobj_cnt, objnum prep, objnum iobj) { int err2; /* presume no error */ err2 = 0; /* execute fuses and daemons if desired - trap any errors that occur */ if (do_fuses) { ERRBEGIN(ctx->voccxerr) { /* execute daemons */ exedaem(ctx); /* execute fuses */ (void)exefuse(ctx, TRUE); } ERRCATCH(ctx->voccxerr, err2) { /* * if 'abort' was invoked, ignore it, since it's now had the * desired effect of skipping any remaining fuses and * daemons; resignal any other error */ if (err2 != ERR_RUNABRT) errrse(ctx->voccxerr); /* replace any previous error with the new error code */ err = err2; } ERREND(ctx->voccxerr); } /* execute endCommand if it's defined */ if (ctx->voccxendcmd != MCMONINV) { /* push the arguments */ runpnum(ctx->voccxrun, err); runpobj(ctx->voccxrun, iobj); runpobj(ctx->voccxrun, prep); voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt); runpobj(ctx->voccxrun, verb); runpobj(ctx->voccxrun, actor); /* call endCommand */ runfn(ctx->voccxrun, ctx->voccxendcmd, 6); } /* return the error status */ return err; } /* ------------------------------------------------------------------------ */ /* * execute iobjGen/dobjGen methods, if appropriate */ static int exegen(voccxdef *ctx, objnum obj, prpnum genprop, prpnum verprop, prpnum actprop) { int hasgen; /* has xobjGen property */ objnum genobj; /* object with xobjGen property */ int hasver; /* has verXoVerb property */ objnum verobj; /* object with verXoVerb property */ int hasact; /* has xoVerb property */ objnum actobj; /* object with xoVerb property */ /* ignore it if there's no object here */ if (obj == MCMONINV) return(FALSE); /* look up the xobjGen property, and ignore if not present */ hasgen = objgetap(ctx->voccxmem, obj, genprop, &genobj, FALSE); if (!hasgen) return(FALSE); /* look up the verXoVerb and xoVerb properties */ hasver = objgetap(ctx->voccxmem, obj, verprop, &verobj, FALSE); hasact = objgetap(ctx->voccxmem, obj, actprop, &actobj, FALSE); /* ignore if verXoVerb or xoVerb "overrides" xobjGen */ if ((hasver && !bifinh(ctx, vocinh(ctx, genobj), verobj)) || (hasact && !bifinh(ctx, vocinh(ctx, genobj), actobj))) return FALSE; /* all conditions are met - execute dobjGen */ return TRUE; } /* ------------------------------------------------------------------------ */ /* * Save "again" information for a direct or indirect object */ static void exe_save_again_obj(vocoldef *againv, const vocoldef *objv, char **bufp) { /* if there's an object, save it */ if (objv != 0) { /* copy the object information structure */ memcpy(againv, objv, sizeof(*againv)); /* copy the original command words to the "again" buffer */ if (objv->vocolfst != 0 && objv->vocollst != 0) { size_t copylen; /* * Compute the length of the entire list. The words are * arranged consecutively in the buffer, separated by null * bytes, so we must copy everything from the first word to * the start of the last word, plus the length of the last * word, plus the last word's trailing null byte. */ copylen = objv->vocollst - objv->vocolfst + strlen(objv->vocollst) + 1; /* copy the text */ memcpy(*bufp, objv->vocolfst, copylen); /* * set the new structure to point into the copy, not the * original */ againv->vocolfst = *bufp; againv->vocollst = *bufp + (objv->vocollst - objv->vocolfst); /* skip past the space we've consumed in the buffer */ *bufp += copylen; } } else { /* there's nothing to save - just set the object ID to invalid */ againv->vocolobj = MCMONINV; } } /* * Restore an "again" object previously saved. Note that we must copy * the saved data to our 2-element arrays so that we can insert a * terminating element after each restored element - other code * occasionally expects these structures to be stored in the standard * object list array format. Returns a pointer to the restored object * list, which is the same as the first argument. */ static vocoldef *exe_restore_again_obj(vocoldef again_array[2], const vocoldef *saved_obj) { /* copy the saved object into the first array element */ memcpy(&again_array[0], saved_obj, sizeof(again_array[0])); /* clear the second element to indicate the end of the object list */ again_array[1].vocolobj = MCMONINV; again_array[1].vocolflg = 0; /* return a pointer to the first array element */ return &again_array[0]; } /* ------------------------------------------------------------------------ */ /* * Execute a single command. 'recursive' indicates whether the routine * is being called for normal command processing or as a recursive call * from within the game; if this flag is true, we'll bypass certain * operations that are only appropriate for normal direct player * commands: we won't remember the command for "again" processing, we * won't do end-of-turn processing, and we won't reset the system stack * before each function invocation. */ static int exe1cmd(voccxdef *ctx, objnum actor, objnum verb, vocoldef *dobjv, objnum *prepptr, vocoldef *iobjv, int endturn, uchar *tpl, int newstyle, int recursive, int validate_dobj, int validate_iobj, vocoldef *dobj_list, int cur_dobj_idx, int dobj_cnt, int show_multi_prefix, int multi_flags) { objnum loc; int err; runcxdef *rcx = ctx->voccxrun; objnum prep = *prepptr; objnum dobj = (dobjv != 0 ? dobjv->vocolobj : MCMONINV); objnum iobj = (iobjv != 0 ? iobjv->vocolobj : MCMONINV); int tplflags; int dobj_first; objnum old_tio_actor; vocoldef *old_ctx_dobj; vocoldef *old_ctx_iobj; objnum old_verb; objnum old_actor; objnum old_prep; int do_fuses; int do_postact; vocoldef again_dobj[2]; vocoldef again_iobj[2]; /* presume no error will occur */ err = 0; /* * Presume we'll run fuses and daemons if this is the end of the * turn. We only do fuses and daemons once per command, even if the * command contains multiple objects; 'endturn' will be true only * when this is the last object of the command. */ do_fuses = endturn; /* presume we will call postAction */ do_postact = TRUE; /* remember the original tio-level actor setting */ old_tio_actor = tiogetactor(ctx->voccxtio); /* remember the original command settings (in case this is recursive) */ old_actor = ctx->voccxactor; old_verb = ctx->voccxverb; old_prep = ctx->voccxprep; old_ctx_dobj = ctx->voccxdobj; old_ctx_iobj = ctx->voccxiobj; /* the default actor is Me */ if (actor == MCMONINV) actor = ctx->voccxme; /* if command is "again", get information from previous command */ if (verb == ctx->voccxvag) { /* it's "again" - repeat the last command */ actor = ctx->voccxlsa; verb = ctx->voccxlsv; dobj = ctx->voccxlsd.vocolobj; iobj = ctx->voccxlsi.vocolobj; prep = ctx->voccxlsp; tpl = ctx->voccxlst; newstyle = ctx->voccxlssty; /* * If we have a direct or indirect object, restore the full * object information structure pointers (in particular, this * restores the word lists). */ if (dobj != MCMONINV) dobjv = exe_restore_again_obj(again_dobj, &ctx->voccxlsd); if (iobj != MCMONINV) iobjv = exe_restore_again_obj(again_iobj, &ctx->voccxlsi); /* * make sure the command is repeatable: there must have been a * verb, and the objects specified must still be accessible */ if (verb == MCMONINV) { /* * if the last command was lost due to an object deletion, * show the message "you can't repeat that command"; * otherwise, show the message "there's no command to * repeat" */ if ((ctx->voccxflg & VOCCXAGAINDEL) != 0) vocerr(ctx, VOCERR(27), "You can't repeat that command."); else vocerr(ctx, VOCERR(26), "There's no command to repeat."); /* flush the output and return failure */ tioflush(ctx->voccxtio); return 0; } else if ((dobj != MCMONINV && !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb)) || (iobj != MCMONINV && !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb)) || !vocchkaccess(ctx, actor, PRP_VALIDACTOR, 0, actor, verb)) { vocerr(ctx, VOCERR(27), "You can't repeat that command."); tioflush(ctx->voccxtio); return(0); } } else { /* verify the direct object if present */ if (validate_dobj && dobj != MCMONINV && !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb)) { /* generate an appropriate message */ if (vocchkvis(ctx, dobj, actor)) { /* it's visible but not accessible */ vocnoreach(ctx, &dobj, 1, actor, verb, prep, PRP_DODEFAULT, FALSE, 0, 0, 1); } else { /* it's not even visible */ if (recursive) vocerr(ctx, VOCERR(39), "You don't see that here."); else vocerr(ctx, VOCERR(38), "You don't see that here any more."); } /* indicate the error */ return ERR_PRS_VAL_DO_FAIL; } /* verify the indirect object if present */ if (validate_iobj && iobj != MCMONINV && !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb)) { /* generate the error message */ if (vocchkvis(ctx, iobj, actor)) { /* it's visible but not accessible */ vocnoreach(ctx, &iobj, 1, actor, verb, prep, PRP_IODEFAULT, FALSE, 0, 0, 1); } else { /* it's not even visible */ if (recursive) vocerr(ctx, VOCERR(39), "You don't see that here."); else vocerr(ctx, VOCERR(38), "You don't see that here any more."); } /* indicate the error */ return ERR_PRS_VAL_IO_FAIL; } /* * save the command, unless this is a recursive call from the * game, so that we can repeat this command if the next is * "again" */ if (!recursive) { char *dst; /* save the command parameters */ ctx->voccxlsa = actor; ctx->voccxlsv = verb; ctx->voccxlsp = prep; ctx->voccxlssty = newstyle; if (tpl != 0) memcpy(ctx->voccxlst, tpl, (size_t)(newstyle ? VOCTPL2SIZ : VOCTPLSIZ)); /* set up to write into the "again" word buffer */ dst = ctx->voccxagainbuf; /* save the direct object information */ exe_save_again_obj(&ctx->voccxlsd, dobjv, &dst); /* save the indirect object information */ exe_save_again_obj(&ctx->voccxlsi, iobjv, &dst); /* * clear the flag indicating that "again" was lost due to * object deletion, because we obviously have a valid * "again" at this point */ ctx->voccxflg &= ~VOCCXAGAINDEL; } } /* remember the flags */ tplflags = (tpl != 0 && newstyle ? voctplflg(tpl) : 0); dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST); /* set up actor for tio subsystem - format strings need to know */ tiosetactor(ctx->voccxtio, actor); /* store current dobj and iobj vocoldef's for later reference */ ctx->voccxdobj = dobjv; ctx->voccxiobj = iobjv; /* store the rest of the current command objects for reference */ ctx->voccxactor = actor; ctx->voccxverb = verb; ctx->voccxprep = prep; ERRBEGIN(ctx->voccxerr) /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* * if this is the first object, invoke the game's preCommand * function, passing the list of all of the direct objects */ if (cur_dobj_idx == 0 && ctx->voccxprecmd != MCMONINV) { /* push the arguments: actor, verb, dobj-list, prep, iobj */ runpobj(rcx, iobj); runpobj(rcx, prep); voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt); runpobj(rcx, verb); runpobj(rcx, actor); /* catch errors specially for preCommand */ // ERRBEGIN(ctx->voccxerr) // { /* invoke preCommand */ runfn(rcx, ctx->voccxprecmd, 5); // } // ERRCATCH(ctx->voccxerr, err) // { /* * if the error was 'exit', translate it to EXITPRECMD so * that we handle the outer loop correctly (exiting from * preCommand skips execution for all subsequent objects, * but doesn't skip fuses and daemons) */ if (err == ERR_RUNEXIT) errsig(ctx->voccxerr, ERR_RUNEXITPRECMD); /* no special handling - just resignal the error */ errrse(ctx->voccxerr); // } // ERREND(ctx->voccxerr); } /* show the pre-object prefix if the caller instructed us to do so */ voc_multi_prefix(ctx, dobj, show_multi_prefix, multi_flags, cur_dobj_idx, dobj_cnt); /* * check to see if the verb has verbAction defined - if so, invoke * the method */ if (objgetap(ctx->voccxmem, verb, PRP_VERBACTION, (objnum *)0, FALSE)) { /* call verb.verbAction(actor, dobj, prep, iobj) */ runpobj(rcx, iobj); runpobj(rcx, prep); runpobj(rcx, dobj); runpobj(rcx, actor); runppr(rcx, verb, PRP_VERBACTION, 4); } /* invoke cmdActor.actorAction(verb, dobj, prep, iobj) */ runpobj(rcx, iobj); runpobj(rcx, prep); runpobj(rcx, dobj); runpobj(rcx, verb); runppr(rcx, actor, PRP_ACTORACTION, 4); /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* invoke actor.location.roomAction(actor, verb, dobj, prep, iobj) */ runppr(rcx, actor, PRP_LOCATION, 0); if (runtostyp(rcx) == DAT_OBJECT) { loc = runpopobj(rcx); /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* call roomAction */ runpobj(rcx, iobj); runpobj(rcx, prep); runpobj(rcx, dobj); runpobj(rcx, verb); runpobj(rcx, actor); runppr(rcx, loc, PRP_ROOMACTION, 5); } else { /* the location isn't an object, so discard it */ rundisc(rcx); } /* if there's an indirect object, execute iobjCheck */ if (iobj != MCMONINV) { /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* invoke iobjCheck */ runpobj(rcx, prep); runpobj(rcx, dobj); runpobj(rcx, verb); runpobj(rcx, actor); runppr(rcx, iobj, PRP_IOBJCHECK, 4); } /* * If there's an indirect object, and the indirect object doesn't * directly define io, call iobj.iobjGen(actor, verb, dobj, * prep) */ if (iobj != MCMONINV && exegen(ctx, iobj, PRP_IOBJGEN, voctplvi(tpl), voctplio(tpl))) { /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* invoke iobjGen */ runpobj(rcx, prep); runpobj(rcx, dobj); runpobj(rcx, verb); runpobj(rcx, actor); runppr(rcx, iobj, PRP_IOBJGEN, 4); } /* if there's an direct object, execute dobjCheck */ if (dobj != MCMONINV) { /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* invoke dobjCheck */ runpobj(rcx, prep); runpobj(rcx, iobj); runpobj(rcx, verb); runpobj(rcx, actor); runppr(rcx, dobj, PRP_DOBJCHECK, 4); } /* * If there's a direct object, and the direct object doesn't * directly define do, call dobj.dobjGen(actor, verb, iobj, * prep) */ if (dobj != MCMONINV && exegen(ctx, dobj, PRP_DOBJGEN, voctplvd(tpl), voctpldo(tpl))) { /* reset the run-time context if this is a top-level call */ if (!recursive) runrst(rcx); /* invoke dobjGen */ runpobj(rcx, prep); runpobj(rcx, iobj); runpobj(rcx, verb); runpobj(rcx, actor); runppr(rcx, dobj, PRP_DOBJGEN, 4); } /* reset the hidden-text flag */ tiohide(ctx->voccxtio); tioshow(ctx->voccxtio); /* * Now do what needs to be done, depending on the sentence structure: * * No objects ==> cmdVerb.action( cmdActor ) * * Direct object only ==> cmdDobj.verDo( actor ) *. cmdDobj.do( actor ) * * Indirect + direct ==> cmdDobj.verDo( actor, cmdIobj ) *. cmdIobj.verIo( actor, cmdDobj ) *. cmdIobj.io( actor, cmdDobj ) */ if (dobj == MCMONINV) { /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* invoke verb.action */ runpobj(rcx, actor); runppr(rcx, verb, PRP_ACTION, 1); } else if (iobj == MCMONINV) { if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE)) { /* display the error */ exeperr(ctx, verb, dobj, MCMONINV, MCMONINV); /* note that verDoVerb failed */ err = ERR_PRS_NO_VERDO; /* we're done with this command */ goto skipToFuses; } /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* invoke dobj.verDoVerb */ runpobj(rcx, actor); runppr(rcx, dobj, voctplvd(tpl), 1); /* check for an error message from verDoVerb */ if (!tioshow(ctx->voccxtio)) { /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* dobj.verDoVerb displayed no output - process dobj.doVerb */ runpobj(rcx, actor); runppr(rcx, dobj, voctpldo(tpl), 1); } else { /* note that verDoVerb failed */ err = ERR_PRS_VERDO_FAIL; } } else { /* check to see if the verDoVerb and verIoVerb methods exist */ if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE)) { /* no verDoVerb method - show a default message */ exeperr(ctx, verb, dobj, MCMONINV, MCMONINV); /* note the error */ err = ERR_PRS_NO_VERDO; /* skip to the end of the turn */ goto skipToFuses; } else if (!objgetap(ctx->voccxmem, iobj, voctplvi(tpl), (objnum *)0, FALSE)) { /* no verIoVerb method - show a default mesage */ exeperr(ctx, verb, MCMONINV, prep, iobj); /* note the error */ err = ERR_PRS_NO_VERIO; /* skip to the end of the turn */ goto skipToFuses; } /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* call verDoVerb(actor [,iobj]) */ if (!dobj_first) runpobj(rcx, iobj); runpobj(rcx, actor); runppr(rcx, dobj, voctplvd(tpl), (dobj_first ? 1 : 2)); /* check for error output from verDoVerb */ if (!tioshow(ctx->voccxtio)) { /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* no error from verDoVerb - call verIoVerb(actor [,dobj]) */ if (dobj_first) runpobj(rcx, dobj); runpobj(rcx, actor); runppr(rcx, iobj, voctplvi(tpl), (dobj_first ? 2 : 1)); /* check for error output from verIoVerb */ if (!tioshow(ctx->voccxtio)) { /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* no error from verDoVerb or verIoVerb - call ioVerb */ runpobj(rcx, dobj); runpobj(rcx, actor); runppr(rcx, iobj, voctplio(tpl), 2); } else { /* note the error */ err = ERR_PRS_VERIO_FAIL; } } else { /* note the error */ err = ERR_PRS_VERDO_FAIL; } } skipToFuses: ERRCATCH(ctx->voccxerr, err) { /* if askIo was invoked, get the preposition from the error stack */ if (err == ERR_RUNASKI) *prepptr = errargint(0); /* * If we executed 'abort', we'll skip straight to endCommand. * * If we executed askDo or askIo, we won't execute anything * more, because the command is being interrupted. * * If 'exit' or 'exitobj' was executed, proceed through * postAction and subsequent steps. * * If any error occurred other than 'exit' or 'exitobj' being * invoked, resignal the error. * * We don't need to do anything more at this point if 'exit' was * invoked, because 'exit' merely skips to the end-of-turn * phase, which is where we'll go next from here. * * If 'exitobj' was invoked, we don't want to return an error at * all, since we just want to skip the remainder of the normal * processing for the current object and proceed to the next * object (in a command with multiple direct objects). */ if (err == ERR_RUNABRT) { /* * aborting - we're going to call postAction, but we're not * going to execute fuses and daemons */ do_fuses = FALSE; endturn = TRUE; } else if (err == ERR_RUNASKD || err == ERR_RUNASKI) { /* we're going to skip all end-of-turn action */ do_fuses = FALSE; do_postact = FALSE; endturn = FALSE; } else if (err == ERR_RUNEXIT) { /* * Proceed with the remainder of the processing for this * turn, but retain the error code to return to our caller, * so they know that the rest of the turn is to be skipped. * * In addition, set 'do_fuses' to true, since we want to go * directly to the fuse and daemon processing for this turn, * regardless of whether any other objects are present * (because we'll skip any remaining objects). */ endturn = TRUE; do_fuses = TRUE; } else if (err == ERR_RUNEXITPRECMD) { /* * exited from preCommand - end the turn, but do not run the * postAction routine */ do_fuses = TRUE; do_postact = FALSE; endturn = TRUE; } else if (err == ERR_RUNEXITOBJ) { /* * Proceed with the remainder of processing for this turn - * we want to proceed to the next object, if any, and * process it as normal. We don't need to update 'endturn' * or 'do_fuses', since we want to do all of those in the * normal fashion. */ } else { /* * We can't handle any other errors. Restore the enclosing * command context, and resignal the error. */ /* restore the previous tio actor setting */ tiosetactor(ctx->voccxtio, old_tio_actor); /* restore the original context iobj and dobj settings */ ctx->voccxdobj = old_ctx_dobj; ctx->voccxiobj = old_ctx_iobj; /* restore the original context command objects */ ctx->voccxactor = old_actor; ctx->voccxverb = old_verb; ctx->voccxprep = old_prep; /* resignal the error */ errrse(ctx->voccxerr); } } ERREND(ctx->voccxerr); /* * If desired, call postAction(actor, verb, dobj, prep, iobj, * error_status). */ if (do_postact && ctx->voccxpostact != MCMONINV) { int err2; ERRBEGIN(ctx->voccxerr) { /* push the arguments */ runpnum(rcx, err); runpobj(rcx, iobj); runpobj(rcx, prep); runpobj(rcx, dobj); runpobj(rcx, verb); runpobj(rcx, actor); /* invoke postAction */ runfn(rcx, ctx->voccxpostact, 6); } ERRCATCH(ctx->voccxerr, err2) { /* remember the new error condition */ err = err2; /* if we're aborting, skip fuses and daemons */ if (err == ERR_RUNABRT) { endturn = TRUE; do_fuses = FALSE; } } ERREND(ctx->voccxerr); } /* restore the original context iobj and dobj settings */ ctx->voccxdobj = old_ctx_dobj; ctx->voccxiobj = old_ctx_iobj; /* restore the original context command objects */ ctx->voccxverb = old_verb; ctx->voccxprep = old_prep; /* reset the stack for top-level calls */ if (!recursive) runrst(rcx); /* * If this is the end of the turn, execute fuses and daemons. Skip * fuses on recursive calls, since we want to count them as part of * the enclosing turn. */ if (endturn && !recursive) { /* catch errors so that we can restore the actor globals */ ERRBEGIN(ctx->voccxerr) { /* run fuses, daemons, and endCommand */ err = exe_fuses_and_daemons(ctx, err, do_fuses, actor, verb, dobj_list, dobj_cnt, prep, iobj); } ERRCLEAN(ctx->voccxerr) { /* restore the previous actor globals */ ctx->voccxactor = old_actor; tiosetactor(ctx->voccxtio, old_tio_actor); } ERRENDCLN(ctx->voccxerr); } /* restore the previous actor globals */ ctx->voccxactor = old_actor; tiosetactor(ctx->voccxtio, old_tio_actor); /* success */ return err; } /* * saveit stores the current direct object list in 'it' or 'them'. */ static void exesaveit(voccxdef *ctx, vocoldef *dolist) { int cnt; int i; int dbg = ctx->voccxflg & VOCCXFDBG; runcxdef *rcx = ctx->voccxrun; cnt = voclistlen(dolist); if (cnt == 1) { /* * check to make sure they're not referring to a number or a * string; if so, it doesn't make any sense to save it */ if (dolist[0].vocolflg == VOCS_STR || dolist[0].vocolflg == VOCS_NUM) { /* * As of 2.5.11, don't clear 'it' on a number or string; * rather, just leave it as it was from the prior command. * Players will almost never expect a number or string to have * anything to do with pronoun antecedents, and in fact some * players reported finding it confusing to have the antecedant * implied by the second-most-recent command disappear when the * most recent command used a number of string. */ #if 0 /* save a nil 'it' */ ctx->voccxit = MCMONINV; if (dbg) tioputs(ctx->voccxtio, ".. setting 'it' to nil (strObj/numObj)\\n"); #endif /* we're done */ return; } /* save 'it' */ ctx->voccxit = dolist[0].vocolobj; ctx->voccxthc = 0; if (dbg) { tioputs(ctx->voccxtio, ".. setting it: "); runppr(rcx, ctx->voccxit, PRP_SDESC, 0); tioputs(ctx->voccxtio, "\\n"); } /* set "him" if appropriate */ runppr(rcx, ctx->voccxit, PRP_ISHIM, 0); if (runtostyp(rcx) == DAT_TRUE) { ctx->voccxhim = ctx->voccxit; if (dbg) tioputs(ctx->voccxtio, "... [setting \"him\" to same object]\\n"); } rundisc(rcx); /* set "her" if appropriate */ runppr(rcx, ctx->voccxit, PRP_ISHER, 0); if (runtostyp(rcx) == DAT_TRUE) { ctx->voccxher = ctx->voccxit; if (dbg) tioputs(ctx->voccxtio, "... [setting \"her\" to same object]\\n"); } rundisc(rcx); } else if (cnt > 1) { ctx->voccxthc = cnt; ctx->voccxit = MCMONINV; if (dbg) tioputs(ctx->voccxtio, ".. setting \"them\": ["); for (i = 0 ; i < cnt ; ++i) { ctx->voccxthm[i] = dolist[i].vocolobj; if (dbg) { runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0); tioputs(ctx->voccxtio, i+1 < cnt ? ", " : "]\\n"); } } } } /* display a multiple-object prefix */ void voc_multi_prefix(voccxdef *ctx, objnum objn, int show_prefix, int multi_flags, int cur_index, int count) { runcxdef *rcx = ctx->voccxrun; /* if the object is invalid, ignore it */ if (objn == MCMONINV) return; /* * if there's a prefixdesc method defined, call it rather than the * older multisdesc (or even older sdesc) approach */ if (objgetap(ctx->voccxmem, objn, PRP_PREFIXDESC, (objnum *)0, FALSE) != 0) { runsdef val; /* push the word flags */ runpnum(rcx, multi_flags); /* * push the object count and the current index (adjusted to a * 1-based value) */ runpnum(rcx, count); runpnum(rcx, cur_index + 1); /* push the 'show' flag */ val.runstyp = runclog(show_prefix); runpush(rcx, val.runstyp, &val); /* call the method */ runppr(rcx, objn, PRP_PREFIXDESC, 4); /* we're done */ return; } /* * if we're not showing the prefix, don't use the multisdesc/sdesc * display */ if (!show_prefix) return; /* * use multisdesc if defined (for compatibility with older games, * use sdesc if multisdesc doesn't exist for this object) */ if (objgetap(ctx->voccxmem, objn, PRP_MULTISDESC, (objnum *)0, FALSE) == 0) { /* there's no multisdesc defined - use the plain sdesc */ runppr(rcx, objn, PRP_SDESC, 0); } else { /* multisdesc is defined - use it */ runppr(rcx, objn, PRP_MULTISDESC, 0); } /* show the colon */ vocerr_info(ctx, VOCERR(120), ": "); } /* execute command for each object in direct object list */ static int exeloop(voccxdef *ctx, objnum actor, objnum verb, vocoldef *dolist, objnum *prep, vocoldef *iobj, int multi_flags, uchar *tpl, int newstyle) { runcxdef *rcx = ctx->voccxrun; int err; int i; int dobj_cnt; int exec_cnt; vocoldef *dobj; /* * count the direct objects; we'll iterate over the direct objects, * so we execute the command once per direct object */ exec_cnt = dobj_cnt = (dolist != 0 ? voclistlen(dolist) : 0); /* * if there are no direct objects, we still must execute the command * once */ if (exec_cnt < 1) exec_cnt = 1; /* * If we have multiple direct objects, or we're using "all" with * just one direct object, check with the verb to see if multiple * words are acceptable: call verb.rejectMultiDobj, and see what it * returns; if it returns true, don't allow multiple words, and * expect that rejectMultiDobj displayed an error message. * Otherwise, proceed. */ if (((multi_flags & VOCS_ALL) != 0 || dobj_cnt > 1) && dolist && dolist[0].vocolobj != MCMONINV) { int typ; ERRBEGIN(ctx->voccxerr) runrst(rcx); if (!prep || *prep == MCMONINV) runpnil(rcx); else runpobj(rcx, *prep); runppr(rcx, verb, PRP_REJECTMDO, 1); typ = runtostyp(rcx); rundisc(rcx); ERRCATCH(ctx->voccxerr, err) if (err == ERR_RUNEXIT || err == ERR_RUNEXITOBJ || err == ERR_RUNABRT) return err; else errrse(ctx->voccxerr); ERREND(ctx->voccxerr) /* if they returned 'true', don't bother continuing */ if (typ == DAT_TRUE) return 0; } /* * execute the command the required number of times */ for (i = 0 ; i < exec_cnt ; ++i) { int show_multi_prefix; /* get the current direct object, if we have one */ dobj = (dolist != 0 ? &dolist[i] : 0); /* * If we have a number or string, set the current one in * numObj/strObj */ if (dolist != 0) { if (dolist[i].vocolflg == VOCS_STR) { /* it's a string - set strObj.value */ vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING, dolist[i].vocolfst + 1, &dolist[i], &dolist[i]); } else if (dolist[i].vocolflg == VOCS_NUM) { long v1, v2; /* it's a number - set numObj.value */ v1 = atol(dolist[i].vocolfst); oswp4s(&v2, v1); vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, &v2, &dolist[i], &dolist[i]); } } /* * For cases where we have a bunch of direct objects (or even * one when "all" was used), we want to preface the output from * each iteration with the name of the object we're acting on * currently. In other cases, there is no prefix. */ show_multi_prefix = ((multi_flags != 0 || dobj_cnt > 1) && dobj != 0); /* * Execute the command for this object. For every object except * the first, re-validate the direct and indirect objects. * There's no need to re-validate the objects on the first * object in a command, because that will already have been done * during object resolution. */ err = exe1cmd(ctx, actor, verb, dobj, prep, iobj, (i + 1 == exec_cnt), tpl, newstyle, FALSE, i != 0, i != 0, dolist, i, dobj_cnt, show_multi_prefix, multi_flags); /* check the error - ignore any verification failures */ switch(err) { case ERR_PRS_VERDO_FAIL: case ERR_PRS_VERIO_FAIL: case ERR_PRS_NO_VERDO: case ERR_PRS_NO_VERIO: case ERR_RUNEXITOBJ: case ERR_RUNEXIT: /* ignore the error and continue */ err = 0; break; case ERR_RUNEXITPRECMD: /* * exited from preCommand - skip execution of subsequent * objects, but return success */ return 0; case 0: /* no error; continue */ break; default: /* anything else stops this command */ return err; } /* flush output */ tioflush(ctx->voccxtio); } /* success */ return 0; } /* * Execute a command recursively. Game code can call this routine * (indirectly through a built-in function) to execute a command, using * all of the same steps that would be applied for the command if the * player had typed it. */ int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb, objnum dobj, objnum prep, objnum iobj, int validate_dobj, int validate_iobj) { int err; int newstyle; uchar tpl[VOCTPL2SIZ]; vocoldef dobjv; vocoldef iobjv; voccxdef ctx_copy; runsdef *orig_sp; runsdef *orig_bp; /* * Save the stack and base pointers as they are on entry. Since * exe1cmd() is being called recursively, it won't automatically clear * the stack after it's done as it would at the top level; this means * that an aborted frame can be left on the stack if we throw an * 'exit' or 'abort' in the course of executing the command. To make * sure we don't leave any aborted frames on the stack before * returning to our caller, we simply need to restore the stack and * frame pointers on the way out as they were on the way in. */ orig_sp = ctx->voccxrun->runcxsp; orig_bp = ctx->voccxrun->runcxbp; /* make a copy of the voc context, so that changes aren't permanent */ ctx_copy = *ctx; ctx = &ctx_copy; /* * there are no unknown words in the recursive command, since the * command was prepared directly from resolved objects */ ctx->voccxunknown = 0; /* set up the vocoldef structure for the direct object, if present */ if (dobj != MCMONINV) { dobjv.vocolobj = dobj; dobjv.vocolfst = dobjv.vocollst = ""; dobjv.vocolflg = 0; } /* set up the vocoldef structure for the indirect object, if present */ if (iobj != MCMONINV) { iobjv.vocolobj = iobj; iobjv.vocolfst = iobjv.vocollst = ""; iobjv.vocolflg = 0; } /* figure out which template we need, based on the objects provided */ if (dobj == MCMONINV) { uint actofs; uint tplofs; /* * No objects were provided - use the verb's "action" method. * Make sure that there is in fact an "action" method. */ exe_get_tpl(ctx, verb, &tplofs, &actofs); if (actofs != 0) { /* execute the "action" method */ err = exe1cmd(ctx, actor, verb, 0, &prep, 0, FALSE, 0, FALSE, TRUE, validate_dobj, validate_iobj, 0, 0, 0, FALSE, 0); } else { /* indicate that the sentence structure wasn't understood */ err = ERR_PRS_SENT_UNK; } } else if (iobj == MCMONINV) { /* * No indirect object was provided, but a direct object is * present - use the one-object template. First, look up the * template. */ if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle)) { /* execute the command */ err = exe1cmd(ctx, actor, verb, &dobjv, &prep, 0, FALSE, tpl, newstyle, TRUE, validate_dobj, validate_iobj, &dobjv, 0, 1, FALSE, 0); } else { /* indicate that the sentence structure wasn't understood */ err = ERR_PRS_SENT_UNK; } } else { /* * Both a direct and indirect object were provided - find the * two-object template for the given preposition. */ if (voctplfnd(ctx, verb, prep, tpl, &newstyle)) { /* execute the command */ err = exe1cmd(ctx, actor, verb, &dobjv, &prep, &iobjv, FALSE, tpl, newstyle, TRUE, validate_dobj, validate_iobj, &dobjv, 0, 1, FALSE, 0); } else { /* indicate that the sentence structure wasn't understood */ err = ERR_PRS_SENT_UNK; } } /* * if the error was EXITPRECMD, change it to EXIT - EXITPRECMD is a * special flag indicating that we exited from a preCommand * function, which is different than normal exiting internally but * not to the game */ if (err == ERR_RUNEXITPRECMD) err = ERR_RUNEXIT; /* * restore the original stack and base pointers, to ensure that we * don't leave any aborted frames on the stack */ ctx->voccxrun->runcxsp = orig_sp; ctx->voccxrun->runcxbp = orig_bp; /* return the result code */ return err; } /* * Check for ALL, ANY, or THEM in the list - use multi-mode if found, * even if we have only one object. Returns a combination of any of the * VOCS_ALL, VOCS_ANY, or VOCS_THEM flags that we find. */ static int check_for_multi(vocoldef *dolist) { int dolen; int i; int result; /* presume we won't find any flags */ result = 0; /* * scan the list for ALL, ANY, or THEM flags, combining any such * flags we find into the result */ dolen = voclistlen(dolist); for (i = 0 ; i < dolen ; ++i) result |= (dolist[i].vocolflg & (VOCS_ALL | VOCS_ANY | VOCS_THEM)); /* return the result */ return result; } /* ------------------------------------------------------------------------ */ /* * Try running the preparseCmd user function. Returns 0 if the * function doesn't exist or returns 'true', ERR_PREPRSCMDCAN if it * returns 'nil' (and thus wants to cancel the command), and * ERR_PREPRSCMDREDO if it returns a list (and thus wants to redo the * command). */ int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt, uchar **preparse_list) { uchar listbuf[VOCBUFSIZ + 2 + 3*VOCBUFSIZ]; int i; uchar *p; size_t len; runsdef val; int typ; int err; /* if there's no preparseCmd, keep processing */ if (ctx->voccxppc == MCMONINV) return 0; /* build a list of the words */ for (p = listbuf + 2, i = 0 ; i < wrdcnt ; ++i) { char *src; int add_quote; /* check for strings - they require special handling */ if (cmd[i][0] == '"') { /* * it's a string - what follows is a run-time style string, * with a length prefix followed by the text of the string */ len = osrp2(cmd[i] + 1) - 2; src = cmd[i] + 3; /* add quotes to the result */ add_quote = TRUE; } else { /* ordinary word - copy directly */ src = (char *)cmd[i]; /* it's a null-terminated string */ len = strlen(src); /* don't add quotes to the result */ add_quote = FALSE; } /* write the type prefix */ *p++ = DAT_SSTRING; /* write the length prefix */ oswp2(p, len + 2 + (add_quote ? 2 : 0)); p += 2; /* add an open quote if necessary */ if (add_quote) *p++ = '"'; /* copy the text */ memcpy(p, src, len); p += len; /* add the closing quote if necessary */ if (add_quote) *p++ = '"'; } /* set the length of the whole list */ len = p - listbuf; oswp2(listbuf, len); /* push the list as the argument, and call the user's preparseCmd */ val.runstyp = DAT_LIST; val.runsv.runsvstr = listbuf; runpush(ctx->voccxrun, DAT_LIST, &val); /* presume that no error will occur */ err = 0; /* catch errors that occur within preparseCmd */ ERRBEGIN(ctx->voccxerr) { /* call preparseCmd */ runfn(ctx->voccxrun, ctx->voccxppc, 1); } ERRCATCH(ctx->voccxerr, err) { /* * if it's abort/exit/exitobj, just return it; for any other * errors, just re-throw the same error */ switch(err) { case ERR_RUNABRT: case ERR_RUNEXIT: case ERR_RUNEXITOBJ: /* simply return these errors to the caller */ break; default: /* re-throw anything else */ errrse(ctx->voccxerr); } } ERREND(ctx->voccxerr); /* if an error occurred, return the error code */ if (err != 0) return err; /* get the result */ typ = runtostyp(ctx->voccxrun); /* if they returned a list, it's a new command to execute */ if (typ == DAT_LIST) { /* get the list and give it to the caller */ *preparse_list = runpoplst(ctx->voccxrun); /* * indicate that the command is to be reparsed with the new word * list */ return ERR_PREPRSCMDREDO; } /* for any other type, we don't need the value, so discard it */ rundisc(ctx->voccxrun); /* if the result is nil, don't process this command further */ if (typ == DAT_NIL) return ERR_PREPRSCMDCAN; else return 0; } /* ------------------------------------------------------------------------ */ /* * Call parseAskobjIndirect */ static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist, objnum actor, objnum verb, objnum prep) { int cnt; int i; size_t len; uchar *lstp; /* * Generate the direct object list argument. This argument is a * list of lists. For each noun phrase, we generate one sublist in * the main list. Each sublist itself consists of three * sub-sublists: first, a list of strings giving the words in the * noun phrase; second, a list of the objects matching the noun * phrase; third, a list of the flags for the matching objects. * * So, if the player typed "put red box and blue ball", we might * generate a list something like this: * * [ [ ['red', 'box'], [redBox1, redBox2], [0, 0] ], [ ['blue', * 'ball'], [blueBall], [0, 0] ] ] */ /* * First, figure out how much space we need for this list of lists * of lists. Scan the direct object list for distinct noun phrases * - we need one sublist for each distinct noun phrase. */ cnt = voclistlen(dolist); for (len = 0, i = 0 ; i < cnt ; ) { const char *p; size_t curlen; int j; /* * we need the sublist type prefix (one byte) plus the sublist * length prefix (two bytes), plus the type and length prefixes * (one plus two bytes) for each of the three sub-sublist */ len += (1+2) + 3*(1+2); /* * we need space to store the strings for the words in this noun * phrase */ for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ; p += curlen + 1) { /* * add in the space needed for this string element in the * sub-sublist - we need one byte for the type prefix, two * bytes for the length prefix, and the bytes for the string * itself */ curlen = strlen(p); len += (1+2) + curlen; } /* * scan each object for this same noun phrase (i.e., for which * the vocabulary words are the same) */ for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ; ++j) { /* * Add in space for this object in the sub-sublist for the * current noun phrase. If this object is nil, we need only * one byte for the type; otherwise, we need one byte for * the type prefix plus two bytes for the object ID. */ if (dolist[i].vocolobj == MCMONINV) len += 1; else len += (1 + 2); /* * Add in space for the flags sub-sublist for the current * object. We need one byte for the type and four for the * integer value. */ len += (1 + 4); } /* skip to the next distinct noun phrase */ i = j; } /* allocate the list */ lstp = voc_push_list_siz(ctx, len); /* * Go through our object array again, and this time actually build * the list. */ for (i = 0 ; i < cnt ; ) { const char *p; uchar *subp; uchar *subsubp; size_t curlen; int j; /* start the sublist with the type prefix */ *lstp++ = DAT_LIST; /* leave a placeholder for our length prefix */ subp = lstp; lstp += 2; /* start the sub-sublist with the word strings */ *lstp++ = DAT_LIST; subsubp = lstp; lstp += 2; /* store the word strings in the sub-sublist */ for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ; p += curlen + 1) { /* get this string's length */ curlen = strlen(p); /* store the type and length prefixes */ *lstp++ = DAT_SSTRING; oswp2(lstp, curlen + 2); lstp += 2; /* store the string */ memcpy(lstp, p, curlen); lstp += curlen; } /* fix up the string sub-sublist length */ oswp2(subsubp, lstp - subsubp); /* start the second sub-sublist, for the objects */ *lstp++ = DAT_LIST; subsubp = lstp; lstp += 2; /* write each object */ for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ; ++j) { /* * if this object isn't nil, write it to the sub-sublist; * otherwise, just put nil in the sub-sublist */ if (dolist[j].vocolobj != MCMONINV) { *lstp++ = DAT_OBJECT; oswp2(lstp, dolist[j].vocolobj); lstp += 2; } else { /* no object - just store nil */ *lstp++ = DAT_NIL; } } /* fix up the object sub-sublist length */ oswp2(subsubp, lstp - subsubp); /* start the third sub-sublist, for the flags */ *lstp++ = DAT_LIST; subsubp = lstp; lstp += 2; /* write each object's flags */ for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ; ++j) { /* write the flags */ *lstp++ = DAT_NUMBER; oswp4s(lstp, dolist[j].vocolflg); lstp += 4; } /* fix up the flag sub-sublist length */ oswp2(subsubp, lstp - subsubp); /* skip to the start of the next distinct noun phrase */ i = j; /* fix up the sublist length */ oswp2(subp, lstp - subp); } /* push the prep, verb, and actor arguments */ runpobj(ctx->voccxrun, prep); runpobj(ctx->voccxrun, verb); runpobj(ctx->voccxrun, (objnum)(actor == MCMONINV ? ctx->voccxme : actor)); /* call the function */ runfn(ctx->voccxrun, ctx->voccxpask3, 4); } /* ------------------------------------------------------------------------ */ /* * execmd() - executes a user's command given the verb's verb and * preposition words, a list of nouns to be used as indirect objects, * and a list to be used for direct objects. The globals cmdActor and * cmdPrep should already be set. This routine tries to find a template * for the verb which matches the player's command. If no template * matches, we try (using default objects and, if that fails, requests * to the player for objects) to fill in any missing information in the * player's command. If that still fails, we will say we don't * understand the sentence and leave it at that. */ int execmd(voccxdef *ctx, objnum actor, objnum prep, char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist, char **cmd, int *typelist, char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_word) { objnum verb; objnum iobj; int multi_flags = 0; vocwdef *n; int cnt; vocoldef *newnoun; int next; char *exenewcmd; char *donewcmd; char *ionewcmd; char *exenewbuf; char *donewbuf; char *ionewbuf; char **exenewlist; char **donewlist; char **ionewlist; int *exenewtype; int *donewtype; int *ionewtype; vocoldef *dolist1; vocoldef *iolist1; uchar tpl[VOCTPL2SIZ]; int foundtpl; /* used to determine success of tpl searches */ runcxdef *rcx = ctx->voccxrun; uint tplofs; /* offset of template object */ uint actofs; /* offset of 'action' property */ int askflags; /* flag for what we need to ask user */ int newstyle; /* flag indicating new-style template definitions */ int tplflags; int err; uchar *save_sp; /* run preparseCmd */ switch(try_preparse_cmd(ctx, cmd, wrdcnt, preparse_list)) { case 0: /* proceed with the command */ break; case ERR_PREPRSCMDCAN: /* command cancelled */ return 0; case ERR_RUNEXIT: case ERR_RUNABRT: case ERR_RUNEXITOBJ: /* abort/exit/exitobj - treat this the same as command cancellation */ return 0; case ERR_PREPRSCMDREDO: /* redo the command - so indicate to the caller */ return ERR_PREPRSCMDREDO; } /* look up the verb based on the verb and verb-prep */ n = vocffw(ctx, vverb, (int)strlen(vverb), vprep, (vprep ? (int)strlen(vprep) : 0), PRP_VERB, (vocseadef *)0); /* if we didn't find a verb template, we can't process the sentence */ if (n == 0) { /* try parseUnknownVerb, and show an error if that doesn't handle it */ if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word, TRUE, VOCERR(18), "I don't understand that sentence.")) { /* they handled it successfully - end the command with success */ return 0; } else { /* * parseUnknownVerb failed or aborted - end the command with * an error */ return 1; } } /* get the deepverb object */ verb = n->vocwobj; /* default actor is "Me" */ if (actor == MCMONINV) actor = ctx->voccxme; /* set a savepoint, if we're keeping undo information */ if (ctx->voccxundo) objusav(ctx->voccxundo); /* * Check that the room will allow this command -- it may not * due to darkness or other ailment. We can find out with the * roomCheck(verb) message, sent to the meobj. */ { int t; /* call roomCheck */ runrst(rcx); runpobj(rcx, verb); runppr(rcx, ctx->voccxme, PRP_ROOMCHECK, 1); t = runpoplog(rcx); /* if they returned nil, stop the command, but indicate success */ if (!t) return 0; } /* look for a new-style template first, then the old-style template */ exe_get_tpl(ctx, verb, &tplofs, &actofs); /* make sure we found a verb */ if (tplofs == 0 && actofs == 0 && verb != ctx->voccxvag) { /* try parseUnknownVerb, and show an error if that doesn't handle it */ if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word, TRUE, VOCERR(23), "internal error: verb has no action, doAction, or ioAction")) return 0; else return 1; } /* * Check to see if we have an "all" - if we do, we'll need to * display the direct object's name even if only one direct object * comes of it. */ multi_flags = check_for_multi(dolist); /* * set up dobj word list in case objwords is used in doDefault (the * game may want to check for "all" and disallow it, for example) */ ctx->voccxdobj = dolist; /* set up our stack allocations, which we may need from now on */ voc_enter(ctx, &save_sp); VOC_STK_ARRAY(ctx, char, donewcmd, VOCBUFSIZ); VOC_STK_ARRAY(ctx, char, ionewcmd, VOCBUFSIZ); VOC_STK_ARRAY(ctx, char, donewbuf, 2*VOCBUFSIZ); VOC_STK_ARRAY(ctx, char, ionewbuf, 2*VOCBUFSIZ); VOC_STK_ARRAY(ctx, char *, donewlist, VOCBUFSIZ); VOC_STK_ARRAY(ctx, char *, ionewlist, VOCBUFSIZ); VOC_MAX_ARRAY(ctx, int, donewtype); VOC_MAX_ARRAY(ctx, int, ionewtype); VOC_MAX_ARRAY(ctx, vocoldef, dolist1); VOC_MAX_ARRAY(ctx, vocoldef, iolist1); /* keep going until we're done with the sentence */ for ( ;; ) { askflags = err = 0; ERRBEGIN(ctx->voccxerr) /* * Now see what kind of sentence we have. If we have no * objects and an action, use the action. If we have a direct * object and a doAction, use the doAction. If we have an * indirect object and an ioAction with a matching preposition, * use the ioAction. If we have an indirect object and no * matching ioAction, complain. If we have a direct object and * no doAction or ioAction, complain. If we have fewer objects * than we really want, ask the user for more of them. */ if (voclistlen(dolist) == 0 && voclistlen(iolist) == 0) { if (actofs || verb == ctx->voccxvag) { if ((err = exeloop(ctx, actor, verb, (vocoldef *)0, &prep, (vocoldef *)0, multi_flags, (uchar *)0, 0)) != 0) goto exit_error; } else { /* * The player has not specified any objects, but the * verb seems to require one. See if there's a unique * default. */ runrst(rcx); runpnil(rcx); runpobj(rcx, prep); runpobj(rcx, actor); runppr(rcx, verb, PRP_DODEFAULT, 3); if (runtostyp(rcx) == DAT_LIST) { uchar *l = runpoplst(rcx); uint lstsiz; objnum defobj = 0; int objcnt; objnum newprep; runsdef val; objnum o; /* push list back on stack, to keep in heap */ val.runsv.runsvstr = l; val.runstyp = DAT_LIST; runrepush(rcx, &val); /* get list size out of list */ lstsiz = osrp2(l) - 2; l += 2; /* find default preposition for verb, if any */ runppr(rcx, verb, PRP_PREPDEFAULT, 0); if (runtostyp(rcx) == DAT_OBJECT) newprep = runpopobj(rcx); else { newprep = MCMONINV; rundisc(rcx); } if (!voctplfnd(ctx, verb, newprep, tpl, &newstyle)) { for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz)) { if (*l == DAT_OBJECT) { ++objcnt; defobj = osrp2(l + 1); } } } else { int dobj_first; /* * Get the template flags. If we must * disambiguate the direct object first for this * verb, do so now. */ tplflags = (newstyle ? voctplflg(tpl) : 0); dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST); for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz)) { if (*l == DAT_OBJECT) { o = osrp2(l + 1); if (!objgetap(ctx->voccxmem, o, voctplvd(tpl), (objnum *)0, FALSE)) continue; tiohide(ctx->voccxtio); if (newprep != MCMONINV && !dobj_first) runpnil(rcx); runpobj(rcx, actor); runppr(rcx, o, voctplvd(tpl), ((newprep != MCMONINV && !dobj_first) ? 2 : 1)); if (!tioshow(ctx->voccxtio)) { ++objcnt; defobj = o; } } } /* no longer need list in heap, so discard it */ rundisc(rcx); /* use default object if there's exactly one */ if (objcnt == 1) { dolist[0].vocolobj = defobj; dolist[0].vocolflg = 0; dolist[0].vocolfst = dolist[0].vocollst = 0; dolist[1].vocolobj = MCMONINV; dolist[1].vocolflg = 0; dolist[1].vocolfst = dolist[1].vocollst = 0; runrst(rcx); if (ctx->voccxpdef2 != MCMONINV) { runpnil(rcx); runpobj(rcx, defobj); runpobj(rcx, verb); runpobj(rcx, actor); runfn(rcx, ctx->voccxpdef2, 4); } else if (ctx->voccxpdef != MCMONINV) { runpnil(rcx); runpobj(rcx, defobj); runfn(rcx, ctx->voccxpdef, 2); } else { /* tell the player what we're doing */ vocerr_info(ctx, VOCERR(130), "("); runppr(rcx, defobj, PRP_THEDESC, 0); vocerr_info(ctx, VOCERR(131), ")"); tioflush(ctx->voccxtio); } err = -2; /* "continue" */ goto exit_error; } } } else rundisc(rcx); /* * No unique default; ask the player for a direct * object, and try the command again if he is kind * enough to provide one. */ askflags = ERR_RUNASKD; } } else if (voclistlen(iolist) == 0) { /* direct object(s), but no indirect object -- find doAction */ if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle)) { /* disambiguate the direct object list, now that we can */ if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT, PRP_VALIDDO, voctplvd(tpl), cmd, MCMONINV, actor, verb, prep, cmdbuf, FALSE)) { err = -1; goto exit_error; } iobj = MCMONINV; /* * save the disambiguated direct object list, in case * we hit an askio in the course of processing it */ memcpy(dolist, dolist1, (size_t)(voclistlen(dolist1) + 1)*sizeof(dolist[0])); /* re-check for multi-mode */ if (multi_flags == 0) multi_flags = check_for_multi(dolist1); /* save it/them/him/her, and execute the command */ exesaveit(ctx, dolist1); if ((err = exeloop(ctx, actor, verb, dolist1, &prep, (vocoldef *)0, multi_flags, tpl, newstyle)) != 0) goto exit_error; } else { /* no doAction - we'll need to find an indirect object */ runrst(rcx); runppr(rcx, verb, PRP_PREPDEFAULT, 0); if (runtostyp(rcx) != DAT_OBJECT) { /* discard the result */ rundisc(rcx); /* call parseUnknownVerb to handle it */ if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word, TRUE, VOCERR(24), "I don't recognize that sentence.")) { /* handled - end the command successfully */ err = 0; } else { /* not handled - indicate failure */ err = -1; } goto exit_error; } prep = runpopobj(rcx); runrst(rcx); runpobj(rcx, prep); runpobj(rcx, actor); runppr(rcx, verb, PRP_IODEFAULT, 2); if (runtostyp(rcx) == DAT_LIST) { uchar *l = runpoplst(rcx); uint lstsiz; objnum defobj = 0; int objcnt; runsdef val; objnum o; /* push list back on stack, to keep in heap */ val.runsv.runsvstr = l; val.runstyp = DAT_LIST; runrepush(rcx, &val); /* get list size out of list */ lstsiz = osrp2(l) - 2; l += 2; if (!voctplfnd(ctx, verb, prep, tpl, &newstyle)) { for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz)) { if (*l == DAT_OBJECT) { objcnt++; defobj = osrp2(l + 1); } } } else { int dobj_first; /* * Get the template flags. If we must * disambiguate the direct object first for this * verb, do so now. */ tplflags = (newstyle ? voctplflg(tpl) : 0); dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST); if (dobj_first) { if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT, PRP_VALIDDO, voctplvd(tpl), cmd, MCMONINV, actor, verb, prep, cmdbuf, FALSE)) { err = -1; goto exit_error; } /* only one direct object is allowed here */ if (voclistlen(dolist1) > 1) { vocerr(ctx, VOCERR(28), "You can't use multiple objects with this command."); err = -1; goto exit_error; } /* save the object in the original list */ memcpy(dolist, dolist1, (size_t)(2 * sizeof(dolist[0]))); } for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz)) { if (*l == DAT_OBJECT) { o = osrp2(l + 1); if (!objgetap(ctx->voccxmem, o, voctplvi(tpl), (objnum *)0, FALSE)) continue; tiohide(ctx->voccxtio); if (dobj_first) runpobj(rcx, dolist[0].vocolobj); runpobj(rcx, actor); runppr(rcx, o, voctplvi(tpl), (dobj_first ? 2 : 1)); if (!tioshow(ctx->voccxtio)) { objcnt++; defobj = o; } } } } /* no longer need list in heap, so discard it */ rundisc(rcx); /* if there's exactly one default object, use it */ if (objcnt == 1) { iolist[0].vocolobj = defobj; iolist[0].vocolflg = 0; iolist[0].vocolfst = iolist[0].vocollst = 0; iolist[1].vocolobj = MCMONINV; iolist[1].vocolflg = 0; iolist[1].vocolfst = iolist[1].vocollst = 0; /* tell the user what we're assuming */ runrst(rcx); if (ctx->voccxpdef2 != MCMONINV) { runpobj(rcx, prep); runpobj(rcx, defobj); runpobj(rcx, verb); runpobj(rcx, actor); runfn(rcx, ctx->voccxpdef2, 4); } else if (ctx->voccxpdef != MCMONINV) { runpobj(rcx, prep); runpobj(rcx, defobj); runfn(rcx, ctx->voccxpdef, 2); } else { vocerr_info(ctx, VOCERR(130), "("); runppr(rcx, prep, PRP_SDESC, 0); vocerr_info(ctx, VOCERR(132), " "); runppr(rcx, defobj, PRP_THEDESC, 0); vocerr_info(ctx, VOCERR(131), ")"); } tioflush(ctx->voccxtio); err = -2; /* "continue" */ goto exit_error; } } else rundisc(rcx); /* * We didn't get a unique default indirect object, so * we should ask the player for an indirct object, and * repeat the command should he provide one. */ askflags = ERR_RUNASKI; } } else { objnum otherobj; /* find the template for this verb/prep combination */ if (!voctplfnd(ctx, verb, prep, tpl, &newstyle)) { vocoldef *np1; /* * If we could have used the preposition in the first noun * phrase rather than in the verb, and this would have * yielded a valid verb phrase, the error is "I don't see * any here". * * Otherwise, it's a verb phrasing error. In this case, * call parseUnknownVerb to handle the error; the default * error is "I don't recognize that sentence". */ np1 = dolist[0].vocolfst < iolist[0].vocolfst ? dolist : iolist; if ((np1->vocolflg & VOCS_TRIMPREP) != 0) { char namebuf[VOCBUFSIZ]; /* * it's a trimmed prep phrase, so we actually have an * unmatched object - report the error */ voc_make_obj_name_from_list( ctx, namebuf, cmd, np1->vocolfst, np1->vocolhlst); vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf); /* terminate the command with an error */ err = -1; } else if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word, TRUE, VOCERR(24), "I don't recognize that sentence.")) { /* they handled it - terminate command successfully */ err = 0; } else { /* that failed - terminate the command with an error */ err = -1; } /* terminate the command */ goto exit_error; } /* * We have both direct and indirect objects. If we don't * yet have the direct object, go ask for it */ if (voclistlen(dolist) == 0) { askflags = ERR_RUNASKD; goto exit_error; } /* get the flags (if old-style, flags are always zero) */ tplflags = (newstyle ? voctplflg(tpl) : 0); /* * the "other" object (dobj if doing iobj, iobj if doing * dobj) is not known when the first object is disambiguated */ otherobj = MCMONINV; /* disambiguate the objects in the proper order */ if (tplflags & VOCTPLFLG_DOBJ_FIRST) { /* disambiguate the direct object list */ if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT, PRP_VALIDDO, voctplvd(tpl), cmd, otherobj, actor, verb, prep, cmdbuf, FALSE)) { err = -1; goto exit_error; } /* * only one direct object is allowed if it's * disambiguated first */ if (voclistlen(dolist1) > 1) { vocerr(ctx, VOCERR(28), "You can't use multiple objects with this command."); err = -1; goto exit_error; } /* the other object is now known for iboj disambiguation */ otherobj = dolist1[0].vocolobj; } /* disambiguate the indirect object list */ if (vocdisambig(ctx, iolist1, iolist, PRP_IODEFAULT, PRP_VALIDIO, voctplvi(tpl), cmd, otherobj, actor, verb, prep, cmdbuf, FALSE)) { err = -1; goto exit_error; } /* only one indirect object is allowed */ if (voclistlen(iolist1) > 1) { vocerr(ctx, VOCERR(25), "You can't use multiple indirect objects."); err = -1; goto exit_error; } otherobj = iobj = iolist1[0].vocolobj; /* * disambiguate the direct object list if we haven't * already done so (we might have disambiguated it first due * to the DisambigDobjFirst flag being set in the template) */ if (!(tplflags & VOCTPLFLG_DOBJ_FIRST) && vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT, PRP_VALIDDO, voctplvd(tpl), cmd, otherobj, actor, verb, prep, cmdbuf, FALSE)) { err = -1; goto exit_error; } /* re-check for multi-mode */ if (multi_flags == 0) multi_flags = check_for_multi(dolist1); /* save it/them/him/her, and execute the command */ exesaveit(ctx, dolist1); if ((err = exeloop(ctx, actor, verb, dolist1, &prep, iolist1, multi_flags, tpl, newstyle)) != 0) goto exit_error; } exit_error: ; ERRCATCH(ctx->voccxerr, err) if (err == ERR_RUNASKI) prep = errargint(0); if (err != ERR_RUNASKD && err != ERR_RUNASKI) errrse(ctx->voccxerr); ERREND(ctx->voccxerr) switch(err) { case 0: break; case ERR_RUNABRT: /* "abort" executed - return the ABORT code */ VOC_RETVAL(ctx, save_sp, err); case ERR_RUNEXIT: /* * "exit" executed - terminate the command, but return * success, since we want to process any additional commands */ VOC_RETVAL(ctx, save_sp, 0); case ERR_RUNEXITOBJ: /* * "exitobj" executed - indicate success, since this merely * indicates that the game decided it was done processing an * object early */ VOC_RETVAL(ctx, save_sp, 0); case ERR_RUNASKI: case ERR_RUNASKD: askflags = err; break; case -2: /* special code: continue with main loop */ continue; case -1: /* special code: return an error */ default: VOC_RETVAL(ctx, save_sp, 1); } /* * If we got this far, we probably want more information. The * askflags can tell us what to do from here. */ if (askflags) { int old_unknown; int exenewpos; /* * if we had unknown words, don't ask for more information * at this point; simply give up and report the unknown word */ if (ctx->voccxunknown != 0) { VOC_RETVAL(ctx, save_sp, 1); } /* find new template indicated by the additional object */ foundtpl = voctplfnd(ctx, verb, prep, tpl, &newstyle); tplflags = (newstyle ? voctplflg(tpl) : 0); /* find a default object of the type requested */ runrst(rcx); if (askflags == ERR_RUNASKD) runpnil(rcx); runpobj(rcx, prep); runpobj(rcx, actor); runppr(rcx, verb, (prpnum)(askflags == ERR_RUNASKD ? PRP_DODEFAULT : PRP_IODEFAULT), (askflags == ERR_RUNASKD ? 3 : 2)); /* * If we got a list back from ?oDefault, and we have a new * template for the command, process the list normally with * the object verification routine for this template. If we * end up with exactly one object, we will assume it is the * object to be used; otherwise, make no assumption and ask * the user for guidance. */ if (runtostyp(rcx) == DAT_LIST && foundtpl) { uchar *l = runpoplst(rcx); uint lstsiz; int objcnt; objnum defobj = 0; objnum o; runsdef val; /* push list back on stack, to keep it in the heap */ val.runsv.runsvstr = l; val.runstyp = DAT_LIST; runrepush(rcx, &val); /* get list size out of list */ lstsiz = osrp2(l) - 2; l += 2; for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz)) { if (*l == DAT_OBJECT) { prpnum verprop; int argc = 1; o = osrp2(l + 1); verprop = (askflags == ERR_RUNASKD ? voctplvd(tpl) : voctplvi(tpl)); if (!objgetap(ctx->voccxmem, o, verprop, (objnum *)0, FALSE)) continue; tiohide(ctx->voccxtio); /* * In the unlikely event that we have an * indirect object but no direct object, push * the iobj. This can happen when the player * types a sentence such as "verb prep iobj". */ if (voclistlen(iolist) != 0 && askflags == ERR_RUNASKD && !(tplflags & VOCTPLFLG_DOBJ_FIRST)) { /* push the indirect object */ runpobj(rcx, iolist[0].vocolobj); /* note the second argument */ argc = 2; } /* * If this is a disambigDobjFirst verb, and * we're validating an indirect object list, * then we must push the direct object argument * to the indirect object validation routine. */ if (askflags == ERR_RUNASKI && (tplflags & VOCTPLFLG_DOBJ_FIRST) != 0) { /* push the diret object */ runpobj(rcx, dolist[0].vocolobj); /* note the second argument */ argc = 2; } /* push the actor and call the verXoVerb routine */ runpobj(rcx, actor); runppr(rcx, o, verprop, argc); if (!tioshow(ctx->voccxtio)) { ++objcnt; defobj = o; } } } /* no longer need list in heap, so discard it */ rundisc(rcx); /* if we found exactly one object, it's the default */ if (objcnt == 1) { if (askflags == ERR_RUNASKD) { dolist[0].vocolobj = defobj; dolist[0].vocolflg = 0; dolist[0].vocolfst = dolist[0].vocollst = 0; dolist[1].vocolobj = MCMONINV; dolist[1].vocolflg = 0; dolist[1].vocolfst = dolist[1].vocollst = 0; } else { iolist[0].vocolobj = defobj; iolist[0].vocolflg = 0; iolist[0].vocolfst = iolist[0].vocollst = 0; iolist[1].vocolobj = MCMONINV; iolist[1].vocolflg = 0; iolist[1].vocolfst = iolist[1].vocollst = 0; } /* tell the user what we're assuming */ if (ctx->voccxpdef2 != MCMONINV) { if (askflags == ERR_RUNASKI) runpobj(rcx, prep); else runpnil(rcx); runpobj(rcx, defobj); runpobj(rcx, verb); runpobj(rcx, actor); runfn(rcx, ctx->voccxpdef2, 4); } else if (ctx->voccxpdef != MCMONINV) { if (askflags == ERR_RUNASKI) runpobj(rcx, prep); else runpnil(rcx); runpobj(rcx, defobj); runfn(rcx, ctx->voccxpdef, 2); } else { vocerr_info(ctx, VOCERR(130), "("); if (askflags == ERR_RUNASKI) { runppr(rcx, prep, PRP_SDESC, 0); vocerr_info(ctx, VOCERR(132), " "); } runppr(rcx, defobj, PRP_THEDESC, 0); vocerr_info(ctx, VOCERR(131), ")"); } tioflush(ctx->voccxtio); continue; /* try the command again */ } } else rundisc(rcx); /* make sure output capturing is off for the prompt */ tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE); tioclrcapture(ctx->voccxtio); /* * If we're asking for an indirect object, and we have a * list of direct objects, and parseAskobjIndirect is * defined, call it. Otherwise, if there's a * parseAskobjActor routine, call it. Otherwise, if there's * a parseAskobj routine, use that. Finally, if none of * those are defined, generate the default phrasing. */ if (ctx->voccxpask3 != MCMONINV && askflags == ERR_RUNASKI && voclistlen(dolist) != 0) { /* call parseAskobjIndirect */ voc_askobj_indirect(ctx, dolist, actor, verb, prep); } else if (ctx->voccxpask2 != MCMONINV) { if (askflags == ERR_RUNASKI) runpobj(ctx->voccxrun, prep); runpobj(ctx->voccxrun, verb); runpobj(ctx->voccxrun, (objnum)(actor == MCMONINV ? ctx->voccxme : actor)); runfn(ctx->voccxrun, ctx->voccxpask2, askflags == ERR_RUNASKI ? 3 : 2); } else if (ctx->voccxpask != MCMONINV) { if (askflags == ERR_RUNASKI) runpobj(ctx->voccxrun, prep); runpobj(ctx->voccxrun, verb); runfn(ctx->voccxrun, ctx->voccxpask, askflags == ERR_RUNASKI ? 2 : 1); } else { /* * Phrase the question: askDo: "What do you want * to ?" askIo: "What do you want * to it ?" If the actor is Me, leave the * actor out of it. */ if (actor != MCMONINV && actor != ctx->voccxme) { vocerr_info(ctx, VOCERR(148), "What do you want "); runppr(rcx, actor, PRP_THEDESC, 0); vocerr_info(ctx, VOCERR(149), " to "); } else { /* no actor - don't mention one */ vocerr_info(ctx, VOCERR(140), "What do you want to "); } /* add the verb */ runppr(rcx, verb, PRP_SDESC, 0); /* * add an appropriate pronoun for the direct object, * and the preposition, if we're asking for an indirect * object */ if (askflags == ERR_RUNASKI) { int i; int vcnt; int distinct; const char *lastfst; /* * If possible, tailor the pronoun to the situation * rather than using "it"; if we have multiple * objects, use "them", and if we have agreement * with the possible single objects about "him" or * "her", use that. Otherwise, use "it". If "all" * was specified for any word, automatically assume * multiple distinct objects were specified. */ vcnt = voclistlen(dolist); for (distinct = 0, i = 0, lastfst = 0 ; i < vcnt ; ++i) { /* if the first word is different here, note it */ if (lastfst != dolist[i].vocolfst) { /* this is a different word - count it */ ++distinct; lastfst = dolist[i].vocolfst; } /* always assume multiple distinct objects on "all" */ if (dolist[i].vocolflg & VOCS_ALL) { distinct = 2; break; } } /* * If we have multiple words, use "them"; * otherwise, see if we can find agreement about * using "him" or "her". */ if (distinct > 1) { /* multiple words specified by user - use "them" */ vocerr_info(ctx, VOCERR(144), " them "); } else { int is_him = 0; int is_her = 0; int is_them = 0; /* run through the objects and check him/her */ for (i = 0 ; i < vcnt ; ++i) { int him1, her1, them1; /* if it's special (number, string), use "it" */ if (dolist[i].vocolobj == MCMONINV) { him1 = FALSE; her1 = FALSE; them1 = FALSE; } else { /* check for "him" */ runppr(rcx, dolist[i].vocolobj, PRP_ISHIM, 0); him1 = (runtostyp(rcx) == DAT_TRUE); rundisc(rcx); /* check for "her" */ runppr(rcx, dolist[i].vocolobj, PRP_ISHER, 0); her1 = (runtostyp(rcx) == DAT_TRUE); rundisc(rcx); /* check for "them" */ runppr(rcx, dolist[i].vocolobj, PRP_ISTHEM, 0); them1 = (runtostyp(rcx) == DAT_TRUE); rundisc(rcx); } /* * if this is the first object, it * definitely agrees; otherwise, keep going * only if it agrees with what we found on * the last pass */ if (i == 0) { is_him = him1; is_her = her1; is_them = them1; } else { /* turn off either that is no longer true */ if (!him1) is_him = FALSE; if (!her1) is_her = FALSE; if (!them1) is_them = FALSE; } /* if all are false, stop now */ if (!is_him && !is_her && !is_them) break; } /* * If we could agree on "him", "her", or "them", * use that pronoun; otherwise, use "it". If we * found both "him" and "her" are acceptable for * all objects, use "them". */ if ((is_him && is_her) || is_them) vocerr_info(ctx, VOCERR(147), " them "); else if (is_him) vocerr_info(ctx, VOCERR(145), " him "); else if (is_her) vocerr_info(ctx, VOCERR(146), " her "); else vocerr_info(ctx, VOCERR(141), " it "); } /* finish off the question with the prep and a "?" */ if (prep != MCMONINV) runppr(rcx, prep, PRP_SDESC, 0); else vocerr_info(ctx, VOCERR(142), "to"); } vocerr_info(ctx, VOCERR(143), "?"); } tioflush(ctx->voccxtio); /* * Get a new command line. If the player gives us * something that looks like a noun list, and nothing more, * he anwered our question; otherwise, he's typing a new * command, so we must return to the caller with the reparse * flag set. */ if (askflags == ERR_RUNASKD) { exenewbuf = donewbuf; exenewcmd = donewcmd; exenewlist = donewlist; exenewtype = donewtype; } else { exenewbuf = ionewbuf; exenewcmd = ionewcmd; exenewlist = ionewlist; exenewtype = ionewtype; } /* read the new command */ if (vocread(ctx, actor, verb, exenewcmd, VOCBUFSIZ, askflags == ERR_RUNASKD ? 3 : 4) == VOCREAD_REDO) { /* * we got an input line, but we want to treat it as a brand * new command line - copy the new text to the command * buffer, set the 'redo' flag, and give up */ strcpy(cmdbuf, exenewcmd); ctx->voccxredo = TRUE; VOC_RETVAL(ctx, save_sp, 1); } if (!(cnt = voctok(ctx, exenewcmd, exenewbuf, exenewlist, TRUE, FALSE, TRUE))) { runrst(rcx); runfn(rcx, ctx->voccxprd, 0); VOC_RETVAL(ctx, save_sp, 1); } if (cnt < 0) { ctx->voccxunknown = 0; VOC_RETVAL(ctx, save_sp, 1); } /* * Save the unknown word count while getting types, and set * the count to a non-zero value - this will force the type * checker to generate an error on an unknown word. This * removes a little control from the game (since * parseUnknownXobj won't be called), but there's not much * else we can do here. */ old_unknown = ctx->voccxunknown; ctx->voccxunknown = 1; /* get the types */ exenewlist[cnt] = 0; if (vocgtyp(ctx, exenewlist, exenewtype, cmdbuf)) { /* * clear the unknown word count so that we fail with * this error rather than trying to deal with unknown * words */ ctx->voccxunknown = 0; /* return failure */ VOC_RETVAL(ctx, save_sp, 1); } /* restore the unknown word count */ ctx->voccxunknown = old_unknown; /* start at the first word */ exenewpos = 0; /* * if we're asking for an indirect object, and the first * word is a preposition, and matches the preposition that * we supplied to precede the indirect object, skip the * preposition */ if (askflags == ERR_RUNASKI && prep != MCMONINV && (exenewtype[0] & VOCT_PREP) != 0) { vocwdef *vp; /* get the preposition */ vp = vocffw(ctx, exenewlist[0], (int)strlen(exenewlist[0]), (char *)0, 0, PRP_PREP, (vocseadef *)0); if (vp != 0 && vp->vocwobj == prep) ++exenewpos; } /* check for a noun */ newnoun = (askflags == ERR_RUNASKD ? dolist : iolist); cnt = vocchknoun(ctx, exenewlist, exenewtype, exenewpos, &next, newnoun, FALSE); if (cnt < 0) { VOC_RETVAL(ctx, save_sp, 1); } /* invalid syntax */ if (cnt == 0 || (exenewlist[next] && !vocspec(exenewlist[next], VOCW_THEN) && *exenewlist[next] != '\0')) { strcpy(cmdbuf, exenewcmd); ctx->voccxredo = TRUE; VOC_RETVAL(ctx, save_sp, 1); } /* re-check the 'multi' flags */ multi_flags = check_for_multi(newnoun); /* give it another go by going back to the top of the loop */ } else { /* normal exit flags - return success */ VOC_RETVAL(ctx, save_sp, 0); } } } } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk