/*      EXTEND.T - An Extension Set for TADS
*
*       by Neil deMause ([email protected]), 3/26/96
*
*       In the course of writing two games using TADS
*       (MacWesleyan/PC University and Lost New York)
*       I've compiled a collection of modifications to the
*       basic ADV.T library that make programming a lot
*       less painful. (Okay, *some* programming a lot less
*       painful.) Included are many new verbs, a couple of
*       new object classes, some useful functions, and a
*       couple of fixes for parser oddities in ADV.T. Pick
*       and choose from among these as you like; they're
*       all for public consumption.
*
*       All questions or comments regarding this code
*       should be directed to me at the above address.
*
*       This code is freeware. Do with it as you will.
*/



/*      NOTIFY - Notifying when you earn points
*
*       This adds the verb "notify", which toggles back and
*       forth between on and off. When on, the message
*
*       ***You have just gained X points.***
*
*       is printed each time incscore() is run. Notification
*       is off by default; add the line
*
*       notified=true
*
*       to the global object if you want the default to be
*       notification on.
*/

replace incscore: function( amount )
{
       global.score := global.score + amount;
       scoreStatus( global.score, global.turnsofar );
       global.addthis:=amount;
       if (global.notified) notify(global,&tellscore,1);
}

notifyVerb:deepverb
       sdesc="notify"
       action(actor)=
       {
       if (not global.notified)
               {
               "Notification turned on.";
               global.notified:=true;
               }
       else
               {
               "Notification turned off.";
               global.notified:=nil;
               }
       }
       verb='notify'
;

modify global
/*      I also make the default mode for my games
*       "verbose", just because I like it that way.
*/
       verbose = true
       tellscore={"\b***You have just gained <<self.addthis>> points.***";}
;

/*      ISINSIDE - Search an object's entire contents hierarchy
*
*       This function enables you to determine if one
*       object contains another, even if the contained object
*       is buried several levels deep. Actually, it works
*       from the bottom up -- cycling through the
*       contained item's location hierarchy until it either
*       hits the desired container, or nil, in which case it
*       stops.
*
*       Here's how to use it: Say you have a puzzle where
*       carrying a gun through an airport metal detector
*       will set off an alarm. Obviously, you want this to
*       occur even if the player is carrying the gun in their
*       bag, or their pocket, or even hidden inside a
*       hollowed-out book in a secret compartment in their
*       briefcase. To check on this, include the following
*       code:
*
*       if (isinside(gun,Me)) alarm.ring;
*
*       isinside() returns true if the item is anywhere within
*       the location, nil otherwise.
*/

isinside: function(item,loc)
{
       if (item.location=loc) return(true);
       else if (item.location) return(isinside(item.location,loc));
       else return(nil);
}

/*      MOVEFROMTO - Bulk relocation
*
*       Dan Shiovitz deserves all the credit for this one; I
*       was looking for a way to move the entire contents
*       of one object to another, and he came up with this
*       nifty code.
*/

moveFromTo: function (from, to)
{
       local l, i;
       l := from.contents;
       for (i := 1; i <= length(l); ++i)
               {
               l[i].moveInto(to);
               }
}

/*      DISABLING "ALL"
*
*       Another one that isn't my doing, though I've
*       unfortunately forgotten who on rec.arts.int-fiction
*       provided this code, long ago. I've changed the
*       defaults for take, drop, and put to allow the use of
*       "all" (which seems logical); adding "allowall=true"
*       to other verbs will let you use "all" with them as well.
*/

modify deepverb
   doDefault (actor, prep, iobj) =
       {
       if (self.allowall=nil)
               {
              if (objwords(1) = ['A'])
                 {
                  global.allMessage := 'You can\'t use "all" with this verb.';
                  return [];
                  }
               pass doDefault;
              }
       else pass doDefault;
       }
;

