/* Copyright (c) 2000 by Kevin Forchione.  All Rights Reserved. */
/*
*  TADS ADV.T/STD.T LIBRARY EXTENSION
*  WALLS.T
*  version 1.1
*
*      walls.t implements walls, celings, and floors for actor
*      locations. Simply #include the module after sense.t and your
*      rooms will have walls, etc. Rooms with the attribute isOutside
*      set to true will not have walls or ceilings.
*
*----------------------------------------------------------------------
*  REQUIREMENTS
*
*      + HTML TADS 2.5.1 or later
*      + Requires ADV.T and STD.T
*      + Requires SENSE.T 3.1
*      + Should be #included after SENSE.T
*
*----------------------------------------------------------------------
*  IMPORTANT LIBRARY INTERFACE AND MODIFICATION
*
*      This module modifies the functioning of the following ADV.T
*      listing functions:
*
*      Module performs the following process:
*
*      1. Define the abstract class
*          a. Setup instance
*          b. Check requiresInstance
*          c. Check for cleanup
*          d. Clean up
*      2. Define walls, ceiling, and floor classes
*      3. Put abstract objects in scope
*      4. Disambiguate abstract / non-abstract objects
*      5. Redirect command from abstract to instance
*      6. Delete the instance
*
*      In addition this module modifies the following in Sense.t:
*
*          + deepverb validXo()
*          + deepverb disambigXobj()
*          + deepverb cantReach()
*
*----------------------------------------------------------------------
*  COPYRIGHT NOTICE
*
*      You may modify and use this file in any way you want, provided that
*              if you redistribute modified copies of this file in source form, the
*      copies must include the original copyright notice (including this
*      paragraph), and must be clearly marked as modified from the original
*      version.
*
*------------------------------------------------------------------------------
*  REVISION HISTORY
*
*      26-Jan-2000 Creation.
*      30-Jan-2000 Modified creation location of instances.
*                  Modified floor to move objects to the room instead
*                  of the floor contents, except for actors sitting or
*                  lying on the floor.
*/

#define __WALLS_MODULE_

#ifndef __SENSE_MODULE_
#error THIS MODULE REQUIRES THAT YOU #INCLUDE SENSE.T
#endif

#pragma C+

/*======================================================================
*  A B S T R A C T   C L A S S
*====================================================================*/

class abstract: object
   isabstract = true
   /*
    *  Setup an instance of our abstract object. This object is not
    *  considered abstract. It is moved into the actor's location and
    *  the clean-up daemon is started.
    */
   newAbstract(actor) = {
       local obj, loc;

       if (self.requiresInstance(actor))
       {
           obj = new self;
           loc = scopeCeiling(actor, nil, &access_reachable);
           obj.moveInto(loc);
           obj.isabstract = nil;
           notify(obj, &checkForCleanup, 0);
       }
       else
           obj = self;
       return obj;
   }
   /*
    *  Used to check if the actor's location requires an instance of
    *  the abstract. If not then the "You don't see any foo..." message
    *  is displayed.
    */
   requiresInstance(actor) = { return true; }
   /*
    *  Don't delete the object if it is holding things or the actor
    *  location == this object or the object is being referenced by a
    *  command. Because daemons run before endCommand we do not delete
    *  the object if either it or its class is referenced by a command
    *  to allow for endCommand() each_turn style processing.
    */
   checkForCleanup = {
       if (length(self.contents) > 0
       || command.actorPtr.location == self
       || (command.dobjPtr && isclass(self, command.dobjPtr))
       || (command.dobjPtr && self == command.dobjPtr)
       || (command.iobjPtr && isclass(self, command.iobjPtr))
       || (command.iobjPtr && self == command.iobjPtr))
           return;
       else self.cleanup;
   }
   /*
    *  unnotify the daemon for this object, move it into nil (to clean
    *  up the object tree) and then delete it.
    */
   cleanup = {
       unnotify(self, &checkForCleanup);
       self.moveInto(nil);
       delete self;
   }
;

/*======================================================================
*  W A L L S   A N D   C E I L I N G
*====================================================================*/

/*
*  class wall: abstract, decoration
*
*  The wall class has been defined as an abstract decoration, which
*  means that most actions will be regarded as unimportant.
*
*  This class defines requires instance to check the actor.location
*  isOutside attribute. If the attribute is true then we don't require
*  an instance of wall; otherwise one is dynamically created.
*/
class wall: abstract, decoration
   requiresInstance(actor) = {
       if (actor.location.isOutside) return nil;
       return true;
   }
;

northWall: wall
   adjective = 'north' 'n'
   noun = 'wall'
   sdesc = "north wall"
   isNorthWall = true
;
southWall: wall
   adjective = 'south' 's'
   noun = 'wall'
   sdesc = "south wall"
   isSouthWall = true
;
eastWall: wall
   adjective = 'east' 'e'
   noun = 'wall'
   sdesc = "east wall"
   isEastWall = true
;
westWall: wall
   adjective = 'west' 'w'
   noun = 'wall'
   sdesc = "west wall"
   isWestWall = true
;
neWall: wall
   adjective = 'northeast' 'north-east' 'ne'
   noun = 'wall'
   sdesc = "northeast wall"
   isNeWall = true
;
nwWall: wall
   adjective = 'northwest' 'north-west' 'nw'
   noun = 'wall'
   sdesc = "northwest wall"
   isNwWall = true
;
seWall: wall
   adjective = 'southeast' 'south-east' 'se'
   noun = 'wall'
   sdesc = "southeast wall"
   isSeWall = true
;
swWall: wall
   adjective = 'southwest' 'south-west' 'sw'
   noun = 'wall'
   sdesc = "southwest wall"
   isSwWall = true
