TADS Programming - Water This file is public domain. I am giving up all of my claims to its ownership and other legal matters. I am washing my hands of it. It is yours to alter or copy or even distribute as you see fit. I really don't even mind if you fail to mention me in your 'CREDITS' command. Kevin Wilson Vertigo Software Hello, these two files, wateroom.t and watexamp.t are intended to form the backbone of a waterRoom class that you can use in your games to simulate a real body of water fairly realistically. Of course, like any good game system, it is simplified immensely from real life. I didn't even add a 'dive' and 'surface' command to it, although you may well wish to. I'll probably add it to my own later on. Anyways, the way to use wateroom.t is just include it in your game, then add a few rooms of class waterRoom or even underwaterRoom. The file will take care of everything else so long as your floatingItems are being initialized properly in init() or preinit(). You can just look through watexamp.t to see some interesting ways to use this system, you never really need to look at wateroom.t itself. There's not much to it, which is good. I even added some error checks in the waterRooms to make sure that you didn't botch up your bottom or top methods. :) Anyways, that's all there is. Test it out vigorously, and if you find a bug, feel free to fix it and post the fix to the Internet. Also, if you make a drastic improvement to this file, I'd love to receive a copy at whizzard@uclink.berkeley.edu. I might be able to help you with any problems that occur when you use this file, but then again, maybe not. Caveat emptor. (Or something like that. Let the buyer beware. If you have trouble, read the code, it's the best place to start.) Bye. --==oOo==-- /************************************************************ This file is an attempt to implement a water-filled room class that is fairly generic and easily modifiable. I will use modify and replace where necessary rather than repost material already available. ***********************************************************8*/ /******************* Pre-define functions. *******************/ GetWet: function; addbuoyancy: function; warning1: function; warning2: function; warning3: function; drown: function; /************************* Used for dousing torches *************************/ class fireItem: lightsource getwet = { caps(); self.thedesc; " goes out with a hiss as it gets wet. "; self.islit := nil; self.wet := true; // In case it is ruined by getting wet. } ; modify item buoyancy = 0 ; floatItem: item buoyancy = 1 ; lakeItem: supplyHolder, decoration, floatingItem, sinkItem noun = 'lake' thedesc = "the lake" location = { if (isclass(Me.location, waterRoom)) return(Me.location); return(nil); } locationOK = true ldesc = "The lake stretches out around you. " ; water: liquid sdesc = "water" adesc = "some water" ldesc = "This is water. " noun = 'water' 'agua' verDoDrink(actor) = {} doDrink(actor) = { local which; which := self.uniqueHolder(actor); if(not (isclass(which, lakeItem))) { "You drink the water from <>. \n "; which.empty; } else "You take a drink of water.\n "; } ; /************************************************************ This and underwaterRoom make up the core of the water system. It handles all the floating and sinking bits without any help from the programmer. All you do is add items and if it floats, then make it a floatItem and give it a buoyancy (default 1). ************************************************************* waterRoom: room bottom = nil roomDrop(obj) = { if (bottom <> nil) { if (proptype(self, &bottom) = 2) { if (isclass(obj, floatItem)) { caps(); obj.thedesc; " remains floating on the surface of the water. "; obj.moveInto(self); } else { caps(); obj.thedesc; " sinks quickly out of sight. "; obj.moveInto(bottom); } } else "There is a bug with your bottom pointer in <>. "; } else { if (isclass(obj, floatItem)) { caps(); obj.thedesc; " bobs gently on the surface of the water. "; } else { caps(); obj.thedesc; " sinks to the bottom and rests there. "; } obj.moveInto(self); } if (addbuoyancy(Me.contents) < 0 and self.bottom <> nil) { "Bereft of its buoyancy, you quickly sink to the bottom.\n "; Me.moveInto(bottom); } } enterRoom (actor) = { GetWet(actor.contents); if (addbuoyancy(Me.contents) < -3 and bottom <> nil) { if(not (proptype( self, &bottom) = 2) ) "Error in bottom."; "The weight you are carrying pulls you underwater.\n "; Me.moveInto(bottom); return(nil); } if (global.holding) { "You take a deep gasp of air.\b "; global.holding := nil; } pass enterRoom; } ; /************************************************************ underwaterRooms are like waterRooms except in two main things... 1.) The directions that things float in are reversed. 2.) The player will drown without air. (He can hold his breath a bit.) ************************************************************* /******************************* NOTE: You should always define a top room for underwaterRooms. *******************************/ modify global // This aids the implementation of holding your breath. holding = nil ; underwaterRoom: room top = nil roomDrop(obj) = { if (proptype(self, &top) = 2) { if (isclass(obj, floatItem)) { caps(); obj.thedesc; " floats up to the surface of the water.\n "; obj.moveInto(top); } else { caps(); obj.thedesc; " settles to the bottom. "; obj.moveInto(self); } } else "There is a bug with your top pointer in <>. "; if (addbuoyancy(Me.contents) > 0) { "Relieved of its weight, you shoot to the surface.\n "; Me.moveInto(top); } } enterRoom (actor) = { // Me.hasair is yours to play // with as you like. if (actor <> Me) return (nil); // This may be removed if // you want to allow // NPCs to drown, but it'll make your life //complicated. if ( not (global.holding) and not (Me.hasair) ) { "(Holding your breath first.)\n"; setfuse(warning1, 2, 0); // Change the second number to increase or // decrease breath holding time. global.holding := true; } GetWet(actor.contents); if (addbuoyancy(Me.contents) > 3 ) { if(not (proptype( self, &top) = 2) ) "Error in top."; "You bob back up to the surface of the water.\n "; Me.moveInto(top); return(nil); } pass enterRoom; } ; /*********************************** Ok, definately check out this function. It adds up the total buoyancy of an object. I would suggest that if you give something a buoyancy, then you also make it a floatItem, otherwise the laws of Nature begin to fray. Any item with a buoyancy will add that to the total buoyancy. Any item with buoyancy of 0 will have its weight subtracted from total, so a positive buoyancy floats, while a negative sinks. ***********************************/ addbuoyancy: function( l ) { local tot, i, c, totbuoyancy; tot := length( l ); i := 1; totbuoyancy := 0; while ( i <= tot ) { c := l[i]; if (c.buoyancy <> 0) totbuoyancy := totbuoyancy + c.buoyancy; totbuoyancy := totbuoyancy - c.weight; if (length( c.contents )) totbuoyancy := totbuoyancy + addbuoyancy( c.contents ); i := i + 1; } return( totbuoyancy ); } /******************************************** GetWet is called in every item the player carries when he/she enters a waterRoom or underwaterRoom unless the item is in a waterproof container. Check out the sweater example to see one interesting way to use this. ********************************************/ GetWet: function( l ) { local i, c, tot; tot := length( l ); i := 1; while (i <= tot) { c := l[i]; c.getwet; if (length(c.contents) and not (c.iswaterproof)) // This bit lets you GetWet(c.contents); // dilute liquids and such in i := i + 1; // non-waterproof containers. :) There is a //potion to show this in the example. } return ( nil ); } /************************************* These functions handle all drowning. ************************************/ warning1: function(l) { if(not (global.holding) or Me.hasair) return ( nil ); // Doublechecking. "Your face is beginning to turn blue. "; setfuse(warning2, 1, 0); return( nil ); } warning2: function(l) { if(not (global.holding) or Me.hasair) return ( nil ); // Doublechecking. "Your face is turning purple. "; setfuse(warning3, 1, 0); return( nil ); } warning3: function(l) { if(not (global.holding) or Me.hasair) return ( nil ); //Doublechecking. "Everything is going black! "; setfuse(drown, 1, 0); return( nil ); } drown: function(l) { if(not (global.holding) or Me.hasair) return ( nil ); // Doublechecking. "\b\b* * * BLOOP * * *\b\b"; die(); abort; } /************************************************************ What follows is part of quantity.t from safe.zip. Thanks to Grim Reaper for pointing it out. I'm still not sure what the hell it does though. ************************************************************* /*********************** Hack for reaching water. ************************/ modify room reachable = { local i, j, len, len2, list, obj, obj2, result; result := []; list := reachableList(self); for (i := 1, len := length(list); i <= len; i++) { obj := list[i]; if (isclass(obj, waterContainer) and obj.full ) { result += water; } result += obj; } return result; } ; /* * A substance which is in more than one part, but not * amount differentiated. */ class substance: item, floatingItem sdesc = "generic substance" mass = 1 /* Is any of this substance visible to the actor? */ isVisible(actor) = { return length(self.visibleHolders(actor)) > 0; } /* Is any of this substance reachable by the actor? */ isReachable(actor) = { return length(self.reachableHolders(actor)) > 0; } /* Return list of visible containers holding this substance */ visibleHolders(actor) = { local p, result; result := []; p := firstobj(waterContainer); while (p) { if (p.isVisible(actor) and p.full) result += p; p := nextobj(p, waterContainer); } return result; } /* Return list of reachable containers holding this substance */ reachableHolders(actor) = { local p, result; result := []; p := firstobj(waterContainer); while (p) { if (p.isReachable(actor) and p.full) result += p; p := nextobj(p, waterContainer); } return result; } /* * Find a container of this substance reachable * by the actor * and ensure that it is uniquely identified. */ uniqueHolder(actor) = { local all; all := self.holdersFrom(reachableList(actor)); if (length(all) = 0) all := self.reachableHolders(actor); if (length(all) = 0) { "There's nothing here with any water in it.\n "; exit; } if (length(all) > 1) { "It's not clear whether you mean "; self.thedesc; " from "; listAlternatives(all); ". If you want to empty one container into another, say so; otherwise, pick up the container of "; self.sdesc; " you want to use, and put all the others down."; exit; } return all[1]; } /* Extract from a list of items those which hold this substance * holdersFrom(list) = { local i, len, result; result := []; for (i := 1, len := length(list); i <= len; i++) if (isclass(list[i], waterContainer) and list[i].full) result += list[i]; return result; } /* * Substances can only be put in waterContainers */ verDoTake(actor) = "You'll have to find a container to put that in. " verDoPutIn(actor, dest) = { if (not isclass(dest, waterContainer)) { dest.thedesc; " is not a suitable container for "; self.sdesc; ". "; } } doPutIn(actor, dest) = { local source; source := self.uniqueHolder(actor); if (not source.isopen) { "You'll have to open "; source.thedesc; " first. "; exit; } dest.verAcceptFrom(source); "You fill <> with water.\n "; source.empty; dest.fill; } ; /* Write out a list of item descriptions separated by "or". */ listAlternatives: function(list) { local i, len; for (i := 1, len := length(list); i <= len; i++) { if (i > 1) (i = len) ? " or " : ", "; list[i].thedesc; } } /* Find objects visible to or reachable by an actor. */ visibleTo: function(actor) { local loc; loc := actor.location; while (loc.location) loc := loc.location; return visibleList(actor) + visibleList(loc); } reachableBy : function(actor) { local loc; loc := actor.location; while (loc.location) loc := loc.location; return reachableList(actor) + reachableList(loc); } /* * A container which can hold * water. More than one may hold water at once. */ class waterContainer: container iswaterContainer = true full = nil ldesc = { self.doLookin(Me); } fill = { self.full := true; } empty = { self.full := nil; } verDoFillFrom(actor, iobj) = {} doFillFrom(actor, iobj) = { "You fill <> with water.\n "; self.fill; } verDoLookin(actor) = {} doLookin(actor) = { if (self.contentsVisible) { if ( self.full ) { "In "; self.thedesc; " %you% see%s% some water. "; } else { "There's nothing in "; self.thedesc; ". "; } } else if (self.isopenable and not self.isopen) { caps(); self.thedesc; " is closed. "; } } /* * If this container will not accept the * water, print a message and * exit. */ verAcceptFrom(source) = { if (source = self) { "The water is already in "; self.thedesc; ". "; } else if (not (source.full) ) { "There is no water in "; self.thedesc; ".\n "; } return true; } reportExactTransfer(source) = { "You empty "; source.thedesc; " into "; self.thedesc; ". "; } /* * Empty the contents into another container. */ verDoEmptyInto(actor, dest) = { /***/ if (not (self.full) ) { caps(); self.thedesc; " is already empty. "; return; } } verIoEmptyInto(actor) = { } ioEmptyInto(actor, source) = { self.reportEmptyInto(source); self.executeEmptyInto(actor, source); } reportEmptyInto(source) = { "You empty "; source.thedesc; " into "; self.thedesc; ". "; } executeEmptyInto(actor, source) = { local dest; dest := self; self.verAcceptFrom(source); source.empty; dest.fill; } ; /* * A supplyHolder holds a supply of a substance. */ class supplyHolder: waterContainer verIoFillFrom(actor) = {} ioFillFrom(actor, dobj) = { dobj.doFillFrom(actor, self); } full = true empty = {} // Supplyholder never goes empty in my implementation. ; /* * EMPTY waterContainer INTO waterContainer */ emptyVerb: deepverb verb = 'empty' 'dump' 'pour' 'tip' sdesc = "empty" prepDefault = inPrep ioAction(inPrep) = 'EmptyInto' ; intoPrep: Prep preposition = 'into' sdesc = "into" ; fillVerb: deepverb verb = 'fill' sdesc = "fill" prepDefault = fromPrep ioAction(fromPrep) = 'FillFrom' ; /* * A sink item accepts any amount of any substance and * destroys it. */ sinkItem: waterContainer fill = {} // Never gets full. ; class liquid: substance sdesc = "generic liquid" isLiquid = true ; --==oOo==-- #include #include #include /********************************************* In this part of the file, I have attempted to demonstrate all the features I put in. To play this sample, compile and run water.t *********************************************/ startroom: waterRoom sdesc = "Surface of lake" ldesc = "This is the surface of the lake. East is the shore. " bottom = Bottom south = surface2 down = Bottom east = shore ; Bottom: underwaterRoom sdesc = "Bottom of lake" ldesc = "This is the bottom of the lake. " south = Bottom2 top = startroom up = startroom ; surface2: waterRoom sdesc = "Surface of lake" ldesc = "This is the surface of the lake. North is more lake. " bottom = Bottom2 down = Bottom2 north = startroom ; Bottom2: underwaterRoom sdesc = "Bottom of lake" ldesc = "More lake bottom" top = surface2 up = surface2 north = Bottom ; stone: item weight = 4 noun = 'rock' 'stone' sdesc = "rock" ldesc = "It is heavy. " location = Bottom ; wood: floatItem buoyancy = 4 noun = 'wood' 'log' 'chip' sdesc = "wood" ldesc = "It is light, and floats. " location = startroom ; /************************ A wool sweater, and you know what happens when you get it wet. ************************/ sweater: clothingItem wet = nil weight = 1 noun = 'sweater' adjective = 'wool' sdesc = "Woolen sweater" ldesc = { if (wet) "This is a rather soggy wool sweater. It's quite heavy. "; "This is a nice wool sweater. "; } getwet = { local owner; if (wet) return ( nil ); owner := self.location; "Oh dear, the sweater soaks up an incredible amount of water! "; self.wet := true; weight := 5; } location = shore ; shore: room sdesc = "Shore" ldesc = "This is the shore. West is the water. " west = startroom ; potionBottle: waterContainer, openable iswaterproof = nil isopen = true noun = 'bottle' sdesc = "bottle" getwet = { if (self.contents = [] and not (self.iswaterproof) and not (self.full) ) { self.fill; caps(); self.thedesc; " fills with water.\b "; } } location = shore doOpen( actor ) = { if(isclass(Me.location, waterRoom) or isclass(Me.location, underwaterRoom) ) { GetWet(self.contents); } self.iswaterproof := nil; pass doOpen; } doClose( actor ) = { self.iswaterproof := true; pass doClose; } ; potion: item noun = 'potion' verDoTake(actor) = { "Being liquid, it evades your grasp. "; } sdesc = "potion" ldesc = "It is a nasty green. " location = potionBottle verDoDrink( actor) = {} doDrink( actor ) = { "You grow a set of gills. "; // An example of using Me.hasair Me.hasair := true; self.moveInto( nil ); } getwet = { "Your potion is diluted.\n "; self.location.fill; // Since this is a watercontainer, // it can fill itself up. self.moveInto(nil); } ; - o -