parseError: function (str, num)
   {
   // if there's an allMessage waiting, use it instead of the default
   if (global.allMessage <> nil)
       {
       local r;

       r := global.allMessage;
       global.allMessage := nil;
       return r;
       }
   else
       return nil;
   }

modify takeVerb
       allowall=true
;

modify dropVerb
       allowall=true
       ioAction(onPrep)='PutOn'  //while we're at it...
;

modify putVerb
       allowall=true
;

/*      PLATFORMITEM - Neither chair nor bed...
*
*       I once beta-tested a game where if you sat on the
*       toilet then tried to leave, you got the response
*       "You're not going anywhere until you get out of
*       the toilet!" If that toilet had been a platformItem,
*       much embarrassment could have been avoided.
*       (See also doUnboard under "modify thing".)
*/

class platformItem:chairitem
       statusPrep='on'
       noexit =
       {
       "%You're% not going anywhere until %you%
       get%s% off of <<thedesc>>. ";
       return( nil );
       }
;


/*      VERBS! - I got a million of 'em...
*
*       These are some of the verbs I use the most often,
*       along with new ioActions for some verb-
*       preposition pairs that ADV.T doesn't recognize,
*       and the prepositions "for" and "against", which
*       ADV.T inexplicably omits.
*/

modify throwVerb
       ioAction(thruPrep) = 'ThrowThru'
       ioAction(onPrep) = 'PutOn'
;

liftVerb:deepverb
       verb='lift' 'raise'
       sdesc="lift"
       doAction='Lift'
;

smellVerb:deepverb
       verb='smell'
       sdesc="smell"
       doAction='Smell'
;

modify openVerb
       ioAction(withPrep)='OpenWith'
;

modify class openable
       doOpenWith(actor,io)=
       {
       "I don't know how to open <<self.adesc>> with <<io.adesc>>.";
       }
;

modify inVerb
       verb='jump in'
;

modify climbVerb
       ioAction(thruPrep)='ClimbThru'
;

againstPrep:Prep
       preposition='against'
       sdesc="against"
;

forPrep:Prep
       preposition='for'
       sdesc="for"
;

modify askVerb
       ioAction(forPrep)='AskFor'
;

listenverb:deepverb
       verb='listen'
       sdesc="listen"
       action(actor)={Me.location.listendesc;} //add a listendesc
;                                                               //for any location
                                                               //where "listen"
                                                               //should get a
                                                               //specific response


listentoverb:deepverb
       verb='listen to'
       sdesc="listen to"
       doAction='ListenTo'
;

/*      "Empty" requires a modification for the container
*       class, using moveFromTo()
*/

emptyVerb:deepverb
       verb='empty'
       sdesc="empty"
       doAction='Empty'
;

modify container
       verDoEmpty(actor)={}
       doEmpty(actor)=
       {
       if (not self.isopen) "\^<<self.thedesc>> is closed.";
       else
               {
               "You empty the contents of <<self.thedesc>> onto the ground.";
               moveFromTo (self, Me.location);
               }
       }
;

/*Of course, now we need to code in default responses for many of these new verbs...*/

modify thing
       verDoSmell(actor)={}
       doSmell(actor)={self.smelldesc;}
       smelldesc="\^<<self.thedesc>> doesn't smell like anything in particular."

/*Fixes a TADS bug that creates responses like "Okay, you're no longer in the toilet."*/

       doUnboard( actor ) =
       {
       if ( self.fastenitem )
               {
               "%You%'ll have to unfasten "; actor.location.fastenitem.thedesc;
               " first. ";
               }
       else
               {
                "Okay, %you're% no longer <<self.statusPrep>> "; self.thedesc; ". ";
               self.leaveRoom( actor );
               actor.moveInto( self.location );
               }
       }
       verDoTouch(actor)={}
       doTouch(actor)=self.touchdesc
       touchdesc="It feels just like <<self.adesc>>."
       listendesc={"You don't hear anything.";}
       verDoListenTo(actor)={}
       doListenTo(actor)={"<<self.listendesc>>";}
       verDoFind(actor)={"You'll have to find that on your own.";}
       verIoAskFor(actor)={}
       ioAskFor(actor,dobj)=
       {
       dobj.doAskFor(actor,self);       //redirects the action to the
       }                                        //person you're asking
