#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
;