1. Introduction == ============ The mca.h is a replacement library for Inform. It allows authors to create so-called "choose your own adventure" games in a somewhat convenient way. This is a type of game in which the reader, insted of typing a command, chooses one of several options after each passage of text. This could mean anything from a basic CYOA book to a full-fledged adventure game -- mca.h allows for both types and everything in between. The library was first designed for the latter kind of game, which explains the acronym: "multiple choice adventure". This manual, though a bit terse, should hopefully contain all the information needed to make the mca.h work. It won't go into the Inform language and its syntax, but "The Inform Designer's Manual" does that well enough. There are sections on the following topics: 1. Introduction 2. Getting started 3. The Node object 4. Changing options 5. Adding new Node subclasses 6. Ending the story 7. Events 8. Useful routines 9. Inventory management 10. Special commands 11. The repeat command 12. Customising output 13. Entry points 14. Other details 2. Getting started == =============== Just like the Inform library, mca.h allows you to define the constants Story and Headline, as well as a release number and a serial number. They all work in the same way here. The Initialise() entry point also looks the same. It may set up initial conditions and/or print some introductory text, but the one thing it MUST do is to set the global variable curnode to the node where the story is supposed to begin. The library then displays the usual banner and calls MoveTo(), which displays the text of the starting node. After that, most of the action is carried out by either nodes or events. If you want to delay the printing of the banner, return false from Initialise(), then manually call Banner() to display it where appropriate. The library itself consists of three files. The first one, which is simply called "mca.h", should be included after the above mentioned constants, but preferably before anything else. The second file, "mcastubs.h", can go anywhere as long as it is included after the definitions of any of the special entry points covered in section 14. The third file contains all the language specific bits of the library, and doesn't have to be included by hand. 3. The Node object == =============== A node is the main element of a story. It contains a text to be printed when it is entered, as well as a range of options and their associated actions. Each option is represented by a number, counting from zero. This numbering is only internal, and the player will not see any of it, except that active options will be displayed in the order of their option numbers, from low to high. All nodes belong to the base class Node, which makes use of the following properties: text A text which is displayed when this node is entered, or a routine to print one. Staying in the same node for more than one turn will not cause this text to show up again. initial An optional text to be displayed when the node is entered for the first time, the other text being shown at subsequent visits. title The title, if provided, is printed in bold style before the node's text or initial. prompt Another optional text to be displayed immediately before the listing of the options. If you want an empty line between the prompt and the options, you have to print one here. SayOpt This property can be either a function or an array. In the former case, it is called with a numerical argument corresponding to an option that is to be displayed. Simply look at the option number and print the appropriate text (including a linefeed at the end). When using an array, it should consist of pairs of entries. The first entry in each pair is an option number, and the second entry will usually be a string to be printed, though a function pointer is also allowed. ActOpt A property to carry out the action when the player has chosen an option. Like SayOpt, it can be either a routine or an array. The workings are the same, except that the array version can also contain a new node object to continue to. To manually change nodes in an ActOpt method (or anywhere else for that matter), use the MoveTo() routine mentioned below. Action An optional method that will be called on each turn that the story is in this node. This happens after the ActOpt stage, but before anything else -- most notably before any Events get to fire. If the current node should change in the middle of a turn, then the Action method of the new node is always the one that gets called. InitOpts This method is used to turn on the options that should be on for this node. Before it is called, all the options are turned off. How often this happens depends on the initmode property, as explained in the following paragraph. initmode A single value. If it equals 1, then InitOpts will be called before each turn. If 2 (the default), it will be called every time the node is entered, but not again if we remain in the same node for several consecutive turns. If 3, then InitOpts is called only when the node is entered for the first time, and then never again (unless you remove the node's visited attribute). numopts Often there is no need to turn on options individually, so the Node class provides a default mechanism for InitOpts. If you set this property to an option number, then all options from zero up to that number will be turned on. Also note that InitOpts is an additive property, so it's possible to both supply your own InitOpts AND use the default, but the former takes precedence and will thus always be called first. opts This is the array in which the on/off states of the options are stored. Each entry will allow for sixteen options, and by default, nodes have only one entry. This is usually enough, but if you need more options (or, more likely, higher option numbers), then you can override opts and add a few more zeroes. Note that of these properties, only InitOpts and opts are defined in the Node class itself. The others merely have default values, so unless you explicitly define them, they can only be read from and not written to or changed. E.G: to change a node's initmode during play, the node must have the initmode property somewhere in its object definition. It is also worth saying a few more words about the two ways of defining SayOpt and ActOpt. The array versions are more convenient when no special effects are required, but they do consume more of that precious substance called readable memory. Only in very large stories should this be a problem, but it might be worth avoiding arrays at least in Node subclasses, since every instance would receive a copy of the entire array. 4. Changing options == ================ Options can be turned on or off using a number of routines supplied by the library. They all act on the current node unless you specify a different one (and this node, being optional, is always the last argument in the line). OptOn(option), OptOff(option) These routines turn a single option either on or off. The first argument should be the number of the option in question. OptsOn(from, to), OptsOff(from, to) For convenience, these routines can turn on or off several options at once. They both accept a lower number and a higher number, and all options with numbers in this range will be affected. ClearOpts() For turning off all options, should you ever need to do that. Besides these functions, the library also offers a way of automatically turning off an option when the player selects it. To enable this in a specific node, give it the checkopts attribute. 5. Adding new Node subclasses == ========================== Now and then you may want certain options to be available in several nodes, but copying code around isn't a viable solution. Then you can take advantage of the fact that the properties that deal with options are all additive, and create a new subclass to Node. Creating a Node subclass is no different from creating a regular node. Just supply the class definition with an InitOpts to turn on the needed options, a SayOpt to display them, and an ActOpt to make them do something. Options from both levels should work as expected. You only have to make sure that the same option numbers aren't used on more that one place in the hierarchy. 6. Ending the story == ================ The library defines a Node subclass named EndNode, which provides a default way of ending a story. It has the noevents attribute set (see the section on Events), and will give the player options for restarting, restoring and quitting. Four default EndNode objects are defined by the library. They only differ in their text properties. Here are the objects and what they say: TheEnd: "The End" WonEnd: "You have won!" DiedEnd: "You have died!" FailedEnd: "You have failed!" As you can se, creating a new EndNode object for a different kind of ending is a simple matter. It's also possible to add new options for things like displaying credits or amusing things to try. The option to quit is number 10 and the others 0 and 1 respectively, so any new ones can be put in between. And since an EndNode is no different from a regular node, you can even "escape" back into the story. Finally, if, at any time, there should be no options at all to choose from, the library will assume that the story is over and jump to TheEnd, but this is to be considered a safety net and not a feature. 7. Events == ====== Events are things that happen regardless of the current node. They occur after an option has been chosen and ActOpt has done its job, but before the options for the next turn are given. (By definition, this means that they don't run on the first turn.) An event object can be either of class Daemon or of class Timer. Both types must supply a method named Action, which is called every time the event is invoked. If text is to be printed, you may want to precede it with a linefeed. Daemons with the active attribute (which is not set by default) are invoked every turn. Timers have a timeleft property. It keeps ticking down until it reaches zero. The timer will then be invoked, and after that it will remain dormant until further notice. Consequently, a daemon is turned on by setting its active attribute, and a timer by setting its timeleft property to anything higher than zero. A timeleft value of 1 means that it will fire on the very same turn, 2 on the next turn, and so on. To control in which order events are fired, give them different priority properties. Events with higher priority will always run first. The default priority is zero. On the other hand, events may be completely inappropriate in some nodes. Giving such nodes the noevents attribute will keep them all from running. Timers won't even tick down in the usual manner. It may also be useful to know that the library, for efficiency reasons, moves all event objects to a special mother object called EventHome during initialisation. Removing an event from this object will keep it from ever firing again, even if it should be activated at some other point in the story. Finally, note that activating an event from within another event can be a bad idea, since it may or may not have been checked by the library already. Using different priorities is a crude solution to such problems. 8. Useful routines == =============== The library provides a couple of routines which can be used in ActOpt methods or in events. MoveTo(node, flag) Changes the current node. This normally involves printing the new node's initial or text, but setting the flag argument to a non zero value will make the move completely silent. Wait() Will halt and wait for the player to press either enter or the space bar. Cls() Clears the screen. GetNumber(min, max) Prompts the player to enter a number between min and max, inclusively. It won't give up until a proper number has been entered. 9. Inventory management == ==================== Although mca.h doesn't contain anything resembling a world model, it does provide rudimentary support for inventory and such. The system used here is simply an array of key values, each representing some item in the inventory. An item can be a dictionary word, an object -- anything that boils down to a unique integer value. Using plain numbers is more memory efficient, of course. One could, for instance, enumerate constants for all the items in the story, then use these constants as item references. Two routines are used to manage the inventory. Calling Acquire() with an item as the only argument will add that item to the inventory, returning false if there were no slots left, true otherwise. If the item number is negated, it is instead removed, and the return value is true unless the item wasn't there to remove in the first place. The other routine is called Aquired(). It also expects an item, returning true if it is currently in the inventory, false otherwise. This system is general enough that it can be used for almost any purpose. Not every author will need it, however, so it will not be compiled by default. To change this, define the constant InventorySize to the maximum number of items in the inventory to be created. Note that no inventory command is provided the player. One could be coded that goes through the inventory array (which is called simply "inventory"), printing appropriate names for all the items. The first word in this array holds the number of items, and the items themselves are stored in the succeeding words. 10. Special commands === ================ Apart from choosing between the specified options, the player can also type in one of a few meta commands, namely: repeat, undo, save, restore, restart and quit. (More on the repeat command in the next section.) In addition to these, the author is allowed to add extra commands. However, since mca.h is only supposed to be a multiple choice engine, it tries to avoid anything that complicates the parsing process. In fact, it will only accept single word commands. Now, to add a new command (which will always be a meta command, consuming no game time), use the entry point UnknownCommand(). This routine is called whenever the library doesn't recognise the player's input. The unknown command itself is passed as an argument, and is either a dictionary word or nothing. Return true if something was printed, false otherwise. When adding new commands, it is also useful to replace the library's standard error message for unknown commands, since this lists the ones that are actually understood. This is done by defining a constant called UnknownCommandMsg, as further explained in section 12. 11. The repeat command === ================== Since there usually isn't any way to "look around" in a multiple choice story, and options vary from turn to turn, the player is offered a "repeat" command (it can also be abbreviated to "r") which displays all text that was printed since the last choice, including the list of options for the current turn. This is also done automatically after every restore or undo, since that is where one would normally issue such a command. To implement this feature, text needs to be buffered instead of printed right away, but this method has a couple of side effects. Most notably, all commands that affect the style of printed text will not have the desired effect. Instead, mca.h defines a set of style codes that can be embedded in strings. As it happens, using these codes can be a lot more convenient than using the equivalent Inform directives. The following is a list of supported style codes and the commands that they replace: #b style bold #u style underline #v style reverse #r style roman #f font off #n font on ## (prints a single hash) Apart from these, custom style codes may be added through an entry point named UnknownStyleCode(). This is called, if it exists, when a code is printed which isn't in the above list. The single character after the hash sign is the only argument. The return value is not significant. Unsupported style codes are simply ignored. Another thing to note is that the buffer used to store text internally has to have fixed size. By default, 1 kb (that is 1024 bytes, or characters) is reserved for this purpose. If this isn't enough, then the constant BufferSize can be set to a more appropriate value. Finally, text buffering needs to be interrupted whenever input is expected from the player. The routines Wait() and GetNumber() will do so automatically, but for all other purposes, the correct procedure is to first call BufferOff(), then handle any interactivity, then call BufferOn() before any subsequent text is printed. 12. Customising output === ================== In a typical story, nearly all printed text comes straight from the author, but the library has a few messages that are printed on occasion. These are all contained in a separate file called "english.h". There are two things one may want to do with regard to this. One the one hand, one may wish to replace some of these messages, E.G. the one printed when the player issues an unknown command, or perhaps the texts and options of the four EndNode objects. These are all defined as constants, and can be overridden if they are defined before mca.h is included. Take a look at english.h for a list of these constants and what they mean. On the other hand, one may wish to translate mca.h into another language. This could be done by redefining each and every constant from english.h, but it would of course be easier just to create a new language file. Simply make a copy of english.h, rename it as appropriate (E.G. to "lojban.h"), then go through all the constants and give them new values. To use this new language file in a story, the Inform compiler must be told so, and this is usually done by entering "+language_name=lojban" at the command line, or by making an equivalent selection in a graphical interface. 13. Entry points === ============ Entry points are routines that are (usually) optional, and which are called by the library at certain times. Some have been mentioned before. Here follows a complete list: AfterPrompt(flag) Is called right after a prompt has been printed, but before the player has been given the chance to type anything. This is where you would display a quote box or similar, since this would keep it from scrolling off the screen. The flag argument is set to true if the prompt is a "special prompt", which means anything except the main prompt asking for an option to be selected. DrawStatusLine() This routine can be used to set up a status line for the story. If it isn't provided, then no status line will appear. (The reason why there is no status line by default is that there isn't anything meaningful to write there anyway. There is no score, "moves" aren't defined well enough to be counted, and nodes don't have to provide titles like rooms in a text adventure.) Initialise() This is the only entry point that must be provided. It is called during startup and is responsible for specifying the initial node, which is done by setting the curnode global variable. Return false to delay printing of the story's banner. UnknownCommand(word) Is called when the player has written a command that wasn't understood. The argument is either a dictionary word or nothing. Return true if anything was printed, or false to display the usual error message. UnknownStyleCode(code) Is called when a style code has been written by the story which isn't recognised by mca.h. The argument is a single character. The return value is not significant. Note that these entry points, if provided, must be defined before the inclusion of mcastubs.h. 14. Other details === ============= As previously stated, you can get at the current node by checking the global variable curnode, and the previous node is similarly stored in oldnode. Some nodes might want to customize text or options depending on the previous node, and there's also the useful "MoveTo(oldnode)" command. You can see if a certain node has been visited by checking if it has the visited attribute. A general attribute and a number property are also provided for your amusement.