PlatypusSummary of Platypus release 4

Send questions, comments, and bug reports to Anson Turner.

This documentation assumes at least a basic familiarity with Inform and its standard library. It primarily covers differences between the standard library and Platypus. The Reference provides a somewhat more thorough listing of attributes, properties, and so forth.

Conventions followed in this document:

Attribute:   light
Class, or member:   Rooms
Global variables:   actor
Obsolete:   lockable
Property:   description
Property routine:   parse_name()
Routine:   InDark()
Googly eyes:   (@)(@)

[Square brackets] also indicate optional parameters in routine calls.

I apologize in advance for any omissions or inaccuracies, however, for legal reasons I disavow any and all responsibility for any resulting apocalyptic mayhem.


Platypus-1. Acknowledgements

My gratitude to everyone who has given me feedback on Platypus, and specifically John Bytheway, Nate Cull, Dmitry Ferentsev, Aponar Kestrel, Michael A. Krehan, Andrew MacKinnon, Iain Merrick, Taro Ogawa, Dan Schmidt, and especially Daniel Barkalow and Gary Poster.

Andrew Plotkin performed the dark, ancient rituals which summoned forth the mighty Glulx and created the biplatform library, from which most of the Glulx-specific code in Platypus derives.

And to Graham Nelson, for Inform and its standard library, without which Platypus would be a weird, useless bit of source code in a language that no one had ever heard of, instead of being... um... er... [Note to self: rewrite this paragraph. Try to work in the phrase "Yay Graham!"]

And if you understood that, you should have no difficulty at all with the rest of this documentation (assuming fluency in Navajo, Xhosa, and Venutian, a working knowledge of the socioeconomics of 14th-century Poland, and the ability to visualize in six dimensions).


Platypus0. Contents

  1. Attributes
  2. Properties
  3. Rooms
  4. Actors and Actions
  5. Containers
  6. Finding Paths
  7. Tasks and Footnotes
  8. Scoring
  9. Gizmos and Cogs
  10. The Runtime Dictionary
  11. Useful Routines
  12. Parsing
  13. List-writing
  14. New and Modified Commands
  15. Things Not Yet Covered


Platypus1. Attributes

(a) Some attributes have been eliminated:

Attribute  Instead,
absent   move the object out of FloatingHome, or create it in Storage
door   just give it a door_to property
lockable   just give it a with_key property
scenery   use concealed and static
scored   give it a points property

(b) Some have been added:

Attribute  This object...
activedaemon   has a running daemon.
activetimer   has a running timer.
hider   can hold things under it.
inside   is inside its parent.
known   is known to the player.
quotedmode   has first-person action output.
secret   is permanently unknown to the player.
upon   is on top of its parent.
under   is underneath its parent.

(c) The enterable attribute has become a property routine, allow_entry(). See below.

(d) A complete list of attributes can be found in the Reference.


Platypus2. Properties

(a) All of the direction properties (n_to, w_to, etc.) have been replaced by dirs (see below.)

Three other properties have also been removed: life (use respond() or respond_indirect() instead), capacity (see carrying_capacity, inside_capacity, upon_capacity, and under_capacity below), and door_dir, which is not needed.

(b) Many have been added:

