================================== Jeffs Supplementary TADS functions ================================== This library consists of all the additional functions, classes and objects that I have written over the last two years in order to make some of my game projects possible. Whilst doing so, I decided that it was important to be able to carry any new functionallity along with later revs of TADS and that I should be able to share it with the rest of the world. Caveat: Some of it is still under development and will most likely change over time. However, most of it is stable now and changes are likely to be backward compatible anyway. I have made the following changes to my copy of adv.t over the years. These changes need to be applied every time a new revision arrives to ensure compatibility with the library. - add default 'weight' and 'bulk' to class 'thing. - travelTo(room) should use moveInto() instead of manipulating the location.contents list directly. - remove basicMe.moveInto() method completely - set lookVerb.sdesc to "look at" - remove function 'goToSleep' - this is handled by bodyfunc.t === version.t === The most important problem to be solved (imho) in distributing a game based on a large number of software components is that of keeping track of versions that are released. If a beta-tester reports that a particular problem happens in a section of code that you have edited 5 times since, how do you tell whether the problem was fixed already? This module provides two basic facilities. The first is that it defines a class called 'versionTag' which is used to document the version number of source files. For example, the file 'heartbeat.t' has the following heartbeatVersion: versionTag id="$Id: heartbeat.t_v 1.3 1994/05/09 07:35:47 jeffl Exp jeffl $\n" author='Jeff Laing' func='heartbeat monitoring' ; The verb 'sources' will automatically scan all objects of class 'versionTag' and will perform the method "id", thus printing out whatever arbitrary message is displayed there. I use RCS to keep the field populated with the latest checked-in version number. The other fields 'author=' and 'func=' are used by the verb 'credits'. This is used, primarily, to ensure that you give credit where credit is due. For example, one game in progress currently does this... >credits The following modules were provided by TADS developers who were prepared to share their work with others: Version & credit tracking, system verbs, utility functions, smarter floaters, bodypart support, look code, darkness support, enhanced travelling, heartbeat monitoring, real actors, actor dialog, organized violence and timer management were provided by Jeff Laing. Footnote support was provided by David Etherton (etherton@sdd.hp.com). Scoring was provided by Jeff Laing with suggestions from Mike Roberts and Dave Baggett. If you are a TADS developer, please consider doing the same. All the above mentioned modules should be available for ftp from the interactive-fiction archive maintained by Volker Blasius on ftp.gmd.de. Most components of this output are actually user-configurable so that you can make the text match the feel of your game. === sysfuncs.t === This module is largely a rewrite of std.t, incorporating some other output changes from the net and providing some new, useful, features for supporting modular libraries, etc. Having lots of self-contained modular libraries creates havoc when initialization is required. Typically, the user has to edit their std.t to include code in preinit() or init(). No longer! The preinit() provided in this module scan the world for objects of class "initialization". If found, the method calls "preinit_phase()" for each of them and arranges for "init_phase()" to be called at init() time. This allows a library module to define its own behaviour to be performed at compile or startup time without editing standard libraries. For example, sysverbs.t contains the following sysverbsTag: versionTag,initialization id="$Id: sysverbs.t_v 1.3 1994/05/09 03:29:30 jeffl Exp jeffl $\n" func='system verbs' author='Jeff Laing' /* * called by preinit() */ preinit_phase={ local o; global.scrambler_list := []; // search for scramblers and remember them for (o:=firstobj(game_scrambler);o<>nil;o:=nextobj(o,game_scrambler)) { global.scrambler_list += o; } } /* * called by init() */ init_phase={ if (global.restarting) notify_scramblers(#restartedGame); } ; When preinit() runs, it performed sysverbsTag.preinit_phase (which builds internal lists for later processing). Similiarly for init_phase. When init() is finished, it calls the function gameinit() which is where the game specific initialization should go. This allows a programmer to have their own initialization that won't need to be re-written when the next version of TADS comes along, requiring more in init(). I have used the "initialization" feature to move all of the standard init sequence out of std.t and into seperate libraries. The advantage of this has been to remove the need for "eat/sleep" code if you don't want it. Just don't include it at compile time! Other optional features can disappear in the same way. Or, if you wish to write more complicated code that will be reusable in more than one game (hint, hint, let others use it as well), you can write it as an auto-initting module which can be included into other games *without changing one line of code in them*. === score.t === Nothing is as important (in a *game*) as getting a score. After all, how do you tell how well you have done against the author of the game unless he begrudgingly hands out points for achievement. This module was based, very loosely, on code that has been around for ages. Others have posted variations on this theme, and I have certainly learned from them. Scoring is loosely based on two different types of achievement. The preinit_phase() is responsible for collecting all scores and calculating a possible maximum. The first scorable just corresponds to getting somewhere difficult. For example, it may be worth 10 points to reach the "Pinnacle of Human Experience" room. To provide this feature, merely modify the room definition to include the 'room_value' method. i.e. thePinnacle: room sdesc="Pinnacle of Human Experience" room_value = 10 ; The second scorable corresponds to the notion of a "treasure". The module provides one class called "treasure" and another called "repository". Scoring is implemented as follows... - The first time that "treasure" moves into "Me.contents", the players score is increased by "treasure.find_score" and then "treasure.find_score" is set to zero. - Whenever a "treasure" moves into a "repository", the players score is increased by "treasure.keep_score". - Whenever a "treasure" moves out of a "repository", the players score is decreased by "treasure.keep_score". The module provides the pseudo-standard "Your score just went up" messages and allows the verb "NOTIFY" to turn this feature on/off. To add your own scoring mechanism (i.e. something which doesn't use either of the two schemes), just use the method "scoreobj.adjust(amount)" to change the players score. The scoring module is also responsible for kicking the random number generator in my library. The library then provides Dave Baggett's verb "norandom" which allows scripted-walkthroughs to be recorded that can actually be replayed. This allows the scoring module to determine whether the player deserves a real rating or not. (At present, this information is not used) All features of the scoring module are attached to the object "scoreobj" and are available as method calls, thus allowing programmers the flexibility to define behaviour if required. === heartbeat.t === This module is intended to cut down on the number of individual daemons which are required by a TADS game. Every object which has a method called "heartbeat" will be collected into a list (at preinit time). Once per turn, the objects "wantheartbeat" method will be tested; if it returns true, the objects "heartbeat" will be called. Effectively, the code performs as follows. for all obj in heartbeatlist if (obj.wantheartbeat) obj.heartbeat; This can be used, for example, in candles & lights which would have the wantheartbeat method just return (self.lit) and the heartbeat let the candle melt down or the light's battery run down. Wandering monsters can be enabled based on the players location. i.e. wantheartbeat=(isclass(Me.location, inmonstermaze)) heartbeat={ ... code to move monster around ... } This will call the monsters "heartbeat" method only while the player is in a room which has been tagged as class "inmonstermaze". === utility.t === This module just contains a couple of useful utility functions which may or may not be useful. I use them for debugging and other common list traversals that verbs need. saylist(list) - this will dump out a list of objects, by their sdesc method. This is a debug macro only and should not be used in a finished game. allwithprop(loc,prop) - look through loc.contents for all objects for which the specified property returns true. The fight verbs uses this for defaulting -- ... := allwithprop(loc, isweapon); === timers.t === Keeping track of timed appearance and events can be a real pain. Once upon a time, TADS used to barf if you did an unnotify() without the corresponding notify() - fortunately, this is no longer the case. This module provides a number of entry points which correspond pretty closely to the standard TADS functions but which take an extra argument which can be used for diagnostic information. Basically, the calling sequence is the same as the tads function but the name is *all* uppercase and there is an extra string argument (which is just descriptive text) If you set "timer_info.debug := true", the timer routines will all announce as they are being executed. The routines will also catch the case where an unnotify has been called without a corresponding notify and issue a warning message (since I consider this to be a bug). Since it is impossible to detect when a notify/fuse/daemon has triggered, there are three extra routines which are used for the list management. These are "FORGET_NOTIFY, FORGET_FUSE and FORGET_DAEMON". Typical usage of these functions is as follows. // Player will be fried 6 turns after entering this room enterRoom(a) = { inherited.enterRoom(a); if (a=Me) NOTIFY(self,&intruder_alarm,6,'entered vault'); } // this method is called when the player has been in this room just a // bit too long... intruder_alarm = { FORGET_NOTIFY(self,&intruder_alarm,'bank alarm triggered'); if (Me.location = Vault) { "\bA metallic voice says, \"Hello, Intruder! Your unauthorized presence in the vault area of the Bank of Zork has set off all sorts of nasty surprises, several of which seem to have been fatal. This message brought to you by the Frobozz Magic Alarm Company.\""; jigsup(true); } } Caveat: At present the code for knowing about daemons and fuses in progress does not work due to a bug in TADS concerning the storage of function pointers in lists. Mike Roberts is aware of the bug and it will hopefully be repaired in an upcoming revision of TADS. Until then, only the notify() code is implemented in a useable fashion. === footnote.t === This module borrowed from the one previously provided by David Etherton (and gives credit appropriately; see the versionTag) and built on his work. I made a number of changes to it from a look-and-feel perspective and distribute it here only because it's now part of my standard library. He wrote it originally. I didn't. He deserves the credit. I am just a satisfied user (who tweaked with it slightly) Disclaimers aside, the differences I have added are... - object names have been changed, to avoid namespace collisions - wherever possible, functions are replaced by method calls to allow for easier overriding of behaviour. - a help message is added (ala Adventions. Thanx guys) - an error message is displayed if the number is not specified - reading footnotes doesn't cost turns. Using David's example, the code should be used as follows... ... #include ... gameinit: function footnote.helpmsg; // give footnote intro .... desk: .... noun = 'desk' adjective = 'fine' 'oak' sdesc = "fine oak desk" ldesc = { "The desk is stained a deep brown color, and has that wonderful rustic appeal of a Norman Rockwell painting. "; footnote.new( 'In fact, this entire room looks like an explosion at a Normal Rockwell factory.' ); } ... > EXAMINE THE DESK The desk is stained a deep brown color, and has that wonderful rustic appeal of a Norman Rockwell painting. [3] > FOOTNOTE 3 [3]. In fact, this entire room looks like an explosion at a Norman Rockwell factory. === actor.t === This module is definitely not complete but represents the beginning of a framework that supports actors that you can give things to and who will respond to instructions. For example, the following comes from a game in progress. > LOOK Entrance You are standing in the entrance to a large building. Jane is here. John is here. John seems to have a sword. > TAKE SWORD John is carrying the sword and won't let you have it. > JANE,TAKE SWORD John is carrying the sword and won't let her have it. > JOHN,GIVE SWORD TO JANE Jane accepts the sword gladly. > JANE,INVENTORY Jane has a sword. > JOHN,INVENTORY John is empty-handed. > JANE,HIT JOHN WITH SWORD John doesn't understand why Jane attacked. Feeling foolish, Jane desists before any damage is done. >JOHN, NORTH THEN LOOK THEN SOUTH John leaves the area. Other room John is in the other room. South leads back to the startroom John enters the area. > and so on. Its by no means complete nor is it without its bugs. It does, however open up a framework in which more complicated actions can be experimented with. For example, should you see the description that John sees when he goes into the other room or should it be deferred until he returns to you? I use bits of it already and want to use more in the future so I will continue to change/expand/etc. the whole thing. In fact, as currently distributed it has a bunch of debug messages which will need to be deleted before it can be useful in any released software; I have left the messages in because it helped me find problems in routing verb processing from indirects to directs, etc and found the problem that "lookVerb" did not have an "sdesc" property. === askme.t === This is a generalised question/answer module which only really works to provide the framework for NPC information ability. It should be able to be tied in to other schemes that have been discussed on rec.arts.int-fiction. The method used is based on the suggested technique in the TADS manual (did I suggest you register? You should!) but with an extra indirection added. Each "actor" needs to have a property called "askme" which should be set to a property pointer. This pointer is then used to find information that this actor knows about any other object. For example, if our two actors John and Jane are implemented, they would look like this ... John: maleactor askme=&askJohn ; Jane: femaleactor askme=&askJane ; and then we add information to each of our other objects in the game. football: item askJohn='It\'s a present I got from my father' askJane="It smells!" askAnybody(askedactor)='You throw them. Thats why they call it a football' ; > ASK JOHN ABOUT FOOTBALL John says "It's a present I got from my father" > ASK JANE ABOUT FOOTBALL "It smells!" > ASK JEFF ABOUT FOOTBALL Jeff says "You throw them. Thats why they call it a football" In this last case, an actor who didn't have the "askme" property defined, or for whom there was no specific information recorded against the "football" object, the "askAnybody(askedactor)" method is used. The methods are also inspected to determine whether any special formatting needs to be done. If the "askXxxx" method is of type single-quoted-string (as the examples above were), the response will be displayed as follows. <> says "<>" If the method is of type double-quoted string or procedure, it is just executed directly with no special formatting. If it is any other type, a vanilla "I don't know about that" message is displayed. There are a couple of trivial exceptions that are also built into the module as an aside. Getting one actor to ask another about you (the player) results in various gags of very low laugh-value. Asking yourself questions is bizarre as well. === sysverbs.t === This module provides support for what I consider the "smart-ass" non-player character. Suppose we have the classic "lady behind one door, tiger behind the other" puzzle and we have given a reasonable number of clues to the player but they haven't bothered to think and just use "OPEN DOOR 1. UNDO. OPEN DOOR 2" To combat this, I have introduced the notion of a "game_scrambler". This object would be able to detect whether the player has opened the door already and actually intercept the "undo". For example, one game in progress looks like this ... >undo [Undone 1 turn] Inside the Entrance You are standing just inside the entrance to the fun-park. Outside (to the south) you can see orange mist that seems to surround this place. There is a carnival barker standing here. The barker leans over and says "If you use undo, we reserve the right to change the games!" The same traps can be applied to save and restore >save "xxx" [Saved as "xxx"] As you finish saving, you notice that the barker has just given you a big wink as though he realised what you were doing. You get an uneasy feeling that this might not be as easy as it looked! >restore "xxx" [Restored "xxx"] Inside the Entrance You are standing just inside the entrance to the fun-park. Outside (to the south) you can see orange mist that seems to surround this place. There is a carnival barker standing here. The barker leans over and says "I hope you wern't hopin to cheat us honest carny folk by just guessing the answers. We know about that stuff and we just mix things up again while you're away!" You can even trap restart if you wish (which is getting a mite paranoid in my opinion but hey, paranoids are right sometimes!). At present, you can only trap *after* the verb has occurred. I may allow scramblers to actually disallow those verbs if the need arises. The module also provides some other cosmetic niceties like "UNDO 5" which will roll back 5 turns, and allowing a save at "quit". i.e. >quit In 7 turn(s), your score is 0 out of a possible 1. This gives you the rank of Beginner. If you wish, you can save before quitting. Do you really wish to quit now? (Y=yes, N=no, S=save) >S File to save game in > XXX One last function is jigsup() which can be called whenever the player has "died". This works in the same way as the standard die() but takes a parameter which indicates whether undo should be allowed or not. === travel.t === The first big project I worked on (and in fact haven't finished yet) is a port of Dungeon (Zork). One of its neat features was a sword that glowed when a bad-guy was "nearby". Implementing "nearby" checking was impossible with the current TADS structures and eventually I decided to change them as little as possible but with as much flexibility. To each motion vector (north, west, southeast, etc), you need to add an extra parameter (actor). If this parameter is 'Me', the method should perform normal operation since it is the player actually travelling in that direction. If the parameter is 'nil', the method should just return the actual room that can be reached (or nil) because this is being called by a 'probing' method. The module also provides the ability to destroy a room completely (Dungeon needed to be able to blow one up and to fill another with poison gas). The following fragment ... loc.kaboom('The path ahead is blocked by rubble.'); ... will stop the location 'loc' from being entered and will display the specified message instead. The module provides a verb called 'exits' which will display a message that shows all the exit directions that can be used from the current room. > EXITS [It would appear that you can go east, west, in or out] However, in order to diffuse mazes, etc, there is a hook. If you replace the "room.roomexits" method with one which returns a message, that will be displayed. Thus.. class mazeroom: room roomexits='Hey, its a maze. I don\'t know any better than you' ; > EXITS [Hey, its a maze. I don't know any better than you] If the roomexits method returns anything other than a list, it will be fed into "say()". === wallpaper.t === Floating items always seemed to confuse me so I sat down and tryed once and for all to nail down exactly why they did/didn't work. This module includes the standard processing that I now apply to floaters. Class 'wallpaper' is a subclass of 'fixedItem' and 'floatingItem'. I make the assumption that you can never 'take' the wallpaper. Any room where wallpaper can exist should have a list property that contains the wallpaper items that exist there. For example, one game which has a lot of orange mist scattered around in it defines it ... orange_mist: wallpaper noun='mist' adjective='orange' sdesc="mist" ldesc="The mist is orange. Nothing more exciting that that." ; and then the rooms where it is visible do this ... startroom: room sdesc="Orange mist" ldesc=" You seem to be standing in an orange mist. There appears to be a light to the north." floating_items=[orange_mist] ; All verbs should now be able to refer to the "orange mist" in any of the rooms which include it. This module has also redefined a number of the low-level methods used in object disambiguation and verification to be interceptable by other modules. Thus, darkness can be implemented in a more sensible fashion, bodyparts can work, etc. === bodypart.t === Body parts are a little problematic in TADS. Sure, most players have two hands, two feet, etc. However, no-one wants to see the following... > INVENTORY You have a left hand, a right hand, a head, a torso, a left foot, a right foot and a torch (providing light). On the other hand, its annoying to see the following > OPEN JAR What do you want to open the jar with? > HANDS I don't know the word "hands" This module implements body-parts as wallpaper that is located inside the player. All of the code related to "all" objects (inventory, doDefault methods, etc) are adjusted to not return body parts unless they are explicitly mentioned by the player. There will still be the annoyance to the player that the above dialog will now look like > OPEN JAR What do you want to open the jar with? > HAND Which hand do you mean, the left hand or the right hand? but I think this can still be conquered if needed. More work required. === look.t === This module mimics the existing "look" methods provided in TADS but allows another variation. At present, the default method will print ... > LOOK Play Pen. You are currently standing in the play pen. There is a dinosaur, an armchair and a pound of butter here. In order to add to the "fiction" part of "interactive-fiction", we would prefer to have > LOOK Play Pen. You are currently standing in the play pen. Over to one side is a small plastic dinosaur which has been gnawed on by the child who seems to be absent. There is a large armchair which looks quite comfortable and yet the bloodstains fail to make it look welcoming. On the ground is a pound of butter which seems to be melting. This module allows objects to have an extra method called 'hdesc'. The code in look.t will automatically detect this method and will use it instead of just constructing the list of objects. However, in some circumstances, an object will decide that it doesn't want to be listed specially. For example, the first time you see this room, ... > LOOK The Stone Room .... Lying on the ground is the most magnificent sword you have ever seen. However, once you have taken the sword, played with it a while, carried it somewhere else, etc, the description should become > LOOK Play Pen .... There is a sword here. In order to provide this flexibility, the method 'has_hdesc' is used. By default, this will return true if the method 'hdesc' exists. Objects can have custom versions which only return true when they require special output. A lot of my code looks like this ... sword: item hdesc="Lying on the ground " has_hdesc=(self.istouched) moveInto(loc)= inherited.moveInto(loc); self.istouched:=true; } ; so that the sword will maintain its long description until it is moved somewhere other than its original location. The look code understands about wallpaper and will not describe it by accident. === darkness.t === The original implementation of darkness in TADS was pretty simple. Either a verb worked in the dark or it didn't. Its quite apparent that "drop" should definitely work, even in the dark, but it didn't! This module adds a new method to each of the verbs called 'validDark'. This method allows the verb to specify whether it operates in the dark or not. The module provides patches to three verbs as samples. "INSPECT X" will work currently if X is held by the actor. "TURN ON X" will also work if X is held, thus allowing the "TURN ON TORCH" that sensible players do when plunged into darkness. "READ X" will not work but will report a customised error message. The module also patches into the "xxDefault" routines so that "EXAMINE ALL" will not reveal objects in the darkness. === bodyfunc.t === This is just where I put all the standard eat/sleep code from std.t At the moment, it gains you nothing to use this. However, it does provide a repository for code which I don't want to incorporate into normal games all that often. === clothing.t === This is another module I have swiped from the net. It was originally written by Jonathan D. Feinberg. He wrote it originally. I didn't. He deserves the credit. I am just a satisfied user (who tweaked with it slightly) Disclaimers aside, the differences I have added are... - I have integrated it with "look.t" so that it uses cleaner methods - Sped it up by searching specific classes (in nextobj) - Use actor so that other actors than the player can wear clothing Look to his original documentation for information about this. I actually haven't ever used it except as a test to ensure that the version dumping information was working and the credits verb handled different authors. === violence.t === The other thing that I hated about porting Dungeon was that there were a zillion ways of attacking someone, not the least of which was "MUNG". This module is intended to be a vocabulary mapper. It defines two classes (victim and weapon) and maps as many sentence structures as possible onto a vanilla combat method. All sentences which seem agressive directed at a victim will be caught and redirected to either the weapon (if one was used) or to the victim if not. In both cases, the method called is 'attack_with'. The default behaviour of the weapon is to pass the call on to the victim so the logic is (roughly) as follows. if a weapon was specified if weapon has custom method weapon.attack_with(attacker,victim,severe) else victim.attack_with(attacker,weapon,severe) else victim.attack_with(attacker,nil,severe) The severe parameter is a flag which indicates how "agressive" the command was. For example, "POKE BOB WITH SCREWDRIVER" is taken to be an attack but not severe so we pass 'nil'. "ATTACK BOB WITH SCREWDRIVER" however is considered to be severe and we pass 'true'. The default behaviour for the victim is to shrug off the attack with a 'I don't understand why you are doing that' message. If real combat is to be implemented, the attack_with method(s) should be overridden. Cursed weapons can be implemented by overriding the weapon.attack_with method (I had to do this for Dungeon) === Versions === This document was written when the files were at the following revision levels. actor.t: $Id: actor.t_v 1.2 1994/04/13 09:20:13 jeffl Exp jeffl $ askme.t: $Id: askme.t_v 1.4 1994/05/04 07:40:53 jeffl Exp jeffl $ bodyfunc.t: $Id: bodyfunc.t_v 1.1 1994/05/04 06:49:35 jeffl Exp jeffl $ bodypart.t: $Id: bodypart.t_v 1.7 1994/05/01 02:11:21 jeffl Exp jeffl $ clothing.t: $Id: clothing.t_v 1.4 1994/04/22 00:53:40 jeffl Exp jeffl $ darkness.t: $Id: darkness.t_v 1.9 1994/05/01 02:10:29 jeffl Exp jeffl $ footnote.t: $Id: footnote.t_v 1.6 1994/04/26 06:23:30 jeffl Exp jeffl $ heartbeat.t: $Id: heartbeat.t_v 1.3 1994/05/09 07:35:47 jeffl Exp jeffl $ instruct.t: $Id: instruct.t_v 1.4 1994/05/04 07:44:19 jeffl Exp jeffl $ look.t: $Id: look.t_v 1.9 1994/05/09 04:15:24 jeffl Exp jeffl $ score.t: $Id: score.t_v 1.6 1994/05/03 07:59:52 jeffl Exp jeffl $ sysfuncs.t: $Id: sysfuncs.t_v 1.3 1994/05/06 09:20:30 jeffl Exp jeffl $ sysverbs.t: $Id: sysverbs.t_v 1.3 1994/05/09 03:29:30 jeffl Exp jeffl $ timers.t: $Id: timers.t_v 1.3 1994/05/05 06:38:15 jeffl Exp jeffl $ travel.t: $Id: travel.t_v 1.10 1994/05/09 04:15:03 jeffl Exp jeffl $ utility.t: $Id: utility.t_v 1.2 1994/04/22 00:53:40 jeffl Exp jeffl $ version.t: $Id: version.t_v 1.10 1994/04/22 00:53:40 jeffl Exp jeffl $ violence.t: $Id: violence.t_v 1.3 1994/04/22 00:53:40 jeffl Exp jeffl $ wallpaper.t: $Id: wallpaper.t_v 1.8 1994/05/01 02:08:26 jeffl Exp jeffl $ Jeff Laing -------------------------------------------------------------------------------- 'I meant,' said Ipslore bitterly, 'what is there in this world that makes living worth while?' Death thought about it. CATS, he said eventually. CATS ARE NICE -- Sourcery (Terry Pratchett)