;
ceiling: wall
   noun = 'ceiling'
   sdesc = "ceiling"
   adesc = "the ceiling"
   isCeiling = true
;

/*======================================================================
*  T H E   F L O O R
*====================================================================*/

/*
*  floor: abstract, fixeditem, surface
*
*  The floor is treated slightly differently because we can sit on it
*  and put things on it. The actor doesn't actually walk on the floor,
*  and it will behave like a nestedroom when they enter it.
*/
floor: abstract, fixeditem, surface
   noun = 'floor' 'ground'
   sdesc = "floor"
   adesc = "the floor"
   isenterable = true
   isFloor = true
   ldesc = {
       "It lies beneath you. ";
       if (itemcnt(self.location.contents))
       {
           "%You% see%s% <<listcont(self.location)>> here. ";
           listcontcont(self.location);
       }
   }
   ioPutOn(actor, dobj) =
   {
       dobj.doDrop(actor);
   }
   ioPutIn(actor, dobj) =
   {
       dobj.doDrop(actor);
   }
   ioThrowAt(actor, dobj) =
   {
       "Thrown. ";
       dobj.moveInto(actor.location);
   }
;

/*======================================================================
*   D E E P V E R B   M O D I F I C A T I O N S
*====================================================================*/

modify deepverb
   /* put abstract direct objects into scope */
   replace validDo(actor, obj, seqno) = {
       command.dobjPtr     = obj;

       if (obj.isabstract) return true;

       if (testScope(actor, obj))
           return true;
       else
           return (testScope(actor, obj, self.dorequires));
   }
   /* put abstract indirect objects into scope */
   replace validIo(actor, obj, seqno) = {
       command.iobjPtr     = obj;

       if (obj.isabstract) return true;

       if (testScope(actor, obj))
           return true;
       else
           return (testScope(actor, obj, self.iorequires));
   }
       /* if abstract has same vocab remove it */
   disambigDobj(actor, prep, dobj, verprop, wordlist, objlist, flaglist,
               numberWanted, isAmbiguous, silent) = {
       local i, j, len, cnt = 0, abslist = [], newlist = [];
       local vi = [], vj = [];

       if (!isAmbiguous) return objlist;

       len = length(objlist);

       for (i = 1; i <= len; ++i)
       {
           for (j = i+1; j <= len; ++j)
           {
               vi = [], vj = [];
               if (objlist[i] == objlist[j]) continue;
               vi += getwords(objlist[i], &adjective);
               vi += getwords(objlist[i], &noun);
               vi += getwords(objlist[i], &plural);

               vj += getwords(objlist[j], &adjective);
               vj += getwords(objlist[j], &noun);
               vj += getwords(objlist[j], &plural);

               if (vi == vj)
               {
                   if (objlist[i] == theFloor)
                       abslist += objlist[i];
                   else if (objlist[j] == theFloor)
                       abslist += objlist[j];
                   else if (objlist[i].isabstract)
                       abslist += objlist[i];
                   else if (objlist[j].isabstract)
                       abslist += objlist[j];
               }
           }
       }
       objlist -= abslist;
       return objlist;
   }
       /* if abstract has same vocab remove it */
   disambigIobj(actor, prep, dobj, verprop, wordlist, objlist, flaglist,
              numberWanted, isAmbiguous, silent) = {
       return self.disambigDobj(actor, prep, dobj, verprop, wordlist, objlist,
           flaglist, numberWanted, isAmbiguous, silent);
   }
   /* modify to handle command redirection for abstract objects */
       replace cantReach(actor, dolist, iolist, prep) = {
           local       i, list, cmdWords;

               if (dolist != nil) {
                       list = dolist;
                       cmdWords = getCmdPhrase(dolist[1], global.cmdList);
                       if (cmdWords == nil)
                           command.cantReachWords = objwords(1);
                   else
                       command.cantReachWords = cmdWords;
               }
               else {
                       list = iolist;
                       cmdWords = getCmdPhrase(iolist[1], global.cmdList);
                       if (cmdWords == nil)
                           command.cantReachWords = objwords(2);
                   else
                       command.cantReachWords = cmdWords;
               }

       if (self.cantSenseActor(actor)) return;

       for (i = 1; i <= length(list); ++i)
       {
           if (dolist)
           {
               if (list[i].isabstract)
                   self.cantSenseDoAbstract(actor, list[i], prep);
               else
                   self.cantSenseDoRedirect(actor, list[i], prep);
           }
           else
           {
               if (list[i].isabstract)
                   self.cantSenseIoAbstract(actor, list[i], prep);
               else
                   self.cantSenseIoRedirect(actor, list[i], prep);
           }
       }
       }
       /* create dynamic object in actor's location and redirect command */
       cantSenseDoAbstract(actor, obj, prep) = {
           local ret;
           local newobj = obj.newAbstract(actor);
           if (newobj.isabstract)
               self.cantSenseDoRedirect(actor, obj, prep);
           else
           {
               ret = execCommand(actor, self, newobj, prep, command.iobjPtr);
               command.dobjPtr = obj;
               if (ret == EC_ABORT) abort;
               exit;
           }
       }
       /* create dynamic object in actor's location and redirect command */
       cantSenseIoAbstract(actor, obj, prep) = {
           local ret;
           local newobj = obj.newAbstract(actor);
           if (newobj.isabstract)
               self.cantSenseIoRedirect(actor, obj, prep);
           else
           {
               ret = execCommand(actor, self, command.dobjPtr, prep, newobj);
               command.iobjPtr = obj;
           if (ret == EC_ABORT) abort;
               exit;
           }
       }
;