#charset "us-ascii" /* DupDobj.t */ #include #include /* * DupDobj.t * Version 1.3 (04-Nov-24) * by Eric Eve * * To use this module, simply include it in your list of TADS 3 source files. * * This file is Freeware and may be incorporated into other software, distributed, * and modified at will. The author (contactable on eric.eve@hmc.ox.ac.uk) would * nevertheless be grateful to receive any bug reports or suggestions for * improvements. * * The function of this module is to control the handling of * duplicate direct objects in commands. In particular it allows * duplicate objects to me removed so that, for example: * * >X CHAIR, BOX AND CHAIR * * would be treated as simply: * * >X CHAIR AND BOX * * This may be particularly useful when a Decoration object, say, exists * under several synonyms. For example suppose we have a Decoration defined thus: * * Decoration 'leaves/twigs/branches' 'leaves' @forest * "The branches and twigs hereabouts are laden with reddening autumnal leaves. " * * We don't really want this: * * >X LEAVES, TWIGS AND BRANCHES * leaves: The branches and twigs hereabouts are laden with reddening autumnal leaves. * leaves: The branches and twigs hereabouts are laden with reddening autumnal leaves. * leaves: The branches and twigs hereabouts are laden with reddening autumnal leaves. * * But this would be the standard library behaviour. By removing the duplicates direct * objects, as this module does, we get simply: * * >X LEAVES, TWIGS AND BRANCHES * The branches and twigs hereabouts are laden with reddening autumnal leaves. * * However, there may be occasional instances where you want a minimally implemented * object to represent several different items, for example passing small animals * mentioned only in an atmosphereList. In such a case you may simply want an Unthing * that reports that the object in question isn't here. E.g., you might ideally like * A single Unthing which can generate: * * X OWL AND FOX * owl: it's not in sight. * fox: it's not in sight. * * For this purpose this module defines a MultiName class for which duplicates are * not removed from the list of direct objects. * * */ ModuleID name = 'Duplicate Dobj Handler' byLine = 'by Eric Eve' htmlByLine = 'by Eric Eve' version = '1.3' // showCredit { "<> <>"; } ; /* Define an extra property and method for use with MultiName. This is to allow * MultiName objects to be reset at the start of every turn. */ modify libGlobal multiNameList = [] resetNameList { foreach(local cur in multiNameList) { cur.names = []; cur.nameIdx = 0; } } ; /* * You can control whether any duplicate direct objects are culled from the list by * setting gameMain.allowAllDuplDobjs to nil * There's probably not a lot of point in including this module at all if you set this * gameMain.allowAllDuplDobjs to true, unless you wanted to offer it as an interface * option for players, and hence to include a command that toggles this value. */ modify GameMainDef allowAllDuplDobjs = nil ; /* Cache a list of MultiName objects for fast processing each turn */ PreinitObject execute { local obj; for(obj = firstObj(MultiName) ; obj != nil; obj = nextObj(obj, MultiName)) libGlobal.multiNameList += obj; } ; /* Set up a prompt daemon to reset each MultiName object each turn */ InitObject execute { new PromptDaemon(libGlobal, &resetNameList); } ; /* A MultiName is an object whose name is always identical to the * words the player enters to refer to it (provided the player's * entry matches its vocabulary). So for example, if we define: * * MultiName, Unthing * vocabWords_ = 'red blue green ball' * desc = "\^<> isn't here. " * ; * * Then >X RED BALL will result in "The red ball isn't here", while * >X BLUE BALL will result in "The blue ball isn't here." * * Moreover >X RED BALL AND BLUE BALL would result it: * red ball: The red ball isn't here. * blue ball: the blue ball isn't here. * * Note that MultiName objects are NOT removed from the list of direct * objects, even when they are duplicated in the list. * * Note also that if the standard Thing Template is used with * a MultiName, the name property defined via the template will * almost be overridden. * */ MultiName : Thing /* Use matchNameCommon to build a list of the names by which * the player has referred to this item as the direct object * of a command. */ matchNameCommon(origTokens, adjustedTokens) { local newname = ''; foreach(local tok in adjustedTokens) { if(dataType(tok) == TypeSString) newname += (tok + ' '); } newname = (newname.substr(1, newname.length - 1)).toLower; names += newname; return inherited(origTokens, adjustedTokens); } /* The next two properties are used internally as part of the * implentation and should not be overriden. Note that they * are in any case reset to these initial values at the start * of each turn. */ nameIdx = 0 names = [] /* The rename method is called by TAction.doActionMain each * time it encounters this object in its dobjList. This ensures * that each time this object is referenced, it has the name * the player used to refer to it. It should not be necessary * to call this method in your own code. */ rename { name = names[++nameIdx]; } /* We set includeDuplicateDobjs to true to prevent the modified TAction from * removing multiple instances of the same MultiName from its list of * direct objects. */ includeDuplicateDobjs = true ; /* This modification to TAction causes duplicate direct objects to be removed * from the list of direct objects before processing, unless their * includeDuplicateDobjs property is true. */ modify TAction /* * The removeDuplDobjs can be set to nil on specific Action classes to force * particular actions always to act multiple times on a multiply supplied * direct object. E.g., if a puzzle involves pushing a series of buttons * in a given sequence, then you would not want a command like * * >PRESS RED BUTTON, GREEN BUTTON, BLUE BUTTON, RED BUTTON * * To have the second instance of "red button" removed from its list of direct * objects. One way to ensure that it was not would be to set PushAction.removeDuplDobjs * to nil. Note, however, that you can also control what classes of objects or individual * objects the duplicate direct object removal applies to. */ removeDuplDobjs = true replace doActionMain() { /* Note that the reason we cannot simply use * dobjList_ = dobjList_.getUnique here is that this would not * cater for direct objects where duplicates are allowed (i.e. for which * includeDuplicateDobjs is true). */ /* What this rather complicated expression does is to replace * The existing dobjList_with a subset of those objects for * which includeDuplicateDobjs is true, or which are the * the first instances of a particular object in the list. * * This is complicated by the fact that dobjList_ contains a * list, not of objects, but of ResolveInfo objects, which will * all be distinct even if the game objects to which they refer * are not. We therefore have to look at the obj_ property of * these ResolveInfo objects, and not just the ResolveInfo objects * themselves. */ if(removeDuplDobjs && !gameMain.allowAllDuplDobjs) dobjList_ = dobjList_.subset({x: x.obj_.includeDuplicateDobjs || dobjList_.indexOf(x) == dobjList_.indexWhich({y: y.obj_ == x.obj_}) }); /* * Set the direct object list as the antecedent, using the * game-specific pronoun setter. Don't set pronouns for a * nested command, because the player didn't necessarily refer * to the objects in a nested command. */ if (parentAction == nil) gActor.setPronoun(dobjList_); /* we haven't yet canceled the iteration */ iterationCanceled = nil; /* run through the sequence once for each direct object */ for (local i = 1, local len = dobjList_.length() ; i <= len && !iterationCanceled ; ++i) { /* make this object our current direct object */ dobjCur_ = dobjList_[i].obj_; /* This statement added to deal with MultiName objects */ if(dobjCur_.ofKind(MultiName)) dobjCur_.rename; /* announce the object if appropriate */ announceActionObject(dobjList_[i], len, whichMessageObject); /* run the execution sequence for the current direct object */ doActionOnce(); /* if we're top-level, count the iteration in the transcript */ if (parentAction == nil) gTranscript.newIter(); } } ; /* By default we leave all duplicate direct objects in the direct object list * unless they're Decoration or Distant objects. This automatically ensures that * commands of the type >PUSH RED BUTTON, GREEN BUTTON, BLUE BUTTON, RED BUTTON * will be executed as expected, while also taking care of the >X LEAVES, TWIGS AND * BRANCHES case, where 'leaves', 'twigs' and 'branches' are three names for the name * Decoration object. * * These defaults may be overriden as desired to make them appropriate for a particular * game. Simply define includeDuplDobjs to be true on all classes and/or objects for * which you want duplicates left in the list of a command's direct objects, and nil * on all those for which you want duplicates suppressed. */ modify Thing includeDuplDobjs = true ; modify Decoration includeDuplDobjs = nil ; modify Distant includeDuplDobjs = nil ;