/* 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 <> 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 <>. "; 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 <> with <>."; } ; 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) "\^<> is closed."; else { "You empty the contents of <> 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="\^<> 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.thedesc; ". "; self.leaveRoom( actor ); actor.moveInto( self.location ); } } verDoTouch(actor)={} doTouch(actor)=self.touchdesc touchdesc="It feels just like <>." listendesc={"You don't hear anything.";} verDoListenTo(actor)={} doListenTo(actor)={"<>";} 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="\^<> isn't saying anything!" disavow="\^<> looks confused." ldesc="\^<> looks just like <>." verDoLookin(actor)={"I don't know how to look in <>.";} verDoSearch(actor)={"How rude!";} ioGiveTo(actor, dobj) = { "\^<> 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<> 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: <>"; } } ;