Property  Use
adjective   Holds adjectives (dictionary words) describing object. (See also section 12g below.)
allow_entry()   Called with upon, inside, or under as a parameter. Should return true if the object can be entered in the specified way.
allow_push()   Called with a direction object parameter, should return true if object can be pushed out that exit.
allow_take()   (For animates) Can return true to allow this creature to be held.
carrying_capacity   (For Actors) how much can be carried.
disambiguate()   Called when parser is trying to choose an object based on player input. Takes no parameters, but can look at global action_to_be.
dirs / dirs()   (For Rooms) This property, which can be an array or a routine, replaces the direction properties (see section 3c below).
perform()   (For Actors) Called to cause the actor to perform the specified action.
fpsa   (For Rooms) Used by FindPath() (see section 6 below).
guide_path()   (Primarily for Actors) called when FindPath() is about to look for a path for this object. By setting fpsa property of Rooms to 0, can exclude them from consideration.
inside_capacity   (For containers) How much this object can hold inside.
join_scope()   Used by ScopeCogs (see below).
location   (For Actors) current location (see below).
messages()   (For Actors or MessageCogs) Provides action messages. (see section 4f below).
moveYN()   (For floating objects) Takes a room as a parameter. If provided and returns false, is not present in the given room (in spite of that room's shared).
Note that as of release 4, floating objects are no longer moved around, making the name of this property archaic.
points   Number of points awarded for entering room, holding object, or accomplishing task.
possessive   Holds possessives (dictionary words) describing possessed (held) objects (e.g. 'fred^s').
shared   (For Rooms) list of floating objects present here.
startup()   Called when the game first starts. Used to initialize this object (or anything else).
upon_capacity   (For supporters) How much this object can hold upon it.
under_capacity   (For hiders) How much this object can hold under it.
words()   Can be used instead of the name and adjective properties. Called with a word and should return 0 if it does not match the object, name if it is a name of the object, and adjective if it is an adjective.

(c) There are ten reaction properties, organized into groupings based on when they are called, and for what objects:

Stage 1  Stage 2  Stage 3
meddle_early   (Action is
determined to
be possible)
meddle
respond_early   respond
respond_early_indirect   respond_indirect
Stage 4
(Action occurs)
Stage 5  Stage 6  Stage 7
meddle_late   (Action result
is printed)
meddle_late_late
respond_late   (always called)
respond_late_indirect

The four meddle properties are called for all objects in scope. The respond properties are called for the direct object, and the respond...indirect properties are called for the indirect object.

When an action is invoked, the following sequence is observed. The library stops executing the action immediately when any of the routines returns true, jumping to step 17:

  1. GamePreRoutine() is called (if provided).
  2. If the actor is the player, the player's orders() routine is called (if provided).
  3. meddle_early() routines are called for all objects in scope. (This is equivalent to react_before.)
  4. respond_early() is called for the direct object (if any). (This is equivalent to before).
  5. respond_early_indirect() is called for the indirect object (if any).
  6. The library determines whether the action is possible (for example, testing whether ##Take is being called for a static object). Implicit actions may be invoked at this point. If the action is not possible, a suitable message is printed and the library stops here.
  7. GameOnRoutine() is called (if provided).
  8. meddle() routines are called for all objects in scope.
  9. respond() is called for the direct object (if any).
  10. respond_indirect() is called for the indirect object (if any).
  11. The library performs the default behavior for the action. For example, moving an object to the actor for a ##Take action.
  12. meddle_late() routines are called for all objects in scope. (This is equivalent to react_after.)
  13. respond_late() is called for the direct object (if any). (This is equivalent to after.)
  14. respond_late_indirect() is called for the indirect object (if any).
  15. GamePostRoutine() is called. If deadflag is set by this point, the library stops here.
  16. The library prints the appropriate default (or messages() provided) message for the completed action.
  17. meddle_late_late() routines are called for all objects in scope.

(d) Some less-used non-additive properties have been made individual rather than common.

(e) You may have any number of active timers and daemons. The activetimer and activedaemon attributes indicate that an object's timer and/or daemon are running. (Thus, "give thing activedaemon" is the same as calling StartDaemon(thing).)

(f) add_to_scope has been made additive.

(g) A more complete list of properties can be found in the Reference.


Platypus3. Rooms

(a) Rooms should be ofclass Rooms.

(b) Rooms can now be in scope. name and adjective properties should contain the actual names and adjectives of the room. Note that respond_early() and respond_late() affect only actions performed on the room itself (unlike the before and after properties of the standard library). However, you can use meddle_early(), meddle(), and meddle_late() to trap any action in the room, even if the room is not in scope (e.g. in the dark, or when the character is inside an opaque container).

Declaring constant DONT_SCOPE_ROOMS prevents rooms from being placed in scope except for GO TO and meddle-property reasons.

(c) Direction properties such as n_to and sw_to are gone. Use the dirs property instead, which takes two forms:

As an array, dirs contains a list of one or more direction objects (ndir, sedir, outdir, etc.), each of which is followed by the room object in that direction (or a door object, or a string). For example:

        dirs udir At_Complex_Junction
             wdir In_Bedquilt
             edir At_Witts_End;

You can also list more than one direction object before a particular location:

        dirs ndir udir Foyer
             edir Narrow_Space;

In the above example, both ndir and udir lead to Foyer.

As a routine, dirs() accepts one parameter, a direction object, and returns a room, 0, 1, or a string. If 1 is returned, it is assumed that the situation has been handled and the library silently cancels the action. 0 results in a "can't go that way" message (either generic or provided by cant_go). If a string is returned, it is printed if and only if the player is actually trying to go that way (and not some other actor). Make sure to check finding_path and actor when appropriate (see sections 4c and 6a). Example:

         dirs [ d;
             if (d == udir) return At_Complex_Junction;
             if (d == wdir) return In_Bedquilt;
             if (d == edir) return At_Witts_end;
         ];

(d) Darkness is no longer handled by moving the player object into the "thedark" object. If the player is sitting in a teacup on the mantel in the library when the lights go out, the player is still sitting in a teacup on the mantel in the now- darkened library rather than hovering in an inky void nestled somewhere amidst the roots of Yggdrasil. The usual constraints of darkness still apply. InDark(player) will return true if the player is in darkness.


Platypus4. Actors and Actions

The ghost who returns to haunt his murderer need not be surprising; most of us play interactive FICTION game as a representation of reality.

- Markovian insight

(a) If you do not want to use the standard player object, set the constant PLAYER_OBJECT to your new player object prior to #including "Middle.h". This is not absolutely necessary; you can change the player at any time via ChangePlayer(). Setting the PLAYER_OBJECT constant to some other object has the effect of preventing the default player object from being compiled at all.

(b) Actors should be ofclass Actors. An "actor" is any character (that is, object) who will perform actions via perform() (see following). The Actors class also provides the properties an object must have if you want to call FindPath() for it (see section 6). Otherwise, it is more efficient to simply use animate.

(c) Non-meta verbs can be used by any actor. To have an actor perform an action call: xxxx.perform(##Action, noun, second), where xxxx is, of course, the actor. If you will be making action calls for NPCs, be sure to keep that in mind when programming respond() routines and the like, since these are triggered no matter who the current actor is. Check the global actor to see who is performing the action.

(d) Take player proximity into consideration when an NPC is performing an action. That is, you will not want to print "Fred opens the can of peanut brittle, and a snake leaps out!" in a respond() routine if Fred is in the attic and the player is in the basement. Use TestScope(Fred) to see if the player can see Fred. Standard action messages ("Fred takes the widget.") are automatically muted in such circumstances.

(e) location is now a property of Actors, NOT a global variable. Look, but don't touch. Use MoveTo(actor, new location[, position]) to relocate Actors (or anything else, for that matter).

Exception: you can define an actor with location already set to the initial position of an actor, but remember to give the actor the upon, inside, or under attribute if appropriate. You can presupply any object as an initial location, but once the game is underway, location will always hold the room the actor is in (and not, for instance, the chair she is sitting in). This is the same way that the location global variable works in the standard library.

(f) Actors have a messages() property which is responsible for the text displayed when an actor performs an action. This works just like the messages() property of a MessageCogs object (see section 9d) except that it only applies to that actor. Generic, default messages are provided by the Actors class for standard actions. However, the player's default messages are currently still stored in the LanguageLM routine in English.h. (Actors::messages() returns false if the actor is the player, which causes the library to "fall down" to the LanguageLM() routine.)

(g) When creating your own actions, take advantage of the messages() property and call ActionMessages(action[, number[, object]]) in order to print the correct message based on the current actor. Example:

        [ WhistleSub;
            if (OnRoutines()) rtrue;
            ActionMessage(##Whistle, 1);
        ];

        Actors Fred "Fred"
        with name 'fred',
             messages [;
                Whistle: "Fred whistles that song you can't stand.";
             ];

This system ensures that no message will be printed if the action takes place where the player cannot perceive it. To provide a default message, either create your own subclass of Actors, or a MessageCogs object (see section 9d below).

(h) Actions such as ##Scream, which are not dependant on sight, can be perceived (that is, will have their messages printed) and reacted to (via meddle(), etc.) even in darkness.

(i) Strings printed by messages() properties can make use of certain codes by enclosing them between #s. For example:

         with
             messages [;
                 Glorp: switch(lm_n) {
                     1: "#Actor# glorp#s# #obj# with #second#.";
                     2: "#Obj# #is# not glorpable.";
             ];

The codes are as follows:

#actor# or #a#  If the actor is not the player, and the actor has not been named yet this turn, prints the name of the actor. Otherwise, prints an appropriate pronoun.
#his# or #its#  Prints possessive pronoun fitting named object. (The "named object" is the last object specified by an "#actor#", "#obj#", "#noun#", or "#second#" print code. If no such code has been used, the named object defaults to the actor.)
#him# or #ito#  Prints objective pronoun fitting...
#he's# or #it's#  Print contraction...
#himself# or #itself#  Prints reflexive pronoun...
#is# or #are#, #has# or #have#  Print verb form...
#s#, #es#  Prints verb ending if appropriate, as in "take#s#" or "toss#es#".
#obj# or #o#  Prints (the) lm_o, that is, the object that was passed to ActionMessage().
#noun# or #n#, #second# or #d#  Print (the) noun or (the) second.
#b#, #r#, #u#, #f#  Activate bold, roman, underline, and fixed-width font modes.

Note that if a code begins with an uppercase letter, the resulting output will also be capitalized.

N.B.: In some cases, the last named object may not be the one you want. For example, the message:

         "#Actor# #has# to put #second# down before #actor# can put things on
         top of #ito#."

might result in:

         You have to put the tray down before you can put things on top of
         yourself.

the last named object before #ito# was the actor, resulting in the above glitch. To get around this, you can change the "named object" without actually printing its name by extending the print code with "-a" for actor, "-o" for object (lm_o), "-n" for noun, or "-s" (or "-d") for second. So, the above message could be changed to:

         "#Actor# #has# to put #second# down before #actor# can put things on
         top of #ito-d#."

resulting in:

         You have to put the tray down before you can put things on top of
         it.

because the named object is changed to second before the code is translated.

These extensions cannot be used with the "naming" codes: #actor#, #obj#, #noun#, and #second#. Or rather, -o and -s can be used, but they do something different.

The library performs pronoun substitution on naming codes, for example, substituting "He" or "She" for #Actor# when the actor has just been named. In order to use the correct pronoun, it is necessary to know whether the thing named is subject or object. That is, whether to use a nominative pronoun such as "he" or an accusative one, such as "him". By default, a nominative pronoun is used if the print code begins with an uppercase letter (and is therefore presumably at the beginning of a sentence), and an accusative pronoun otherwise. This will not always work:

         But #actor# can't do that.

might yield

         But him can't do that.

To fix this, there are three code extensions that can be used with the naming codes: -s for subject (forcing nominative pronouns), -o for object (forcing accusative or reflexive pronouns), and -x to prevent pronoun substitution altogether, always printing the name of the object. (Exceptions: if the object is the player and player_perspective is not THIRD_PERSON, or the object is the actor and has quotedmode, pronoun substitution will still take place.) So:

         But #actor-s# can't do that.

If you wish to use print codes in text other than that printed by messages, you can call HoldX(), print the text, and then call PrintX(). Alternately, you can call PrintX([string]). Calls to HoldX() are not nested; once PrintX() is called, the text is no longer being buffered.

Alternately, you can call PrintX(string[, object]) to print the given string using print codes. If object is supplied, it signifies the object to use for #object# and related codes.

If you actually need to print an # in PrintX()-filtered text, you can do so by doubling it: ##.

If you need to use accented characters in PrintX()-filtered text, you must use a slightly different syntax, always beginning with &:

&<  put a circumflex on the next letter: a,e,i,o,u,A,E,I,O,U
&'  put an acute accent on the next letter: a,e,i,o,u,y,A,E,I,O,U,Y
&`  put a grave accent on the next letter: a,e,i,o,u,A,E,I,O,U
&:  put a dieresis on the next letter: a,e,i,o,u,A,E,I,O,U
&c  put a cedilla on the next letter: c,C
&-  put a tilde on the next letter: a,n,o,A,N,O
&/  put a slash on the next letter: o,O
&o  put a ring on the next letter: a,A
&ss  German sz
&<< &>>  continental European quotation marks
&ae &AE &oe &OE  ligatures
&th &Th &et &Et  Icelandic accents
&LL  pound sign
&!!  inverted (Spanish) exclamation point
&??  inverted (Spanish) question mark
&ct  ^ ct is for "caret"
&bs  \ bs is for "backslash"
&at  @ at is for "a (squiggly) thing"
&td  ~ td is for "tilde"
&&  &

Note that the accented character codes should not be placed in #...#.

If a code after & is not recognized, an asterisk is printed instead. If the code is recognized, but can't be printed, a question mark is printed.

(j) The allow_take() property, if provided by an Animates object, will allow the current actor to pick the animate up if it returns true.


Platypus5. Containers

In any coherent world, things are generally where they are not, there is a sort of theme park maintained by Witt & Co.

- Markovian insight

(a) Objects can now have things inside, on top of, and underneath them, possibly all three at the same time. The upon, under, and inside attributes indicate which position an object is in. However, objects which are merely in a room (i.e., on the ground) have none of these, nor do objects which are carried by an actor. Remember that upon and on are two completely different attributes.

(b) If an object is a child of another object (container) which is neither a room nor ofclass Actors, and it does not have an appropriate attribute (upon, inside, or under), it is not in scope, and will be completely inaccessible. When creating objects inside (or upon or under) other objects remember to give them the appropriate attribute. (However, see 11a).

(c) The enterable attribute has been replaced by the allow_entry() property. If provided, it will be called with the upon, inside, or under attribute. It should return true to indicate that the object can be entered in the specified fashion. It will only be called with an attribute appropriate to any containment class(es) it belongs to, so the parameter can be ignored if, for example, the object is a supporter but not a container or hider. If only certain Actors can enter the object, it is best to return true here and handle individual exclusions via respond().

(d) This space intentionally left blank.

(e) Items which are under hiders do not show up in room descriptions unless the hider is transparent.

(f) If a hider is picked up, anything under it is left behind.


Platypus6. Finding Paths

(a) The FindPath(starting room, destination, actor[, maximum moves]) routine will find the shortest route between two Rooms for the given actor. The actor's guide_path() property will be called first. By setting the fpsa property of Rooms to 0, it can exclude them from consideration (so they won't be allowed in the path). If the routine returns false, no path could be found. The finding_path global will be set to the actor for whom the path is being found.

Since this routine "probes" exits, it is essential to check the finding_path global in your dirs() methods to prevent spurious messages or worse from happening for seemingly no reason. For example, if trying to go through a particular exit causes a trap to go off, you must check the finding_path global to make sure the trap is not triggered by FindPath().

(b) You can return strings from dirs() methods rather than printing them directly. They'll only be printed if the player is actually trying to go through the exit, and will not be printed for FindPath() or NPCs.

(c) The path found, if any, will be stored in the actor's path_moves (as direction objects) and path_rooms property arrays. The number of steps in the path will be stored in the actor's path_length property. path_rooms contains the expected destinations that result from following the directions. Thus, if an NPC is following a path and a step does not result in the expected room, then something has gone wrong with the path (or the NPC has been blocked).


Platypus7. Tasks and Footnotes

(a) To create a task, just make an Object with a points property and a name (not name, but a "short name"). Example:

        Object opened_gate "opening the mysterious gate" with points 5;

Then call Achieved(opened_gate) at the appropriate time.

(b) By giving your task objects number properties, you can control the order in which they appear in the full score. Tasks with lower numbers will appear before tasks with higher numbers. Tasks with the same number appear in the order in which they are Achieved(). Since number defaults to 0, you can give a task a negative number to force it to appear at the top of the list, for example.

(c) Also see Scoring, below.

(d) If you wish to use footnotes, you must #Include "footnotes.h" and your footnote objects must be ofclass Footnotes:

        Footnotes example_footnote "Such as, ~If you shoplift something, they 
            seldom want it back.~";

Then call Note(example_footnote) or use it as a print specifier:

        print "Peter prattles incessantly about the benefits of living in a 
               nudist colony.",(note) example_footnote;

(e) Footnotes provides the number property. If you preset this (to a positive value), you can "fix" the reference numbers for some or all of your footnotes. Otherwise, they are numbered in the order in which they are revealed to the player. (Obviously, you should not assign the same number to more than one footnote. In fact, to do so is to precipitate the age of Ragnarok. Or possibly the age of Aquarius. I forget which. Better to play it safe.)

(f) If you give FootnoteGizmo general, note references will no longer appear once their associated notes have been read.

(g) NOTE (number) or FOOTNOTE (number) are used by the player to view notes.

(h) NOTES will list all of the notes the player has previously read.

(i) The library sets the general attribute of Footnotes whose references have been displayed, and the light attribute of notes that have been read.


Platypus8. Scoring

(a) The MAX_SCORE constant has been replaced by the maximum_score global. This is automatically calculated at startup based on the sum of all positive points properties in the game. You can easily override this by setting maximum_score = 100 (or whatever) in your Initialise routine or a startup() method.

(b) If you give a takeable object a points property (with a number), the player will receive the points upon first holding the item. If you give a room a points property, the player will receive the points upon first entering the room.

(c) The "finding sundry items" and "visiting various places" lines (which appear if the player receives points from a taking an item or entering a room with points) are treated as tasks numbered 30000 and 30001, respectively. The number properties of the finding_items and visiting_places objects can be changed (e.g. during startup) to relocate them in the list of achievements.


Platypus9. Gizmos and Cogs

(a) Gizmos and cogs are used to extend certain library functions. For instance, the SHOWOBJ command can be extended with a ShowObjCogs object, or the definition of scope can be extended with a ScopeCogs object.

(b) A ShowObjCogs object is used to tell ##ShowObj how to print the values contained in a new property. For example:

        ShowObjCogs
          with
            knows_property [ prop;
                if (prop == bibble) rtrue;
            ],
            print_property [ prop val;
                print (name) val;
            ];

This cog "teaches" ShowObj that the contents of the (fictional) "bibble" property should be printed as an object.

knows_property() is passed one parameter, a property, and should return true if this cog knows how to display it.

print_property() is passed two parameters, a property and a value, and should print the value in the format appropriate to the property.

(c) A ScopeCogs object is used to bring into scope objects which would normally be out of scope. For example:

        ScopeCogs
          with
            join_scope [ act;
                if (act == Wibble) PlaceInScope(Wobble);
            ];

This cog ensures that Wobble is always in scope to Wibble. join_scope() is called with one parameter, an object (usually an actor) for which scope is being determined. It can use PlaceInScope(object) to add things to scope, and if it returns true, the cog itself is also put into scope.

(d) A MessageCogs object is used to override library messages. This is only useful for the player's messages, or for new actions, because the messages() property of an actor will supersede it. It works like the standard library's LibraryMessages object, except that you must use the messages() property instead of before. (See section 4f).

e) Cogs only function when they are in the associated Gizmos object. The classes listed above are automatically moved into their associated Gizmos at startup, except those created as children of Storage. You can move cogs into and out of their Gizmos at any time to enable or disable them.

For instance, if you need to change many of the default messages at a certain point in your IF, you could swap MessageCogs in and out. The Gizmos have matching names ending in Gizmo, e.g. MessageGizmo, ShowobjGizmo, etc.

(f) Gizmos is a class, however there is no "Cogs" class to which all cogs belong.

g) Not all "cog" classes have names ending in "Cogs". For example, the Footnotes class provided by the "footnotes.h" add-on is a cog class. (That is, all Footnotes objects are moved into FootnoteGizmo at startup, where they must be in order to be used.)


Platypus10. The Runtime Dictionary

(a) If you need to add words to the program's vocabulary while it is running, you can use the Runtime Dictionary for this. Set the constant RUNTIME_DICTIONARY_MAX_WORDS at the start of your program to the maximum number of words you need to add to the dictionary. Then, call AddWord(address, length) where address is the place in memory the word is located (in characters) and length is the number of characters in the word. For example, if you had:

        Array newWord -> 'c' 'a' 'r' 'g' 'o';

And called:

        AddWord(newWord, 5);

Then 'cargo' would become a dictionary word. If AddWord returns false, there is no room left in the Runtime Dictionary. Otherwise, it returns the address of the new word. Note that if you try to add a word already in the dictionary (initial or Runtime), AddWord returns the address of the existing word. (b) "nameable.h" makes use of the Runtime Dictionary to allow the player to give objects names during play.


Platypus11. Useful Routines

(a) SetDefaultObjectPositions() will go over all of the objects in the game and automatically set the upon, inside, or under attribute for any object which is in a supporter, container, or hider and does not have any of the three attributes set. This is primarily intended to be called at startup, and is simply to save the effort of manually creating every object with an appropriate attribute.

If the parent object has more than one containment attribute (e.g. container and supporter), upon takes precedence over inside, which takes precedence over under. If the parent object is ofclass Gizmos, Class, or Rooms, then no attribute will be set for its children even if it also belongs to a containment class. SetDefaultPosition(object) works the same way, for the specified object only.

(b) MoveTo(object, destination[, position[, look flag]]) is the easiest way to move objects around during play. MoveTo(object, destination) will move the given object to the destination object, clear the upon, inside, and under attributes, and call SetDefaultPosition(object). If the object is ofclass Actors, its location will be set.

The position attribute can also be specified as a third parameter, if the destination has (or may have) more than one containment attribute. Example: MoveTo(Fred, Freds_bed, upon).

The "look flag" is only useful if moving the player. If 1, no location description is printed at all after moving the player. If 2, the usual description the player would get upon walking into a room (which may be shortened if moving to a room that has visited) is printed. Note that this is a fourth parameter; if you wish to set the look flag and do not want to specify a position attribute, use 0 as the third parameter, e.g. MoveTo(player, bedroom, 0, 2).

(c) InDark(object) returns true if the given object is in the dark.

(d) OffersLight() is no longer available. It is no longer possible to answer the question of whether an object "offers light" in general or not, since it might offer light to something upon it but not to things inside it. You can instead use InDark() (see above), or HasLightSource() in conjunction with TestScope(), if necessary.

(e) DrawCompass(x-position) will draw a compass in the status line, which must be at least 3 lines tall. finding_path is set to ExitsSub when DrawCompass() is checking exits.

(f) IndirectlyContains(object1, object2) returns 0 if object2 is not a descendant of object1 (unless they are the same; see below). Otherwise, it returns the position of the child of object1 from which object2 is descended. For example:

           table
             !-----------------------!
           book (upon)            box (under)
                                     !
                                  kitten (inside)

Then IndirectlyContains(table, kitten) == under.

If the child of object1 has no position attribute, or object1 and object2 are the same, then the return value is -1.

(g) Also see the list of routines in the Reference.


Platypus12. Parsing

But it's a lot easier for YOU to parse English than it is for a machine, because you've had a lot more practice at it, and you started life with a LALR grammar and one-token look-ahead.

- Markovian insight

(a) Descriptors have been reworked. There is now an entry point routine ParseDescriptors() which can be used to add to them. For example:

        [ ParseDescriptor obj wd fl;

            switch(wd)
            {   'sparkling', 'enchanted': if (obj has magic) rtrue;
                default: return -1;
            }
            rfalse;
        ];

This creates a new descriptor (which is essentially a universal adjective) which applies to any object with the (hypothetical) "magic" attribute. The ParseDescriptor() routine takes three parameters: an object, a word, and a flag. Allow for the flag, but ignore it. The routine should return 1 (or true) if the word is a descriptor which does apply to the object, 0 (false) if it's a descriptor that doesn't apply, and -1 if it doesn't know the word at all. (Study the example above.)

(b) Standard (already-defined) descriptors are 'open'/'opened', 'closed', 'worn', 'unworn', 'empty', 'my'/'mine'/'this'/'these', 'your', 'that'/'those', 'his', 'her', 'its', and 'their'.

(c) Early descriptors (which must come before the name of the object, not as part of it) are 'one'...'twenty', 'the', 'a'/'an', 'any', 'all'/'each'/'every'/'everything', 'both', 'another'/'other', and 'some'.

(d) 'himself', 'herself', 'itself', and 'themselves' are understood in certain contexts, e.g.: ASK SHEILA ABOUT HERSELF is the same as ASK SHEILA ABOUT SHEILA.

(e) parse_name() and ParseNoun() have been changed a bit. They can now return a count of adjectives as well as names, by multiplying the adjectives by 100 before adding them to the count. Adjectives are treated as "weaker" than names when the parser is trying to determine which object is being referred to. Also, if you add 10000 to the return value, this result is final: the parser will not attempt to match any further descriptors to the object, nor allow positional description (as described in the next entry).

For more information on parse_name() or ParseNoun(), see their respective entries in the Reference.

(f) Objects can be identified by their location, e.g.:

        >EXAMINE THE BOX ON THE TABLE
        >EXAMINE THE BAG IN THE BOX ON THE TABLE
        >PUT THE MARBLE IN THE BAG IN THE BOX ON THE TABLE

and so forth.

(g) By default, it is possible to refer to objects using only adjectives (although names will take priority over them, so that 'orange' alone might refer to a piece of fruit over an orange bowl, for instance). By setting the constant WEAK_ADJECTIVES, you can prevent this, thus requiring at least one name, TADS-fashion.


Platypus13. List-writing

(a) WriteListFrom() takes parameters in the form (object, style bitmap, depth, attribute) where attribute is an attribute that is required for an object to be listed. (This is used for upon, under, etc.) If the attribute is workflag, it will be checked only for depth 0.

(b) The WORKFLAG_BIT is no longer available. Use the attribute parameter instead (see preceding entry).

(c) The NEWSTYLE_BIT produces a "new-style" list, e.g.:

        a sack (which is empty) and a box. In the box is a telescope.

where contents are listed in separate sentences.

(d) The SORT_BIT will cause the list to be printed in alphabetical order by the objects' printed names.


Platypus14. New and Modified Commands

(a) The GO TO command can be used by the player to be automatically walked back to a previously-visited room. Only rooms the player has visited are allowed on the path. The CONTINUE command will repeat the last GO TO command, and is useful if the player was interrupted while trying to get there. ##GoToRoom only prints a message if called for an NPC. Note that GoToRoomSub() makes use of FindPath() (see section 6).

(b) The EXITS command will give the player a list of exits. finding_path is set to ExitsSub when it is checking exits. The list will indicate where a given exit goes, if the destination has the visited attribute.

(c) Inventory and Places lists are now sorted into alphabetical order. This can be changed for inventories by altering invtall_style and invwide_style.

(d) (Debugging command) DBDISTANCE will indicate how many moves away a given room is, using the shortest path FindPath() can produce, e.g.:

        >DBDISTANCE LIBRARY
        3 rooms

(e) See also section 7h, above.


Platypus15. Things Not Yet Covered

Chess is an example of a game that breaks the coherence of its fictional world as a work of IF.

- Markovian insight

(a) The Sacks class takes the place of the old SACK_OBJECT. You can have as many as you like. (Note that these work for all Actors and not just the player.) Note that objects will only be put inside the sack, not upon it, even if it is also a supporter. Also, you can forgo the Sacks class altogether if you like and #Replace the IsASack() routine, which returns true for any object that... you know. Just be sure the object has container!

(b) In case you missed it, note that the direction objects now have names ending in "dir" like ndir, swdir, etc.

(c) action is set to ##WhichOne when the player is asked "Which do you mean..?" This might be useful if you want a short_name() routine to print "elvish sword" instead of "sword", for example.

(d) Instead of "@output_stream 3 xxxx;" and "@output_stream -3", you should call OpenBuffer(xxxx) and CloseBuffer(). These calls can be nested to a maximum depth of MAXIMUM_OPEN_BUFFERS (defaults to 5). You should not, however, nest calls to the same buffer (i.e. the "xxxx" above).

(e) "Narrative" mode (for lack of a better name) alters the way the results of multiple-object commands are displayed. Instead of:

         >TAKE ALL
             table: The table is fixed in place.
             orange shirt: Taken.
             orange shirt: Taken.
             sack: Taken.
             red box: Taken.
             blue box: Taken.
             blue box: Taken.
             orange hat box: Taken.

if the global narrative_mode is set to true, the output would be:

         You take four boxes (the orange hat box, the two blue boxes and 
         the red box), the sack and the two orange shirts, but the table is 
         fixed in place.

This feature is a work in progress. First limitation: In order for it to work correctly, the meddle and respond property families (meddle_early, etc.) must return message codes instead of printing directly for any action that allows multiple objects. For example, do NOT code:

        respond [;
            Take: "You wouldn't touch that with a ten-foot pole!";
        ];

But instead:

        respond [;
            Take: return ActionMessage(##Take, 9999);
        ];

        MessageCogs
          with
            messages [;
                Take: if (lm_n == 9999) "You wouldn't touch #obj# with a 
                      ten-foot pole!";
            ];

Note that the example code given above is not NPC-friendly, because it assumes the player ("You") will be the one trying to take the object. (You could replace this with "#Actor#".) If the code were attached to the red box, the previous example would become:

         You take three boxes (the orange hat box and the two blue boxes), the 
         sack and the two orange shirts, but the table is fixed in place, and 
         you wouldn't touch the red box with a ten-foot pole.

The exclamation point is lost in this case, but would have been printed if the player had entered TAKE THE RED BOX, as narrative mode would not apply.

Second limitation: If any implicit actions are generated by the command, the output will be, er, wrong. Thus, implicit actions for standard multiple-object verbs are disabled when narrative mode is on.

Third limitation: If the player is moved in the middle of the command, there will be no output from the command at all.

You can test "if (multiflag)" to determine when a multiple- object command is being processed, and thus when narrative mode is in effect (assuming narrative_mode == true). (Most likely, you would use this test in a reaction or messages() property.)

(f) The global variable player_perspective can be set to FIRST_PERSON, SECOND_PERSON, or THIRD_PERSON to determine how the PC is referred to.


Platypus16. There's still no section 16.

-- Anson Turner
(formatted by John G. Wood)


This page originally found at http://www.elvwood.org/InteractiveFiction/Platypus/Summary.html
Last updated: 14 March 2002

Notices & Disclaimers

Home
Home

Interactive Fiction
IF

Platypus Index