/*	EXTEND.T - An Extension Set for TADS
*
*	by Neil deMause (neild@echonyc.com), 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>>";
		}
	}
;