;

/*      UNLISTEDITEM - Not fixed, but not listed
*
*       Often you (well, I) want to have an item that you
*       can take, but that is included in the room
*       description rather than listed separately. This item
*       is unlisted until you take it, after which it behaves
*       like a regular item. (But be sure to include code in
*       your ldesc removing it from the room description once it's
*       taken as well.)
*/

class unlisteditem:item
       isListed=nil
       doTake(actor)={self.isListed:=true; pass doTake;}
;

/*      INTANGIBLE - For things like smells, sounds, etc., a special
*       class.
*/

class intangible:fixeditem
       verDoTake(actor)={"That can't be taken.";}
       verDoTakeWith(actor,io)={"That can't be taken.";}
       verDoMove(actor)={"That can't be moved.";}
       verDoTouch(actor)={"That can't be touched.";}
       verDoTouchWith(actor,io)={"That can't be touched.";}
       ldesc="That's not visible."
       verDoLookbehind(actor)="That's not visible."
       verDoAttack(actor)={"That can't be attacked.";}
       verDoAttackWith(actor)={"That can't be attacked.";}
       verIoPutOn(actor)={"You can't put anything on that.";}
;

/*      A whole bunch of modifications to the basic Actor class.
*/

modify Actor

/*This automatically translates "ask actor for object" as "actor, give object to me," which can avoid a lot of unnecessary coding.*/

       verDoAskFor(actor,io)={}
       doAskFor(actor,io)={self.actorAction(giveVerb,io,toPrep,Me);}

/*Likewise, this translates "actor, tell me about item" as "ask actor about item."*/

       actorAction(v,d,p,i)={if (v=tellVerb and d=Me and p=aboutPrep) {self.doAskAbout(i); exit;}}
       listendesc="\^<<self.thedesc>> isn't saying anything!"
       disavow="\^<<self.thedesc>> looks confused."
       ldesc="\^<<self.thedesc>> looks just like <<self.adesc>>."
       verDoLookin(actor)={"I don't know how to look in <<self.thedesc>>.";}
       verDoSearch(actor)={"How rude!";}
       ioGiveTo(actor, dobj) =
       {
       "\^<<self.thedesc>> doesn't want it.";
       }
;

/*      Another ADV.T bug - currently, asking someone about a
*       distantItem gives the odd response "It's too far away."
*/

modify distantItem
       dobjGen(a, v, i, p) =
       {
       if (v <> inspectVerb and v <> askVerb and v <> tellVerb)
               {
               "It's too far away.";
               exit;
               }
       }
;

/*      FULL - Giving a detailed score
*
*       Inform has (built-in, I believe) an easy way to
*       give a listing of all the actions that have earned
*       you points. The following code adds the same
*       functionality to TADS.
*

fullVerb:deepverb
       verb= 'full'
       action(actor)=
       {
               if (global.score=0) "You have no points.";
               else
               {
               "You have earned the following:\b";

/*      In here is where you insert the list of actions that
*       can earn the player points. For example, if the player
*       gets 5 points for finding the magic carrot peeler, and
*       1 points for each carrot they peel with it, you would
*       insert the following:
*
*       if (global.scPeeler) "\n5 points for finding the carrot peeler";
*       if (global.scCarrots>0) "\n<<global.scCarrots>> points for peeling
*       carrots";
*
*       You also, naturally, need to add the appropriate code in
*       the place where the actual puzzle is solved -- so that at
*       the same time you call incscore(5) for finding the carrot
*       peeler, you also set scPeeler:=true. (I just do a search
*       for "incscore" through the entire game once I'm done, and
*       insert the proper code next to each instance.)
*
               "\bTotal score: <<global.score>>";
               }
       }
;