A circulating library in a town is as an ever-green tree of diabolical knowledge! It blossoms through the year!
— R. B. Sheridan (1751–1816), The Rivals
In The History of Zork, Tim Anderson summed up the genesis of the ‘Zork I’ world model – and perhaps also its exodus, that is, its adaptation to other games – when he commented that “the general problem always remained: anything that changes the world you're modelling changes practically everything in the world you're modelling.” Substantial changes to the world model often have profound implications and can lead to endless headaches in play-testing if not thought through. Even a single object can upset the plausibility of the whole: a spray-can, for instance, unless its use is carefully circumscribed. On the other hand, introducing whole categories of objects often causes no difficulty at all, if they do not upset existing ideas such as that of door, location, container and so on. For instance, the set of collectable Tarot cards in the game ‘Curses’ have numerous rules governing their behaviour, but never cause the basic rules of play to alter for other items or places.
· · · · ·
In making such an extension the natural strategy is
simply to define a new class of objects, and to take advantage of Inform's
message system to make designing such objects as easy and flexible as
possible. For example, suppose we need a class of Egyptian magical amulets,
small arrowhead-like totems worn on the wrist and made of semi-precious
minerals, with cartoon-like carvings. (The Ashmolean Museum, Oxford,
has an extensive collection.) Each amulet is to have the power (but
only if worn) to cast a different spell. Almost all of the code for this
will go into a class definition called Amulet
. This means
that
if (noun ofclass Amulet) ...
provides a convenient test to see if an object noun
is an amulet, and so forth. (This imposes a restriction that an object
can't start or stop being an amulet in the course of play, because class
membership is forever. If this restriction were unacceptable, a new
attribute would need to be created instead, in the same way that the
standard world model recognises any object with the attribute
container
as a container, rather than having a Container
class.)
Suppose the requirement is that the player should
be able to type “cast jasper amulet”, which would work
so long as the jasper amulet were being worn. It seems sensible to
create an action called Cast
, and this necessitates
creating an action subroutine to deal with it:
[ CastSub; "Nothing happens."; ]; Verb 'cast' 'invoke' * noun -> Cast;
Nothing happens here because the code is kept with
the Amulet
class instead:
Class Amulet with amulet_spell "Nothing happens.", before [ destination; Cast: if (self hasnt worn) "The amulet rattles loosely in your hand."; destination = self.amulet_spell(); switch (destination) { false: "Nothing happens."; true: ; default: print "Osiris summons you to...^"; PlayerTo(destination); } rtrue; ], has clothing;
Thus every Amulet
provides an
amulet_spell
message, which answers the question “you
have been cast: what happens now?” The reply is either false
,
meaning nothing has happened; true, meaning that something did happen;
or else an object or room to teleport the player to.
From the designer's point of view, once the above extension has been made, amulets are easy to create and have legible code. Here are four example spells:
amulet_spell "The spell fizzles out with a dull phut! sound.", amulet_spell [; if (location == thedark) { give real_location light; "There is a burst of magical light!"; } ], amulet_spell HiddenVault, amulet_spell [; return random(LeadRoom, SilverRoom, GoldRoom); ],
An elaborate library extension will end up defining
many classes, grammar, actions and verb definitions, and these may
neatly be packaged up into an Include
file and to be
placed among the library files.
▲▲
Such a file should contain the directive System_file;
,
as then other designers will be able to Replace
routines
from it, just as with the rest of the library.
· · · · ·
So much for extending the Inform model with new classes: the rest of the section is about modifying what's ordinarily there. The simplest change, but often all that's needed, is to change a few of the standard responses called “library messages”, such as the “Nothing is on sale.” which ends to be printed when the player asks to buy something, or the “Taken.” when something is picked up. (To change every message, and with it the language of the game, see §34.)
To set new library messages, provide a special
object called LibraryMessages
, which must be defined between
the inclusion of the "Parser.h"
and
"Verblib.h"
library files. This object should
have just one property, a before
rule.
For example:
Object LibraryMessages with before [; Jump: if (real_location ofclass ISS_Module) "You jump and float helplessly for a while in zero gravity here on the International Space Station."; SwitchOn: if (lm_n == 3) { "You power up ", (the) lm_o, "."; } ];
This object is never visible in the game, but its
before
rule is consulted before any message is printed:
if it returns false
, the standard message is printed;
if true
, then nothing is printed, as it's assumed
that this has already happened.
The Jump
action only ever prints one
message (usually “You jump on the spot.”), but more
elaborate actions such as SwitchOn
have several, and
Take
has thirteen. The library's variable lm_n
holds the message number, which counts upwards from 1. In some cases,
the object being talked about is held
in lm_o
. The
messages and numbers are given in §A4.
New message numbers may possibly be added in future, but old ones will
not be renumbered.
An especially useful library message to change is
the prompt, normally set to "^>"
(new-line followed by
>). This is printed under the action Prompt
(actually
a fake action existing for this very purpose). You can use this to
make the game's prompt context-sensitive, or to remove the new-line
from before the prompt.
•
EXERCISE 59
Infocom's game ‘The Witness’ has the prompt “What
should you, the detective, do next?” on turn one and “What
next?” subsequently. Implement this.
▲
LibraryMessages
can also be used as a flexible way to
alter the rules governing individual actions. Here are two examples
in the guise of exercises.
•▲
EXERCISE 60
Under the standard world model (¶6.7.4 in §24
above), a player standing on top of something is not allowed to
type, say, “east” to leave the room: the message “You'll
have to get off… first” is printed instead. Change this.
•▲
EXERCISE 61
Under standard rules (¶6.6.1 in §24
above), a player trying to “push” something which is
not static
or scenery
or animate
will find that “Nothing obvious happens”. Add the rule
that an attempt to push a switchable
item is to be
considered as an attempt to switch it on, if it's off, and vice versa.
(This might be useful if a game has many buttons and levers.)
· · · · ·
The Library is itself written in Inform, and with
experience it's not too hard to alter it if need be. But to edit and
change the library files themselves is an inconvenience and an inelegant
way to carry on, because it would lead to needing a separate copy
of all the files for each project you work on. Because of this, Inform
allows you to Replace
any routine or routines of your choice
from the library, giving a definition of your own which is to be used
instead of the one in the standard files. For example, if the directive
Replace BurnSub;
is placed in your file before the library files
are included, Inform ignores the definition of BurnSub
in
the library files. You then have to define a routine called BurnSub
yourself: looking in the library file "Verblib.h",
the original turns out to be tiny:
[ BurnSub; L__M(##Burn,1,noun); ];
All this does is to print out library message number
1 for Burn
, the somewhat preachy “This dangerous
act would achieve little.” You could instead write a fuller
BurnSub
providing for a new concept of an object being
“on fire”.
▲▲
Inform even allows you to Replace
“hardware”
functions like random
or parent
, which
would normally be translated directly to machine opcodes.
This is even more “at your own risk” than ordinary usages
of Replace
.
· · · · ·
What are the implications of fire likely to be? One way to find out is to read through the world model (§24) and see how fire ought to affect each group of rules. Evidently we have just created a new possible internal state for an object, which means a new rule under ¶1, but it doesn't stop there:
1.4.4. Some objects are “flammable” and therefore in one of two mutually exclusive states, “on fire” and “not on fire”.
2.4. If an object on fire is placed in a flammable container or on a flammable supporter, that too catches fire.
2.5. If a container or supporter is on fire, any flammable object within or on top of it catches fire.
4.3.3.1. Any object on fire provides light.
4.4.3. The player cannot touch any object on fire unless (say) wearing the asbestos gloves.
5.4.1. All flammable objects have a “lifespan”, a length of time for which they can burn before being consumed. The each-turn daemon subtracts one from the lifespan of any object on fire, and removes it from play if the lifespan reaches zero.
One could go further than this: arguably, certain
rooms should also be flammable, so that an object on fire which is
dropped there would set the room ablaze; and the player should not
survive long in a burning room; and we have not even provided a way
to douse the flames, except by waiting for fires to burn themselves
out. But the above rules will maintain a reasonable level of
plausibility. ¶1.4.4 is provided by defining a new class of
Flammable
objects, which contains an each_turn
routine implementing ¶5.4.1, and an on_fire
attribute.
The same each_turn
can take care of ¶2.4 and
¶2.5, and can give the object light
if it's currently
on_fire
, thus solving ¶4.3.3.1. But, while it would
be easy to add simple rules like “you can't take an
object which is on fire”, ¶4.4.3 in its fullest form is
more problematic, and means replacing the ObjectIsUntouchable
routine. Giving any object on fire a before
rule
preventing the player from using any of the “touchy”
actions on it would go some of the way, but wouldn't handle subtler
cases, like a player not being
allowed to reach for something through
a burning hoop. Nor is this everything: burning objects will need to
be talked about differently when alight, and this will call for
using the powerful descriptive features in Chapter IV.
•
REFERENCES
‘Balances’ implements the ‘Enchanter’ trilogy's
magic system by methods like the above.
•Approximately seventy library
extensions have been contributed by members of the Inform community
and more are placed at ftp.gmd.de with each month that goes
by. Often short and legible, they make good examples of Inform coding
even if you don't want to use them. Many are cited in “references”
paragraphs throughout the book: here are others which seem more appropriate
here.
•"money.h",
by Erik Hetzner, is a textbook case of a class-based extension to
Inform providing a new aspect to the world model which doesn't much
overlap with what's already there: notes and coinage.
•Conversely, Marnie Parker's
"OutOfRch.h" exemplifies a change that needs to
permeate the existing world model to be effective: it defines which
areas of a location are within reach from which other areas, so that
for instance a player sitting on a chair might only be able to reach
items on the adjacent table and not a window on the far wall.
•In some graphical adventure
games, interactivity sometimes cuts out at a significant event and
an unchangeable movie animation is shown instead: this is sometimes
called a “cut-scene”. Such games sometimes allow the player
to replay any movies seen so far, reviewing them for clues missed
previously. "movie.h", by L. Ross Raszewski, projects
the textual version of movies like this, thus providing a framework
for cut-scenes.
•"infotake.h",
by Joe Merical, shifts the Inform model world back to the style of
‘Zork’: printing Zorkesque messages, providing a
“diagnose” verb and so on.
•Anson Turner's "animalib"
retains the core algorithms of the Inform library (principally the
parser and list-writer) but redesigns the superstructure of properties
and attributes with the aim of a cleaner, more consistent world model.
Although this alternative library is in its early stages of development,
its code makes interesting reading. For instance, some child-objects
represent items held inside their parents and others represent items
on top of their parents. The standard Inform library distinguishes
these cases by looking at the attributes of the parent-object
– whether it is a supporter
or a container
.
Contrariwise, "animalib" distinguishes them by
looking at attributes of the child, so that the different children
of a single parent can all be contained in different ways.