#charset "us-ascii"
/* DupDobj.t */
#include <adv3.h>
#include <en_us.h>
/*
* 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
[email protected]) 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 <A href="mailto:
[email protected]">Eric Eve</a>'
version = '1.3'
// showCredit { "<<name>> <<htmlByLine>>"; }
;
/* 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 = "\^<<theName>> 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
;