Roger Firth's IF pages

Home

Inform 6: Frequently Asked Questions

Email
Back up

Into
the Intro

Setting
the scene

Preparing
to program

Learning
the lingo

Dabbling
in data

Operating
on objects

Verbal
versatility

Bothered
by bugs

History and
hereafter

Worldly
woes

Inside
information

Tips and
techniques

These topics are about using and supplementing Inform's extensive repertoire of verbs:

How do I define a new verb?
    Examples: SMILE and FROWN
When should I use before/react_before properties?
    Actions stages (simplified)
Where do life and orders fit in?
Surely the syntax of these properties is a little odd?
How do I change an existing verb?
    Example: WRAP and FOLD
    Extend directive
    Separating actions: DRINK, SWALLOW, SIP
Can I remove an existing verb?
Why are actions labelled Group 1, Group 2 or Group 3?
    Changing Group 3 actions into Group 2 actions
    Example: CUT
How do I detect the player entering a room, or trying to leave?
    Room object's before and after properties
    Sequence of library actions triggered by movement
    Example: Two tell-tale room classes
Where have I been?
    Knowing about recently-visited rooms
    Enabling the player to jump to a previous-visited room
How can I parse a number?
    xExample: Setting course to 180 (or cardinal directions)
    Example: The Steersman NPC
Which action is triggered by each verb?
Can I distinguish SIT ON BED from LIE ON BED?
Which verb did the player use?
How do 'meta' verbs work?

How do I define a new verb?

Whereas new nouns and adjectives are defined throughout the game file -- more or less whenever you create an object -- the process of adding new verbs needs more careful consideration. It's a complex subject, so we'll just scratch the surface. Basically, you need to define the verb's grammar -- what word(s) the player is permitted to type -- and an action routine which is invoked when he actually does type them. The simplest case is an intransitive verb; one that doesn't take an object. For example, to enable the player to SMILE and FROWN, you might add these lines at the end of your game, after the Include "Grammar":

More information in the DM:
§30 §31

  [ SmileSub; "Your face lights up in a cheery smile."; ];
  [ FrownSub; "A brief hint of annoyance crosses your face."; ];

  Verb 'smile' 'grin' 'smirk' 'beam' 'twinkle'
      *           -> Smile;
  Verb 'frown' 'scowl' 'glower' 'glare' 'lour'
      *           -> Frown;

Here, 'smile' and the other words in apostrophes are the verbs being added to the dictionary; since you can't be sure exactly which word the player might think of, you can collect several verbs which are roughly similar in effect, and make them all behave the same. The blank space within "*  ->" means that you'll recognise SMILE only on its own, not when it's followed by another word. The "Smile" after the "->" is the name of the action to be performed when you recognise SMILE. And finally, Inform automatically derives the name of the routine called to deal with an action by appending "Sub" to the name of the action. So, SmileSub is the routine which is called to deal with the Smile action.

The player can now express some basic facial emotion to the world in general. But what if he wants to SMILE AT something or someone? Here's a revised grammar:

  Verb 'smile' 'grin' 'smirk' 'beam' 'twinkle'
      *           -> Smile
      * 'at' noun -> Smile;

And here's an enhanced action routine, generating responses which are better-tailored to the subject:

  [ SmileSub;
      if (noun == nothing) "Your face lights up in a cheery smile.";
      if (noun == player) "Easily amused, heh?";
      if (noun has animate) print_ret (The) noun, " looks at you quizzically.";
      "There is no reaction.";
      ];

When writing an action routine -- FrownSub, SmileSub, etc -- you should generally provide just a standard default response which can safely be used in the majority of circumstances; that is, don't try to handle special cases -- ones that actually affect the game world -- in the action routine. Then you'll use before (and occasionally after) properties to define the behaviour of the handful of objects, often only one, which need to respond in a specific way.

When should I use before/react_before properties?

You use a before property to intercept an action which is aimed specifically at that object; you use a react_before property to intercept an action which is aimed at another, nearby, object. For example, if you introduce the grumpy old man into the room, you can SMILE AT MAN to elicit a quizzical glance in return. Or, with a suitable before property, he can be taught to respond differently:

More information in the DM:
§4 §6 §8 §17

  Object  old_man "grumpy old man" study
    with  name 'man' 'grumpy' 'cross' 'old' 'elderly' 'ancient' 'gnome',
          description "The grumpy old man stands silently in the corner.",
          before [; Smile: "The grumpy old man nods slightly."; ],
    has   animate;

To make him respond when you SMILE AT DESK, or at something else in the room, just provide a react_before property:

  Object  old_man "grumpy old man" study
    with  name 'man' 'grumpy' 'cross' 'old' 'elderly' 'ancient' 'gnome',
          description "The grumpy old man stands silently in the corner.",
          before [; Smile: "The grumpy old man nods slightly."; ],
          react_before [; Smile: "The grumpy old man looks puzzled."; ],
    has   animate;

Actually, that's not quite right. The old man is now looking puzzled too often; his react_before is intercepting every SMILE in the vicinity, even ones aimed at him. You'd be better using:

          react_before [; Smile: if (noun == writing_desk)
                                      "The grumpy old man looks puzzled."; ],

or perhaps:

          react_before [; Smile: if (noun ~= self or player or nothing)
                                      "The grumpy old man looks puzzled."; ],

Understanding the power of the before (and to a lesser extent, react_before) properties is the key to making specific objects respond to specific actions. There are also after and react_after properties, but you don't use them quite so often, because they apply only to Group 2 actions -- those like Take and Open and Insert which change the game's state. Actions like Push or Show, or Smile, are in Group 3; they don't change the state unless by supplying a suitable before property you cause them to do so. For example, you'd be wasting your time adding after/react_after properties for Smile to the old man -- they'd simply never be invoked by this example code. There's more on this in a later topic.

More information in the DM:
§6

A slightly simplified list of the order in which a command like DOTHIS TOTHAT is processed is:

"Before" stage

