/*
* There's a couple bugs in this sample game. I'm posting it
* anyway because it's not supposed to be a 'plug-n-play'
* module, but rather just here to demonstrate a technique.
* I rather doubt that anyone will use it in its present form.
* (It works quite well enough to see what's going on here.)
* If you fix it gooder, please repost it on ifarchive. thanks.
* Anyway:
*
* I'm trying to achieve several things with this example game.
*
* First and most important, there's a technique here by which
* the parser can deal intelligently with standard English
* dependent clauses during disambiguation. (It recognizes and
* correctly understands "the water that is in the bucket" for
* example.)
*
* Second, this is a liquid handler with special capabilities.
*
* The liquid handler explores a bit how this parser stuff can
* be applied practically, but I bet you can think of even
* cooler uses for the parser tricks in this example game.
*
* I don't have a lot of time on my hands (contrary to
* appearances...), but if you want to you can bug me with
* questions about this thing. I hope it's well enough
* commented, though.
*
* I don't know how useful this is for T3. (If you decide to
* adapt it, I'd be happy to hear from you.) I wrote this before
* T3 was released. It's probably totally obsolete as it is now,
* but hopefully it will be useful as an illustration of a
* technique.
*
* Anyhow,
* Do whatever you like with this. (hack, steal, sell, ridicule, adapt,
* and/or take complete credit for.)
* Drop me a line if you do something clever with it that you think I
* might want to see.
* - breslin
*
[email protected]
*
*/
#include <adv.t>
#include <std.t>
addliquidlocationadjective: function;
delliquidlocationadjective: function;
replace commonInit: function
{
setdaemon(delliquidlocationadjective, nil);
}
/****************************************
* this is a complex section. our execution has to do
* specifically with the reservoir class (see below) and related
* object handling.
*
* however, the general potential of this code is impressive, in our
* opinion. this is recommended reading for anyone interested in parser
* systems and techniques.
*
* we're making clauses such as 'the water thats in the bottle'
* parseable. that's the goal. here's the plan:
* first, we make the final word of the phrase a noun for the target
* object.
* the target object in this case is the water (the water that's in the
* bottle in particular). so to the corresponding object, we add the noun
* 'bottle'. we then make its other noun ('water') into an adjective.
* to recap: at this point,
* we have an object _water_, and to this object has been added the noun
* 'bottle' (the same name which its location has)
* and we have also added to this object the adjective 'water' (its
* primary noun before we started the name adding). we can't refer to it
* as 'the water thats in the bottle' yet, but
* we can already refer to it as 'water bottle'. (this doesnt help much
* yet, of course.)
*
* now all we have to do is contort each of the words in the 'thats in the'
* part of the clause into simple adjectives which we assign to the target
* object. so, now we have the target object _water_ which has adjectives
* 'water' 'thats' 'in' and 'the', and for one of its nouns, 'bottle'.
* viola.
*
* Beware:
* this contortion is a problem if not handled carefully. for one example,
* "in" and "from" are used in other senses. if we don't treat "in"
* carefully, for example, it can be misinterpreted by the parser as an
* adjective elsewhere:
*
* > put the water in the bottle
*
* What do you want to put it in?
*
* by 'it', the parser is referring to the water that's inside the bottle.
* the parser thinks this construction was attempting: "put the water
* (thats currently) in the bottle (into someplace as yet unspecified)".
* for this reason, if not for several others, the changes we make to the
* object for the purposes of parsing clauses like 'the water thats in the
* bottle' or 'the lotion thats in the basket' must be temporary changes.
* in other words, if we don't revert to basic parsing, we introduce more
* problems than we solve.
*
* in the present game, we're only using this technique during
* disambiguation of specific classes of objects.
*
* our implementation of this plan works fine in this game, but we submit
* a few questions/ideas to anyone interested in developing this further:
*
* once we spoil the player with definite clauses (clauses that begin
* with "that" -- or alternatively "which"), they'll want to use them
* elsewhere. developing this clause-parsing system might involve hooking
* into the parser near the point where the parser would normally complain,
* "there's words after your command I can't use". (?)
* it seems as though we then could add our contorted adjectives to the
* words in question, assign them the noun from the end of the phrase, and
* run the command through again. (?)
*
* it's possible that doing this would merely complicate, and not improve
* the parser. (?)
*
* it might be desireable to move all this stuff from parseDisambig to
* disambigDobj/Iobj... although for the moment we can't see any particular
* benefit. (?)
*
**********************************************************************/
parseDisambig: function(str, lst)
{
local i, cnt, liquidinlist;
liquidinlist := nil;
for (i := 1, cnt := length(lst) ; i <= cnt ; ++i)
{
if ((isclass(lst[i], liquid)) or (isclass(lst[i], liquidreservoir)))
liquidinlist := true;
}
if (liquidinlist)
{
local i, cnt, testdog;
"Do %you% mean the ";
for (i := 1, cnt := length(lst) ; i <= cnt ; ++i)
{
if (isclass(lst[i], liquid))
{
lst[i].thedesc;
" thats in <<lst[i].location.thedesc>>";
addliquidlocationadjective (lst[i]);
}
else if (isclass(lst[i], liquidreservoir))
{
if (isclass(lst[i].location, liquidcontainer))
{
lst[i].thedesc;
" thats in <<lst[i].location.thedesc>>";
}
else
{
if (lst[i].preferredDisambName <> 'undefined')
"<<lst[i].preferredDisambName>>";
else
"<<str>> from <<lst[i].thedesc>>";
}
addliquidlocationadjective (lst[i]);
}
else
{
say(car(getwords(lst[i], &adjective)));
" ";
lst[i].sdesc;
}
if (i < cnt) ", ";
if (i + 1 = cnt) "or ";
}
"?";
}
else
{
local i, tot, cnt;
"Which << str >> do %you% mean, ";
for (i := 1, cnt := length(lst) ; i <= cnt ; ++i)
{
lst[i].thedesc;
if (i < cnt) ", ";
if (i + 1 = cnt) "or ";
}
"?";
}
}
addliquidlocationadjective: function(item)
{
local loc, newnoun, newadj;
if (isclass(item.location, liquidcontainer) = nil)
return; // unnecessary unless container
// is involved (!)
if (item.locationadjective = true) // should always be redundant
return;
loc := item.location;
newnoun := car(getwords(loc, &noun)); // container's name.
// reservoirs inside (fixed)
// liquidcontainers _should_
// naturally inherit the
// container name. if the
/* // reservoir did not, we're
* need to save the noun, because // going to add it here, and
* the location potentially changes. // we're not going to take it
*/ // away with the del-function
// liquids inside
// liquidcontainers (!) are
// assigned the container
// name as a temporary
// noun (!)
if (isclass(item, liquid))
item.tempnoun := newnoun;
newadj := car(getwords(item, &noun)); // actually many new adjectives
// (see below). the only
// _item_specific_ one is
// the name of the item.
// (!) the name of the
// item is re-assigned to the
// item as an adjective (!)
addword(item, &noun, newnoun); // these two are the only
addword(item, &adjective, newadj); // _item_specific_ changes.
addword(item, &adjective, 'in'); // the remaining changes
addword(item, &adjective, 'from'); // are generic
addword(item, &adjective, 'inside');
item.locationadjective := true; // changes have been made (!)
global.liquidlocationadjectivelist := global.liquidlocationadjectivelist + item;
}
delliquidlocationadjective: function(parm) // reverses (!) the effect of
{ // addliquidlocationadjective.
if (global.liquidlocationadjectivelist <> []) // we apologise for the
{local llal, i; // obsessive doublechecking
llal := global.liquidlocationadjectivelist; // you're about to witness.
for (i := 1; i <= length(llal); i++) // we're leaving this in, in
// case you're debugging
// and need the help.
{
local loc, newnoun, newadj, liqlist;
if (llal[i] = nil) return;
liqlist := global.liquidlist;
loc := llal[i].location;
if (
(length(liqlist) > length(liqlist - llal[i])) // if item isn't on the
and // global.liquidlist,
(isclass(loc, liquidcontainer) = true) // we leave it alone.
and // this shouldn't happen.
(llal[i].locationadjective <> nil) // should always be a redundant check
)
{
newnoun := llal[i].tempnoun; // the noun that was newly
// assigned during
// addliquidlocationadjective.
// primary noun of the item's
// container. we're stripping
// that from the item if the
// item isn't a reservoir.
if (isclass(llal[i], liquid)) // only liquids, not reservoirs, need
delword(llal[i], &noun, newnoun); // to be adjusted here. reservoirs
// are supposed to inherit their
// container name, if they have a
// container. so we don't want to
// delete that.
newadj := car(getwords(llal[i], &noun)); // primary noun of the item, which
// had been made into an adjective
// (reservoir or not).
delword(llal[i], &adjective, newadj); // we remove the item's primary
// noun from the item's adjective
// list. This may or may not be
// really necessary.... (?)
delword(llal[i], &adjective, 'in'); // now we remove the other generic
delword(llal[i], &adjective, 'from'); // adjectives.
delword(llal[i], &adjective, 'inside');
llal[i].locationadjective := nil; // changes reversed, remove flag
}
}
global.liquidlocationadjectivelist := [];
}
}
modify thing
adjective = 'thats' 'that\'s' 'the' // see parsedisamb for explanation
doPutIn(actor, io) = // this deals with foreign objects
{ // (solid objects) going into
// reservoirs.
if ((isclass(io, liquidreservoir)) and (isclass(io.location, room)))
// if the reservoir doesn't have a container, objects placed in it
// are destroyed. (like throwing an object in a lake or so. change this
// whenever necessary.)
{
"%You% throw%s% <<self.thedesc>> into <<io.thedesc>>. Hope you didn't
need that!";
self.moveInto(nil);
}
else if (isclass(io, liquidreservoir))
{
"%You% put%s% <<self.thedesc>> in <<io.location.thedesc>>.";
self.moveInto(io.location);
}
else // not a reservoir. regular putIn.
{
self.moveInto(io);
"Done. ";
}
}
verIoPourIn(actor) = { }
verDoPourIn(actor, iobj) = { }
verIoPourInto(actor) = { }
verDoPourInto(actor, iobj) = { }
verDoFillWith(actor, iobj) = { }
verIoFillWith(actor) = { }
ioFillWith(actor, dobj) =
{
execCommand(actor, putVerb, self, inPrep, dobj);
}
ioPourInto(actor, dobj) =
{
execCommand(actor, putVerb, dobj, inPrep, self);
}
ioPourIn(actor, dobj) =
{
execCommand(actor, putVerb, dobj, inPrep, self);
}
verDoEmpty(actor) = { "\^<<self.thedesc>> isn't the sort of thing that
can be filled and emptied."; }
ioEmptyOn(actor, dobj) = { dobj.doEmptyOn(actor, self); }
ioEmptyIn(actor, dobj) = { dobj.doEmptyInto(actor, self); }
ioEmptyInto(actor, dobj) = { dobj.doEmptyInto(actor, self); }
;
modify global
liquidlocationadjectivelist = []
lamplist = [] // list of all known light providers in the game
liquidlist = [] // list of all liquids and liquidreservoirs
;
startroom: room
sdesc = "The Demonstration Room"
ldesc = {
"There's a toilet here, a river, and a gruesome stream of ";
"blood. You might be carrying some liquid containers.";
}
north = startroom
;
toilet: liquidcontainer, fixeditem
location = startroom
sdesc = "toilet"
noun = 'toilet'
adjective = 'white'
canReachContents = true
ldesc = {
"It's a standard public toilet:
white porcelain, and a flusher for flushing.\n";
"There's water in the bowl. ";
}
verDoFlush( actor ) = {}
doFlush( actor ) = {
"The toilet gurgles loudly as water rushes through the bowl.\n";
}
;
bottle: liquidcontainer
location = startroom
sdesc = "bottle"
noun = 'bottle'
ldesc = {
"It's made of glass, and somewhat ribbed around the sides,
much like an old style cocacola bottle. It has no lid. ";
pass ldesc;
}
adjective = 'glass'
ioPutIn(actor, dobj) = {
if (
(isclass(dobj, liquid) = nil)
and
(isclass(dobj, liquidreservoir) = nil)
)
"The rim of the bottle is too narrow. You can't force it in.";
else
dobj.doPutIn(actor, self);
}
;
/****************
* the whole purpose of liquidreservoir is, you don't empty
* the liquidreservoir when you get the liquid from it.
*
* the implementation adopted for liquids is as follows:
*
* there's a liquid class, and then there's the
* liquidreservoir class.
* the player actually only takes an object of class 'liquid',
* which is dynamically created and moved into the player's
* container when s/he gets/pours/etc the liquidfountain object
* with/into/etc an appropriate container. for each liquidreservoir
* type object, there should be a corresponding liquid
* but not necessarily vice-versa. liquidreservoir
* items should be lakes, swamps, streams, fountains,
* etc. liquids are 'water/riverwater' 'slime' etc.
* the player never touches the actual reservoir
* while pouring. the corresponding liquid is substituted
* for it. liquidreservoir items can be inside other items,
* as in the water inside a fountain. see below for info about
* liquidcontainers (items designed to contain liquids, as opposed
* to items designed to contain liquidcontainers).
*
* now here's some slightly more complicated technical stuff that you
* only need to read if you want to improve our system:
* the command involved in moving and creating liquids
* center around the verb PutIn. the other verbs (such
* as FillWith, PourIn, PourInto) execute PutIn.
* one important thing to remember: normally, the
* io<Verb>[Prep] actually carries out the action of the
* verb after it passes its verIo<Verb>[Prep] and
* verDo<Verb>[Prep], but with the verb "PutIn", this action
* is transferred from IoPutIn to DoPutIn. (this is its
* natural library definition, and wasn't our idea -- wether
* it makes more sense or not... probably does or mike would have
* made it more consistant.) anyway,
* in other words, the object being "contained" actually
* performs the action of the verb, not the container.
* There's nothing wrong with changing this if you want
* to, but we've adopted this for liquids, to keep things
* (?) simpler (?), and standard, and hopefully more
* intuitive for people who know TADS well enough to be
* making improvements on our system.
*
* as a side note, dynamically created liquids are, so to
* speak, only one volume, as are liquidcontainers (i.e.,
* liquidcontainers are either filled, or empty.) this means
* that you can fill a barrel with a teacup, and vice versa.
* if you feel like you need to fix this for your game, check
* out safe.t in if-archive\programming\tads\examples. that
* might get you started.
* also, you might consider testing containability
* based on wether or not one container will fit inside another.
* you could use container bulk for this, or some other measure,
* for example dan shovitz' (unpublished ?) 1 thru 6 size gague,
* a la LFPhoenix.
************************************************/
modify theFloor
ioPutOn(actor, dobj) =
{
if (isclass(dobj,liquid))
{ dobj.doPutOn(actor, self); }
else
dobj.doDrop(actor);
}
;
modify class container
verDoEmpty(actor) = { }
verDoEmptyOn(actor, io) = { }
verIoEmptyIn(actor) = { self.verIoEmptyInto(actor); }
verDoEmptyIn(actor, io) = { self.verDoEmptyInto(actor, io); }
verIoEmptyInto(actor) = { }
verDoEmptyInto(actor, io) = {
if (io.isIn(self))
{
"%You%'ll have to take <<io.thedesc>> out of
<<self.thedesc>> before %you% can do that.";
}
}
doEmptyIn(actor, io) = { self.doEmptyInto(actor,io); }
doEmpty(actor) = {
"(onto the ground)\n";
self.doEmptyOn(actor, theFloor);
}
doEmptyOn(actor, io) = {
if ((self.contents) = [])
{
"%You% might want to try putting something inside <<self.thedesc>> ";
"before %you% decide%s% to empty it.";
return;
}
if ((self.isIn(parserGetMe()) = nil) and (isclass(self, fixeditem)))
{
"%You%'d have to pick up <<self.thedesc>> to empty it, and %you%
can't pick it up. Maybe %you% should try just taking its
contents.";
return;
}
else
{
local i, c;
if (self.location <> parserGetMe())
{
"%You% grab%s% <<self.thedesc>> and empt%ies% its contents onto
<<io.thedesc>>.\n";
self.moveInto(parserGetMe());
}
c := self.contents;
for (i := 1; i <= length(c); i++)
{
if (c[i].isOnSurface = nil)
{
"\n\^";
c[i].sdesc;
": ";
execCommand(actor, putVerb, c[i], onPrep, io);
}
}
}
}
doEmptyInto(actor, io) = {
if ((self.contents) = [])
{
"%You% might want to try putting something inside <<self.thedesc>>
before %you% decide%s% to empty it.";
return;
}
if ((self.isIn(parserGetMe()) = nil) and (isclass(self, fixeditem)))
{
"%You%'d have to pick up <<self.thedesc>> to empty it, and %you%
can't pick it up. Perhaps %you% should try just taking its
contents.";
return;
}
else
{
local i, c;
if (self.location <> parserGetMe())
{
"%You% grab%s% <<self.thedesc>> and empt%ies% its contents into
<<io.thedesc>>.\n";
self.moveInto(parserGetMe());
}
c := self.contents;
for (i := 1; i <= length(c); i++)
{
if (c[i].isOnSurface = nil)
{
"\n\^";
c[i].sdesc;
": ";
execCommand(actor, putVerb, c[i], inPrep, io);
}
}
}
}
;
class liquidreservoir: fixeditem
preferredDisambName = 'undefined' // useful for reservoirs
// without containers (lakes, etc.)
verIoPutIn(actor) = { }
ioPutIn(actor, dobj) = { dobj.doPutIn(actor, self); }
ispoison = nil
liquidContentsClassPointer = nil // must put the contents class here
verDoTake(actor) = {
"\^<<self.liquidContentsClassPointer.thedesc>> would just
slip through %your% fingers.";
}
doPutIn(actor, io) =
{
local contentsClassPointer := new liquidContentsClassPointer;
if (io.isIn(actor)=nil)
{
"(takeing <<io.thedesc>>)\n";
io.moveInto(actor);
}
global.liquidlist := global.liquidlist + contentsClassPointer;
contentsClassPointer.moveInto(io);
(io.isfilled := true);
/******************************
* we're assuming that carriable reservoirs haven't been implemented.
* if you have a reservoir that's carried, you might
* want to add a custom message about that in the
* following lines. if you want to change its behavior, you might need
* to do something with verIoPutIn/verDoPutIn
*********************************************/
if ((self.location) and (firstsc(self.location) <> room))
{
"%You% dip%s% <<io.thedesc>> into <<self.location.thedesc>> and
fill%s% it with <<liquidContentsClassPointer.sdesc>>.";
}
else
{
"%You% dip%s% <<io.thedesc>> into <<self.thedesc>> and
fill%s% it with <<liquidContentsClassPointer.sdesc>>. ";
}
}
verDoPutIn(actor, io) =
{
if (io = nil)
return;
else if (
((isclass(io, liquidcontainer)) = nil)
and
((isclass(io, liquidreservoir)) = nil)
)
{
"\^<<io.thedesc>> isn't a suitable container
for <<self.liquidContentsClassPointer.sdesc>>. ";
}
else if ((io = self) or (io = self.location))
{
"%You% can't pour "; self.thedesc; " in <<self.itselfdesc>>, pal. ";
}
else if (io.isIn(self))
self.circularMessage(io);
else if (isclass(io, fixeditem))
{
"How do%es% %you% expect to do that? %You% can move
neither ";
if (firstsc(io.location) = room)
io.thedesc;
else
io.location.thedesc;
" nor ";
if (firstsc(self.location) = room)
self.thedesc;
else
self.location.thedesc;
".";
}
else if (length(io.contents) > 0)
{
local i, c;
c := io.contents;
for (i := 1; i <= length(c); i++)
{
if (isclass(c[i], liquidContentsClassPointer))
{
local liquidInsideIo := c[i];
"%You're% too late. It's already filled
with <<io.thedesc>>.\n";
}
else if (isclass(c[i], liquid))
{
local liquidInsideIo := c[i];
"\^<<io.thedesc>> is already filled
with <<liquidInsideIo.sdesc>>. %You%'ll have to empty it
first.";
}
}
/* optional: else "There's already something inside <<io.thedesc>>."; */
}
else
self.verifyRemove(actor);
}
verDoPourOn(actor, io) =
{
"If %you% could figure out a way to pick up <<self.thedesc>>, %you%
might have a chance.";
}
verIoDouseWith(actor, io) = {self.verDoPourOn(actor, io);}
verDoPutOn(actor, io) = {self.verDoPourOn(actor, io);}
doPutOn(actor, io) = {self.verDoPourOn(actor, io);}
verDoDrink(actor) = { }
doDrink(actor) = {
if (self.ispoison)
{
if (actor = parserGetMe())
{
"Hmm, that tasted strange...\nYou sputter and gag for a moment,
then roll up into a ball and die a quick but painful death.\n";
die();
}
else
{
"<<actor.sdesc>> refuses.\n"; // if this ever happens, you'll want to
} // change the message here anyway
}
else
{
"%You% gulp%s% down <<self.thedesc>>. Aah, refreshing!";
}
}
verDoGiveTo(actor, io) =
{
"%You% can't grab <<self.liquidContentsClasspointer.thedesc>> without spilling it.
Perhaps %you% should find something to carry it in.";
}
verDoSmell( actor ) = {}
doSmell( actor ) =
{
"It smells like <<self.sdesc>>";
}
;
class liquidcontainer: container
isfilled = nil
;
class liquid: item
verDoEmptyOn(actor, io) = { }
doEmptyOn(actor, io) = { io.ioPourOn(actor, self); }
verDoEmptyIn(actor, io) = { }
doEmptyIn(actor, io) = { self.doPutIn(actor, io); }
verDoEmpty(actor) = { }
doEmpty(actor) = {
"(onto the ground)\n";
self.doPutOn(actor, theFloor);
}
doDrop(actor) = {
self.doPutOn(actor, theFloor);
}
tempnoun = ''
isEquivalent = nil
ispoison = nil
verDoTake(actor) = {
"\^<<self.thedesc>> would just slip through %your% fingers.";
}
/**************************************************************
* the way we've implemented it here, you can dump as much
* foreign liquids into a liquidreservoir as you want, and
* they'll just dissolve into the reservoir. doing that does not
* change the reservoir at all. even if you dump tons of poison
* into the reservoir, it dissolves safely, no problem. depending on
* the imagined size of a specific reservoir, this might need to be
* over-ridden (on a case by case basis.) keep in mind that you'll
* need to change the behavior of liquids, not the behavior of the
* reservoir. (one of the magics of ioPutIn = dobj.doPutIn)
**************************************************************/
doPutIn(actor, io) =
{
self.location.isfilled := nil;
if (isclass(io, liquidreservoir))
{
if (isclass(self, io.liquidContentsClassPointer))
{
"%You% pour%s% <<self.thedesc>> back into <<io.thedesc>>.";
}
else
{
"%You% pour%s% <<self.thedesc>> from <<self.location.thedesc>> into ";
if (firstsc(io.location) = room)
{
io.thedesc;
". It mixes in ";
}
else
{
io.location.thedesc;
". It mixes with
<<io.liquidContentsClassPointer.thedesc>> ";
}
"and slowly dissolves.";
}
global.liquidlist -= self;
delete self;
}
else if (
(io.location)
and
(isclass(io.location, fixeditem) = nil)
)
{
if (io.isIn(actor)=nil)
{
"(takeing <<io.thedesc>>)\n";
io.moveInto(actor);
}
"%You% pour%s% <<self.thedesc>> from <<self.location.thedesc>> into
<<io.thedesc>>. ";
self.moveInto(io);
}
else if (io.location)
{
"%You% pour%s% <<self.thedesc>> from <<self.location.thedesc>> into
<<io.thedesc>>. ";
self.moveInto(io);
}
else
pass doPutIn; // this won't happen unless your game is wierd
}
verDoPutIn(actor, io) =
{
local i;
if (io = nil)
return;
else if (self.isIn(actor) = nil)
{
if (isclass(self.location, liquidcontainer))
"%You're% not carrying <<self.location.thedesc>>.";
else
"%You're% not carrying <<self.thedesc>>.";
}
else if (self.location = io)
{
"\^<<self.thedesc>> is already inside <<io.thedesc>>, ace. ";
}
else if (io = self)
{
"%You% can't pour "; self.thedesc; " in <<self.itselfdesc>>, pal. ";
}
else if (
((isclass(io, liquidcontainer)) = nil) // it can go into
and // containers.
((isclass(io, liquidreservoir)) = nil) // allow liquids to
) // mix into reservoirs
{
"\^<<io.thedesc>> isn't a suitable container for <<self.sdesc>>. ";
}
else if (io.isIn(self))
self.circularMessage(io);
else if (io.isfilled)
{
for (i := 1; i <= length(io.contents); i++)
{
if (firstsc(io.contents[i]) = firstsc(self))
{
"%You're% a bit late, pal. There's already <<self.thedesc>> in
<<io.thedesc>>.";
return;
}
}
"\^<<io.thedesc>> already has another liquid in it. Maybe %you%
should empty it first.";
}
else
self.verifyRemove(actor);
}
verDoDrink(actor) = { }
doDrink(actor) =
{
if (self.ispoison)
{
if (actor = parserGetMe())
{
(self.location.isfilled := nil);
"Hmm, that tasted strange...\nYou sputter and gag for a moment,
then roll up into a ball and die a quick but painful death.\n";
die();
}
else
{
"<<actor.sdesc>> adamantly refuses."; // you'll change this if it
} // ever becomes relevant
}
(self.location.isfilled := nil);
"%You% gulp%s% down <<self.thedesc>>. Aah, refreshing!";
(global.liquidlist := global.liquidlist - self);
delete self;
}
verDoGiveTo(actor, io) =
{
if (isclass(self.location, liquidcontainer))
{
"%You% can't grab <<self.thedesc>> without spilling it. ";
if (isclass(self.location, fixeditem) = nil)
{
"Perhaps %you% should give <<io.thedesc>> ";
self.location.thedesc;
".";
}
else
{
"Perhaps %you% should find something to carry <<self.thedesc>> in.";
}
}
else if (not self.location = parserGetMe())
{
"%You%'ll have to figure out some way to pick it up first.";
}
}
;
toiletbowlwater: liquidreservoir
location = toilet
ispoison = true
noun = 'water'
sdesc = "water"
adjective = 'toilet'
ldesc = "You carefully study the inside of the toilet bowl. It's rather
dirty... stinky... hmm, filthy... disgusting...."
liquidContentsClassPointer = toiletwater
;
class toiletwater: liquid
ispoison = true
ldesc = "You study the yellowish-brownish toilet water closely.
Evidently, you find it quite interesting."
location = nil
noun = 'water'
adjective = 'brownish' 'yellowish' 'yellowish-brownish' 'dirty' 'brown' 'yellow' 'toilet'
sdesc = "yellowish-brownish toilet water"
adesc = "some yellowish-brownish toilet water"
;
/*****************
* The next few items are examples of liquids
* and liquidreservoirs.
* You will find that assigning names, nouns, and
* adjectives to the liquids and reservoirs is
* slightly trickier that you might expect.
* This is an unavoidable (?) result of the disambiguation
* tricks we're using. If you can read the code well
* enough to figure out the way that disambiguation
* works in this game, you should have no problem
* picking unproblematic names and unambiguous
* adjectives. If we haven't commented the code
* well enough for you to understand it, we apologise,
* and offer these examples in compensation.
*
* The best advice is, keep it as simple as possible.
* Dynamic objects are difficult enough to disambiguate
* already, and making them come from quasi-identical
* sources (sources that a player will imagine are just
* bigger versions of the dynamically created object
* which they come away with) makes disambiguation
* really tough.
* So keep the names and adjectives as simple and clear
* as possible.
*
* we'll load Me with a couple containers, and put the examples
* in the startroom
*/
jug: liquidcontainer // must be liquidcontainer. normal
location = Me // containers don't work for liquids.
sdesc = "jug"
noun = 'jug'
;
cup: liquidcontainer
location = Me
sdesc = "cup"
noun = 'cup'
;
river: liquidreservoir // name and class, same as usual
// these kinds of things you'll normally
// want to leave unlisted, so you can
// talk about them in room descriptions.
noun = 'river' 'water' // name of reservoir, AND name of liquid
// which comes out of reservoir. (MUST
// have the latter. You might want to
// eliminate the former, but only if the
// reservoir has a container. check out
// the toiletbowlwater for further ideas.
// if it does have a container, it must
// be a fixed container, or wierd stuff
// happens.)
adjective = 'river' // MUST have name of reservoir as adjective.
// if the reservoir is in a fixedcontainer,
// this is optional.
sdesc = "river" // keep it as simple as possible. adding
// words here might confuse the player.
ldesc = "It is running uphill. How odd."
location = startroom
ispoison = nil
preferredDisambName = 'river water' // useful ONLY if the reservoir is
// _not_ in another object. Lakes,
// streams, rivers, etc. get a
// preferred disambiguation name.
// reservoirs that are inside
// containers, like the
// toiletbowlwater in this game,
// don't use this name, but instead
// are disambiguated based on their
// location. (e.g., the water that's
// in the toilet). You can assign
// this name anyway, but it will never
// be used by the game. If you fail
// to assign a preferred disamb name,
// our disambiguator will figure out
// something, but it might not be
// quite as elegant. (then again, it
// might be more elegant. hehe.)
// (!) IMPORTANT: (!)
// one other thing about this
// preferredDisambName: the last
// word in the string MUST be the
// NOUN of the reservoir.
// also, ALL of the other words in
// the string MUST be adjectives of
// the reservoir. If the noun you
// choose is the noun which the
// reservoir shares with the liquid,
// the adjectives are crucial, and
// should be obviously different from
// the adjectives for the liquid. (as
// in this particular example).
liquidContentsClassPointer = riverwater // _MUST_ put the contents
// class here. (the liquid
// you want it to serve.)
// this is what makes the
// fountain work.
;
class riverwater: liquid // this object works
// basically as a template for
// all of the dynamically generated
// objects (liquids) which come
// out of the corresponding reservoir.
// therefore, it is a CLASS, NOT an
// object. and it doesn't have a
// location.
noun = 'water' // keep it simple. shares this noun
// with its reservoir source (!)
adjective = 'clear' 'clean' // intuitive, simple.
adesc = "water"
sdesc = "some water"
thedesc = "the water"
ldesc = "It's clean, cool, refreshing. Just the thing after a
long adventure! Or a long codeing session!!"
;
stream: liquidreservoir // another example, basically the same as river
location = startroom
ispoison = true // careful!
noun = 'stream' 'blood' // must share noun (here, 'blood')
sdesc = "terrible stream"
adjective = 'stream'
preferredDisambName = 'the stream' // last word is primary noun
// not shared noun. so we're not
// worried about making clear
// adjectives in this string.
ldesc = "It's a terrible stream of seeping blood."
liquidContentsClassPointer = blood // must put the contents class here
;
class blood: liquid
ispoison = true
adesc = "some blood"
noun = 'blood' // must share noun
sdesc = "blood"
adjective = 'succulent'
ldesc = "Eww. Oozy and yucky. You can almost feel it pulsing!"
;
/*
* the most interesting example is the toilet, toiletwater, and
* toiletbowlwater, above. that's somewhat more complex, because the
* toiletbowlwater (reservoir) is inside a container (namely, the toilet).
* such containers, as noted above, can't be moveable. (because we didn't
* implement that).
* you will notice there that a liquidreservoir CAN have only
* one noun, as long as it's the same noun as its corresponding
* liquid. (depends on the specifics of the situation. with the toilet,
* one noun works best. with a fountain or a well, it probably needs
* to be named after its container, in addition to sharing a name with
* its liquid. you'll figure it out when you tinker with it.)
* perhaps obvious (?): if your liquidreservoir is in a container, you
* should probably make the container a liquidcontainer. (that way,
* liquids can
* be poured into it.)
*********************************************************************/
fillVerb: deepverb
sdesc = "fill"
verb = 'fill'
ioAction( withPrep ) = 'FillWith'
action( actor ) = 'Fill'
prepDefault = withPrep
;
emptyVerb : deepverb
sdesc = "empty"
verb = 'empty'
ioAction( onPrep ) = 'EmptyOn'
ioAction( inPrep ) = 'EmptyIn'
ioAction( inPrep ) = 'EmptyInto'
doAction = 'Empty'
;