/* 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>>";
}
}
;