home *** CD-ROM | disk | FTP | other *** search
/ Amiga MA Magazine 1998 #7 / amigamamagazinepolishissue1998.iso / rozrywka / rpg / amigamud / doc / progconcepts.txt < prev    next >
Text File  |  1997-06-21  |  119KB  |  2,666 lines

  1. AmigaMUD, Copyright 1997 by Chris Gray
  2.  
  3.  
  4.     Some of the Concepts and Techniques in AmigaMUD
  5.  
  6. This file will attempt to be a bit more of a tutorial than the
  7. "Progamming.txt" and "Builtins.txt" files, which are reference
  8. material. Here, I will cover some of the areas of special interest
  9. when progamming in AmigaMUD, including:
  10.  
  11.     utility routines
  12.     parsing
  13.     generating good English output
  14.     how effects work
  15.     security issues
  16.     how to code efficiently in AmigaMUD
  17.     limitations of the system
  18.  
  19. I will list the names of builtin functions that are relevant to the
  20. topic. See file "Builtins.txt" for individual descriptions of the
  21. functions. See file "Scenario.txt" for more details on how the
  22. standard scenario works, and how to program within its framework.
  23.  
  24.  
  25. Utility Functions
  26.  
  27.  
  28. Recall from "Programming.txt" that AmigaMUD includes types "string",
  29. "int", and "fixed". Several utility functions are generally useful
  30. in dealing with those types:
  31.  
  32.     Capitalize - capitalize the first letter of a string
  33.     IntToFixed/FixedToInt - conversion
  34.     IntToString/StringToInt - conversion
  35.     FixedToString/StringToFixed - conversion
  36.     Index - search for one string in another
  37.     Length - length of a string
  38.     StringReplace - replace a substring of a string with another
  39.     Strip - strip quotation marks from a string
  40.     SubString - take a substring
  41.     Trim - trim leading and trailing spaces from a string
  42.  
  43. A few others are just generally useful:
  44.  
  45.     Count - return the number of elements in a list
  46.     Date/DateShort - return the current time and date
  47.     Execute - execute a string as an AmigaDOS command on the host
  48.     FileXXX routines - provide access to AmigaDOS files on the host
  49.     SetSeed/GetSeed/Random - deal with pseudo-random numbers
  50.     StringToAction/StringToProc - compile a string into a function
  51.     ProcToString - decompile an action into a string
  52.     Time - return the current time as a count of seconds
  53.  
  54. There are also a number of builtins to query or change the status of
  55. the server:
  56.  
  57.     ClientsActive - return 'true' if any player clients are active
  58.     Log - add a message to the "MUD.log" file
  59.     NewCreationPassword - allow SysAdmin to change that password
  60.     RunLimit - set/return the execution time run limit
  61.     ServerVersion - return the version of the server
  62.     SetContinue - control definition of erroneous functions
  63.     SetMachinesActive - control activity of machines
  64.     SetRemoteSysAdminOK - enable/disable remote SysAdmin logins
  65.     SetSingleUser - enable/disable single-user (adventure) mode
  66.     ShowCharacter - show one character's status
  67.     ShowCharacters - show the status of existing characters
  68.     ShowClients - show the status of the active clients
  69.     ShutDown - request shutdown of server
  70.     Trace - add an entry to the debugging trace buffer
  71.     Note - a copyright notice
  72.  
  73. Here is a group of builtins that deal with characters. More are
  74. described in the sections dealing with player characters, machines and
  75. agents:
  76.  
  77.     Editing - tell if the active character is currently editing
  78.     EditProc - start editing a proc (action)
  79.     EditString - start editing a string
  80.     GetString - get a string from the user with a requester
  81.     It - return a handy global variable
  82.     Me - return the active agent (machine or player)
  83.     MeCharacter - return the active character
  84.     NewCharacterPassword - change the player's password
  85.     Normal - switch out of wizard mode
  86.     PrivateTable - return the character's private table
  87.     Quit - request that the client terminate
  88.     SetIt - set the handy global variable
  89.     SetMeString - set the string naming the active client
  90.     SetPrompt - set the prompt for the active client
  91.     TrueMe - return the active agent, even if ForceAction is used
  92.     WizardMode - switch into wizard mode
  93.  
  94. A further set of functions are classed as utility functions since they
  95. don't properly fit into any other categories, but they are often
  96. related to other categories:
  97.  
  98.     DumpThing - dump a thing in all its gory detail
  99.     FindKey - trust SysAdmin and show what a code might be
  100.     PublicTable - return the system-wide public table
  101.     SetEffectiveTo - change the "effective character"
  102.     SetEffectiveToNone - remove the "effective character"
  103.     SetEffectiveToReal - reset the "effective character"
  104.  
  105.  
  106. Output Functions
  107.  
  108.  
  109. The following builtins can be considered as utility routines, but are
  110. classed separately to make them easier to find. They relate to doing
  111. text output from AmigaMUD. The first set are routines which can be
  112. used to produce nice looking English language output. These are the
  113. main ones that would need to be replaced to produce a non-English
  114. version of AmigaMUD:
  115.  
  116.     AAn - insert "a" or "an" in front of a word, as appropriate
  117.     FormatName - convert from "internal form" to English form
  118.     GetIndent - return the current indentation setting
  119.     IsAre - insert "is" or "are" into a string, as appropriate
  120.     Pluralize - simple attempt to pluralize a word
  121.     PrintAction - pretty-print a function
  122.     PrintNoAts - control an anti-spoofing feature
  123.     SetIndent - set indent amount for pretty output
  124.     SetPrefix - set a prefix for output lines
  125.     TextHeight - set/query text output height
  126.     TextWidth - set/query text output width
  127.  
  128. These routines actually do output in various ways:
  129.  
  130.     ABPrint - print to "all but" two agents in a given location
  131.     APrint - print to all active agents
  132.     IPrint - print an int to the active agent
  133.     NPrint - an anti-spoofing print to the active agent
  134.     OPrint - print to others in the same room
  135.     Print - standard print to the active agent
  136.     SPrint - print to a specific agent
  137.  
  138.  
  139. Parsing Functions
  140.  
  141.  
  142. In AmigaMUD, there is a function associated with each character which
  143. is passed each line of input typed by the player. This function is
  144. free to handle the input lines as it sees fit. Thus, it is possible to
  145. write whatever kind of parser is desired. However, it is a lot easier,
  146. and usually sufficient, to use the facilities provided in the AmigaMUD
  147. system to make parsing easier. Note that, unfortunately, these
  148. facilities are currently keyed to the English language and its style. I
  149. welcome detailed specifications or example code of how to do similar
  150. handling in other natural languages.
  151.  
  152.  
  153. The AmigaMUD server maintains a string variable which is useful during
  154. parsing. This variable cannot be relied upon outside of the handling
  155. of a single server event, e.g. the parsing of an input line, an action
  156. called for a machine, etc. The variable is referred to as the "tail
  157. buffer", since it contains the tail of the input command after it has
  158. been handled for a "VerbTail" verb (see later). The following
  159. functions deal with the tail buffer:
  160.  
  161.     GetTail - return the current contents of the tail buffer
  162.     GetWord - strip off and return the next "word" in the tail buffer
  163.     SetSay - check for a "says" form, putting text into tail buffer
  164.     SetTail - set the tail buffer to the passed string
  165.     SetWhisperMe - check for "whispers", and put rest in tail buffer
  166.     SetWhisperOther - check for "whispers", put rest in tail buffer
  167.  
  168.  
  169. There are a few concepts that must be understood in order to make good
  170. use of the AmigaMUD parsing facilities. One of those, which is also
  171. discussed in the "Building.txt" document, is the internal form used
  172. for object names. This form is simple, but fairly general. The basic
  173. idea is to take the English form of a noun-phrase, which is a series
  174. of adjectives followed by a noun, and store it in a form which does
  175. not contain any spaces, and in which the noun is first. The simplest
  176. example is then just a noun all by itself. Adjectives can be added
  177. after a semicolon, and separated by commas. E.g.
  178.  
  179.     noun
  180.     noun;
  181.     noun;adjective
  182.     noun;adjective,adjective
  183.     noun;adjective,adjective,adjective
  184.  
  185. If there is more than one form for the noun, then the other forms can
  186. be given after the first, separated by commas. E.g.
  187.  
  188.     noun1,noun2
  189.     noun1,noun2,noun3;adjective,adjective
  190.  
  191. When handling these forms, the AmigaMUD parsing tools are able to
  192. automatically handle simple plural forms. When the plural forms are
  193. not simple enough, they can be given explicitly, by including other
  194. forms of the noun phrase, separated from previous forms by a period.
  195. E.g.
  196.  
  197.     noun;adjective.noun;adjective,adjective
  198.     noun1,noun2;adjective.noun3;adjective,adjective.noun4;adjective
  199.  
  200. Only the first form ("noun1,noun2;adjective" in the last example) is
  201. ever printed out, so it is possible to include nonsensical or improper
  202. forms in other alternatives, in order to make parsing of irregular
  203. English forms work. The builtin "FormatName" is used to convert from
  204. one of these internal forms into an English external form. E.g. if
  205. passed the last example, "FormatName" would return "adjective noun1".
  206.  
  207. These internal forms are the forms that are stored in AmigaMUD
  208. "things" representing objects, as the names of those objects. Then,
  209. "FormatName" is used to print out the name of the object, "MatchName"
  210. is used to match a single internal form against an internal form with
  211. several alternatives, and "FindName" is used to find a "thing" on a
  212. list of things, which has an internal form name which matches the
  213. internal form given. This latter use is the key link between the
  214. parsing of input commands and the AmigaMUD database. There are a few
  215. builtin functions for dealing with internal name forms:
  216.  
  217.     HasAdjective - determine if a given adjective is present
  218.     MatchName - match a simple name form against a complex one
  219.     SelectName - select one simple name form from a complex one
  220.     SelectWord - select one word from an internal form
  221.  
  222. Here follows an example of some of these ideas:
  223.  
  224.     private nameProperty CreateStringProp()$
  225.  
  226.     [Create a new string property, i.e. a property whose values
  227.     are strings, and enter it into the user's private symbol table
  228.     with name 'nameProperty'.]
  229.  
  230.     private testThing CreateThing(nil)$
  231.  
  232.     [Create a new 'thing' (basic database entity), which has no
  233.     parent (doesn't inherit from anywhere), and enter it into the
  234.     user's private symbol table with name 'testThing'.]
  235.  
  236.     testThing@nameProperty :=
  237.         "bookcase;tall,oak."
  238.         "case,shelf,shelve,bookcase,bookshelf,bookshelve,"
  239.             "book-case,book-shelf,book-shelve;"
  240.         "book,high,tall,oak,oaken,wood,wooden"$
  241.  
  242.     [Give a name to the thing. The name is just a string, but the
  243.     string is written on three lines for clarity. The name is in
  244.     the internal form, and has two main alternatives. The first
  245.     alternative is the one that we will use on output, and the
  246.     second is present to allow for a variety of user input in
  247.     naming the object.]
  248.  
  249.     FormatName(testThing@nameProperty)$
  250.     ==> "tall oak bookcase"
  251.  
  252.     [Use builtin "FormatName" to produce a printable form of the
  253.     name. Note that only the first alternative is printed.]
  254.  
  255.     MatchName(testThing@nameProperty, "bookcase")$
  256.     ==> 0
  257.     MatchName(testThing@nameProperty, "bookcase;tall,oak")$
  258.     ==> 0
  259.     MatchName(testThing@nameProperty, "bookshelves;high,oaken")$
  260.     ==> 1
  261.     MatchName(testThing@nameProperty, "shelf;tall,wood,book")$
  262.     ==> 1
  263.     MatchName(testThing@nameProperty, "book-case;high,wooden")$
  264.     ==> 1
  265.  
  266.     [Show builtin "MatchName" being used on the stored name and
  267.     several internal-form possibilities. All are accepted. These
  268.     internal forms are the form that the AmigaMUD parsing
  269.     facilities described below would present to verbs. They
  270.     correspond to user input of the forms "bookcase", "tall oak
  271.     bookcase", "high oaken bookshelves", "tall wood book shelf",
  272.     and "high wooden book-case". The result returned from
  273.     "MatchName" is the zero-origin index of the matched
  274.     alternative within the set of alternatives.]
  275.  
  276.     private otherThing CreateThing(nil)$
  277.     otherThing@nameProperty := "shelf;walnut"$
  278.  
  279.     [Create another "thing", which is known as "walnut shelf".]
  280.  
  281.     private thingListProp CreateThingListProp()$
  282.     Me()@thingListProp := CreateThingList()$
  283.     AddTail(Me()@thingListProp, testThing)$
  284.     AddTail(Me()@thingListProp, otherThing)$
  285.  
  286.     [Create a list of things (attached to the player character),
  287.     and initialize it to contain our two things.]
  288.  
  289.     FindName(Me()@thingListProp, nameProperty, "bookcase;tall,oak")$
  290.     ==> succeed
  291.  
  292.     [Use the "FindName" builtin to scan the list of things,
  293.     looking for one whose 'nameProperty' value matches the string
  294.     we give. This is the central link in AmigaMUD between the
  295.     textual input from user commands to the meaning stored in the
  296.     database. The result of 'succeed' indicates that FindName was
  297.     successful in finding a matching thing on the list. It does
  298.     its search using "MatchName" internally.]
  299.  
  300.     FindResult()$
  301.     ==> testThing
  302.  
  303.     [After "FindName" has succeeded, we can use "FindResult" to
  304.     find the first matching name from the latest "FindName" call.
  305.     In this case we got our first created thing.]
  306.  
  307.     FindName(Me()@thingListProp, nameProperty, "shelf;walnut")$
  308.     ==> succeed
  309.     FindResult()$
  310.     ==> otherThing
  311.  
  312.     [The internal form "shelf;walnut" does not match (using
  313.     "MatchName" the internal name of 'testThing', so "FindName"
  314.     had to continue searching, and found 'otherThing'.]
  315.  
  316.     FindName(Me()@thingListProp, nameProperty, "shelf")$
  317.     ==> continue
  318.     FindResult()$
  319.     ==> testThing
  320.  
  321.     ["FindName" has returned 'continue', indicating that more than
  322.     one thing in the list matched the string. In such cases,
  323.     "FindResult" will return the first such one in the list.]
  324.  
  325.     FindName(Me()@thingListProp, nameProperty, "book;red")$
  326.     ==> fail
  327.     FindResult()$
  328.     ==> nil (thing)
  329.  
  330.     ["FindName" could not find a match, so it returns 'fail'.]
  331.  
  332. Rather than searching something like 'Me()@thingListProp', 'FindName'
  333. is usually used to search through the list of items in a room, the
  334. list of items being carried by a character, the list of items in a
  335. container, etc.
  336.  
  337.  
  338. Another important concept in AmigaMUD parsing is that of the
  339. "grammar". A grammar is a lot like a table, in that it is indexed by
  340. strings and contains a set of values associated with those strings.
  341. The values in a grammar, however, are not of any of the standard types
  342. in AmigaMUD. Instead, they are completely internal forms, which are
  343. known only to the AmigaMUD parsing and grammar handling code. Grammars
  344. contain verbs and their definitions, as supplied by the scenario. All
  345. of the main parsing and grammar related functions in AmigaMUD take a
  346. grammar as their first argument, identifying which set of verbs they
  347. are to work with. Wizards can create and manipulate new grammars, and
  348. can thus set up entire schemes for parsing. For example, the standard
  349. scenario creates and uses grammars for the build commands in general,
  350. and for "build room" and "build object" commands in particular. There
  351. are a number of utility builtins for dealing with grammars:
  352.  
  353.     CreateGrammar - create a new grammar
  354.     FindAnyWord - find a word in a grammar
  355.     FindWord - find a non-synonym word in a grammar
  356.     ShowWord - show the definition of a word in a grammar
  357.     ShowWords - show all of the words in a grammar
  358.     Synonym - enter a synonym into a grammar
  359.     Word - enter a non-verb word into a grammar
  360.  
  361.  
  362. Another aspect of AmigaMUD parsing that it is important to understand
  363. is the flow of control that happens during parsing. There can be
  364. variations on this scheme, but the scheme used in the standard
  365. scenario is shown here. Note that, under normal circumstances, the
  366. function t_util/parseInput is set as the input-line handler on all
  367. active characters, and is thus called by the system whenever an input
  368. command line arrives for that character.
  369.  
  370.     - parseInput checks for some special cases, such as aliases setup
  371.     by the player, commands starting with a quotation mark (") or
  372.     a colon (:), commands special to the current location, etc.
  373.  
  374.     - parseInput passes the input line to the builtin function
  375.     "Parse", passing the main grammar maintained by the scenario
  376.     as the first parameter to "Parse".
  377.  
  378.     - Parse handles strings containing multiple input commands,
  379.     separated by periods or semicolons. It calls a lower level
  380.     routine for each one. If that lower level routine returns
  381.     'false', then Parse stops processing the commands. Parse
  382.     returns the number of commands successfully handled.
  383.  
  384.     - the internal processing routine strips off the first word of
  385.     each command. It looks that word up in the grammar. If the
  386.     word is found, it does any handling required by the type of
  387.     the verb found (none for a "VerbTail" verb, getting a pair of
  388.     noun phrases and a separator word for a "Verb2" verb, etc.)
  389.  
  390.     - if all is well, the internal processing routine calls the
  391.     (interpreted) scenario routine associated with the matched
  392.     verb form. It will pass zero, one or two internal name form
  393.     strings to that routine, which it has parsed from the input
  394.     command.
  395.  
  396.     - the scenario verb routine attempts to find the objects in the
  397.     database that the internal name forms are referring to. This
  398.     usually involves using "FindName" with those forms on the list
  399.     of things at the current location, and the list of things that
  400.     the character is carrying. If appropriate objects are found,
  401.     the verb routine will do the semantic action associated with
  402.     the verb, such as picking the object up. The verb routine
  403.     returns 'true' to signal that all is well, else 'false' if
  404.     there was some kind of problem. Most verb routines will also
  405.     look for and call any relevant functions attached to the found
  406.     objects, the character, or the current room. These routines
  407.     can perform additional actions, or can prevent the main action
  408.     from happening.
  409.  
  410.     - the internal processing routine may call the scenario verb
  411.     routine multiple times if more than one object noun-phrase is
  412.     detected in the input command.
  413.  
  414. Thus, the call sequence can look like this:
  415.  
  416.     - system automatically calls scenario input handler routine
  417.     - scenario routine calls server internal Parse builtin
  418.     - Parse calls scenario verb routine
  419.     - verb routine calls several server internal builtins
  420.  
  421.  
  422. There are four forms of verb supported by the AmigaMUD parsing code.
  423.  
  424. The simplest form to understand is the "VerbTail" form. In this verb
  425. form, Parse will simply remove the verb itself from the command, and
  426. will put the rest of the command into the tail buffer, where it can be
  427. manipulated with "GetTail" and "GetWord". For example, commands like
  428. "alias" and "say", which do not interpret the entire command, are
  429. usually done as VerbTail forms. This form can also be used when the
  430. parsing facilities in AmigaMUD are not adequate to handle the verb
  431. directly. For example:
  432.  
  433.     private proc v_tail()bool:
  434.     Print("The tail of the command is '" + GetTail() + "'.\n");
  435.     true
  436.     corp;
  437.     VerbTail(G, "tail", v_tail)$
  438.     Parse(G, "tail this. tail all the other stuff. tail of dragon")$
  439.  
  440. would produce output:
  441.  
  442.     The tail of the command is 'this'.
  443.     The tail of the command is 'all the other stuff'.
  444.     The tail of the command is 'of dragon'.
  445.  
  446. As with other verb routines, the routine used for a VerbTail verb
  447. returns 'true' to indicate that all is well, and 'false' to indicate
  448. that something is wrong and that parsing of the rest of the commands
  449. in the input line should be abandoned.
  450.  
  451.  
  452. The next verb form is the "Verb0" form. This form accepts no (zero)
  453. noun phrases on the command, but accepts an optional "separator word"
  454. as part of the command. The optional word is so named because of its
  455. function in the "Verb2" form (see later). The Verb0 verb must be given
  456. by itself as a command, perhaps with its separator if one is given.
  457. The prototype of Verb0:
  458.  
  459. proc utility Verb0(grammar theGrammar; string theVerb; int separatorCode;
  460.     action theAction)void
  461.  
  462. indicates that the separatorCode is an int. This is the code for that
  463. word in the grammar. Each word in a grammar is assigned a unique code,
  464. which can be found by looking the word up in the grammar with
  465. "FindWord" or "FindAnyWord". If the word is used in other
  466. circumstances as a verb (like "up" in "stand up"), then it will be
  467. entered into the grammar as a verb. If the word is not so used,
  468. however, then it can be entered into the grammar using "Word", which
  469. adds the word to the grammar, but not as a verb. No word in a grammar
  470. has code 0, so that value is used to indicate that no separator word
  471. is required or allowed. Examples:
  472.  
  473.     private proc v_dream()bool:
  474.     if Me()@p_pAsleep then
  475.         Print("You dream pleasant dreams.\n");
  476.         true
  477.     else
  478.         Print("You are not asleep.\n");
  479.         false
  480.     fi
  481.     corp;
  482.     Verb0(G, "dream", 0)$
  483.  
  484.     private proc v_sitUp()bool:
  485.     if Me()@p_pLyingDown then
  486.         Print("You sit up.\n");
  487.         Me() -- p_pLyingDown;
  488.         true
  489.     else
  490.         Print("You are not lying down.\n");
  491.         false
  492.     fi
  493.     corp;
  494.     Verb0(G, "sit", FindWord(G, "up"))$
  495.  
  496.     private proc v_sitDown()bool:
  497.     if Me()@p_pLyingDown then
  498.         Print("You are already lying down.\n");
  499.         false
  500.     elif Me()@p_pSittingDown then
  501.         Print("You are already sitting down.\n");
  502.         false
  503.     else
  504.         Print("You sit down.\n");
  505.         Me()@p_pSittingDown := true;
  506.         true
  507.     fi
  508.     corp;
  509.     Verb0(G, "sit", FindWord(G, "down"))$
  510.  
  511. Here, verb "dream" has no separator word. Verbs "sit up" and "sit
  512. down" do, and those words are used to decide which form of the verb
  513. "sit" is being used. If "sit" is used without a separator word, then
  514. a verb form without a separator word will be used, but if there isn't
  515. one, then one of "sit up" or "sit down" will be picked, in no defined
  516. way. The programmer should define a "sit" without a separator word,
  517. to pick which of the two should be used, or to print an error message.
  518.  
  519.  
  520. A "Verb1" verb is one which accepts a direct object, to which the verb
  521. should be applied. E.g. "take the red ball", "launch rocket", etc.
  522. Such verbs can also have a separator word, and it is accepted either
  523. before the object or after it. The object is any sequence of words -
  524. the system does not try to interpret them in any way. An occurrence of
  525. any of "a", "an" or "the" at the start of the noun phrase is stripped
  526. off by the parser. The sequence of words for the direct object is
  527. terminated by the separator word, the end of the command or by a comma
  528. or the word "and". The sequence of words is taken to be the English
  529. form of a noun phrase, i.e. a sequence of adjectives followed by a
  530. noun. It is converted to the internal form, consisting of the noun, a
  531. semicolon, and the adjectives, separated by commas.
  532.  
  533. When a Verb1 verb routine is called by the parser, it is passed the
  534. internal form of the noun phrase as its single string parameter. If
  535. more than one noun phrase is given, separated by commas or the word
  536. "and", then the verb routine will be called once for each noun phrase
  537. in sequence, or until it returns 'false', indicating that the rest of
  538. the noun phrases in the command, and the rest of the commands in the
  539. input string, should be abandoned. Note that if a Verb1 verb is
  540. matched by the parser, but no noun phrase was given in the command,
  541. then the parser will call the verb routine once with an empty string
  542. as parameter. The verb routine should check for and handle this case.
  543.  
  544. It is up to the verb routine to decide if the noun phrase is a valid
  545. one for the circumstances, and to find an object in the database that
  546. can be referred to by the noun phrase. Typically, this will be a
  547. matter of looking for a matching internal form object name in the
  548. objects in the player's inventory, and in the objects in the room.
  549. This can be done using "FindName", and perhaps some of the other
  550. 'Find' builtins. Many verb routines will look for and handle a
  551. routine attached to the object, the player, or the room, in order to
  552. allow special-case processing. Here is a fairly complete example:
  553.  
  554.     private G CreateGrammar()$
  555.     private p_pCarrying CreateThingListProp()$
  556.     private p_oName CreateStringProp()$
  557.     private pEatCheck CreateActionProp()$
  558.     private p_pFoodCount CreateIntProp()$
  559.     private p_oFoodValue CreateIntProp()$
  560.     private p_pStomachAche CreateBoolProp()$
  561.  
  562.     private apple1 CreateThing(nil)$
  563.     apple1@p_oName := "apple;juicy,red"$
  564.     apple1@p_oFoodValue := 5$
  565.  
  566.     private apple2 CreateThing(nil)$
  567.     apple2@p_oName := "apple;sour,green"$
  568.     apple2@p_oFoodValue := 2$
  569.     private proc apple2Eat(thing theApple)status:
  570.     Me()@p_pStomachAche := true;
  571.     continue
  572.     corp;
  573.     apple2@pEatCheck := apple2Eat$
  574.  
  575.     private apple3 CreateThing(nil)$
  576.     apple3@p_oName := "apple;rosy,red"$
  577.     private TheWickedWitch CreateThing(nil)$
  578.     private proc apple3Eat(thing theApple)status:
  579.     if Me() = TheWickedWitch then
  580.         Print("Stupid - you just wasted a poison apple!\n");
  581.     else
  582.         Print("Ack! The rosy red apple was poison!\n");
  583.         Me()@p_pFoodCount := 0;
  584.     fi;
  585.     succeed
  586.     corp;
  587.     apple3@pEatCheck := apple3Eat$
  588.  
  589.     private rock1 CreateThing(nil)$
  590.     rock1@p_oName := "rock;small,round"$
  591.  
  592.     Me()@p_pCarrying := CreateThingList()$
  593.     AddTail(Me()@p_pCarrying, apple1)$
  594.     AddTail(Me()@p_pCarrying, apple2)$
  595.     AddTail(Me()@p_pCarrying, apple3)$
  596.     AddTail(Me()@p_pCarrying, rock1)$
  597.  
  598.     private room CreateThing(nil)$
  599.     SetLocation(room)$
  600.  
  601.     private proc v_eat(string what)bool:
  602.     thing me, theFood;
  603.     string foodName;
  604.     status st;
  605.     action specialAction;
  606.  
  607.     if what = "" then
  608.         Print("You must say what you want to eat.\n");
  609.         false
  610.     else
  611.         me := Me();
  612.         foodName := FormatName(what);
  613.         st := FindName(me@p_pCarrying, p_oName, what);
  614.         if st = fail then
  615.         Print(AAn("You aren't carrying", foodName) + ".\n");
  616.         false
  617.         elif st = continue then
  618.         Print(Capitalize(foodName) + " is ambiguous.\n");
  619.         false
  620.         else
  621.         theFood := FindResult();
  622.         st := continue;
  623.         specialAction := me@pEatCheck;
  624.         if specialAction ~= nil then
  625.             st := call(specialAction, status)(theFood);
  626.         fi;
  627.         if st = continue then
  628.             specialAction := Here()@pEatCheck;
  629.             if specialAction ~= nil then
  630.             st := call(specialAction, status)(theFood);
  631.             fi;
  632.         fi;
  633.         if st = continue then
  634.             specialAction := theFood@pEatCheck;
  635.             if specialAction ~= nil then
  636.             st := call(specialAction, status)(theFood);
  637.             fi;
  638.             if st = continue then
  639.             if theFood@p_oFoodValue ~= 0 then
  640.                 Print("You eat the " + foodName + ".\n");
  641.                 me@p_pFoodCount := me@p_pFoodCount +
  642.                 theFood@p_oFoodValue;
  643.                 DelElement(me@p_pCarrying, theFood);
  644.                 true
  645.             else
  646.                 Print("You can't eat the " + foodName +
  647.                 ".\n");
  648.                 false
  649.             fi
  650.             else
  651.             st = succeed
  652.             fi
  653.         else
  654.             false
  655.         fi
  656.         fi
  657.     fi
  658.     corp;
  659.     Verb1(G, "eat", 0, v_eat)$
  660.  
  661.     Parse(G, "eat")$
  662.     Parse(G, "eat apple. eat rock")$
  663.     Parse(G, "eat carrot")$
  664.     Parse(G, "eat orange")$
  665.     Parse(G, "eat rock")$
  666.     Parse(G, "Eat the small round rock.")$
  667.     Parse(G, "eat juicy red apple, green apple and rosy red apple")$
  668.  
  669. The (numbered) output from sourcing this example is:
  670.  
  671.  1 > source test.m
  672.  2 Sourcing file "test.m".
  673.  3 You must say what you want to eat.
  674.  4 ==> 0
  675.  5 Apple is ambiguous.
  676.  6 ==> 0
  677.  7 You aren't carrying a carrot.
  678.  8 ==> 0
  679.  9 You aren't carrying an orange.
  680. 10 ==> 0
  681. 11 You can't eat the rock.
  682. 12 ==> 0
  683. 13 You can't eat the small round rock.
  684. 14 ==> 0
  685. 15 You eat the juicy red apple.
  686. 16 You eat the green apple.
  687. 17 Ack! The rosy red apple was poison!
  688. 18 ==> 1
  689. 19 > d Me()$
  690. 20 thing, parent <NIL-THING>, owner SysAdmin, useCount 1, propCount 5,
  691. 21     ts_public:
  692. 22   p_pName: "SysAdmin"
  693. 23   p_pIcon: {16380, 1073890242, 1206011394, 600055908, 669258696,
  694. 24 268961808, 69206592, 25165824}
  695. 25   p_pCarrying: {apple3, rock1}
  696. 26   p_pFoodCount: 0
  697. 27   p_pStomachAche: true
  698.  
  699. This sample is a complete example, which can be run on an empty
  700. database as created by MUDCre. It starts out by creating a grammar,
  701. and giving it symbol "G" in the user's (most likely SysAdmin) private
  702. symbol table. Next, a number of properties are defined. These are
  703. attributes which can be attached to things. All of the property
  704. symbols start with the letter "p" as a memory aid. Those which are to
  705. be attached only to players start with "p_p", and those which are to
  706. be attached only to objects start with "p_o". Since this is only a
  707. small sample, and not a complete scenario, the properties are used for
  708. illustration, and are not fully implemented.
  709.  
  710. Following the properties, the example code creates four objects, and
  711. gives them to the active character, by adding them to an inventory
  712. list (property p_pCarrying) attached to the character. The first item,
  713. "apple1", is nothing special, and has a food value of 5. The second is
  714. a sour apple, and has an attached "pEatCheck" action, "apple2Eat".
  715. This routine will be called dynamically by the "eat" verb. The third
  716. object, "apple3", has a slightly more drastic "pEatCheck" routine. The
  717. thing "TheWickedWitch" is an example only - it has no properties and
  718. exists only to be tested against in "apple3Eat". The fourth and last
  719. object, "rock1", is again very simple, and it doesn't even have a food
  720. value, and hence is not considered to be "edible" by this example. The
  721. five lines starting with "Me()@p_pCarrying := ..." create a new list
  722. of things, attach it to the active character (the one sourcing the
  723. file), and append the four newly created objects to that list. The
  724. final lines before "v_eat" create a dummy location and move the active
  725. character to that location. This is needed since "v_eat" uses the
  726. value returned by the "Here" builtin.
  727.  
  728. "v_eat" is the central portion of this example. It is a fairly
  729. complete Verb1 routine to handle attempts by the player to eat things.
  730. It starts out by declaring a bunch of local variables. Local variables
  731. are used here for two reasons. One is the convenience of typing just a
  732. variable name instead of a longer expression. The other reason is that
  733. of efficiency - it is quicker in AmigaMUD to reference a local
  734. variable than it is to call some function, even a builtin function.
  735.  
  736. v_eat first tests to see if the string it was passed is the empty
  737. string. Recall from the previous discussion of Verb1 verbs, that if
  738. the verb is used without an accompanying noun phrase, the verb handler
  739. routine (here v_eat) is called with an empty string as argument. v_eat
  740. simply prints a helpful message, and returns 'false', indicating that
  741. some kind of error has occurred. Note that it is a good idea to phrase
  742. such error comments as suggestions, rather than as questions like
  743. "What do you want to eat?", since then there is no confusion on the
  744. part of the player over what is acceptable input.
  745.  
  746. v_eat then gets a pointer to the active player, and saves it in a
  747. quicker-to-access local variable. It then gets a printable version of
  748. the object name string that the player entered. Recall that the
  749. internal form passed to verb handler routines can be turned into a
  750. normal English form using the "FormatName" builtin.
  751.  
  752. The next line, containing the call to "FindName" is the crucial link
  753. between input commands and the database representing the "world". It
  754. looks for a thing with property "p_oName" whose value matches the
  755. string it is passed, which here is the internal form name passed to
  756. this verb handler by the AmigaMUD parsing code. FindName returns a
  757. value of type status, with values indicating:
  758.  
  759.     fail - no thing with a matching name was found in the list
  760.     succeed - one thing with a matching name was found
  761.     continue - more than one thing with a matching name was found
  762.  
  763. So, if the result of FindName, stored in local variable "st", is
  764. 'fail', then no matching name was found, i.e. the player is not
  765. carrying anything that matches the noun-phrase given in the command.
  766. This example (and the standard scenario) does not attempt to pick an
  767. alternative in the case of multiple matches, so it prints an error
  768. message if FindName returned 'continue'. Note the use of "AAn" and
  769. "Capitalize" in these messages in order to produce tidy English
  770. output. If FindName returned 'succeed' or 'continue', then it saves a
  771. pointer to the found thing, and that pointer is retrieved by calling
  772. "FindResult".
  773.  
  774. After retrieving the found thing, i.e. identifying the object in the
  775. world that the command is referring to, all that remains is to perform
  776. the "eat" operation on that object. Many scenarios will have a number
  777. of special cases for such an operation. Rather than coding them
  778. explicitly in v_eat, it is easier, cleaner and less error prone to use
  779. an "object-oriented" approach and attach those special actions
  780. directly to the object. Such special actions can also be attached to
  781. the character, and to rooms. So, the code in v_eat checks for special
  782. actions first on the character, then in the room the player is in, and
  783. finally on the object being eaten. These special actions are here
  784. assumed to return a value of type status, indicating:
  785.  
  786.     succeed - the object is eaten - no further checks should take
  787.     place, but the eating action is successful
  788.     fail - the object cannot be eaten for some reason
  789.     continue - nothing has happened which affects the later processing
  790.     of this action - continue on
  791.  
  792. Note the uses of the "call" construct. This is how to call a function
  793. obtained dynamically in AmigaMUD. The "call" specifies the function to
  794. be called and the type it is supposed to return, and is followed by a
  795. parenthesized list of any parameters to the called function. The
  796. function's return value and parameter count and types will be checked
  797. at runtime, and execution will be aborted if any do not match.
  798.  
  799. If none of the special actions (pEatCheck's) stop the eating
  800. operation, then v_eat looks for a "p_oFoodValue" property on the
  801. object. If the object does not have one, it is considered to be not
  802. eatable and v_eat complains. Otherwise, the food value is added to
  803. the character's food count, which is the normal action here of eating
  804. something. After an item has been eaten, it is deleted from the list
  805. of things being carried by the character.
  806.  
  807. The odd-looking line reading "st = succeed" is the return value of the
  808. v_eat function (which returns a bool) if one of the special actions
  809. does not return 'continue'. The result of the last special action
  810. executed is stored in local variable "st". If that value is 'fail',
  811. then the eating failed, else it succeeded, so the expression tests for
  812. "st = succeed" to generate the appropriate bool value.
  813.  
  814. Following the definition of v_eat is the following line:
  815.  
  816.     Verb1(G, "eat", 0, v_eat)$
  817.  
  818. This adds verb "eat" to grammar "G", specifying v_eat as the function
  819. to be called to execute the verb. The value 0 for the "separator word"
  820. indicates that no special extra word is needed or allowed with "eat".
  821.  
  822. The next seven lines pass some test strings to "Parse" to try out the
  823. "eat" verb. Normally, such input lines come from players, via their
  824. "input action". The first test checks out our handling of a missing
  825. noun phrase, and produces output lines 3 and 4 (recall that "Parse"
  826. returns the number of successfully handled commands, and that the
  827. AmigaMUD client programs print any result of an interactively entered
  828. expression).
  829.  
  830. Next, we try to eat any apple and then the rock. v_eat finds that
  831. "apple" is ambiguous, says so, and returns 'false'. This tells Parse
  832. to not execute any more commands in its parameters, thus it does not
  833. execute the "eat rock" command, and there is no complaint here about
  834. trying to eat the rock.
  835.  
  836. Output lines 7 and 8 come from trying to eat a "carrot" - the
  837. character is not carrying anything with a name which matches that.
  838. Similarly for "orange". Note that the use of "AAn" in the complaint
  839. message results in proper use of "a" versus "an". Eating a rock
  840. doesn't work because it has no p_oFoodValue property. This is an
  841. example of how attempting to retrieve a non-existant property from a
  842. thing is not an error, but produces a default value, here 0. Output
  843. lines 13 and 14 come from the second attempt to eat the rock. Note
  844. that Parse has automatically handled the capitalized first letter of
  845. "Eat", the period at the end of the command, and the use of the
  846. article "the".
  847.  
  848. Output lines 15 - 18 are the most interesting. Here we are actually
  849. eating something. Again, Parse automatically handles the list of noun
  850. phrases, calling v_eat sequentially with each one. Eating the "juicy
  851. red apple", which matches only "apple1", works without incident and
  852. adds that object's food value to the character's food count. Eating
  853. the "green apple" (matches apple2) appears to work just as well, but
  854. actually will give the character a stomach ache. This is because the
  855. special action routine "apple2Eat" returns 'continue', indicating
  856. that processing should continue as normal.
  857.  
  858. The special action for apple3, matched by "rosy red apple", returns
  859. 'succeed', indicating that the apple has been eaten, and no further
  860. processing of eating this object should happen. Since all three of the
  861. calls to v_eat returned 'true', Parse considers the entire command to
  862. have been executed successfully, and returns a count of 1.
  863.  
  864. Output lines 20 - 27 show the state of the character after executing
  865. the various "eat" commands. The character is still carrying the poison
  866. apple and the rock. The special action "apple3Eat", since it returns
  867. 'succeed', should have deleted that apple from the character's list of
  868. things being carried. The character has a food count of 0, set that
  869. way by apple3Eat, and has flag p_pStomachAche, set by apple2Eat.
  870.  
  871.  
  872. The third kind of verb supported by the AmigaMUD parsing routines is
  873. the "Verb2" verb. The general form of the input accepted for this verb
  874. form is:
  875.  
  876.     verb {noun-phrase}* separator noun-phrase
  877.  
  878. E.g.
  879.  
  880.     Put the sword, the brass lamp and the book into the trophy case.
  881.  
  882. Here, the verb is "put", and the separator word is "into". The direct
  883. objects are "the sword", "the brass lamp", and "the book", and the
  884. single indirect object is "the trophy case". A verb action for a
  885. Verb2 verb has two parameters. The first is the direct object, and the
  886. second is the indirect object (both in internal form as usual). When
  887. more than one direct object is given, as in this example, the verb
  888. action is called repeatedly, with the various direct objects, and the
  889. single fixed indirect object. A Verb2 handler will never be called
  890. with either parameter being an empty string, since Parse checks for
  891. that case and handles it directly.
  892.  
  893. I do not give an example of a Verb2 handler here. Refer to file
  894. "verbs.m" in the standard scenario sources for examples.
  895.  
  896. Note that the separator word and the presence of objects can be used
  897. to decide which form of a verb to use. Thus, a scenario could define
  898. all of:
  899.  
  900.     Verb0(G, "stand", FindWord("up"), v_standUp)$
  901.     Verb0(G, "stand", FindWord("down"), v_standDown)$
  902.     Verb1(G, "stand", FindWord("on"), v_standOn1)$
  903.     Verb2(G, "stand", FindWord("on"), v_standOn2)$
  904.  
  905. and thus handle input commands like:
  906.  
  907.     stand up
  908.     stand down
  909.     stand on the bench
  910.     stand the statue on the pedestal
  911.  
  912. The builtins useful for setting up verbs and for parsing are:
  913.  
  914.     FindName - key input/database link - find a named item
  915.     FindResult - return a found thing
  916.     GetNounPhrase - get a noun phrase from a string
  917.     ItName - return the original direct object internal form
  918.     Parse - parse a string using a grammar
  919.     Punctuation - returns the command terminator character
  920.     Verb - return the original form of the verb used
  921.     Verb0 - add a Verb0 verb to a grammar
  922.     Verb1 - add a Verb1 verb to a grammar
  923.     Verb2 - add a Verb2 verb to a grammar
  924.     VerbTail - add a tail verb to a grammar
  925.     WhoName - return the original indirect object internal form
  926.  
  927.  
  928. Generating Good English Output
  929.  
  930.  
  931. Many MUD players are quite picky about the text they read. They are
  932. disdainful of graphics output, and desire all MUDs to read like well-
  933. written novels. Few, if any, MUDs meet the desires of such players.
  934. However, it is a good idea to generate good output text from a MUD, so
  935. that a few vocal players can't make it sound like a terrible MUD. This
  936. includes such simple things as spelling words correctly, getting the
  937. grammar correct, proper capitalization, etc. The standard AmigaMUD
  938. scenario is by no means perfect in this respect, but I have tried to
  939. handle most cases. There are some builtin functions in AmigaMUD that
  940. can help:
  941.  
  942.     Capitalize - capitalize the first letter in a string
  943.     AAn - insert either "a" or "an", and spaces as appropriate,
  944.     between two strings, based on whether or not the first letter
  945.     in the second string is a vowel or a consonant
  946.     IsAre - this routine takes four string parameters. The second
  947.     string can be empty, or can be a word like "no". If the second
  948.     string is empty and the third string ends in "s", then IsAre
  949.     inserts "are some" between the first and third strings. If the
  950.     second string is empty and the third string does not end in
  951.     "s", then IsAre inserts either "is a" or "is an" between the
  952.     second and third strings, depending on whether the third
  953.     string begins with a vowel or not. If the second string is not
  954.     empty, then IsAre inserts either "is" or "are" between the
  955.     first and second strings. IsAre also inserts all needed
  956.     spaces between strings.
  957.     Pluralize - attempts to pluralize the string passed
  958.  
  959. Even with these functions, it is often not possible to generate proper
  960. output in all cases. Sometimes the scenario programmer will put up
  961. with bad output, and sometimes he will re-arrange things so that
  962. different phrasing, with correct grammar, can be used. The important
  963. thing is to avoid obvious errors, and to show that some care has been
  964. taken in producing output.
  965.  
  966.  
  967. Effects
  968.  
  969.  
  970. The term "effects" in AmigaMUD refers to a variety of output events
  971. that occur in the MUD client program, but which are governed by
  972. scenario code running in the MUDServ server program. Simple text
  973. output is not considered to be an effect. Effects include graphics
  974. output, sound output, voice output, music output (not yet supported),
  975. icon displaying and mouse-button displaying and definition.
  976.  
  977. These various effects are implemented via a simple interpreter built
  978. in to the MUD program. The interpreter has an 'if' construct (although
  979. there is only one thing that can be tested), and can do subroutine
  980. calls (calls to other effect routines from a main effect routine). The
  981. effect routines are sent from the server to the client as a stream of
  982. bytes which are the "machine code" for the effects "machine", and are
  983. executed entirely in the MUD client, as directed by scenario code
  984. running in the server.
  985.  
  986. The client keeps effects routines that it has been sent, unless it
  987. runs out of memory or the user explicitly flushes something out of the
  988. "effects cache" maintained by the client. The server keeps track of
  989. which effects routines are known by which active client program, so
  990. that they do not have to be transmitted unneccessarily. In addition,
  991. the client will not discard an effect that is called by another effect
  992. that it has in its cache - the upper level one must be freed first.
  993. This is so that once the execution of an effect routine starts in the
  994. client program, it cannot fail because of a missing effect routine.
  995.  
  996. If the client program ever does find itself trying to execute an
  997. effect routine it does not know, it will simply do nothing. This can
  998. happen because the scenario code must explicitly define the contents
  999. of an effect, thus sending the definition to the client, when it sees
  1000. that the client does not know the needed effect. In other words, it
  1001. isn't foolproof - a buggy scenario can mess up effects so that they
  1002. don't appear when desired.
  1003.  
  1004. The execution of an effect routine in the client is triggered when
  1005. scenario code in the server uses the "CallEffect" builtin when it is
  1006. not in the middle of defining an effect routine. In the latter case,
  1007. the "CallEffect" is an effects subroutine call to the called effect.
  1008. The definition of an effects subroutine is started when the scenario
  1009. code executes the "DefineEffect" builtin, and ends when a matching
  1010. call to "EndEffect" occurs. The DefineEffect call is given an
  1011. identifier by which the effect is known. These identifiers should all
  1012. be unique, else effects will be messed up. The easiest way to do this
  1013. is to use a unique-id generator routine in the scenario. The standard
  1014. scenario has routine "NextEffectId" for this purpose. Special effect
  1015. id 0 can be used as a temporary effect, callable only once - it is
  1016. never cached by the clients.
  1017.  
  1018. As an example, here is the definition of a simple effects routine
  1019. which will clear the graphics screen and draw a blue box on it:
  1020.  
  1021. private EXAMPLE_EFFECT_ID 1$
  1022.  
  1023. private proc setupExampleEffect()void:
  1024.  
  1025.     DefineEffect(nil, EXAMPLE_EFFECT_ID);
  1026.     GClear(nil);
  1027.     GSetAPen(nil, C_BLUE);
  1028.     GAMove(nil, 0.3, 0.2);
  1029.     GRectangle(nil, 0.6, 0.5, true);
  1030.     EndEffect();
  1031. corp;
  1032.  
  1033. We can now trigger the effect using:
  1034.  
  1035. CallEffect(nil, EXAMPLE_EFFECT_ID)$
  1036.  
  1037. The builtin routine "KnowsEffect" returns 'true' if the given active
  1038. client knows the specified effect, thus allowing the scenario code to
  1039. test whether or not it has to define the effect for that client. Thus,
  1040. the sequence for defining and using an effect is usually like:
  1041.  
  1042. private EXAMPLE_EFFECT_ID 1$
  1043.  
  1044. private proc doExampleEffect()void:
  1045.  
  1046.     if not KnowsEffect(nil, EXAMPLE_EFFECT_ID) then
  1047.     DefineEffect(nil, EXAMPLE_EFFECT_ID);
  1048.     GClear(nil);
  1049.     GSetAPen(nil, C_BLUE);
  1050.     GAMove(nil, 0.3, 0.2);
  1051.     GRectangle(nil, 0.6, 0.5, true);
  1052.     EndEffect();
  1053.     fi;
  1054.     CallEffect(nil, EXAMPLE_EFFECT_ID);
  1055. corp;
  1056.  
  1057. This will cause the effect to be executed, with it being defined
  1058. before the execution, if needed.
  1059.  
  1060. Note that in these examples, the 'who' parameter has always been
  1061. 'nil'. This directs the effects to the active agent, i.e. the player
  1062. on whose behalf the code is being executed. This is the normal
  1063. situation for effects which define scenery, etc. for locations in the
  1064. scenario. It is possible to use the 'thing' value for some other
  1065. active agent, and the effect will be defined and executed for that
  1066. agent instead of for the active one. Do not try to mix agents inside
  1067. an effect definition, however, as this can result in havoc! Also, it
  1068. is a good idea to do all effects for a given client before moving on
  1069. to another client, to minimize the number of separate messages that
  1070. must be sent to the various clients. The server buffers up effects
  1071. requests and sends as much as possible in large batches. These batches
  1072. are flushed to the clients when the client for effects changes, or the
  1073. processing of the original event (input line, mouse click, timer
  1074. driven action, etc.) completes.
  1075.  
  1076. Most effects can be considered to have taken place as soon as the
  1077. effects routine is called. Some, however, take time to execute. This
  1078. is the case for voice output, sound output and music output. Thus, for
  1079. these effects, the main builtin which triggers them is given another
  1080. identifying integer for that effect. When the effect is done (e.g. the
  1081. speech completes, or the sound sample ends), the client will send a
  1082. message to the server indicating that, and the server can then call an
  1083. "effect complete" action on the character, thus notifying the scenario
  1084. code of the completion. The scenario code can then start another such
  1085. effect, thus having things going on continuously. Such ongoing effects
  1086. can also be aborted by a call to "AbortEffect" in the server, which
  1087. specifies the identifier of the ongoing affect to abort.
  1088.  
  1089.  
  1090. The following kinds of effects are possible:
  1091.  
  1092.     general graphics:
  1093.     - simple drawing primitives
  1094.     - loading of IFF ILBM backgrounds
  1095.     - insertion of smaller IFF ILBM images
  1096.     - overlaying of IFF ILBM brushes
  1097.  
  1098.     sound:
  1099.     - speech using the Amiga's "narrator.device"
  1100.     - playing IFF 8SVX sound samples
  1101.  
  1102.     mouse input control:
  1103.     - control of visible "mouse buttons"
  1104.     - control of invisible "mouse regions"
  1105.  
  1106.     special purpose graphics:
  1107.     - icon control
  1108.     - cursor control
  1109.     - colour palette control
  1110.     - text in graphics window
  1111.     - control and use of rectangular "tiles"
  1112.  
  1113. The user of the MUD client program can control, via menus or function
  1114. keys, whether or not certain types of effects are active. This
  1115. information is available to scenario code in the server, so that
  1116. messages for disabled effects are not sent to the client. Also, the
  1117. scenario code can use alternative methods (such as simple text output)
  1118. to show the user what is happening. To allow scenario code to properly
  1119. customize effects sent to a client, a number of builtin routines are
  1120. available to return information about the effects capabilities of the
  1121. client (note that the standard scenario does not make use of some of
  1122. this information):
  1123.  
  1124.     GColours - return the number of colours the client can display
  1125.     GCols - return the horizontal pixel width of the output area
  1126.     GOn - query if the client is currently handling graphics
  1127.     GPalette - query if the client has a changeable colour palette
  1128.     GRows - return the vertical pixel height of the output area
  1129.     GType - return the type of the client (e.g. "Amiga")
  1130.     MOn - query if the client is currently handling music
  1131.     QueryFile - check for a file under AmigaMUD: on the client
  1132.     SOn - query if the client is currently handling sound
  1133.     VOn - query if the client is currently handling voice
  1134.  
  1135. As of the V1.1 MUD system, the type 'fixed' has been added to the
  1136. system. This type is a fixed-point type, which allows fractional
  1137. values to be represented. Most of the graphics drawing primitives now
  1138. come in two forms. The old forms, which accepts integer pixel
  1139. positions, have been renamed, and new forms which accept 'fixed'
  1140. position values have been added. The 'fixed' forms represent a
  1141. fraction of the full graphics X or Y size, and thus graphics done
  1142. using them will scale to different sizes of client graphics windows.
  1143. This is important since the V1.1 MUD client supports multiple
  1144. resolutions.
  1145.  
  1146. The introduction of the new resolution capability required redoing
  1147. most of the graphics in the standard scenario, and some lessons have
  1148. come from that exercise:
  1149.  
  1150.     - use movement to absolute positions instead of relative movement
  1151.     wherever possible. This reduces the effect of errors
  1152.     introduced by rounding.
  1153.  
  1154.     - in a pixel-coordinate system, a one-pixel wide line or boundary
  1155.     can be drawn beside other graphics, and everything works. When
  1156.     the resolution can vary, however, what used to be a line is
  1157.     now a rectangle. This affects drawing code. For example, the
  1158.     various "autographics" rooms often have border lines around a
  1159.     rectangle. This is now done by first drawing a rectangle in
  1160.     the border colour, which is filled, and which covers the
  1161.     entire area. Then, an inner filled rectangle is drawn over top
  1162.     of that, which results in the desired image, regardless of
  1163.     what the resolution is.
  1164.  
  1165.     - rounding problems often result in things not lining up as
  1166.     desired in some resolution. Trial and error is sometimes
  1167.     needed to get things right. If you can't get the ends of
  1168.     things to line up, try moving the start-points as well.
  1169.  
  1170.     - some things look better using pixel-based positioning, rather
  1171.     than being spread out in a higher resolution window. For
  1172.     example, the "mouse buttons" in the standard scenario look
  1173.     best if they maintain their close, pixel-based spacing. This
  1174.     is because the size of the buttons themselves, in terms of
  1175.     pixels, does not change as the size of the window changes.
  1176.  
  1177. The builtin functions for adding primitive graphics operations to an
  1178. effect routine (or doing them right away) are:
  1179.  
  1180.     GADraw/GADrawPixels - draw from current to given absolute position
  1181.     GAMove/GAMovePixels - move drawing point to a given absolute position
  1182.     GCircle - draw an outline or filled circle
  1183.     GClear - clear the graphics area to pen 0
  1184.     GEllipse - draw an outline or filled ellipse
  1185.     GPixel - set a single pixel
  1186.     GPolygonEnd - end the drawing of a polygon
  1187.     GPolygonStart - start the drawing of a polygon
  1188.     GRDraw/GRDrawPixels - draw a line in a relative direction
  1189.     GRectangle/GRectanglePixels - draw an outline or filled rectangle
  1190.     GRMove/GRMovePixels - move drawing point in a relative direction
  1191.  
  1192. Note that the clipping of circles and ellipses is not very good in the
  1193. MUD client, so make sure all of them are within the graphics window.
  1194. The capabilities of the polygon drawing are limited to those of the
  1195. Amiga's AreaXXX calls. On most display devices used with Amiga's, the
  1196. aspect ratio of the image is not square, so that a circle appears as
  1197. an ellipse.
  1198.  
  1199. The following miscellaneous effects routines are often used with
  1200. simple graphics effects, but can be used in other circumstances as
  1201. well:
  1202.  
  1203.     GResetColours - reset graphics palette to the default
  1204.     GScrollRectangle - scroll a rectangle of the graphics area
  1205.     GSetColour - define one colour of the graphics palette
  1206.     GSetPen - select the active graphics pen
  1207.  
  1208.  
  1209. Builtins for dealing with IFF ILBM files (which must exist on the
  1210. client machine, not on the server) are:
  1211.  
  1212.     GLoadBackGround - load and display a background image
  1213.     GSetImage - set a default image
  1214.     GShowBrush - overlay a brush onto the current graphics
  1215.     GShowImage/GShowImagePixels - display a rectangular image piece
  1216.  
  1217. Loading a background replaces the graphics area with the background
  1218. image loaded from a file. On V2.04 and above systems, the client will
  1219. scale the image to fit within the entire graphics area. On earlier
  1220. systems, the image is clipped to fit within the display area, and any
  1221. display area not covered by the image is left unchanged. If the IFF
  1222. file contains a colour palette, then that palette will replace the
  1223. active pallete used for the graphics screen. Note that the this can
  1224. make the pointer and the mouse buttons look awful, so care should be
  1225. used in choosing palletes for backgrounds. The name of a background is
  1226. just a file name, which will be evaluated relative to "AmigaMUD:
  1227. BackGrounds/" on the client machine.
  1228.  
  1229. GSetImage is used to set a default image. If GShowImage is given an
  1230. empty string as the name of the image file to use, then it will use
  1231. the default image instead. This is useful when a single IFF ILBM file
  1232. contains several smaller images which are to be pieced together to
  1233. create an entire picture. On V2.04 and above systems, the selected
  1234. portion of the image is scaled to fit into the selected portion of the
  1235. display. On earlier systems, or when using GShowImagePixels, images
  1236. are clipped against both the display area and the image in the file.
  1237. Any palette in the image file is used to remap the entire image to the
  1238. current palette, which is either the default palette or the palette
  1239. last successfully loaded with a background image. This remapping,
  1240. which must be done on a pixel-by-pixel basis, can take a while. Image
  1241. names are relative to "AmigaMUD:Images/".
  1242.  
  1243. Brushes are clipped against the display area. Any palette in a brush
  1244. file is used for remapping the colours of the brush. Brushes can have
  1245. either an explicit stored mask plane, or can have a transparent colour
  1246. indicated. If they have neither (i.e. aren't brushes), then they will
  1247. be blitted rectangularly into the window, just like images are. Brush
  1248. names are relative to "AmigaMUD:Brushes/".
  1249.  
  1250. The MUD program caches IFF ILBM files in memory, so that they can be
  1251. referenced repeatedly without disk I/O. This caching takes place in
  1252. the Amiga's "chip" memory, so that the images can be accessed quickly.
  1253. The current set of cached files, of various kinds, can be displayed
  1254. with a menu item in the MUD program.
  1255.  
  1256.  
  1257. Builtins for dealing with sound and voice output are:
  1258.  
  1259.     SPlaySound - start playing an IFF 8SVX sound sample
  1260.     SVolume - set the overall volume for sound playback
  1261.     VNarrate - narrate a set of phonemes
  1262.     VParams - set the overall voice output parameters
  1263.     VReset - reset the voice parameters to default values
  1264.     VSpeak - speak some English text
  1265.     VTranslate - translate English text to phonemes (this is not
  1266.     really an effects routine, since it executes entirely in the
  1267.     AmigaMUD server)
  1268.     VVolume - set the overall volume for speech output
  1269.     AbortEffect - cancel an ongoing effect (sound, speech, music)
  1270.  
  1271. Sound samples names are relative to "AmigaMUD:Sounds/" on the client
  1272. machine. If SMUS music is supported, it will be relative to
  1273. "AmigaMUD:Music/" with instruments from "AmigaMUD:Instruments/".
  1274.  
  1275.  
  1276. Builtins for dealing with "mouse buttons" and "mouse regions" are:
  1277.  
  1278.     AddButton/AddButtonPixels - add mouse button to graphics window
  1279.     AddRegion/AddRegionPixels - add a mouse region to the user's client
  1280.     ClearButtons - remove all mouse buttons from the client
  1281.     ClearRegions - remove all mouse regions from the client
  1282.     EraseButton - erase a given button from the client
  1283.     EraseRegion - erase a given region from the client
  1284.     SetButtonPen - set a pen to use when drawing mouse buttons
  1285.  
  1286. Note that none of these routines takes an agent as a parameter - they
  1287. all operate only on the active agent. A "mouse button" is a
  1288. rectangular "button" drawn on the graphics screen. It usually contains
  1289. a small amount of text. The user can click on the button with the left
  1290. mouse button, and trigger actions within the MUD. A "mouse region" is
  1291. similar to a button, except that it is an invisible rectangular region
  1292. that the user can click in. The icon editor in the Beauty Shop is a
  1293. mouse region.
  1294.  
  1295. Each button or region has an identifier (an integer), that is supplied
  1296. when it is created. When the user clicks on a mouse button, the
  1297. "button handler" routine associated with the character is called, with
  1298. that identifier as a parameter. If there is no button handler attached
  1299. to the character, then the clicks are ignored. The handler can do what
  1300. it wants - in the standard scenario the standard movement buttons echo
  1301. and execute a movement or "look around" command. When the user clicks
  1302. within a mouse region, the character's "mouse down" handler is called,
  1303. with the identifier of the region, and the relative offset of the
  1304. click within the region. If mouse regions overlap, and the mouse is
  1305. clicked in an overlap area, then the region with the lowest identifier
  1306. is selected and reported. The standard scenario uses a mouse region
  1307. with identifier 1000 over the entire left-half of the graphics window.
  1308. This is used to implement movement by clicking relative to the
  1309. character cursor.
  1310.  
  1311.  
  1312. The following effects functions deal with the character cursor:
  1313.  
  1314.     PlaceCursor/PlaceCursorPixels - display cursor at indicated position
  1315.     RemoveCursor - remove the cursor from the display
  1316.     SetCursorPattern - set the pattern for the cursor
  1317.     SetCursorPen - set which pen to draw the cursor with
  1318.  
  1319. The "character cursor" is a small one-colour bitmap that the MUD
  1320. client program can display to represent the location of the active
  1321. character within an overhead-view map area. Conceptually, this cursor
  1322. is the top-most of the graphics items, so it will appear "over" the
  1323. background image and any icons. The MUD program keeps track of the
  1324. graphics "behind" the cursor, so that the cursor can be moved (taken
  1325. away and put back elsewhere) without having to redraw the entire
  1326. image. The cursor can be up to 16 pixels high and 16 pixels wide,
  1327. just like icons. The default cursor in MUD is a large cross. The
  1328. standard scenario has been set up assuming a used cursor size of seven
  1329. pixels by seven pixels. A scenario does not have to use a cursor - it
  1330. is only present when requested by the scenario. It would be possible
  1331. to use brushes as a cursor, but the background behind them is not
  1332. automatically saved, so redrawing would be necessary when moving it.
  1333.  
  1334.  
  1335. The following builtins deal with icons:
  1336.  
  1337.     GDeleteIcon - delete an icon from a MUD client
  1338.     GNewIcon - specify a new icon pattern for a character
  1339.     GRedrawIcons - redraw the current set of icons
  1340.     GRemoveIcon - undraw a single icon
  1341.     GResetIcons - clear the set of icons
  1342.     GSetIconPen - set the colour to draw icons with
  1343.     GShowIcon - add an icon to a client
  1344.     GUndrawIcons - undraw all icons from the client
  1345.  
  1346. "Icons" are similar to the cursor in that they are 16 x 16 single-
  1347. colour patterns that are maintained by the MUD program. Conceptually,
  1348. they are behind the cursor, but in front of the main graphics. Like
  1349. the cursor, MUD saves the background imagery behind icons, so they can
  1350. be removed from the display without having to redraw the picture.
  1351. Unlike the cursor, the scenario does not have any control over the
  1352. placement of icons. MUD will place the first one in the top-left
  1353. corner of the graphics screen, the next one to the right of that, etc.
  1354. Empty icon slots are reused first, so most icons will appear in the
  1355. top-left corner of the display.
  1356.  
  1357. The standard scenario uses icons to represent other characters, both
  1358. player characters and non-player characters, in the same room as the
  1359. player. Players can edit their own icon (which does not show up on
  1360. their display) in the Beauty Shop, just as they can edit their cursor.
  1361. When a character moves from one room to another, that character's icon
  1362. should be removed from the displays of all players in the first room
  1363. and added to the displays of all players in the second room. Thus, the
  1364. various icon calls all take a 'who' parameter to make this easier to
  1365. code in the scenario.
  1366.  
  1367. When a player moves from one room to another, the set of visible icons
  1368. must be replaced. Thus, GResetIcons is available to reset MUD's idea
  1369. of which icons are visible, without having to actually erase them.
  1370. Sometimes the graphics imagery for the room needs to be changed,
  1371. without the player leaving the room. GRedrawIcons can be used to
  1372. redraw the current set of icons over a new background. GUndrawIcons
  1373. can be used to undraw the full set, thus allowing a small change to be
  1374. made in the background image, then followed by GRedrawIcons.
  1375.  
  1376. The pattern for an icon is obtained by MUDServ from the thing for the
  1377. character whose icon is to be displayed. Thus, property "p_pIcon",
  1378. like "p_pName" is predefined in an empty database, and the scenario
  1379. coder should use GNewIcon to change the value of a character's icon,
  1380. rather than assigning directly to that property. Another reason for
  1381. using GNewIcon is that it will immediately send the new definition of
  1382. the icon to any MUDs that have it cached, and those MUDs will
  1383. immediately display the new icon.
  1384.  
  1385.  
  1386. Text can be displayed in the graphics area using:
  1387.  
  1388.     GSetTextColour - set the colour to draw text in
  1389.     GText - draw a text string at the current position
  1390.  
  1391.  
  1392. "Tile" graphics are supported by the AmigaMUD system, even though the
  1393. first release of the standard scenario does not use them. The
  1394. available calls are:
  1395.  
  1396.     GDefineTile - define the appearance and size of a tile
  1397.     GDisplayTile - display the given tile at the current position
  1398.  
  1399. Tile graphics is a way to show a more detailed overhead view image
  1400. without having to create and save a huge image of the entire map area.
  1401. The map is divided into many small rectangles, or tiles, which are
  1402. displayed from a fixed set of such tiles. If the tiles are designed
  1403. carefully, the effect is that of a large hand-drawn image. Usually,
  1404. the map area is much larger than can be displayed in the graphics
  1405. view, so when the player nears the edge of the visible portion, the
  1406. display is scrolled, and a new set of tiles is drawn in the exposed
  1407. space. Smooth scrolling does the scrolling one pixel at a time instead
  1408. of one tile at a time. AmigaMUD does not directly support smooth
  1409. scrolling.
  1410.  
  1411. Here is a complete source file which shows a small, non-scrolling
  1412. example of displaying tiles:
  1413.  
  1414.     private t_tiles CreateTable()$
  1415.     use t_tiles
  1416.  
  1417.     define t_tiles TILE_WIDTH 32$
  1418.     define t_tiles TILE_HEIGHT 20$
  1419.  
  1420.     define t_tiles TILES_WIDTH 5$
  1421.     define t_tiles TILES_HEIGHT 5$
  1422.  
  1423.     source AmigaMUD:Src/Tiles/town.tile
  1424.     source AmigaMUD:Src/Tiles/trees.tile
  1425.     source AmigaMUD:Src/Tiles/river.tile
  1426.  
  1427.     GDefineTile(nil, 1, TILE_WIDTH, TILE_HEIGHT, makeTownTile())$
  1428.     GDefineTile(nil, 2, TILE_WIDTH, TILE_HEIGHT, makeTreesTile())$
  1429.     GDefineTile(nil, 3, TILE_WIDTH, TILE_HEIGHT, makeRiverTile())$
  1430.  
  1431.     define t_tiles proc makeTerrain()list int:
  1432.     list int terrain;
  1433.     int row, col;
  1434.  
  1435.     terrain := CreateIntArray(TILES_WIDTH * TILES_HEIGHT);
  1436.     for row from 0 upto TILES_HEIGHT - 1 do
  1437.         terrain[row * TILES_WIDTH] := 3;
  1438.         for col from 1 upto TILES_WIDTH - 1 do
  1439.         terrain[row * TILES_WIDTH + col] := 2;
  1440.         od;
  1441.     od;
  1442.     terrain[2] := 1;
  1443.     terrain
  1444.     corp;
  1445.  
  1446.     define t_tiles TileThing CreateThing(nil)$
  1447.     define t_tiles TileProp CreateIntListProp()$
  1448.     TileThing@TileProp := makeTerrain()$
  1449.  
  1450.     define t_tiles proc drawTiles()void:
  1451.     list int terrain;
  1452.     int row, col;
  1453.  
  1454.     terrain := TileThing@TileProp;
  1455.     GAMovePixels(nil, 0, 0);
  1456.     for row from 0 upto TILES_HEIGHT - 1 do
  1457.         for col from 0 upto TILES_WIDTH - 1 do
  1458.         GDisplayTile(nil, terrain[row * TILES_WIDTH + col]);
  1459.         GRMovePixels(nil, TILE_WIDTH, 0);
  1460.         od;
  1461.         GRMovePixels(nil, - TILE_WIDTH * TILES_WIDTH, TILE_HEIGHT);
  1462.     od;
  1463.     corp;
  1464.  
  1465. The '.tile' files simply define a tile as an array of ints:
  1466.  
  1467.     define t_tiles proc makeTownTile()list int:
  1468.     list int tile;
  1469.  
  1470.     tile := CreateIntArray(160);
  1471.     tile[0] := 0x1d1d1d1d;
  1472.     tile[1] := 0x1d1d1d1d;
  1473.     tile[2] := 0x1d1d1d1d;
  1474.     tile[3] := 0x1d1d0301;
  1475.     tile[4] := 0x0101031d;
  1476.     ...
  1477.     tile[155] := 0x1d1d0301;
  1478.     tile[156] := 0x01010303;
  1479.     tile[157] := 0x03030303;
  1480.     tile[158] := 0x03030303;
  1481.     tile[159] := 0x03030303;
  1482.     tile
  1483.     corp;
  1484.  
  1485. In this example, the tile definitions are all created on the server
  1486. and sent to the client via calls to GDefineTile. The MUD client
  1487. programs cache tile definitions just like they do icons and the
  1488. cursor. Thus, the scenario need only send the tile definitions to the
  1489. client once per session. Note, however, that there is no way by which
  1490. the scenario can know if the client has seen a tile yet. Thus, the
  1491. scenario has to keep track of that by itself. Re-sending a tile
  1492. definition does not hurt, but is expensive in terms of communication.
  1493. Builtin "GScrollRectangle" can be used to scroll the tile display.
  1494.  
  1495. Another way to define tiles is to have a file containing them on the
  1496. remote client machine. Then, calls to GSetImage/GShowImage can be used
  1497. to piece the full view together from a single file containing a
  1498. standard set of tiles. GDefineTile/GDisplayTile can then be used for
  1499. special tiles, that are not part of the standard set. This was the
  1500. original plan for the use of tiles in AmigaMUD.
  1501.  
  1502. GDefineTile takes an array of integers as the definition of the tile.
  1503. The array must be of size WIDTH * HEIGHT / 4, where WIDTH and HEIGHT
  1504. are the size of the tile. This gives one byte per pixel in the tile.
  1505. The byte gives the colour of the corresponding pixel of the tile. The
  1506. bytes are supplied by rows, with the colour of the top-left pixel of
  1507. the tile being the high-order byte of the first integer in the array.
  1508. If the tile width is a multiple of 4, then hexadecimal values for the
  1509. integers provide a somewhat readable way of defining the tiles, as in
  1510. the above example.
  1511.  
  1512.  
  1513. Examples of defining effects were given above. The builtin functions
  1514. involved are:
  1515.  
  1516.     CallEffect - call up a previously defined effect
  1517.     DefineEffect - start the definition of an effect
  1518.     EndEffect - end the definition of an effect
  1519.     KnowsEffect - ask if a client knows an effect
  1520.  
  1521. CallEffect is used to call up a previously defined effect. If the
  1522. selected client (the MUD program) does not know an effect with the
  1523. indicated effect-id, then it will simply do nothing. CallEffect is
  1524. like a subroutine call of effects. It can be used from the effects
  1525. "top-level" or from inside some other effects routine. KnowsEffect
  1526. tells the scenario whether or not a client knows an effect. If the
  1527. client does not know the effect, that effect should be defined for the
  1528. client before it is called. EndEffect ends the definition of the
  1529. effect currently being defined. It is like the "corp" to end the body
  1530. of an AmigaMUD function. Note that effect id 0 is special - any effect
  1531. with that id is removed from the client cache as soon as it is called.
  1532. Thus, this id can be re-used many times, as a temporary effect id.
  1533.  
  1534. It is possible to nest the definition of effects. This can actually
  1535. happen quite frequently. For example, the effect which draws the
  1536. normal view of the mini-mall in the standard scenario calls on the
  1537. effects routines for vertical, horizontal and diagonal doors. When a
  1538. player first enters the game in the Arrivals Room, the scenario wants
  1539. to run the effect for the mini-mall view. The client does not know
  1540. that effect, which the scenario learns from KnowsEffect, so the
  1541. scenario uses DefineEffect to define the mini-mall view effect. The
  1542. definition of that effect calls scenario routines for the doors, which
  1543. in turn check to see if the client knows the effects for the doors. A
  1544. new client doesn't know those effects either, so the routines all
  1545. define the effects. This happens in the middle of the definition of
  1546. the mini-mall view. Since this is a common occurrence, and is
  1547. difficult for the scenario to work-around, AmigaMUD was made to
  1548. support it, by allowing nested effects definitions.
  1549.  
  1550.  
  1551. The AmigaMUD server is single threaded. That means that it is only
  1552. running one thread of code execution at a time. Thus, it should never
  1553. wait for something to happen in a client, since whatever it is waiting
  1554. for could take a long time, especially if it has to wait for the user.
  1555. Hand-drawn graphics are usually nicer than the kind of graphics that
  1556. can be drawn using effects. So, it is desireable that a scenario call
  1557. up that kind of graphics, from files on the client machine, rather
  1558. than use pictures drawn via effects calls. However, if the client
  1559. machine does not have the needed bitmap image, the drawn effect should
  1560. be displayed. This decision can only be made on the client machine. To
  1561. avoid having the AmigaMUD server wait for the answer to that question
  1562. from a client, the result of that question must also be executed on
  1563. the client. This means that the effects interpreting code in the
  1564. clients needs to be able to support conditional execution of effects.
  1565. Currently this conditional execution is very limited. The builtin
  1566. functions involved are:
  1567.  
  1568.     Else - flip the conditional execution of effects
  1569.     FailText - display text along with the name of the missing file
  1570.     Fi - end a conditional effects section
  1571.     IfFound - start a conditional effects section
  1572.  
  1573. IfFound is the effect that is the condition test. It tests the "found"
  1574. flag, which is set in the client by the GLoadBackGround, GSetImage,
  1575. GShowImage, GShowBrush, SPlaySound, and MPlaySong effects requests.
  1576. These requests all specify the name of a file to be accessed. If the
  1577. file is found on the client, then the "found" flag is set, else that
  1578. flag is cleared. IfFound tells the client to execute the following
  1579. effects requests only if the file was found. The Else effect tells the
  1580. client to reverse the current value of the "found" flag. Thus, if the
  1581. client was currently executing effects, it will stop doing so, and if
  1582. it was not executing effects it will start doing so. The Fi effect
  1583. marks the end of the conditional effect section - effects will always
  1584. be enabled after the Fi effect. Thus, the normal structure of
  1585. conditional effects is like this:
  1586.  
  1587.     GSetImage(client, "file-name");
  1588.     IfFound(client);
  1589.     GShowImage(client, "", fiX, fiY, fiW, fiH, fdX, fdY, fdW, fdH);
  1590.     Else(client)
  1591.     /* effects code to approximate the image */
  1592.     Fi(client);
  1593.  
  1594. or
  1595.  
  1596.     SPlaySound(client, "file-name", effectId);
  1597.     IfFound(client);
  1598.     Else(client);
  1599.     FailText(client, "text message describing the sound");
  1600.     Fi(client);
  1601.  
  1602. Either the image is shown (the empty string says to use the filename
  1603. set (and loaded) with GSetImage), or the failure string is displayed.
  1604. FailText will include the name of the file that was not found in its
  1605. printout, so that the player can tell what file he/she is missing.
  1606. Note that there is no "not" in the effects condition, so in the second
  1607. example there is nothing between the IfFound and Else calls.
  1608.  
  1609.  
  1610. The Database
  1611.  
  1612.  
  1613. The database in AmigaMUD, stored in files MUD.data and MUD.index,
  1614. contains everything that is permanent about the MUD scenario: rooms,
  1615. objects, characters, players, machines, code, text, etc. The database
  1616. is maintained on disk, and does not have to be all in memory. Thus,
  1617. you can run a very large database without having to have many
  1618. megabytes of memory. The AmigaMUD server program, MUDServ, maintains a
  1619. cache of the most recently used database items. This cache is a
  1620. "write-back" cache. This means that changes to database items are not
  1621. written to the disk immediately. The changes are entered into the
  1622. database cache, and only get written to disk when the database cache
  1623. is flushed. The database cache is flushed to disk when:
  1624.  
  1625.     - the server is shut down
  1626.     - the Flush builtin is called
  1627.     - the MUDFlush program is run
  1628.     - the cache has no room for a needed entry
  1629.  
  1630. In the last case, entries written to disk can then be deleted from the
  1631. cache, to make room for new entries.
  1632.  
  1633. In actual fact, the implementation of MUDServ is quite a bit more
  1634. complicated. There are more levels of caches that are not visible to
  1635. the user or the scenario programmer. One example is this:
  1636.  
  1637.     "things", "properties", etc. have use counts on them, which
  1638.     indicate how many pointers to them exist in the database. This is
  1639.     done so that database entries can be deleted when the last pointer
  1640.     to them is removed. The sequence of actions that happens when a
  1641.     player or machine picks an object up is something like this:
  1642.  
  1643.     - append object to character's inventory
  1644.     - delete object from room's contents
  1645.  
  1646.     For a short period of time the object is on both lists. That means
  1647.     that it's use count is one higher. Almost immediately the use
  1648.     count goes back to what it was before. So, there really is no need
  1649.     to write the object back to disk. MUDServ has a cache of changed
  1650.     thing use counts, and, when flushing the database, will write to
  1651.     the database cache any thing whose use count is different from the
  1652.     one stored on disk. Similar caches exist for other types of
  1653.     entries.
  1654.  
  1655. Something to be very careful of is the fact that a reference to
  1656. something from a local variable or function parameter does not count
  1657. as a reference from the database. Note the order of the operations in
  1658. the "pick up" example just above. The object is added to the inventory
  1659. list before it is removed from the contents list. This is very
  1660. important! If the operations are done in the other order, then if
  1661. there are no other references to the object than the ones involved
  1662. here, it will have no references for a short period of time. This is
  1663. bad, since the system will conclude that the object can be freed, and
  1664. will remove it from the database and reuse the space! If your code
  1665. appears to corrupt the database, check that you have not let go of
  1666. something before you are truly done with it. The reader will certainly
  1667. want to ask why I chose to implement things this way. The answer is
  1668. mostly one of efficiency - changing the use count all the time takes a
  1669. lot of extra instructions in the server. I estimate that typical
  1670. execution would slow down by a factor of two or three, and a lot of
  1671. extra code would have to be added in order to properly make references
  1672. from local variables and parameters count as database references.
  1673.  
  1674. Another fairly important cache is the function cache. When functions
  1675. are stored in the database, it is as a sequence of bytes. This is not
  1676. the form that the interpreter understands. So, when a function needs
  1677. to be interpreted, it must be read from the database and converted
  1678. into the interpretable form. The server maintains a cache of functions
  1679. in this interpretable form. When the server runs low on memory, it
  1680. will delete non-active functions from this cache. Similar caches exist
  1681. for tables and grammars.
  1682.  
  1683.  
  1684. Every entry in the database must be pointed to by some other entry in
  1685. the database, with the sole exceptions being the public table and a
  1686. list of active machines. When a new database is created by the MUDCre
  1687. program, it contains very little. Everything else must be explicitly
  1688. created and pointed to by something in the database. Builtin functions
  1689. exist to create all kinds of database entries:
  1690.  
  1691.     CreateActionList - create a list of actions
  1692.     CreateActionListProp - create a property of that type
  1693.     CreateActionProp - create a property that can reference actions
  1694.     CreateBoolProp - create a boolean property
  1695.     CreateFixedList - create an empty list of fixeds
  1696.     CreateFixedListProp - create a property of that type
  1697.     CreateFixedProp - create a fixed property
  1698.     CreateGrammarProp - create a property that references grammars
  1699.     CreateIntArray - create and initialize a list of ints
  1700.     CreateIntList - create an empty list of ints
  1701.     CreateIntListProp - create a property of that type
  1702.     CreateIntProp - create an int property
  1703.     CreateStringProp - create a string property
  1704.     CreateTable - create a new table
  1705.     CreateTableProp - create a property that can reference tables
  1706.     CreateThing - create a new thing
  1707.     CreateThingList - create an empty list of things
  1708.     CreateThingListProp - create a property of that type
  1709.     CreateThingProp - create a property that references other things
  1710.  
  1711.  
  1712. Lists of several types exist in AmigaMUD. In addition to the indexing
  1713. operation that is available in the AmigaMUD programming language,
  1714. several builtin functions deal with lists. They are "generic" in the
  1715. sense that they work with any type of list, and, when appropriate, the
  1716. corresponding type of element:
  1717.  
  1718.     AddHead - insert an element onto the head of a list
  1719.     AddTail - append an element onto the tail of a list
  1720.     DelElement - delete an element from a list
  1721.     FindChildOnList - search for a thing's child in a list
  1722.     FindElement - search for an element in a list
  1723.     FindFlagOnList - search for a flagged thing in a list
  1724.     FindIntOnList - search for a thing with an appropriate int property
  1725.     RemHead - remove the first element from a list
  1726.     RemTail - remove the last element from a list
  1727.  
  1728.  
  1729. There are also a number of builtins that deal with "things" in the
  1730. database:
  1731.  
  1732.     ClearThing - remove all properties from a thing
  1733.     DescribeKey - let SysAdmin find out what a key value is
  1734.     GetThingStatus - return the status of a thing
  1735.     GiveThing - change the owner of a thing
  1736.     IsAncestor - check for an ancestor of a thing
  1737.     Mine - check the ownership of a thing
  1738.     Owner - find the owner of a thing
  1739.     Parent - find the parent of a thing
  1740.     SetParent - set a new parent for a thing
  1741.  
  1742.  
  1743. Dealing With Player Characters
  1744.  
  1745.  
  1746. Player characters are the entities in AmigaMUD that represent players.
  1747. They are of type 'character', which is one of the basic types in the
  1748. system. Associated with each character are a number of functions,
  1749. which the system will automatically call in appropriate circumstances.
  1750. There are builtin functions to set these function on the active
  1751. character. The builtins all return whatever function was the previous
  1752. value (or 'nil'). Those functions are:
  1753.  
  1754.     SetCharacterActiveAction - set the action which is called when the
  1755.     player re-enters the game. The action is not called when the
  1756.     player first enters the game - see SetNewCharacterAction for
  1757.     that situation. Typically, a scenario will use this action to
  1758.     initialize the graphics for a client, and give the client a
  1759.     description of his/here location, who is nearby, who is in the
  1760.     MUD, etc.
  1761.  
  1762.     SetCharacterButtonAction - set the action which is called when the
  1763.     player clicks on a mouse-click button. The action is passed
  1764.     the code for the button that was clicked. The action will
  1765.     typically echo and execute a standard command like a movement
  1766.     command. The on-line building code in the standard scenario
  1767.     uses many mouse-click buttons for other purposes.
  1768.  
  1769.     SetCharacterEffectDoneAction - set the action which is called when
  1770.     an ongoing effect (sound, voice, music) completes in the
  1771.     client. The action is passed the type and identifier of the
  1772.     effect which has completed.
  1773.  
  1774.     SetCharacterIdleAction - set the action which is called when the
  1775.     player leaves the game. This action can do things like
  1776.     removing light from a room if the leaving player has the only
  1777.     source of light. It will usually tell everyone in the room
  1778.     that the player is leaving.
  1779.  
  1780.     SetCharacterInputAction - set the action which is called when the
  1781.     player enters an input line. Input lines are usually commands
  1782.     which are sent through builtin "Parse", but some special
  1783.     processing is often done. The standard scenario checks for a
  1784.     leading quote (") or colon (:), and handles command aliases.
  1785.  
  1786.     SetCharacterMouseDownAction - set the action which is called when
  1787.     the player clicks within a mouse region. The action is passed
  1788.     the identifier for the region, and the offset of the click
  1789.     within the region. The standard scenario uses this action to
  1790.     handle movement when the player clicks in the left-hand
  1791.     portion of the graphics area. Another such region is used to
  1792.     implement the icon/cursor editor.
  1793.  
  1794.     SetCharacterRawKeyAction - set the action which is called when the
  1795.     player presses a numeric keypad key or the HELP key. The
  1796.     action is passed a keycode for the key pressed. Like mouse
  1797.     buttons, these events are usually made to trigger standard
  1798.     movement or other commands.
  1799.  
  1800.     SetNewCharacterAction - set the action that is executed whenever a
  1801.     newly created character first enters the game. This action is
  1802.     usually used to initialize the character's handlers and any
  1803.     scenario-specific stuff. Note that when this action is called,
  1804.     the character's "active" action will not also be called, so
  1805.     any of the stuff that it does that is also needed here should
  1806.     be done explicitly (or this action can directly call the
  1807.     normal "active" action).
  1808.  
  1809. There are circumstances when "nesting" of some of these handler
  1810. actions is useful. For example, the icon/cursor editor sets up a
  1811. mouse-button handler to handle the three new buttons it displays. It
  1812. is better if it does not assume anything about what the previous
  1813. handler was. So, when it calls SetCharacterButtonAction, it should
  1814. record the returned value, and when it is finished, restore it. Also,
  1815. unless the icon/cursor editor code removes the standard movement
  1816. buttons, the player can still click on them. The code could chose to
  1817. ignore such clicks, but can also simply pass them on to the previous
  1818. routine, which it has saved away. If that previous routine is not the
  1819. standard movement one, it can do the same thing, resulting in a whole
  1820. stack of actions, one of which should understand the mouse click. How
  1821. this sort of thing is handled depends on what the scenario writer
  1822. wants to do.
  1823.  
  1824. Nesting used to be used for things like "idle actions", so that
  1825. special areas like Questor's Office could arrange for a character who
  1826. exits from the game to be moved out of Questor's Office so that other
  1827. characters can go in. With the addition of the ability to run from a
  1828. backup database, and the "reset actions" run when such a database is
  1829. used, more generality was useful. So, a list of "idle actions" was
  1830. setup, and now the Questor's Office code simply needs to add an action
  1831. to that code, and it will be called along with any others in that
  1832. list, when the character goes idle.
  1833.  
  1834. Such complexities can happen in other cases as well. For example, if
  1835. the player walks out of the Beauty Shop while in the middle of editing
  1836. his/her cursor, what should happen, and how is it achieved? The
  1837. interested player might want to study that code and to experiment to
  1838. see what it does.
  1839.  
  1840. There are a number of other builtin functions that deal with player
  1841. characters:
  1842.  
  1843.     BootClient - force the player off, politely
  1844.     CanEdit - can the client do editing?
  1845.     ChangeName - change the player name
  1846.     Character - return the character of the thing
  1847.     CharacterLocation - return the location of the character
  1848.     CharacterTable - return the private table of the character
  1849.     CharacterThing - return the main "thing" of the character
  1850.     ClientVersion - return the version of the client program
  1851.     CreateCharacter - create a new player character
  1852.     DestroyCharacter - destroy a player character
  1853.     IsApprentice - is the player an apprentice?
  1854.     IsNormal - is the player normal (not apprentice or wizard)?
  1855.     IsProgrammer - is the player an apprentice or wizard?
  1856.     IsWizard - is the player a wizard?
  1857.     MakeApprentice - make the player an apprentice
  1858.     MakeNormal - make the player normal
  1859.     MakeWizard - make the player a wizard
  1860.     NukeClient - force the player off, impolitely (and dangerously)
  1861.     SetCharacterLocation - move a character (also SetLocation)
  1862.     ThingCharacter - return the character associated with a thing
  1863.  
  1864.  
  1865. Dealing With Machines
  1866.  
  1867.  
  1868. Machines are the method used in AmigaMUD to cause events to happen
  1869. independent of any player character. Machines can have icons and
  1870. appear in rooms just like player characters can. They can speak,
  1871. whisper, hear, move around, pick things up, etc. Together, players and
  1872. machines are referred to as "agents". The builtin functions discussed
  1873. above under "Dealing With Player Characters" do not apply to machines.
  1874. There are specific functions for dealing with machines, and there are
  1875. a set of functions that work with any agent. The latter ability is
  1876. quite important, as it allows a lot of scenario code to not care
  1877. whether it is executing on behalf of a player or a machine.
  1878.  
  1879. Machines are created using "CreateMachine". It takes a thing, which
  1880. will become the main thing for the machine just like player characters
  1881. have a main thing which hold their properties. It also takes a second
  1882. thing which is the room to create the machine in, and an action to
  1883. execute on behalf of the new machine to start the machine running.
  1884. Note that CreateMachine creates a new data structure for the machine,
  1885. which is stored in the database and manipulated by the server, but the
  1886. new structure is not visible to scenario programmers other than
  1887. through a few specific builtin functions. Builtins specific to
  1888. machines are:
  1889.  
  1890.     CreateMachine - create and start a new machine
  1891.  
  1892.     DestroyMachine - destroy a machine
  1893.  
  1894.     FindMachineIndexed - globally find a machine
  1895.  
  1896.     SetMachineActive - set the action which the system will call
  1897.     automatically whenever the server is restarted. This allows
  1898.     the machine to start itself going again.
  1899.  
  1900.     SetMachineIdle - set the action which the system will call when
  1901.     the server is shutting down. This allows the machine to
  1902.     properly save its state and prepare for restart.
  1903.  
  1904.     SetMachineOther - set the action which the system will call when
  1905.     any message is sent to the room the machine is in using any of
  1906.     'OPrint', 'ABPrint', 'Pose' or 'Say'. The string so sent is
  1907.     passed as an argument to this routine. This facility is fairly
  1908.     powerful, and allows machines to participate in nearly all
  1909.     activities. However, this power is easy to misuse. The
  1910.     activities this routine sets up can be expensive, so try not
  1911.     to use it unless absolutely necessary. For example, the
  1912.     standard scenario has more specific, hence cheaper, ways of
  1913.     watching who enters and leaves a room. Also, beware of setting
  1914.     up an infinite recusive loop using this facility.
  1915.  
  1916.     SetMachinePose - set the action which the system will call
  1917.     whenever any agent in the same room as the machine does a pose
  1918.     using the "Pose" builtin. The action is passed the entire pose
  1919.     message, so the machine will usually want to split off the
  1920.     name of the agent doing the pose by using SetTail/GetWord.
  1921.  
  1922.     SetMachineSay - set the action which the system will call whenever
  1923.     any agent in the same room as the machine speaks out loud. The
  1924.     action is passed the entire speech message, so it will want to
  1925.     use SetSay to split it up.
  1926.  
  1927.     SetMachineWhisperMe - set the action which the system will call
  1928.     whenever any agent in the same room as the machine whispers
  1929.     specifically to the machine. The action is passed the full
  1930.     whisper message, and so will want to use builtin SetWhisperMe
  1931.     to split it up.
  1932.  
  1933.     SetMachineWhisperOther - set the action which the system will call
  1934.     if the machine overhears someone in the same room whispering
  1935.     to someone else. The action is passed the full whisper
  1936.     message, and so will want to use builtin SetWhisperOther to
  1937.     split it up.
  1938.  
  1939. When machines execute, they have the access rights of the player who
  1940. created them. So, a player can create a machine that can access things
  1941. which other players cannot.
  1942.  
  1943. The standard scenario has five special machines: Packrat, Caretaker,
  1944. Postman, Questor and the rock-pile. More generic machines are used for
  1945. monsters in the Proving Grounds. Some of those monsters have special
  1946. capabilities that others do not. The main difference here is that the
  1947. special machines always exist, but the others are created and
  1948. destroyed in response to player (or other machine!) actions. See
  1949. file "Scenario.txt" for more details on how machines are handled
  1950. there. Here is an example of a simple machine that wanders randomly
  1951. and minimally interacts with other agents:
  1952.  
  1953.     /* grab some stuff from the scenario: */
  1954.     use t_util
  1955.  
  1956.     /* a new table to put new symbols in: */
  1957.     private tp_frog CreateTable()$
  1958.     use tp_frog
  1959.  
  1960.     /* the routine which Frog executes on each "step": */
  1961.     define tp_frog proc frogStep()void:
  1962.     int direction;
  1963.  
  1964.     if not ClientsActive() then
  1965.         After(60.0, frogStep);
  1966.     else
  1967.         direction := Random(12);
  1968.         if TryToMove(direction) then
  1969.         MachineMove(direction);
  1970.         fi;
  1971.         After(IntToFixed(10 + Random(10)), frogStep);
  1972.     fi;
  1973.     corp;
  1974.  
  1975.     /* the routine used to start up Frog */
  1976.     define tp_frog proc frogStart()void:
  1977.  
  1978.     After(10.0, frogStep);
  1979.     corp;
  1980.  
  1981.     /* the routine used to restart Frog: */
  1982.     define tp_frog proc frogRestart()void:
  1983.  
  1984.     After(10.0, frogStep);
  1985.     corp;
  1986.  
  1987.     /* the routine to handle Frog overhearing normal speech */
  1988.     define tp_frog proc frogHear(string what)void:
  1989.     string speaker, word;
  1990.  
  1991.     speaker := SetSay(what);
  1992.     /* Frog croaks if he hears his name */
  1993.     while
  1994.         word := GetWord();
  1995.         word ~= ""
  1996.     do
  1997.         if word == "frog" then
  1998.         DoSay("Croak!");
  1999.         fi;
  2000.     od;
  2001.     corp;
  2002.  
  2003.     /* the routine to handle someone whispering to Frog: */
  2004.     define tp_frog proc frogWhispered(string what)void:
  2005.     string whisperer, word;
  2006.  
  2007.     whisperer := SetWhisperMe(what);
  2008.     /* Frog ribbets if he is whispered his name */
  2009.     while
  2010.         word := GetWord();
  2011.         word ~= ""
  2012.     do
  2013.         if word == "frog" then
  2014.         DoSay("Ribbet!");
  2015.         fi;
  2016.     od;
  2017.     corp;
  2018.  
  2019.     /* the routine to handle Frog overhearing a whisper: */
  2020.     define tp_frog proc frogOverhear(string what)void:
  2021.     string whisperer, whisperedTo, word;
  2022.  
  2023.     whisperer := SetWhisperOther(what);
  2024.     whisperedTo := GetWord();
  2025.     /* Frog simply blabs out loud whatever he overhears. */
  2026.     DoSay(whisperer + " whispered to " + whisperedTo + ": " + GetTail());
  2027.     corp;
  2028.  
  2029.     /* the routine to see someone doing a pose: */
  2030.     define tp_frog proc frogSaw(string what)void:
  2031.     string poser, word;
  2032.  
  2033.     SetTail(what);
  2034.     poser := GetWord();
  2035.     /* Frog gets excited if you reference him in a pose. */
  2036.     while
  2037.         word := GetWord();
  2038.         word ~= ""
  2039.     do
  2040.         if word == "frog" then
  2041.         Pose("", "jumps up and down excitedly.");
  2042.         fi;
  2043.     od;
  2044.     corp;
  2045.  
  2046.     /* the function to create the Frog: */
  2047.     define tp_frog proc createFrog(thing where)void:
  2048.     thing frog;
  2049.  
  2050.     frog := CreateThing(nil);
  2051.     frog@p_pDesc := "Frog is just a plain old frog.");
  2052.     frog@p_pStandard := true;
  2053.     SetupMachine(frog);
  2054.     CreateMachine("Frog", frog, where, frogStart);
  2055.     ignore SetMachineActive(frog, frogRestart);
  2056.     ignore SetMachineSay(frog, frogHear);
  2057.     ignore SetMachineWhisperMe(frog, frogWhispered);
  2058.     ignore SetMachineWhisperOther(frog, frogOverhear);
  2059.     ignore SetMachinePose(frog, frogSaw);
  2060.     corp;
  2061.  
  2062.     /* create Frog and start him up: */
  2063.     createFrog(Here())$
  2064.  
  2065. This code calls functions "TryToMove", "MachineMove" and "DoSay" from
  2066. the standard scenario. They are used so that this Frog machine will
  2067. operate correctly within that scenario. In 'createFrog', Frog is given
  2068. a description, and is set to be "standard". This simply means that no
  2069. "the" or "a" will be used in front of his name, since I chose to make
  2070. his name, "Frog", be a proper name. "SetupMachine" sets up his (empty)
  2071. inventory list, etc. The code here does not reference anything else
  2072. from the standard scenario. CreateMachine will have attached "Frog" as
  2073. his p_pName. Because Frog just moves in a random direction, he can
  2074. take a while to get out of rooms that have only one exit.
  2075.  
  2076.  
  2077. Dealing With Agents in General
  2078.  
  2079.  
  2080. As mentioned above, an agent is either a player character or a
  2081. machine. All agents have a current location (or 'nil' if they are not
  2082. in any room), and can ask the system to execute actions on their
  2083. behalf at a later time. Some builtins and some scenario code is setup
  2084. so that it assumes it is executing on behalf of the agent that it is
  2085. affecting. Sometimes it becomes necessary to make an agent execute
  2086. such code, even though that agent is not currently active. The
  2087. "ForceAction" builtin is provided for that purpose. It temporarily
  2088. switches identity to that of the agent to be forced, and then executes
  2089. the action passed. After the action is complete, the identity will be
  2090. switched back to that of the agent who ran ForceAction. Note that this
  2091. kind of thing can be nested, so the scenario programmer must be
  2092. careful to not accidentally cause infinite nesting.
  2093.  
  2094. There are a number of basic builtins for dealing with agents:
  2095.  
  2096.     After - trigger an action to happen in the future
  2097.     AgentLocation - return the location of the specified agent
  2098.     ForceAction - force an agent to execute an action
  2099.     ForEachAgent - execute an action for each agent in a room
  2100.     ForEachClient - execute an action for each active client
  2101.     Here - return the location of the active agent
  2102.     Pose - have the active agent do a pose
  2103.     Say - have the active agent speak out loud
  2104.     SetAgentLocation - move the specified agent to a room
  2105.     SetLocation - move the active agent to a room
  2106.     Whisper - have the active agent whisper to another
  2107.     FindAgent - find an agent by name in the current room
  2108.     FindAgentAt - find an agent by name in some other room
  2109.  
  2110. Sometimes a scenario needs to find an agent matching some kind of
  2111. specification. For example, when trying to determine if the current
  2112. room is dark or not, the scenario wants to see if any agent in the
  2113. room is glowing, or is carrying something that is glowing. This kind
  2114. of search can be done using some global variables (properties on some
  2115. fixed thing) and ForEachAgent. Such a search can be expensive,
  2116. however, so AmigaMUD provides a number of builtin functions that can
  2117. perform some searches more efficiently.
  2118.  
  2119.     FindAgentAsChild - find an agent with a given parent
  2120.     FindAgentAsDescendant - find an agent with a given ancestor
  2121.     FindAgentWithChildOnList - e.g. find agent carrying something
  2122.     FindAgentWithFlag - find an agent with a flag set
  2123.     FindAgentWithFlagOnList - e.g. find agent carrying glowing object
  2124.     FindAgentWithNameOnList - e.g. find agent carrying an "xxxx"
  2125.  
  2126.  
  2127. Symbol Functions
  2128.  
  2129.  
  2130. Tables in AmigaMUD are usually only needed by advanced scenario
  2131. programmers. For example, the standard scenario uses them in the build
  2132. code. The relevant builtin functions are:
  2133.  
  2134.     DefineAction - enter an action into a table
  2135.     DefineCounter - enter an int property into a table
  2136.     DefineFlag - enter a bool property into a table
  2137.     DefineString - enter a string property into a table
  2138.     DefineTable - enter a table into a table
  2139.     DefineThing - enter a thing into a table
  2140.     DeleteSymbol - delete a symbol from a table
  2141.     DescribeSymbol - describe a symbol in a table
  2142.     FindActionSymbol - find a symbol for an action
  2143.     FindThingSymbol - find a symbol for a thing
  2144.     IsDefined - test if a symbol is defined in a table
  2145.     LookupAction - look up an action symbol
  2146.     LookupCounter - look up an int property symbol
  2147.     LookupFlag - look up a bool property symbol
  2148.     LookupString - look up a string property symbol
  2149.     LookupTable - look up a table in another table
  2150.     LookupThing - look up a thing in a table
  2151.     MoveSymbol - move a symbol from one table to another
  2152.     RenameSymbol - rename a symbol in a table
  2153.     ScanTable - call a function for each symbol in a table
  2154.     ShowTable - show the symbols in a table
  2155.     UnUseTable - remove a table from the "in use" list
  2156.     UseTable - add a table to the "in use" list
  2157.  
  2158.  
  2159. Security Issues
  2160.  
  2161.  
  2162. Security is an odd thing to worry about in a game, but there are some
  2163. aspects of security that arise in a game like AmigaMUD. The first
  2164. aspect is that of the security of the system running the server. The
  2165. builtin function "Execute" allows any arbitrary string to be executed
  2166. as an AmigaDOS command. This can be quite dangerous if not protected
  2167. properly. Second, the system should allow one wizard or apprentice to
  2168. protect his internal structures and properties from tampering by other
  2169. wizards or apprentices. Third, the scenario itself should be
  2170. constructed so as to not allow "cheating". For example, there
  2171. shouldn't be a way for a player to easily get lots of experience in
  2172. the Proving Grounds. This can be considered to be unfair to other
  2173. players who do not try to get around the proper methods.
  2174.  
  2175. There is very little that can be said about the third aspect of
  2176. security - if the scenario allows cheating, then it has a bug. The
  2177. reader is warned that there are things in the standard scenario that
  2178. players can take advantage of, some of which might seem quite
  2179. surprising at first. I have fixed all of the methods that I know of
  2180. and want fixed. I have deliberately left some very minor methods of
  2181. cheating alone, and in one case, have carefully constructed things to
  2182. allow something that some might consider cheating.
  2183.  
  2184.  
  2185. The first aspect of security, that of preventing people from doing
  2186. things like formatting the hard drive of the host system, is the most
  2187. important. The basic protection mechanism here is that of restricting
  2188. such functions to be only executable by the special character
  2189. SysAdmin, or by code owned by SysAdmin. A further restriction is that
  2190. by default the system will not allow SysAdmin to login remotely. In
  2191. any case, the owner of a system running the AmigaMUD server should
  2192. never give out the password to the SysAdmin character. Also, the owner
  2193. should change SysAdmin's password to something other than the standard
  2194. one that the system is shipped with.
  2195.  
  2196. The person who runs the host system, and has access to SysAdmin,
  2197. should also be quite careful with scenario code he/she writes. Such
  2198. code, unless setup otherwise, runs with the full privileges of
  2199. SysAdmin. This is required in cases like the usenet access code, since
  2200. Execute is needed to call up the various UUCP commands. Be very
  2201. careful with any code which calls Execute, or which writes to files on
  2202. the server. Do not write and publish (by making it available in a
  2203. table that others can "use") a function which calls Execute with its
  2204. parameter. Do not attach such a function to things as a property whose
  2205. name others can see. Do not add such a function to a list of functions
  2206. that others can get at. Program defensively, and use Execute and
  2207. FileOpenForWrite as little as possible.
  2208.  
  2209. Some of the builtin functions in AmigaMUD can only be executed by
  2210. SysAdmin. This is not visible by inspecting the functions, but is
  2211. enforced at runtime by the functions themselves. The restricted
  2212. functions are:
  2213.  
  2214.     DescribeKey         (real and effective)
  2215.     SetNewCharacterAction    (real and effective)
  2216.     SetSingleUser        (real and effective)
  2217.     SetRemoteSysAdminOK     (real and effective)
  2218.     SetMachinesActive        (real and effective)
  2219.     CreateCharacter        (effective)
  2220.     DestroyCharacter        (effective)
  2221.     NewCreationPassword     (real and effective)
  2222.     FindKey            (real and effective)
  2223.     DumpThing            (real and effective)
  2224.     SetContinue         (real and effective)
  2225.     ShutDown            (real and effective)
  2226.     APrint            (effective)
  2227.     FileOpenForRead        (effective)
  2228.     FileOpenForWrite        (effective)
  2229.     FileOpenForUpdate        (effective)
  2230.     FileClose            (effective)
  2231.     FileRead            (effective)
  2232.     FileReadBinary        (effective)
  2233.     FileWrite            (effective)
  2234.     FileWriteBinary        (effective)
  2235.     FileSeek            (effective)
  2236.     Execute            (effective)
  2237.  
  2238. The functions marked as "(real and effective)" can only be used if
  2239. both the real and effective user are SysAdmin, i.e. only by SysAdmin
  2240. executing at the command level or executing functions owned by
  2241. SysAdmin. Those marked "(effective)" can be executed by anyone if they
  2242. are in a function owned by SysAdmin which is not marked "utility" (see
  2243. below).
  2244.  
  2245. There are two ways in which special privileges can be given to
  2246. characters other than SysAdmin. The most obvious is by making those
  2247. characters wizards or apprentices. The less obvious is a feature of
  2248. the standard scenario, where "builders" can do very limited
  2249. programming. Within the "PlayPen" room, or any room built off of it,
  2250. all players have builder privileges. Code built as a builder is not
  2251. marked "utility", so it runs with only the privileges of the builder.
  2252. Thus, it should be fairly innocuous. Also, characters who have
  2253. temporary builder status because they are in the PlayPen cannot modify
  2254. the global symbol table.
  2255.  
  2256. Wizards and apprentices can write code which is marked as "utility".
  2257. If this code is executed on behalf of SysAdmin, then it may run with
  2258. SysAdmin's access rights. This is dangerous! So, if you are SysAdmin
  2259. on a MUD where you have given wizard or apprentice status to players
  2260. controlled by someone other than yourself, you should not use the
  2261. SysAdmin character as an active character in the game. You should
  2262. instead create another character for that purpose.
  2263.  
  2264. In general, character SysAdmin should only be used for maintaining an
  2265. active MUD, and not as a participating character. During stand-alone
  2266. development of a scenario, using SysAdmin for testing is often quite
  2267. convenient, but testing should also be done with normal characters, to
  2268. make sure that the code works properly for them.
  2269.  
  2270. The way for a wizard or apprentice to protect their code and data
  2271. structures from others is to not make them visible to others. A
  2272. function in your private symbol table, or in a table within your
  2273. private symbol table, is not visible to others unless you do something
  2274. to make it so. The same is true for properties. Note that there are no
  2275. functions in AmigaMUD that allow you to retrieve the property itself
  2276. from a thing. You can only modify or retrieve the value of a property
  2277. from a thing if you have access to the property itself. Thus, if you
  2278. never make the name of a property publically available, no-one other
  2279. than yourself (and SysAdmin) can see or change the values of that
  2280. property on things it gets attached to. They can know that a given
  2281. thing has properties that they cannot see, but that is all. This can
  2282. be seen by logging on as a wizard or apprentice and dumping out your
  2283. character's thing (using the "describe" wizard-mode command), while
  2284. changing the set of tables "in use".
  2285.  
  2286. When defining a function, you can specify either or both of "public"
  2287. and "utility" between the "proc" and the name of the function. If you
  2288. make a function "public" in this way, then its definition can be seen
  2289. by others using the "describe" command (or the DescribeSymbol
  2290. builtin function). If you do not make the function "public", then
  2291. others can only see its header.
  2292.  
  2293. When a function is called in AmigaMUD, the system will normally change
  2294. the active access rights to those of the owner of the function. This
  2295. allows the function to retrieve and modify properties from things
  2296. owned by the owner of the function. When the function returns, the
  2297. access rights are reset to what they were before the function was
  2298. called. This access rights changing is done in a fully nested way, for
  2299. as many function calls as are needed. If a function is marked as
  2300. "utility", then the access rights are not changed when the function is
  2301. called. This allows programmers to write functions that have no more
  2302. access than the player (or the function calling their functions) would
  2303. normally have. As an example, the input parser in the standard
  2304. scenario is "utility", as are most of the functions that implement the
  2305. build facility. This is done so that when objects and properties are
  2306. created by the build code, they belong to the active player, and not
  2307. to SysAdmin. In some cases (e.g. objects), things are explicitly given
  2308. to SysAdmin (using "GiveThing"), so that they can be accessed properly
  2309. by other players. There isn't much point in going to a lot of work to
  2310. create something if no other player can appreciate it!
  2311.  
  2312. The AmigaMUD server maintains two sets of owner and status variables.
  2313. One is the "real" value, and one is the "effective" value. The
  2314. "effective" values are the ones changed when a non-"utility" function
  2315. is executed. The "real" values are unchanged. The differences between
  2316. the two include those mentioned above relating to builtins that can
  2317. only be executed by SysAdmin. The builtin "Me" returns the "real"
  2318. user. Builtin "Mine" tests against the "effective" player. When
  2319. properties and things, etc. are created they are owned by the
  2320. "effective" player. Access checks are done against the "effective"
  2321. player also.
  2322.  
  2323. Wizards and apprentices should be careful with the idea of making
  2324. things secure, however. A violation of security will result in the
  2325. abortion of execution. So, a normal player using an object, room, or
  2326. whatever, in the normal way, should not get any security violations.
  2327. In effect this simply means that you should test all of your creations
  2328. with some other normal character.
  2329.  
  2330.  
  2331. Each thing in the database has a status, which governs who can
  2332. retrieve and modify the properties on it (assuming they have access to
  2333. some name for the properties themselves). These modes, also discussed
  2334. elsewhere, are:
  2335.  
  2336.     ts_public - anyone can retrieve or modify properties. A surprising
  2337.     number of things have to have this full access in AmigaMUD.
  2338.     For example, all objects should be owned by SysAdmin and have
  2339.     status ts_public. All characters have status ts_public. This
  2340.     is needed so that scenario code written by someone other than
  2341.     SysAdmin can have an effect on objects and characters. There
  2342.     would be little point to the game if this were not so!
  2343.  
  2344.     ts_private - only the owner of a thing can retrieve or modify
  2345.     properties on the object. This status is only needed if you
  2346.     need to make a thing public, but you don't want others to be
  2347.     able to mess with it. Normally, you would make a thing private
  2348.     by simply not letting anyone else get a pointer to it.
  2349.  
  2350.     ts_readonly - only the owner of a thing can change it, but
  2351.     everyone can retrieve properties from it. This is useful when
  2352.     you want to provide a standard object or set of properties to
  2353.     others, but you don't want them able to mess it up. For
  2354.     example, the standard rooms, "r_indoors", "r_outdoors", etc.
  2355.     are set this way in the standard scenario.
  2356.  
  2357.     ts_wizard - an object which is ts_wizard can have its properties
  2358.     retrieved by anyone, but only full wizards can change the
  2359.     properties. This provides an intermediate level of safety, by
  2360.     assuming that only experienced AmigaMUD programmers (or the
  2361.     owner of the system!) are full wizards, and are thus less
  2362.     likely to mess things up.
  2363.  
  2364.  
  2365. There are three classes of character in AmigaMUD: normal, apprentice
  2366. and wizard. Normal characters cannot enter wizard mode, and so cannot
  2367. normally write AmigaMUD programs or define properties, etc. The build
  2368. code in the standard scenario allows anyone to produce simple
  2369. functions, define some properties, etc. in a controlled way. This is
  2370. enabled by setting the "p_pBuilder" flag on the character. However,
  2371. everyone is enabled for building inside the PlayPen room, or in any
  2372. room which is built by someone in a playpen room.
  2373.  
  2374. The concept of an apprentice was added at the insistence of a friend
  2375. (who has yet to do anything significant with AmigaMUD!) The intent
  2376. of what I implemented is to provide programming access to more people,
  2377. while providing other players with some protection from the "errors"
  2378. that apprentices might make. A number of builtin functions cannot be
  2379. used in functions written by apprentices. The choice of these has been
  2380. quite arbitrary, and I am open to reasons for changing the set. The
  2381. current set is:
  2382.  
  2383.     ChangeName
  2384.     Log
  2385.     NewCharacterPassword
  2386.     SetAgentLocation
  2387.     SetCharacterActiveAction
  2388.     SetCharacterButtonAction
  2389.     SetCharacterEffectDoneAction
  2390.     SetCharacterIdleAction
  2391.     SetCharacterInputAction
  2392.     SetCharacterMouseDownAction
  2393.     SetCharacterRawKeyAction
  2394.     SetIndent
  2395.     SetLocation
  2396.     SetPrompt
  2397.     TextHeight
  2398.     TextWidth
  2399.     Trace
  2400.  
  2401. Some of the locations in the more important parts of the standard
  2402. scenario have been set to ts_wizard. This means that full wizards can
  2403. build from them, but apprentices can't.
  2404.  
  2405.  
  2406. One of the "games" that people play in some MUDs is to create output
  2407. that looks the same as normal output from normal commands, in an
  2408. attempt to fool other players into thinking things are happening that
  2409. in fact are not. In AmigaMUD, I have tried to not let this happen
  2410. unless the victim allows it. Any output line which contains text which
  2411. is produced by a function owned by an apprentice will be prefixed by
  2412. an "@", thus warning the player that something might be suspicious.
  2413. The player can disable these warnings.
  2414.  
  2415.  
  2416. When players are created, they are normal. Only a wizard or apprentice
  2417. can promote a player, and an apprentice can only promote a player to
  2418. apprentice status. The system remembers who promoted a player, and
  2419. this can be seen when the character is displayed. Similarly, only
  2420. SysAdmin can demote players. Thus, since SysAdmin is initially the
  2421. only non-normal player, SysAdmin has control over who can program in
  2422. the MUD.
  2423.  
  2424.  
  2425. Efficiency Considerations
  2426.  
  2427.  
  2428. The AmigaMUD programming language is an interpreter. This means that
  2429. it will not execute as fast as compiled or assembled code. It is
  2430. reasonably efficient, however, so executing a couple hundred lines of
  2431. code in response to an input command is not a problem. There are a few
  2432. things that should be taken into consideration when writing AmigaMUD
  2433. code, so as to keep things as efficient as possible.
  2434.  
  2435. The first thing to do is to try to avoid doing things more than once
  2436. when once is enough. This rule will make just about any program more
  2437. efficient, regardless of what language it is written in. In a system
  2438. like AmigaMUD, where some operations are quite a bit more expensive
  2439. than others, the rule is more important if expensive operations are
  2440. being repeated. For example, if you want to get some numbers from
  2441. strings and operate on them, it is much more efficient to use builtin
  2442. functions to get the numbers, and then operate on them as int values,
  2443. than to operate on them using the more expensive string operations.
  2444.  
  2445. Two operations in AmigaMUD are expensive. The first is database
  2446. accesses. Even though the database is cached, and subsequent fetches
  2447. of a given property, thing, etc. will "hit" in the cache, there is
  2448. still a lot of server code executed to retrieve something from the
  2449. cache. For example, instead of doing:
  2450.  
  2451.     private p_weight CreateIntProp()$
  2452.     private p_capacity CreateIntProp()$
  2453.     ...
  2454.     private proc tryToCarry(thing person, object)bool:
  2455.  
  2456.     if object@p_weight > person@p_capacity then
  2457.         Print("There is no way you can pick that up!\n");
  2458.         false
  2459.     elif person@p_weight + object@p_weight > person@p_capacity
  2460.     then
  2461.         Print("You can't pick that up now.\n");
  2462.         false
  2463.     else
  2464.         person@p_weight := person@p_weight + object@p_weight;
  2465.         true
  2466.     fi
  2467.     corp;
  2468.  
  2469. it is more efficient (although perhaps slightly less readable) to do:
  2470.  
  2471.     private p_weight CreateIntProp()$
  2472.     private p_capacity CreateIntProp()$
  2473.     ...
  2474.     private proc tryToCarry(thing person, object)bool:
  2475.     int capacity, current, objWeight;
  2476.  
  2477.     capacity := person@p_capacity
  2478.     current := person@p_weight;
  2479.     objWeight := object@p_weight;
  2480.     if objWeight > capacity then
  2481.         Print("There is no way you can pick that up!\n");
  2482.         false
  2483.     elif current + objWeight > capacity then
  2484.         Print("You can't pick that up now.\n");
  2485.         false
  2486.     else
  2487.         person@p_weight := current + objWeight;
  2488.         true
  2489.     fi
  2490.     corp;
  2491.  
  2492. Note that the changed value must be stored back to the true property,
  2493. of course! If a property is going to be used only once in a function,
  2494. then it is better to not put it into a temporary variable, but if it
  2495. is going to be used more than once, it is quicker to put it into a
  2496. temporary variable. For a short routine that is only going to use a
  2497. property twice, and that is called infrequently, it likely isn't worth
  2498. the effort of making temporary variables.
  2499.  
  2500. The second expensive operation in AmigaMUD is that of calling
  2501. functions, either user functions or builtin functions. User functions
  2502. are more expensive than builtin functions, however, unless the builtin
  2503. function is one that does a lot of work, or results in messages being
  2504. sent to one or more clients. The same technique of keeping copies of
  2505. values in temporary variables can be used to avoid unnecessary calls
  2506. to some builtins. The most commonly "cached" values are Me() and
  2507. Here(), as in:
  2508.  
  2509.     private proc doSomethingOrOther()void:
  2510.     thing me, here, it;
  2511.  
  2512.     me := Me();
  2513.     here := Here();
  2514.     it := It();
  2515.     ...
  2516.     .. several uses of "me", "here" and "it" ..
  2517.  
  2518. Note however, that if your function changes the value returned by one
  2519. of these builtins, you should also change your temporary variable:
  2520.  
  2521.     private proc blahBlahBlah()void:
  2522.     thing me, here;
  2523.  
  2524.     me := Me();
  2525.     here := Here();
  2526.     ...
  2527.     SetLocation(someDir(here));    /* changes Here() */
  2528.     here := Here();
  2529.     ...
  2530.     corp;
  2531.  
  2532. Another aspect of efficiency is that of sending messages to multiple
  2533. remote clients. There is some overhead involved in each message that
  2534. is sent, so it is worthwhile to try to cut down on extra ones. For
  2535. example, if you are doing something that affects the displays of
  2536. everyone in the room, you should try to do all things for a given
  2537. client before going on to the next one. The fact that "ForEachAgent"
  2538. is an expensive builtin adds to the desireabilty of doing this. Keep
  2539. in mind that the active player is a client just like any other.
  2540.  
  2541. Some specific things to watch out for:
  2542.  
  2543.     - avoid using a 'nil' location on ForEachAgent. Try to keep your
  2544.     actions restricted to the agents in a given room.
  2545.  
  2546.  
  2547. Limitations of AmigaMUD
  2548.  
  2549.  
  2550. It would be nice to say that there are no limitations in AmigaMUD, but
  2551. that isn't correct. Where I have had to make a choice between creating
  2552. a limitation and creating considerable inefficiency, I have usually
  2553. chosen to create a minor limitation. Under most circumstances, the
  2554. limitations you will have in running AmigaMUD will be the amount of
  2555. memory and disk space that you have available. There are a few hard,
  2556. fixed limits, however:
  2557.  
  2558.     thing: a maximum of 255 properties
  2559.  
  2560.     table: a maximum of 65535 entries
  2561.     grammar: same as table
  2562.  
  2563.     character: limitations on the attached thing, plus
  2564.     name is limited to 20 characters
  2565.     password is limited to 20 characters
  2566.     machine: same as character
  2567.  
  2568.     proc (action): 65535 bytes of locals
  2569.     (about 16,000 local variables and parameters)
  2570.  
  2571.     list: a maximum of 65535 elements
  2572.  
  2573.     database entry: 65535 bytes
  2574.     (this restricts lists to about 16,000 elements)
  2575.  
  2576.     database: 64 million entries
  2577.  
  2578.     strings: limited by the code to about 4000 characters
  2579.  
  2580. The only serious limit is that on things. The limit on lists is a
  2581. dangerous one, since the system will not be able to handle large lists
  2582. unless a very large cache is given to it. This is since the entire
  2583. list (4 bytes per element) has to be in contiguous space in the cache
  2584. (and in the database). Appending an element to such a list could
  2585. require 2 such regions, since if the current space is not large
  2586. enough, the list would have to be copied.
  2587.  
  2588. If you have a complex quest and want to keep lots of flags and
  2589. properties, think about this alternative: create a new thing for each
  2590. player who enters your quest. Attach the new thing to the player as a
  2591. single property, and store your many properties on the new thing. That
  2592. way, you can feel free to use the full 255 properties to record the
  2593. player's progress in your quest. Remember that your quest may not be
  2594. the only large one in the MUD - it would be very frustrating for the
  2595. players if entering your quest and playing around a bit made it
  2596. impossible for them to complete other quests (or vice versa).
  2597.  
  2598. There is a fairly serious, hidden limit. Because a given entry in the
  2599. database cannot be more than 65535 bytes long, a given table cannot
  2600. need more than that number of bytes in total. This includes the text
  2601. of all entries, and 6 bytes per table entry. This is the easiest limit
  2602. to reach accidentally. Because of this, do not let any of your source
  2603. files get larger than the ones in the standard scenario. This also
  2604. implies that if you add a lot to any of the larger standard scenario
  2605. source files, you should consider splitting that file up so that it
  2606. uses more than one table for its symbols. Putting more symbols into
  2607. private tables is a good way to do this. In many cases, the symbols
  2608. are in public tables simply because there is no good reason to keep
  2609. them private.
  2610.  
  2611.  
  2612. Cleaning it Up
  2613.  
  2614.  
  2615. AmigaMUD is a fully interactive system, where all kinds of creation
  2616. can be done on-line, even while players are active. For fully
  2617. interactive use with no down-time, however, it is also useful if large
  2618. parts of the active scenario can be destroyed, so that they can be
  2619. replaced as a set. Doing this requires care, however. For example, if
  2620. characters are actually present in an area that is to be rebuilt, it
  2621. is unlikely that rebuilding the area will be safe. Such an area will
  2622. also, unless careful provisions are made, come back in a just-
  2623. initialized state. In other words, changes made to the area (such as
  2624. the location of any special objects, the state of doors, etc.) will be
  2625. effectively undone on the rebuild.
  2626.  
  2627. There are a series of "Zap" builtin functions which can be used to
  2628. destroy large parts of a scenario. There are often structures in a
  2629. region which the routines will not be able to destroy, however, so the
  2630. cycle of destroy-rebuild should not be done more often than necessary.
  2631. The main destruction routine is "ZapTable". This routine, when given a
  2632. table, will delete all entries from the table, as well as destroying
  2633. the contents of everything that it deletes. A typical scenario area
  2634. will contain a number of "things" representing the locations and
  2635. special objects in the area, along with actions that define the
  2636. behavior of the area. If a single table contains entries for all of
  2637. those things and actions, then using ZapTable on that table will
  2638. destroy most of the area. This will not, however, destroy any
  2639. dynamically made copies of objects, since they typically are not
  2640. contained in the table. If they have an object from the table as their
  2641. parent, then that object will be cleared (all properties removed), but
  2642. will not itself be destroyed, because of the still existing parent
  2643. pointers. The entry for it in the table, will be deleted, however.
  2644.  
  2645. Some code in the standard scenario creates things and uses them, all
  2646. without putting them into any tables. Often, such things will still be
  2647. destroyed, since the only pointers to them will be zapped during the
  2648. execution of ZapTable. If however, such things contain pointers to
  2649. each other, then they will not be destroyed, since each has references
  2650. to it. No attempt is made by AmigaMUD to notice such circular
  2651. references. If, however, all such things are in the table being
  2652. zapped, then the circular references will be cleared as part of
  2653. clearing the things, and so the things will no longer reference each
  2654. other and will be destroyed. The case of circular pointers occurs most
  2655. often with rooms, since most links between rooms are two-way.
  2656.  
  2657. The full set of zapping builtins is:
  2658.  
  2659.     ZapAction - clear the body of an action
  2660.  
  2661.     ZapCharacter - clear and reset a character
  2662.  
  2663.     ZapGrammar - empty and appropriately dereference a grammar
  2664.  
  2665.     ZapTable - clear a table and all its entries
  2666.