Section 21: Extending and redefining the Library

Contents Back Forward | ### 21. Extending and redefining the Library
| 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 Most large games will need to enrich the 'model world': for instance, by creating a new concept such as "magic amulets''. The game might contain a dozen of these, each with the power to cast a different spell. So it will need routines which can tell whether or not a given object is an amulet, and what to do when the spell is cast. To do this, a game should make a class definition for amulets: called `Amulet`, say. Then
        if (noun ofclass Amulet) ...
will test to see if `noun` is one of the amulets, for instance. The amulet's spell will be represented by the property `amulet_spell`. Typical values for this might be:
        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);
        ],
Then the process of casting the spell for amulet `X` is a matter of sending the message
        X.amulet_spell();
which will reply with either: false, meaning nothing has happened; true, meaning that something did happen; or an object, a room to teleport the player to. Here is a routine which deals with it all:
        [ CastSub destination;
          if (noun ofclass Amulet)
          {   if (~~(noun provides amulet_spell))
                  "[Ooops. Forgot to program this amulet_spell.]";
              destination = noun.amulet_spell();
              switch(destination)
              {   false:   "Nothing happens.";
                  true:    ;
                  default: print "You are magically teleported to...^";
                           PlayerTo(destination);
              }
          }
          else "You only know how to cast spells with amulets.";
        ];
An elaborate library extension will end up defining many classes, grammar, actions and verb definitions. These may neatly be packaged up into an `Include` file and placed with the other library files.
| If this file contains the directive `System_file;` then it will even be possible for games to `Replace` routines from it (see below).
| The ordinary Library's own properties, such as `description` or `e_to`, are called "common properties''. They are special for the following reason: if an object `O` does not give any value for common property `P`, then `O.P` can still be looked up, though it can't be set to something else. (If you tried this with a property of your own invention, such as `amulet_spell` above, an error would be printed out at run-time.) The value of `O.P` is just the "default value'' provided by the Library for property `P`: for example, the default value of `cant_go` is "You can't go that way.''
| But you can change this default value during play, using the library's `ChangeDefault` routine. For instance, at a late stage in the game:
    ChangeDefault(cant_go, "You're a Master Adventurer now, and still
                            you walk into walls!");
Of course this cannot change defaults for properties of your own invention, because they haven't got default values.
| Common properties are also slightly faster to perform calculations with: the down side is that there's a strictly limited supply of them (63 in all), of which the library uses up half already. To indicate that a property needs to be a common property, use the `Property` directive. For example:
        Property door_to;
        Property capacity 100;
        Property cant_go "You can't go that way.";
In the latter cases we are giving default values: in the former case, the default value will just be 0.
| Major library extensions are rarely needed. More often, one would like simply to change the stock of standard messages, such as the "Nothing is on sale.'' which tends to be printed when the player asks to buy something, or the "Taken.'' printed when something is picked up. This facility is available as follows. Provide a special object called `LibraryMessages`, which must be defined between the inclusion of the "Parser" and "VerbLib" library files. This object should have just one property, a `before` rule. For example:
    Object LibraryMessages
      with before
           [;  Jump: "You jump and float uselessly for a while in
                      the zero gravity here on Space Station Alpha.";
               SwitchOn:
                     if (lm_n==3)
                     {   print "You power up ", (the) lm_o, "."; }
           ];
The object never physically appears in the game, of course. The idea is that the `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 (the extreme case is `Take`, with 13). `lm_n` holds the message number, which counts upwards from 1. The messages and numbers are given in Appendix A9. 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 exactly this purpose). In this way, the game's prompt can be made context-sensitive, or the "skipped line on screen each turn'' convention can be removed.
| This prompt is only used in ordinary game play, and not at such keyboard inputs as yes/no questions or the RESTART/RESTORE/QUIT game over choice.
| EXERCISE 47: (link to the answer)
| Infocom's game 'The Witness' has the prompt "What should you, the detective, do next?'' on turn one and "What next?'' subsequently. Implement this.
| An amusing way to see the system in action is to put
    Object LibraryMessages
      with before
           [;  print "[", sw__var, ", ", lm_n, "] ";
           ];
into your game (arcane note: `sw__var`, the "switch variable'', in this case holds the action number). Another amusing effect is to simply write `rtrue;` for the `before` routine, which results in an alarmingly silent game -- blindfold Adventure, perhaps.
| Note that `LibraryMessages` can be used as a sneaky way to add extra rules onto the back of actions, since there's nothing to stop you doing real processing in a call to it; or, more happily, to make messages more sensitive to game context, so that "Nothing is on sale.'' might become "That's not one of the goods on sale.'' inside a shopping mall.
| EXERCISE 48: (link to the answer)
| Write an Inform game in Occitan (a dialect of medieval French spoken in Provence). The Library is itself written in Inform, and with experience it's not too hard to alter it if need be. But this is an inconvenience and an inelegant way to carry on. So here is the last resort in library modification: work out which routine is giving trouble, and `Replace` it. 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. It would be normal to copy the definition of `BurnSub` out of the library files into your own code, and then modify that copy as needed. The most popular routine to replace is `DrawStatusLine`: see Section 33 for several examples.
| Inform even allows you to `Replace` "hardware'' functions like `random`, which would normally be translated directly to machine opcodes. Obviously, replacing something like `child` with a software routine will impose an appreciable speed penalty and slightly increase object code size. Replacing `random` may however be useful when fixing the random number generator for game-testing purposes.
| REFERENCES: 'Balances' contains a section of code (easily extractable to other games) implementing the 'Enchanter' trilogy's magic system by methods like the above. There are several formal library extension files in existence, mostly small: see the Inform home page on the WWW. "pluralobj.h'' by Andrew Clover makes large-scale use of `LibraryMessages` to ensure that the library always uses words like "those'' instead of "that'' when talking about objects with names like "a heap of magazines''.

Contents / Back / Forward
Chapter I / Chapter II / Chapter III / Chapter IV / Chapter V / Chapter VI / Appendix

Mechanically translated to HTML from third edition as revised 16 May 1997. Copyright © Graham Nelson 1993, 1994, 1995, 1996, 1997: all rights reserved.