#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 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 <A href="mailto:eric.eve@hmc.ox.ac.uk">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
;