1. any react_before properties of objects in scope, including player and TOTHAT
2. any before property of the room
3. any before property of TOTHAT
4. any life property of TOTHAT (but only if it's animate) -- see the next topic

"During" stage

5. the DOTHISSub action routine (which is generally buried deep in the Library)

"After" stage
(only for Group 2 actions)

6. any react_after properties of the objects in scope, including player and TOTHAT
7. any after property of the room
8. any after property of TOTHAT

If at any stage the routine doing that step's processing returns true, the whole sequence ends immediately. So, for example, when the old man's react_before printed "The grumpy old man looks puzzled." and returned true at step 1, none of the other steps took place.

Where do life and orders fit in?

Well, unless an object has an animate attribute, the life and orders properties don't have any effect at all (except that if an object has a talkable attribute, you can give it a life property). If your object is animate, there are four cases to consider; note that these processing sequences focus on the objects being addressed, and ignore any reactions of other objects:

Case

For example

Processing sequence

Primary object

ATTACK OLD MAN

1. any before property of old_man (test for ##Attack action)
2. any life property of old_man (test for ##Attack action)
3. the AttackSub action routine

Secondary object
(with THROW)

THROW AXE AT MAN

1. any before property of axe (test for ##ThrowAt action)
2. any before property of old_man (test for ##ThrownAt fake action)
3. any life property of old_man (test for ##ThrowAt action) [1]
4. the ThrowAtSub action routine

Secondary object
(with GIVE/SHOW)

GIVE AXE TO MAN

1. any before property of axe (test for ##Give action)
2. any life property of old_man (test for ##Give action) [2]
3. the GiveSub action routine

Direct order

MAN, TAKE THE AXE

1. any orders property of old_man (test for ##Take action)
2. any life property of old_man (test for ##Order fake action, then ##Take action) [3]
3. parser displays "The grumpy old man has better things to do"

Note 1. Whereas the man's before property is passed the expected ##ThrownAt fake action (because the old man is the target of the axe throwing), his life property -- rather surprisingly -- is passed ##ThrowAt.

Note 2. If you were expecting the man's before property to be called with the ##Receive fake action before this step, you'd be wrong.

Note 3. This clumsy method is a hangover from the way orders were originally handled, and is best avoided. If your NPCs are intended to respond to direct commands, give them an orders property.

The bottom line is: orders is pretty well essential if your NPCs can expect to be requested to perform actions. On the other hand, life isn't so useful; just about its only real advantages over the more general before are that it provides a common point at which to reject the nine animate-only actions (Attack, Kiss, WakeOther, ThrowAt, Give, Show, Ask, Tell and Answer), and that it seems to be the only place where you can customize responses to GIVE and SHOW.

Surely the syntax of these properties is a little odd?

Yes. When you write something like:

          before [;
              Smile: "The grumpy old man nods slightly.";
              Touch: "Be gentle!";
              Smell: "Tobacco and old boots.";
              ],

it's as though you'd written a switch statement, omitting the highlighted material:

          before [;
            switch(sw__var) {
              ##Smile: "The grumpy old man nods slightly.";
              ##Touch: "Be gentle!";
              ##Smell: "Tobacco and old boots.";
            }
              ],

(The implicit switch statement is testing the library variable sw__var, which usually contains the current action value, except in life properties where it contains reason_code.)

Because before (and in fact all properties) pseudo-conforms to the switch syntax, you can add code in two places for special effect:

          before [;
              ! statements here are executed for all actions.
              Smile: "The grumpy old man nods slightly.";
              Touch: "Be gentle!";
              Smell: "Tobacco and old boots.";
              default: ! statements here are executed for non-listed actions.
              ],

How do I change an existing verb?

As you've seen from the earlier topic, the process for creating a brand new verb -- one which the Library doesn't already define -- is reasonably straightforward. Here's another example, perhaps slightly more complex, of verbs which interact with objects and make changes in the model world. Our aim is to create a grammar enabling the player to type FOLD PAPER ROUND BOOK or WRAP BOOK IN PAPER:

More information in the DM:
§30 §31

  [ CoverWithSub; "That's hardly a suitable covering!"; ];

  Verb 'wrap' 'fold' 'enfold'
      * noun 'round'/'around'/'over' held     -> CoverWith
      * held 'in'/'inside'/'with' noun        -> CoverWith reverse;

This time, two objects are involved: the wrapper, and something in the player's possession which is being wrapped. Let's try and write this fairly generally (so that any held object can be parcelled up) by aiming the CoverWith action at the wrapper object, which then needs a before property to do the real work:

  ! The 'paper' object is in the same room as the object to be wrapped.
  Object  -> paper "sheet of wrapping paper"
    with  name 'sheet' 'of' 'wrapping' 'paper',
          before [; CoverWith:
              move parcel to parent(self);
              move second to parcel; move self to parcel;
              "You wrap ", (the) self, " around ", (the) second, ".";
              ];

  ! Initially, the 'parcel' object has no parent
  Object  parcel "folded paper parcel"
    with  name 'folded' 'paper' 'parcel' 'package',
          before [; Open:
              while (child(self)) move child(self) to parent(self);
              remove self;
              "You unwrap ", (the) self, ".";
              ];

So far, so good: our new WRAP verb brings the parcel object into the room, with the paper and the original unwrapped object as children, and the existing OPEN/UNWRAP verbs turn that parcel back into its wrapper and content objects. It all works... providing the player thinks to WRAP (or FOLD or ENFOLD) the paper round the book, or WRAP the book in paper.

But of course he doesn't think of that, at least initially. Not being able to Read The Author's Mind, the first thing our player tries is PUT PAPER AROUND BOOK, followed by COVER BOOK WITH PAPER. Since a fundamental principle of good game design is to respond intelligently to just about any reasonable player command, we need to accomodate those forms as well; that's where things get... interesting. We can't just include 'put' 'cover' in the new grammar after 'wrap' 'fold' 'enfold', because both 'put' and 'cover' are already defined in the Library file Grammar.h (as soon as you start thinking about extensions to Inform's standard list of verbs, you'll need to study the contents of that file):

  Verb 'put'
      * multiexcept 'in'/'inside'/'into' noun -> Insert
      * multiexcept 'on'/'onto' noun          -> PutOn
      * 'on' held                             -> Wear
      * 'down' multiheld                      -> Drop
      * multiheld 'down'                      -> Drop;

  Verb 'close' 'shut' 'cover'
      * noun                                  -> Close
      * 'up' noun                             -> Close
      * 'off' noun                            -> SwitchOff;

So, we need to modify the existing grammar to suit our needs, and Inform provides the Extend directive for this purpose. If we simply want to add a line of grammar, we can code:

  Extend 'put' last
      * noun 'round'/'around'/'over' held     -> CoverWith;

The keyword last places the additional grammar after the existing definitions. In fact, that's the default: you can omit last if you want the new grammar at the end, or you can use instead first to insert it at the start, or replace to override the existing grammar completely. It's worth noting here that the debugging tool SHOWVERB is very useful once you find yourself playing with the verb grammars:

  >SHOWVERB PUT
  Verb 'put'
       * multiexcept 'in' / 'inside' / 'into' noun -> Insert
       * multiexcept 'on' / 'onto' noun -> PutOn
       * 'on' held -> Wear
       * 'down' multiheld -> Drop
       * multiheld 'down' -> Drop
       * noun 'round' / 'around' / 'over' held -> CoverWith

  >

That's extended the existing PUT verb satisfactorily, but COVER needs a little more care. If we code it like this:

  Extend 'cover'
    * held 'in'/'with' noun                   -> CoverWith reverse;

then we're actually extending CLOSE and SHUT as well, as we can see:

  >SHOWVERB COVER
  Verb 'close' 'cover' 'shut'
       * noun -> Close
       * 'up' noun -> Close
       * 'off' noun -> SwitchOff
       * held 'in' / 'with' noun -> CoverWith reverse

Now the player can type CLOSE BOOK WITH PAPER, a most unsatisfactory side effect. So this is where another Extend keyword -- only -- comes into play. To Extend the existing grammar only for the verb 'cover' -- but not for 'close' or 'shut' -- we code thus:

  Extend only 'cover'
      * held 'in'/'with' noun                 -> CoverWith reverse;

And this adjusts the grammar in the desired manner:

  >SHOWVERB COVER
  Verb 'cover'
       * noun -> Close
       * 'up' noun -> Close
       * 'off' noun -> SwitchOff
       * held 'in' / 'with' noun -> CoverWith reverse

  >SHOWVERB CLOSE
  Verb 'close' 'shut'
       * noun -> Close
       * 'up' noun -> Close
       * 'off' noun -> SwitchOff

Finally, just for completeness, we'll define 'unfold' as a synonym for 'open', so that we can UNFOLD PARCEL as well. This is very easy to do:

  Verb 'unfold' = 'open';

Now let's see another example to explore the versatility of the Extend directive. The Library defines this simple drinking grammar, in which 'drink', 'swallow' and 'sip' all behave identically:

  Verb 'drink' 'swallow' 'sip'
        * noun                                -> Drink;

Imagine that you want to distinguish between sipping (a small quantity), drinking (a normal quantity) and swallowing (the whole lot), at the same time adding a few synonyms. Three new action routines must take the place of the standard DrinkSub():

  [ SipSub;     L__M(##Drink, 1, noun); ];
  [ SwigSub;    L__M(##Drink, 1, noun); ];
  [ SwallowSub; L__M(##Drink, 1, noun); ];

These new routines are defined so that they all provide by default the standard library message that was intended for the verb 'drink'. We now code synonyms for 'drink':

  Verb 'imbibe' 'swig' 'gulp' 'quaff' = 'drink';

And lastly, we Extend the grammar so that the verbs are correctly redirected to the new action routines:

  Extend only 'sip' replace
      * noun          -> Sip;

  Extend only 'drink' 'imbibe' 'swig' replace
      * noun          -> Swig
      * 'all' noun    -> Swallow;

  Extend only 'swallow' 'gulp' 'quaff' replace
      * noun          -> Swallow
      * 'all' noun    -> Swallow;

The only keyword ensures that the redirection applies just to the listed verbs. Here we have also added the replace keyword, which tells Inform to ignore completely the old grammar for these verbs and instead utilise the new one we have defined.

Of course, your drinkable objects now need to handle the three actions Sip, Swig and Swallow instead of simply Drink; additional code is usually the price of thoroughness.

Can I remove an existing verb?

Now that we know how to add new verbs, and modify or extend existing ones, only one challenge remains: preventing Inform from recognising a verb that's already defined in Grammar.h. Of course, just editing it out of the file is one way, but as always it's safer to override the Library rather than change it. So, define these routines:

More information in the DM:
§31

  [ Anything; ! Ignore the remaining input line
      while (NextWordStopped() ~= -1);
      return GPR_PREPOSITION;
      ];

  [ NoSuchVerbSub; L__M(##Miscellany, 38); ];

Having done that, you can Extend...replace the existing verbs that you wish to remove. For example, if Momma don't allow no swearwords used round here:

  Extend 'shit' replace
      * Anything -> NoSuchVerb;

  Extend 'bother' replace
      * Anything -> NoSuchVerb;

Why are actions labelled Group 1, Group 2 or Group 3?

The labelling is just a handy way of distinguishing between three types of behaviour:

So, what makes an action 'Group 2' or 'Group 3'? It's pretty simple, really; it just comes down to what you write in the action routine. Here's a typical Group 2 action routine:

  [ CloseSub;
      if (ObjectIsUntouchable(noun)) return;                ! Line 1
      if (noun hasnt openable) return L__M(##Close,1,noun); ! Line 2
      if (noun hasnt open)     return L__M(##Close,2,noun); ! Line 3
      give noun ~open;                                      ! Line 4
      if (AfterRoutines() || keep_silent) rtrue;            ! Line 5
      L__M(##Close,3,noun);                                 ! Line 6
  ];

Contrast that routine with this typical Group 3 action:

  [ CutSub; L__M(##Cut,1,noun); ];

and you can see the difference: just a message; no state change, no call to AfterRoutines(). It doesn't matter what you try to CUT; nothing's going to change unless you make it happen.

So let's do just that -- turn CutSub() into a Group 2 routine. For compatibility with the current library behaviour, we want to be able to classify an object as a 'cutter' or 'cuttable' or -- usually -- neither. A successful CUT will require a 'cutter' working on a 'cuttable' (for example, CUT ICE WITH AXE); other combinations (such as CUT ICE WITH PENGUIN or CUT SNOWMOBILE WITH KNIFE) won't be allowed.

We'll use local property variables for the 'cutter' and 'cuttable' classification, like this:

  Object  -> "hazel tree"
    with  name 'hazel' 'tree' 'branch' 'branches',
          description "The lower branches are firm and straight.",
          cuttable 1;

  Object  -> "knife"
    with  name 'knife',
          description "Sharp enough for light wood-cutting.",
          cutter 1;

And then all we've got to do is to write a more powerful CutSub() to Replace the one in the library, and Extend the grammar a little. Here it all is:

  Replace CutSub;

  Include "VerbLib";
  Include "Grammar";

  [ CutSub x;
      if (~~(noun provides cuttable && noun.cuttable))        ! Line 1
          return L__M(##Cut,1,noun);                          ! Line 2
      if (second) {                                           ! Line 3
          if (~~(second provides cutter && second.cutter))    ! Line 4
              print_ret (The) second, " can't cut anything."; ! Line 5
      }                                                       ! Line 6
      else                                                    ! Line 7
          "You need to specify a sharp implement.";           ! Line 8
      if (AfterRoutines() || keep_silent) rtrue;              ! Line 9
      "You make a small incision in ", (the) noun, ".";       ! Line 10
  ];

  Extend 'cut'
      * noun 'with' noun  -> Cut;

That's all splendid stuff, except that you might notice one tiny omission: we haven't actually changed the state of the model world in any way (as we did with give noun ~open; in CloseSub() above). There's a good reason for this: the library doesn't include a cut attribute, and you can easily see why if you think of a few examples. Consider CUT CAKE, CUT CORN, CUT MYSELF, CUT PHONE LINE, CUT ROPE... the results are significantly different, and you usually need to do something more sophisticated than just setting or clearing an attribute, something specific to the object being cut.

How do we do this? the object's after property provides a perfect spot. By the time we call AfterRoutines() at Line 9, we know that the CUT action is theoretically possible; we can just let the after property decide whether to actually go ahead, and with what outcome. Ideally, the after should always return true, so our "incision" message should never appear in practice. Let's explain this with an example; we'll enhance our cuttable tree so that the player can hack off a walking staff (once only, but that should be sufficient).

  Object  -> "hazel tree"
    with  name 'hazel' 'tree' 'branch' 'branches',
          description "The lower branches are firm and straight.",
          cuttable 1,
          after [;
            Cut:
              if (staff in self) {
                  move staff to player;
                  "You select a straight branch, neatly cut it from the tree,
                   and trim away the side twigs to form a stout walking staff.";
              }
              else
                  "It would be vandalism to hack off another branch.";
          ];

  Object  -> -> staff "walking staff"
    with  name 'walking' 'staff' 'stick' 'pole',
          description "About five feet long, and a little thicker than your thumb.";

Initially, we hide the staff object as a child of the tree (which doesn't have container or transparent attributes, so the player won't know that it's there). When the player cuts the tree with his knife, the after property moves the staff into his possession, and the if (staff in self) ... test prevents any further tree surgery. By using similar principles, you should be able to handle most CUT X WITH Y requirements.

We're nearly done; just time for a couple of enhancements to our CutSub(). Since cuttable and cutter are actually variables, we can use them for more than simple true/false flags. In the following code, they're treated as a sort of Moh's Hardness Scale, wherein for example a cuttable value of 3 can only be cut by a cutter whose value is 3 or more, so that a penknife could cut paper, but not wood. The other enhancement is more intelligent handling of the situation where the player's CUT TREE omits the WITH KNIFE. A objectloop searches for a suitably powerful cutter in scope and, if it finds exactly one such implement, uses it by default. These additional features add only a few lines to the routine:

  [ CutSub x;
      if (~~(noun provides cuttable && noun.cuttable))
          return L__M(##Cut,1,noun);
      if (second) {
          if (~~(second provides cutter && second.cutter))
              print_ret (The) second, " can't cut anything.";
          if (second.cutter < noun.cuttable)
              print_ret (The) second, " isn't sharp enough to cut the ", (the) noun, ".";
      }
      else {
          objectloop (x provides cutter && TestScope(x) && x.cutter >= noun.cuttable )
              if (second == nothing) second = x;
              else                   second = -1;
          if (second <= 0) "You need to specify a sharp implement.";
          print "(with ", (the) second, ")^";
      }
      if (AfterRoutines() || keep_silent) rtrue;
      "You make a small incision in ", (the) noun, ".";
  ];

How do I detect the player entering a room, or trying to leave?

You need to trap the "Go" action in before and after properties of rooms. A room's before routine is considered when the PC is trying to leave that room, while an after routine is triggered for the room where the PC arrives. In both cases noun is one of the direction objects (n_obj, s_obj, and so on) representing the direction of movement, so you can test for paths that need special handling; otherwise, your rules apply to all exits or entrances.

Before: The PC's action of moving in the desired direction triggers whatever special behaviour you have coded. If the routine returns true, the action is interrupted and the PC remains in the same location; if not, the normal movement rules apply. For example:

More information in the DM:
§8

  Object  kitchen "Kitchen"
    with  description
              "An old room, devoid of furniture. There's a big window set in the west wall.",
          before [; Go:
              if (noun == w_obj) {
                  if (kitchen_window has open) "The fall might be hazardous to your health.";
                  "Bonk! You walk into the closed window.";
                  }
              if (kitchen_window has open) {
                  give kitchen_window ~open;
                  print "Before you leave the kitchen, you close the window.^";
                  }
              ],
          n_to corridor,
    has   light;

The lines concerning Westerly movement all return true, preventing the player from heading into oblivion, while those for other movements return false, thus permitting him to leave by any remaining exit, closing the window if necessary and printing an appropriate message on the way out.

After: The coded rules happen just after the PC arrives in the room, but before the room description is printed; if the routine returns true, no description appears on the screen. For example:

  Object  traps_room "Traps Room"
    with  description
              "Yet another dangerous room in the Mad Overlord's castle.",
          after [; Go:
              if (noun == s_obj && black_door.snare == true) {
                  print "Just as you enter the room, you hear a loud click and watch
                    in horror as tens of steel daggers fly in your direction.^";
                  deadflag = 1;
                  rtrue;
                  }
              if (noun == w_obj && children(player)>=3) {
                  print "^Under the weight of your possessions,
                    the floor gives way and you fall into...^";
                  PlayerTo(cellar);
                  rtrue;
                  }
              if (noun == u_obj) {
                  PlayerTo(self);
                  "[That was some nice climbing.]";
                  }
              ],
          n_to passage,
          e_to throne_room,
    has   light;

Quite a few possibilities are going on in this example. The PC gets killed if he enters the room by heading South from the passage (and the trap of the door is set); note that we return true, so that the room description will not be printed after the "Just as you..." message. If the PC comes West from the throne_room with three or more possessions, he will fall into the cellar; the custom message "Under the weight..." is printed before the new room description (which, thanks to PlayerTo(), will be the cellar instead of the traps_room). Again, we return true to prevent the cellar's description being printed twice -- once for PlayerTo() and once for normal movement rules -- though this could also have been avoided by coding PlayerTo(cellar,1). Finally, if the PC climbs back up from the cellar, we get another message, but this time it will be printed after the room description; note that the printing syntax is returning true by itself.

You may trap "Go" actions both in before and after properties of the same room. In the above example, you could easily provide a realistic touch (or a clue) to the falling trap by adding:

          before [; Go: if (noun == e_obj)
                     print "[carefully stepping over the weak-looking section of floor]^";
                     ],

More information in the DM:
§6 §20 §21 §26 §27

It's useful to know the sequence of actions performed by the library when the player moves to a new location -- especially the order in which text is printed -- so that you may interrupt the process at will or include a message in the desired place. This list (which apart from the PC's movement is very similar to the sequence for a "Look" action) is a simplified model, ignoring the evaluation of light and the possibility of the player being in a container or on a supporter.

Let's suppose that the player is in room ORIGIN, where he issues a GO command which takes him into a DESTINATION room:

 

Library action

Commentary

  1

ORIGIN.before();

Then skip to Step 12 (without movement) if before property includes Go: ... rtrue;

  2

location = DESTINATION;

Also move player to DESTINATION

  3

location.after();

Then skip to Step 12 if after property includes Go: ... rtrue;

  4

location.initial();

... if location provides this property

  5

NewRoom();

... if this optional Entry Point exists

  6

print location's room name

Then skip to Step 9 if location has a visited attribute and the game isn't in VERBOSE mode

  7

location.describe();

... if location provides this property; then skip to Step 9

  8

location.description();

 

  9

list objects in location

According to standard description rules

10

LookRoutine();

... if this optional Entry Point exists

11

give location visited;

Also award ROOM_SCORE points if location has scored and ~visited attributes

12

run daemons and timers

 

13

location.each_turn();

... if location provides this property

14

print command prompt ">"

 

Notes: The initial property can be either a routine -- which location.initial() runs -- or a string -- which location.initial() prints; the same applies to the description and each_turn properties. Also, this list excludes the rarely-used Entry Points GamePreRoutine() (called before Step 1) and GamePostRoutine() (called before Step 4 and before Step 12) -- processing then skips to Step 12 if any of these returns true.

Trapping "Go" actions is not limited to prevent PC movement or printing messages; it may perform as much mischief as you desire. You could for instance silently detect arrival into a room so that a daemon or a timer is triggered unbeknownst to the player:

  Object  cell "Cell"
    with  description "A small cube of solid stone walls.",
          before [; Go: StopTimer(self); ],
          after  [; Go: StartTimer(self, 4); ],
          time_left 0,
          time_out [;
              self.w_to = "The door is jammed.";
              give self ~light;
              "The door suddenly slams shut.";
              ],
          w_to torture_chamber,
    has   light;

Finally, here are two Room classes which print a message as you move from one room to another. The first reacts in the old room, before the move happens, while the second waits until you reach the new room:

  Class   Room
    with  description "UNDER CONSTRUCTION",
          before [ dirProp;
            Go:                           ! 'noun' contains a compass object (eg e_obj);
              dirProp = noun.door_dir;    ! convert it to direction property (eg e_to)
              if (self provides dirProp && self.dirProp ofclass Object &&
                 (self.dirProp hasnt door || self.dirProp has open))
                  print "You leave the room.^";
          ],
    has   light;

  Class   Room
    with  description "UNDER CONSTRUCTION",
          after [;
            Go:
              print "You leave the room.^";
              <<Look>>;
          ],
    has   light;

Where have I been?

The location and real_location variables specify the player's current room. You may wish to know the room you were in previously; this isn't held in any library variable, so you need to find a way of remembering it. Here's one method (in an improved version by Fredrik Ramsberg).

  Object  roomTrail
    with  trail 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0,
          pointer -1,                         ! Most recent entry in trail array
          add [ room   max;
              max = self.#trail / WORD_SIZE;  ! Max entries in trail array
              self.pointer = (self.pointer + 1) % max;
              self.&trail-->self.pointer = room;
          ],
          show [ i   max;
              max = self.#trail / WORD_SIZE;  ! Max entries in trail array
              if (i<0 || i>=max) return nothing;
              return self.&trail-->((self.pointer - i + max) % max);
          ];

  Class   Room
    with  description "UNDER CONSTRUCTION",
          after [;
            Go:
              roomTrail.add(real_location);
          ],
    has   light;

We create a roomTrail object, whose role is to provide both storage for the 32 most recently-visited locations (in its trail property), and also two property routines for manipulating the trail. roomTrail.add(room) adds room to the trail, and roomTrail.show(i) returns the ith visited room: 0 is the current location, 1 is the previous room, 2 is the one before that, and so on.

As in the previous topic, we create a Room class with an after property; its role here is to add the current location to the trail. Then, to print the name of the room you were in previously, you might type:

  print "You've just come from ", (name) roomTrail.show(1), ".^";

While we're on the subject, here's an associated technique: enabling the player to jump directly to a previously-visited room. To do this, you need to give each of your rooms a sensible name property, and then add this code:

  [ GoRoomSub;
      if (noun == location) "But you're here already!";
      print "If only it was always this easy to go to the...^";
      PlayerTo(noun);
  ];

  [ scope_room x;
      switch (scope_stage) {
       1: rfalse;
       2: objectloop (x ofclass Room)
              if (x has visited) PlaceInScope(x);
          rtrue;
       3: "You can't quite remember how to get there.";
      }
  ];

  Extend 'go' first
      * 'to' scope=scope_room     -> GoRoom;

How can I parse a number?

You might need to deal with numeric input in a command like ADJUST DIAL TO 3 or SET COURSE TO 180. The Library file Grammar.h includes some suitable grammar:

  Verb 'set' 'adjust'
      * noun              -> Set
      * noun 'to' special -> SetTo;

That special token must be doing the business, but trying to find it in the DM4 proves difficult -- it's not described anywhere! Going back to the DM3 gives us the ominous warning "any single word or number (obsolete and best avoided)" -- hardly an auspicious start -- but it's part of the library and it does work. So here's an abstract scenery object which can be SET:

  Object  course "course"
    with  name 'course' 'heading' 'bearing' 'direction',
          description [; "Your current course is ", self.number, " degrees."; ],
          number 0,               ! current course in degrees 0..359
          before [;
              SetTo:
                  switch (second) {
                    0 to 359: self.number = second;
                    360:      self.number = 0;
                    default:  "There are only 360 degrees in a circle!";
                  }
                  "You set the course to ", self.number, " degrees.";
              Examine: ;
              default: "Don't be silly!";
          ],
          found_in [; return true; ],
    has   scenery;

The object's before property traps the SetTo action, and since special was the second token in the grammar line, the numeric value is available in the second variable. SET and EXAMINE are the only supported verbs: any other action results in "Don't be silly!". The object uses found_in to make itself present throughout the game; you could instead simply position it in one room like the ship's bridge, or restrict it to a single area:

          found_in [; return (location ofclass OnBoardShip); ],

More information in the DM:
§31

Employing an obsolete token doesn't give a warm fuzzy feeling, so let's update the grammar to use the (properly documented) number token instead. While we're here, we'll also extend it to handle SET COURSE TO WEST, and various shortened forms and synonyms:

  Extend 'set' replace
      * noun                              -> Set
      * number                            -> Set
      * 'to'/'towards'/'for' noun         -> SetTo
      * 'to'/'towards'/'for' number       -> SetTo
      * noun 'to'/'towards'/'for' noun    -> SetTo
      * noun 'to'/'towards'/'for' number  -> SetTo;

  Verb 'steer' 'sail' 'navigate' 'head' 'aim' = 'set';

Here's the revised 'course' object to deal with this lot; it's less complex than it looks:

  Object  course "course"
    with  name 'course' 'heading' 'bearing' 'direction',
          description [; "Your current course is ", self.number, " degrees."; ],
          number 0,               ! current course in degrees 0..359
          try_number [ dir;       ! dir is a number of degrees?
              switch (dir) {
                0 to 359: ;
                360:      dir = 0;
                default:  dir = -1000;
                          print "There are only 360 degrees in a circle!^";
              }
              return dir;
          ],
          try_object [ dir;       ! dir is a direction object?
              switch (dir) {
                n_obj:    dir = 0;
                e_obj:    dir = 90;
                s_obj:    dir = 180;
                w_obj:    dir = 270;
                default:  dir = -1000;
                          print "That's not a proper direction!^";
              }
              return dir;
          ],
          try_dict [ dir;     ! dir is a dictionary word?
              switch (dir) {
                'north','n//':    dir = 0;
                'east','e//':     dir = 90;
                'south','s//':    dir = 180;
                'west','w//':     dir = 270;
                default:          dir = -1000;
                                  print "That's not a proper direction!^";
              }
              return dir;
          ],
          react_before [ x;
              Set,SetTo:
                  if (second == nothing)
                      if (inp1 == 1) x = self.try_number(noun);
                      else           x = self.try_object(noun);
                  else
                      if (inp2 == 1) x = self.try_number(second);
                      else           x = self.try_object(second);
                  if (x < 0) rtrue;
                  self.number = x;
                  "You set the course to ", self.number, " degrees.";
          ],
          before [;
              Examine: ;
              default: "Don't be silly!";
          ],
          found_in [; return true; ],
    has   scenery;

More information in the DM:
§6

We've added three property routines -- try_number, try_object, and try_dict -- to provide validation and data mapping. We use these routines when we trap the Set and SetTo actions, which we do in a react_before property rather than in before: this enables us to handle command like STEER 180 and SAIL TO THE SOUTH in which the 'course' object isn't actually mentioned. Notice the use of the library variables inp1 and inp2, normally the same as noun and second respectively, but set to 1 when noun or second contains a literal number rather than an object reference.

Just to round things off, let's add an NPC who can do the steering on our command; that is, SAY SOUTH TO SAILOR or SAILOR,SET COURSE TO WEST or SAILOR,STEER 180 or even SAILOR,270.

  Object  "sailor"
    with  name 'sailor' 'pilot' 'helm' 'helmsman' 'steersman',
          life [ x;
              Answer:
                  if (consult_words ~= 1) return L__M(##Answer,1,noun);
                  x = TryNumber(consult_from);
                  if  (x ~= -1000) x = course.try_number(x);
                  else             x = course.try_dict(noun);
                  if (x < 0) rtrue;
                  course.number = x;
                  "~Course set to ", course.number, " degrees, sir.~";
          ],
          orders [ x;
              Go,Set,SetTo:
                  if (second == nothing)
                      if (inp1 == 1) x = course.try_number(noun);
                      else           x = course.try_object(noun);
                  else
                      if (inp2 == 1) x = course.try_number(second);
                      else           x = course.try_object(second);
                  if (x < 0) rtrue;
                  course.number = x;
                  "~Course set to ", course.number, " degrees, sir.~";
              NotUnderstood:
                  x = course.try_number(special_number);
                  if (x < 0) rtrue;
                  course.number = x;
                  "~Course set to ", course.number, " degrees, sir.~";
          ],
    has   animate;

More information in the DM:
§16 §17 §18

In this skimpy implementation, the life property handles only Answer (SAY word to SAILOR, where word is a single number or a compass direction in the dictionary). The orders property deals with Go (SAILOR,EAST and SAILOR,GO EAST), Set (SAILOR,SET 90 and SAILOR,SAIL EAST), and NotUnderstood (SAILOR,90 which uses the special_number library variable).

Which action is triggered by each verb?

To deal with a particular english verb in a property like before or life, you need to know which action is triggered, because it's actions that you intercept, not verbs. Usually, you can do this by finding the verb in Grammar.h; for example, as we showed in the previous topic, you'll discover that the SET verb triggers the Set and SetTo actions.

Sometimes, one action gets translated to another during processing, which can be a bit confusing. For example, Drop (a worn object) can become Disrobe, Examine (a container object) can become Search, and Transfer can variously become PutOn or Drop or Insert. Here are two techniques to help you discover what's going on.

More information in the DM:
§7

First, use the ACTIONS debugging command, which tells you the current action and its parameters:

  >ACTIONS
  [Action listing on.]

  >SIT ON ROCK
  [ Action Enter with noun 29 (rock) ]
  That's not something you can sit down on.

Second, where that doesn't work (for example, sometimes when issuing an order to an NPC), include this line of debugging code at the start of your before or life or orders property:

  orders [ x;
      #ifdef DEBUG; print "[Action=", (debugAction) action, "]^"; #endif;
      Go,Set,SetTo:
      ...

which tells you this:

  >SAILOR,EAST
  [Action=Go]
  "Course set to 90 degrees, sir."

  >SAILOR,100
  [Action=<fake action 9>]
  "Course set to 100 degrees, sir."

The fake actions are defined in Parser.h; 0 is Receive, through to 9 is NotUnderstood.

Can I distinguish SIT ON BED from LIE ON BED?

More information in the DM:
§12 §15

Inform's standard grammar maps several verbs -- including ENTER, GET INTO/ONTO, STAND ON, SIT ON and LIE ON -- to the single action of Enter. You can Enter an object which has an enterable attribute, so a basic bed object looks and works something like this:

  Object  -> bed "bed"
    with  name 'bed',
          description "It's a pretty regular bed.",
    has   enterable supporter static;

  Your bedroom
  A spartan cell.

  You can see a bed here.

  >SIT ON BED
  You get onto the bed.

  >GET OFF BED
  You get off the bed.

  Your bedroom
  A spartan cell.

  You can see a bed here.

  >

There are a couple of potential problems with this. First, LIE, SIT and STAND are treated identically -- you're either on the bed or you're not -- and second the act of getting off the bed causes the room description to be replayed. This is because it's handled by the Exit action, which is perhaps geared more towards climbing out of a container such as a vehicle or a wardrobe than simply getting off a supporter such as a bed or chair.

In order to deal with these issues, we'll build a Bedlike class, useful for beds, sofas, benches and similar feathered horizontal surfaces. In fact, we'll do it twice, once using the methods described above to Extend the standard grammar, and then again using a different technique which keeps the grammar unchanged. Here's the extended grammar for the first solution:

  [ SitOnSub;   <<Enter noun>>; ];
  [ LieOnSub;   <<Enter noun>>; ];
  [ StandOnSub; <<Enter noun>>; ];

  Extend only 'sit' replace
      * 'on' 'top' 'of' noun      -> SitOn
      * 'on'/'in'/'inside' noun   -> SitOn;

  Extend 'lie' replace
      * 'on' 'top' 'of' noun      -> LieOn
      * 'on'/'in'/'inside' noun   -> LieOn;

  Extend 'stand' replace
      *                           -> Exit
      * 'up'                      -> Exit
      * 'on' 'top' 'of' noun      -> StandOn
      * 'on' noun                 -> StandOn;

  Extend only 'climb'
      * 'on' 'top' 'of' noun      -> StandOn
      * 'on' noun                 -> StandOn;

You'll see that we're pulling apart the existing grammar -- in which all of those verbs trigger the Enter action -- and redirecting them to distinct SitOn, LieOn and StandOn actions. Then, to avoid breaking any other objects which rely on the standard behaviour, we create default handlers for the new actions which simply invoke Enter, so that the whole thing works exactly as before. However... we can now create a Bedlike class which doesn't conform to the normal pattern, but instead intercepts the new actions:

  Class   Bedlike
    with  sit_or_lie false,
          before [;
            Enter,StandOn:
              if (self == parent(player) && self.sit_or_lie == false)
                  return L__M(##Enter,1,self);
              move player to self;
              self.sit_or_lie = false;
              if (AfterRoutines() || keep_silent) rtrue;
              "You stand on ", (the) self, ".";
            SitOn:
              if (self == parent(player) && self.sit_or_lie == action)
                  return L__M(##Enter,1,self);
              move player to self;
              self.sit_or_lie = action;
              if (AfterRoutines() || keep_silent) rtrue;
              "You sit on ", (the) self, ".";
            LieOn:
              if (self == parent(player) && self.sit_or_lie == action)
                  return L__M(##Enter,1,self);
              move player to self;
              self.sit_or_lie = action;
              if (AfterRoutines() || keep_silent) rtrue;
              "You lie on ", (the) self, ".";
          ],
          react_before [;
            Exit:
              if (self == parent(player)) {
                  move player to parent(self);
                  if (self.sit_or_lie) {
                      self.sit_or_lie = false;
                      if (AfterRoutines() || keep_silent) rtrue;
                      "You stand up.";
                  }
                  if (AfterRoutines() || keep_silent) rtrue;
                  "You climb off ", (the) self, ".";
              }
          ],
    has   supporter static;

  Bedlike -> bed "bed"
    with  name 'bed',
          description "It's a pretty regular bed.",
          after [; LieOn: "You settle down for a short nap."; ];

The class's before property deals with the regular Enter action, and with our new StandOn, SitOn and LieOn actions. In each case, the action first tests if it has anything useful to do and if not -- for example SIT ON BED when the player is already seated -- displays the standard refusal message "But you're already on the bed". Otherwise it moves the player into position and uses the sit_or_lie property to remember whether he's sitting or lying down. Next, it calls the AfterRoutines() library routine in case the Bedlike object has provided an after property; a true return value from such a property would prevent us displaying our confirmation message, as would a true value in the keep_silent library variable.

Because the Exit action isn't directed to a specific object, we can't handle it using a before property; instead, we react_before an Exit and trap the action only if this Bedlike object is the player's parent. Effectively, we've completely replaced the Library's standard processing for Enter and Exit of Bedlike objects, and so we've now no need for an enterable attribute. And here's the outcome:

  Your bedroom
  A spartan cell.

  You can see a bed here.

  >GET ON BED
  You stand on the bed.

  >GET OFF BED
  You climb off the bed.

  >SIT ON BED
  You sit on the bed.

  >LIE ON BED
  You settle down for a short nap.

  >GET OFF BED
  You stand up.

  >

More information in the DM:
§18

In our alternative approach, we don't need to Extend the grammar; the class's before property deals only with the Enter action, and distinguishes SIT and LIE by inspecting the Library variable verb_word which holds the dictionary value for the verb in the current command. The remainder of the class definition is the same as our previous example, and it works identically:

  Class   Bedlike
    with  sit_or_lie false,
          before [;
            Enter:
              if (verb_word == 'sit' or 'lie') {
                  if (self == parent(player) && self.sit_or_lie == verb_word)
                      return L__M(##Enter,1,self);
                  move player to self;
                  self.sit_or_lie = verb_word;
                  if (AfterRoutines() || keep_silent) rtrue;
                  if (verb_word == 'sit')
                      "You sit on ", (the) self, ".";
                  else
                      "You lie on ", (the) self, ".";
              }
              else {
                  if (self == parent(player) && self.sit_or_lie == false)
                      return L__M(##Enter,1,self);
                  move player to self;
                  self.sit_or_lie = false;
                  if (AfterRoutines() || keep_silent) rtrue;
                  "You stand on ", (the) self, ".";
              }
          ],
          react_before [;
          ...

Incidentally, should you wish to distinguish between, for example, LIE IN BED and LIE ON BED, you could either Extend the grammar further, by defining separate LieIn and LieOn actions, or you could use this routine to return the word following the verb, and then test it against 'in' and 'on':

  [ WordAfterVerb w;
      w = verb_wordnum + 1;
      if (w > parse->1) return 0; ! Nothing following the verb
      w = parse-->(w*2 - 1);
      if (w) return w;            ! Following word has this value
      return -1;                  ! Following word not in dictionary
  ];

Which verb did the player use?

More information in the DM:
§16 §18 §30

When the player types something like OFFER WATER TO HORSE, the parser sets up four variables to represent the command: action (in this example, 'Give') defines what's to be done, along with noun (the 'water' object) and second (the 'horse' object); in addition, actor (usually 'selfobj') is the object to whom the command is directed.

It's easy to process the noun and second objects, including if necessary printing their names: print (name) noun; is all that it takes. It's a bit trickier, though, to get back to the original command verb if that's what you want to display. You've got three sources of information:

There are a few further complications; the verb may be:

What all this means is that you've got quite a bit of work to do if you want to be able to reproduce the original command as the player mentally formulated it. Here's one way of tackling the problem:

  [ PrintVerbWord
      i j k;

      ! print the (possibly implied, possibly abbreviated) verb

      if (action == ##Go && verb_word ~= 'go' or 'run' or 'walk' or 'leave')
          print "go";
      else
          if (LanguageVerb(verb_word) == false) { ! expand an abbreviation?
              #Ifdef TARGET_ZCODE;
              j = parse->(4*verb_wordnum + 1);    ! start posn in buffer
              k = parse->(4*verb_wordnum);        ! number of characters
              #Ifnot; ! TARGET_GLULX
              j = parse-->(3*verb_wordnum);       ! start posn in buffer
              k = parse-->(3*verb_wordnum - 1);   ! number of characters
              #Endif; ! TARGET_
              for (i=0 : i<k : i++) print (char) Lowercase(buffer->(i+j));
          }

      ! possibly append a modifying preposition

      switch (action) {
        ##Ask:
          print " about";
        ##AskFor:
          print " for";
        ##AskTo:
          print " to";
        ##Consult:
          if (verb_word == 'look' or 'l//') print " up";
          if (verb_word == 'read') print " about";
        ##Disrobe:
          if (verb_word == 'take') print " off";
        ##Drop:
          if (verb_word == 'put') print " down";
        ##EmptyT:
          if (verb_word == 'empty') print " into";
        ##Enter:
          if (verb_word == 'get' or 'go') print " in";
          if (verb_word == 'lie' or 'sit' or 'stand') print " on";
        ##Examine:
          if (verb_word == 'look' or 'l//') print " at";
        ##Exit:
          if (verb_word == 'get') print " off";
          if (verb_word == 'stand') print " up";
        ##GetOff:
          if (verb_word == 'get') print " off";
        ##Go:
          print " "; LanguageDirection(noun.door_dir);
        ##Insert:
          if (verb_word == 'drop' or 'discard' or 'put' or 'throw') print " in";
        ##JumpOver:
          print " over";
        ##LookUnder:
          if (verb_word == 'look' or 'l//') print " under";
        ##PutOn:
          if (verb_word == 'drop' or 'discard' or 'put' or 'throw') print " on";
        ##Remove:
          if (verb_word == 'get' or 'take') print " off";
        ##Search:
          if (verb_word == 'look' or 'l//') print " in";
        ##SetTo:
          print " to";
        ##SwitchOff:
          print " off";
        ##SwitchOn:
          print " on";
        ##Take:
          if (verb_word == 'pick') print " up";
        ##Tell:
          if (verb_word == 'tell') print " about";
        ##ThrowAt:
          print " at";
        ##Wear:
          if (verb_word == 'put') print " on";
      }
  ];

How do 'meta' verbs work?

As we said earlier, a meta verb controls the game itself, rather than affecting the model world within the game. When the parser finds that the player has typed a verb tagged as 'meta', it sets a variable -- also called meta -- to true. This has two main effects:

  1. prior to performing the requested action, the BeforeRoutines() processing is omitted -- that is, GamePreRoutine() isn't called, and no react_before or before properties are executed.
  2. after performing the requested action, the entire InformLibrary.end_turn_sequence() processing is omitted -- that is, the time and turns counters are unchanged, no daemons or timers run, and no each_turn properties are executed.

Also, the meta verbs are of Group 1; this, like Group 3, doesn't change the model world, there isn't an 'afterwards' that differs from the previous state, and so the AfterRoutines() processing is omitted -- that is, no react_after or after properties are executed, and GamePostRoutine() isn't called.

Occasionally, you may find the need to set meta to true in a verb's action routine. Since this is called between steps 1 and 2 above, the result is to prevent the end_turn_sequence() processing, but not any BeforeRoutines() processing (because by then it's too late).

Here's a neat technique allowing a verb to be both meta and non-meta. The author wants to define ABOUT FACE and ABOUT TURN verbs, to cause the PC to look behind him. This is probably a Group 2 action, since it'll change the model world slightly. However, he also wants to permit ABOUT as a synomym for HELP and INFO -- a meta verb providing information about the game itself. This first attempt won't work, because the same verb can't appear in more than one grammar:

  [ HelpSub; ... ];
  [ AboutFaceSub; ... ];

  Verb meta 'about' 'help' 'info'
      *               -> Help;

  Verb 'about'
      * 'face'/'turn' -> AboutFace;

More information in the DM:
§31

But this second try works fine:

  Verb meta 'help' 'info'
      *               -> Help;

  [ isMeta; meta = true; return GPR_PREPOSITION; ];

  Verb 'about'
      * 'face'/'turn' -> AboutFace
      * isMeta        -> Help;

isMeta() is an example of a "general parsing routine". In this case, it's pretending to have matched a preposition (like 'face' or 'turn' in the line above), though actually it's done nothing of the sort. All that's happened is that meta has been set to true; the effect is that ABOUT with nothing following it is now handled the same as HELP and INFO.

Into
the Intro

Setting
the scene

Preparing
to program

Learning
the lingo

Dabbling
in data

Operating
on objects

Verbal
versatility

Bothered
by bugs

History and
hereafter

Worldly
woes

Inside
information

Tips and
techniques