Funeral Quest Scripting Guide
 
 

Contents

Purpose (top)

This document was originally prepared by Joe Johnston for inclusion with a future release of RTSoft's Funeral Quest.

This guide introduces the scripting environment of Funeral Quest to novice programmers. It is expected that the reader has some background in a functional programming language like C, Pascal or JavaScript. Those already familar with scripting Teenage Lawnmower may wish to skip this document in favor of the more brief Scripting Information page.

Funeral Quest is composed of two parts: the win32 Funeral Quest server and the Macromedia Flash client. The client and server talk to each other using standard HTTP, encoding their arguments in GET strings (like a REST web service). The details of that protocol are not covered in this document and aren't needed to understand the Funeral Quest scripting environment. The API described below abstracts the client/server communication from the low-level HTTP calls. However, it is important to understand that all scripting occurs on the server, not the client.

Note: All files referenced in this document are relative to the directory in to which Funeral Quest was installed. In other words, the root of the Funeral Quest directory, as far as this document and and fqserver.exe are concerned, is the directory in which the fqserver.exe is found. Typically this is C:\Program Files\FQServer, but local conditions may vary.

Syntax (top)

The Funeral Quest scripting language (FQ-C) has syntax that bares a striking likeness to C, but without any pre-processing directives, for/while loops, case statements, memory management, arrays or user-definable types. However, FQ-C does support branching, looping and methods for basic data maniplation. FQ-C scripts are best kept simple and relatively small.

User-defined scripts must all be in unicode. On Win98 and up, Wordpad handles unicode correctly. On Win2K and up, Notepad also handles unicode. It is possible to get Emacs to use read unicode using the Mule-USC module, but that is left as an excerise for the reader.

FQ-C supports the four data types described in Table 1.

Data typeDescription
StringOne or more characters delimited by double quotes.
intan integer value (32-bit signed int)
floata real number (32-bit signed float)
vector33D vector (not used in FQ)
Table 1: Values

Of these four, strings and ints appear most frequently in code. Values are used as arguments to functions and may be stored in variables. Values are maniplated by using the operators found in Table 2.

OperatorDescription
=Assignment
+binary addition
-binary subtraction
*binary multiplication
/binary division
%=modulus (division remainder)
==boolean numeric equality
!=boolean inequality test
<boolean numeric less than
>boolean numeric greater than
Table 2: Operators

Variables, functions and labels begin with letters and can be followed by a reasonable number of letters, numbers or underscores. Sometimes variables are declared before they are used so that the interpreter can recognize them later when the variables are used. Variables are declared in a simple statement that contains the data type of the variable and the variable's name.

          String current_player;
          int gold_tags;
       

There are no default values for variables. Do not try to get the value of a variable that has only been declared and not assigned to. Variables may be declared and assigned to in the same statement. For instance, the string "hello, world" can be assigned to the variable hello using the assignment operator.

          // demostrate assigment
          String hello_str = "hello, world";
       

The listing above begins with a comment. Comments begin with two forward slashes. The script interpreter ignores everything to the right of those slashes. Comments are for the coder's benefit, not the computer's.

Although variables, labels and functions are not case-sensitive in FQ-C, they are in most other languages. Consider using a consistent casing for all of these elements to avoid confusion.

The binary math operators may be combined with the assignment operators.

           // take the current value of i and multiply it by 4
           i *= 4; 
       

Note that FQ-C assignments expect immediate values. That is arbitrary expressions aren't allowed on the right hand side of an assigment statement. Instead, only a variable, function call or raw value should be used.

          // These are all acceptable
          int stakes = 4;                           // ok, raw value
          string headstone += " rest in peace";     // ok, appends to headstone
          int gtags = GetTags(i_my_id, C_GOLD_TAG); // ok

          // These all are broken
          gtags =  gtags * 4;                // gtags remains the same
          name  = "Xander" + " " + "Harris"; // name gets "Xander"
          hp    = GetMaxHP(i_my_id) / 4;     // unknown
       

Strings may also be concatenated.

          string firstname = "Buffy";
          string lastname  = "Summers";
          string fullname;

          fullname += firstname;
          fullname += " ";
          fullname += lastname;

          LogMsg(fullname);  // prints "Buffy Summers" to log
       

Inside of strings, there are sequences of characters that have special meanings. Some of these sequences (beginning with a backtick ( ` )) are interpreted by the Flash client when displayed and other sequences (beginning with a backslash ( \ )) are interpreted by the scripting engine before the Flash client sees them. Table 3 lists escape sequences used represent special characters in strings. Table 13 lists escape sequences recognized by the Flash Client.

SequenceDescription
\nGenerates a new line in the text
\\Generates one backslash
\"Generates a double quote
$<variable name>$ Insert the value of the given script variable into the string
Table 3: String escape sequences

In FQ-C, every line of code contains one statement, at most. Statements are somewhat like sentences in English; they represent a complete thought. Statements end with semi-colons. Expressions are bits of code that evaluated to be true of false (expressions that evaluate to zero are false while any other value is true). Expressions do not end in semi-colons. Expression produce values and are only used in the condition test of if blocks. Statements do not produce values and are never used used in condition tests (assigments aren't allowed as condition tests as in C).

FQ-C supports conditional execution. This is done with C-style if blocks, which has the following form.

           if (expression) {
               statement1;
               statement2;
               ...
            }
       

If the expression evaluates to true, then the statements appearing between the curly braces are executed. FQ-C has no else statement. Expressions typically use the boolean operators listed in Table 1.

          if (i < 0) 
          {
             LogMsg("i is negative");
          }        
       

The only data type that that boolean operators work with is int. Unlike C, the braces are required and the opening brace must be on the line following the conditional.

Although FQ-C doesn't support traditional looping constructs like the C-style for and while, it does have the rather notorious goto statement. Used with restraint, goto can be used to simulate the looping and control structures of C. The basic syntax of goto is this:

          goto_label:
            statement1;
            statement2;
          goto goto_label;
       

When a goto statement is executed, the interpreter jumps to the place in the code where the goto label occurs. Goto labels end with a colon, not a semi-colon.

Simulate a C-style while (or for) like this:

          // sum i with itself until that number is greater than 100
          int i = 0;
          int sum;

          loop:
            if (i < 10) 
            {
               i += 1;
               sum += i;
               goto loop;
            }

          LogMsg("i is $i$ and sum is $sum$");
       

Functions are "mini-scripts" that exists inside larger scripts. In FQ-C, user-defined functions cannot accept parameters or return values. Functions are good for collecting related routines together in one place. Functions must be defined before they are used. Functions take the following form:

          return type function name ()
          {
             statement1;
             statement2;
             ...
          }
       

As was mentioned, user-defined functions never return a value. Therefore, all user-defined functions return void. The function name is not case sensitive. Next comes the list of parameters accepted by this function in parentheses, but since user-defined functions never accept arguments, only an empty parentheses pair appears.

         void logger ()
         {
             LogMsg("I like cheese");
             return;
         }

         void Main ()
         {
            // call logger
            logger();
         }
       

Unlike C, all function arguments must appear on the same line.

All scripts should contain three functions: OnCreate(), Main(), and OnKill(). The interpreter calls these functions in that order. If these functions aren't present, a warning is generated in the server log. OnCreate() is typically used to define local variables visible in in the entire script. Main() is expected to contain the main block of code to execute. OnKill() is a good place to take care of any clean-up that's needed before the script exits. While it is possible to put all the action of a script in only of one of these functions (say OnKill()), it's better to use these functions in their intended role. Maintainers will be find a script that puts all the code in OnKill() jarring.

It is possible to control the visibility of variables in scripts. A variable's scope is limited to the file in which it was declared. This is called a local variable. Local variables are created when the script is run and are destroyed after OnKill() is called. When When the script is called again, the local variables are recreated. Remember that Funeral Quest can have many simultaneous users at once. Each instance of the script gets its own local variable.

Up to this point, only local variables have been shown in the code listings. It is possible to define global variables that are visible to all scripts and aren't destroyed until the server shuts down. To declare a global variable, simply prepend 'global' to a variable's declaration.

          global int num_vampires;
       

Through the careful used of local variables, it is possible to simulate argument passing to user-defined functions. Consider this code.

       void OnCreate()
       {
          String _logger;
       }
       
       void logger ()
       {
         string tmp = "From logger: ";
         tmp += _logger;
         StringExpand(tmp);
         LogMsg(tmp);
       }

       void Main () 
       {
          int i = 10;
          _logger = "i is $i$";
         logger();
       }
       

The argument to logger is passed in a local variable called _logger. So that i may be interpolated correctly, the StringExpand() function, which interpolates variables in strings, must be called before passing the string to LogMsg().

Pre-defined Global Variables (top)

Table 4 contains a list of global variables defined in init.c that are available in all scripts. Variable names appearing in all capital letters an begining with "C_" should be considered constants. That is, do not attempt to change the value of such a variable.

NameDescription
C_WOOD_TAG Symbolic constant that for wood tags. Used as an argument to GetTags(), ModTags() and SetTags().
C_SILVER_TAG Symbolic constant that for silver tags. See C_WOOD_TAG for usage.
C_SILVER_TAG Symbolic constant that for silver tags. See C_WOOD_TAG for usage.
C_CUSTOM_FIRE_ALARM Symbolic constant for the fire alarm item. Used in GetCustomByID(), ModCustomByID() and SetCustomByID()
C_CUSTOM_SAFE Symbolic constant for the safe item. See C_CUSTOM_FIRE_ALARM for details.
C_CUSTOM_PADLOCK Symbolic constant for the padlock item. See C_CUSTOM_FIRE_ALARM for details.
C_CUSTOM_LOCKPICK Symbolic constant for the lockpick item. See C_CUSTOM_FIRE_ALARM for details.
C_CUSTOM_PADLOCK_STRENGTH Symbolic constant for the remaining strength of the padlock. See C_CUSTOM_FIRE_ALARM for details.
Table 4: Constants

In addition to constants, there are some mutable global variables. Although changable, these variables almost never need to be modified by scripts.

NametypeDescription
i_my_idint Contains the ID of the current player.
i_my_resultint Populated with the value of the button pressed by the user.
Table 5: Globals

Functions (top)

The heart of the scripting environment is the library of API functions. Most of the function names are prefixed with "Get", "Mod" and "Set". All library functions return at most one value, which is typically a string or int.

NameParametersDescription
GetCustomByIDint i_player_id, int i_custom_idGiven a player_id and an inventory_index, return the amount of this item owned by the player.
GetHomeNameFromIDint i_player_idGiven a player_id, return the name of the associated Funeral Parlor.
GetLuckint i_player_idGiven a player_id, return this player's luck amount.
GetMaxHPint i_player_idGiven a player_id, return this player's maximium HP.
GetNameFromIDint i_player_idReturn the player's "in-game" name for the given player_id.
GetPlayerFightsint i_player_idGiven a player_id, return the number of fights this player has been in for the current game day.
GetPoliceint i_player_idGiven a player_id, return 1 if this player has police protection (not eligible for player ambushes) or 0 otherwise.
GetPsychUpgradesint i_player_idGiven a player_id, return the number of psychology upgrades this player has received.
GetRankPercentint i_player_idGiven a player_id, return the percentage of all the players that this player is better than. This number will be from 1-100 and can be convert a percentile by taking 100 and substracting this function's return value from it (i.e. 100 - rank_percent).
GetStrengthint i_player_idGiven a player_id, return this player's strength attribute.
GetTagsint i_player_id, int i_tag_typeGiven a player_id and a tag type, return the number of tags of the given type that the player has. Use the symbolic constants to specify the type of tag.
GetTodaysBPsint i_player_idGiven a player_id, return this player's BP count for the current day's play.
GetTotalBPsint i_player_idGiven a player_id, return the total number of Burial Points this player has accumulated in this game or tourney.
GetTotalBurialsint i_player_idGiven a player_id, return the total number of burials for this player.
GetTransportationFromIDint i_player_idGiven a player_id, return the text name of the player's transportation.
GetTurnsint i_player_idGiven a player_id, return the number of turns this player has left.
IsPlayerOnNowint i_player_idReturns 1 if the given player_id is currently online. 0 otherwise.
ModCustomByIDint i_player_id, int i_custom_id, int i_mod_amountGiven the player_id, the item index and a integer, modify the amount of the given custom item owned by this player.
ModLuckint i_player_id, int i_mod_amountGiven a player_id and an integer amount, adjust the player's Luck. Range checking is implement so that luck is always between 1-100.
ModMaxHPint i_player_id, int i_mod_amountGiven a player_id and integer amount, adjust the player's HP cap.
ModPlayerFightsint i_player_id, int i_mod_amountGiven a player_id and an integer amount, adjust the number of fights this player has been in for the current game day.
ModPsychUpgradesint i_player_id, int i_mod_amountGiven a player_id and an integer amount, adjust the number of psych upgrades a player has.
ModStrengthint i_player_id, int i_mod_amountGiven a player_id and integer amount, adjust this player's strength attribute.
ModTagsint i_player_id, int i_tag_type, int i_mod_amountGiven a player_id, a tag type and an integer amount, adjust the player's total tag count for the given type of tag. Return the new tag count.
ModTodaysBPsint i_player_id, int i_mod_amountGiven a player_id and integer amount, adjust this player's BP count for the day. Total BP count is also updated. Range checking is also implemented so that BP totals don't go negative.
ModTotalBPsint i_player_id, int i_mod_amountGiven a player_id and an integer amount, adjust this player's total BP count. Most scripts should avoid this function in favor of ModTodaysBPs.
ModTotalBurialsint i_player_id, int i_mod_amountGiven a player_id and an integer amount, adjust this player's burial count.
ModTurnsint i_player_id, int i_mod_amountGiven a player_id and integer amount, adjust this player's remaining number of turns.
SetCustomByIDint i_player_id, int i_custom_id, int i_mod_amountGiven the player_id, the item index and an integer amount, directly set the amount of this item owned by this player.
SetPoliceint i_player_id, int i_police_statusGiven a player_id and an integer status, give this player police protection (when i_police_status is 1) or remove it (when i_police_status is 0).
SetTagsint i_player_id, int i_tag_type, int i_mod_amountGiven a player_id, a tag type and an integer amount, directly set the number of tags of the given type this player has.
Table 6: Player functions

Below are functions related to the Flash client. These are used to format the screens that the player sees.

NameParametersDescription
AddButtonstring packet, int button_loc, string label, int valueInto packet, add a button in location button_loc (1-5) with the given label and value. If the user presses the button, value will be returned in the global i_my_return.
AddButtonChangeNamestring&nbps;packet, int button_loc, string labelConnects to the internal change name code, used in the scripted parlor status screen.
AddButtonHomestring packet, int button_loc, string labelTakes player back to their parlor.
AddButtonStorestring packet, int button_loc, string labelBring users back to the main Di-Mart store screen.
AddButtonTownMapstring packet, int button_loc, string labelTakes player to the town map.
AddCustomstring packet, string name, string valueDirectly set a Flash client variable. Particularly useful for those hacking the Flash client code.
AddUpdateBPsstring packetForces client to update daily and global BP totals.
AddUpdateLuckstring packetUpdates player luck.
AddUpdateStatsstring packetUpdates nearly all player stats. Use sparingly to conserve bandwidth.
AddUpdateTagsstring packetRefresh the client's tag display.
AddUpdateTurnsstring packetRefresh the number of turns the player has left.
MailContinueButtonint player_idPlayer must hit a continue button before reading more mail. Useful when sending pictures.
SendPacketAndWaitstring packetPause the script until the user has pressed a button.
SendPacketAndWaitOverlaystring packetSame as SendPacketAndWait() but creates a pop-up window. Used only for "enter player name".
SendPacketAndWaitStatsstring packetUsed only for the stat screen.
SetLocationstring labelSet the name of the location to the given label. Useful for properly identifying the location while at the bar.
Table 7: Flash client functions

Below are functions that return game properties. These functions do not directly affect a particular player.

NameParametersDescription
AddToNewsstring msgAdd a news item to the front page
GetDaynoneReturns the age of the game in days.
GetRandomPlayerIDint i_not_meReturn a random player_id. Set i_not_me to a player_id that should not be returned. To get any player_id, pass this function -1.
GetTopPlayerIDnoneReturn the player_id of the top player.
GetTourneyDaysLeftnoneReturn the number of days left in the current tourney. 0 indicates the the current day is the last day. -1 indicates that the tourney ended yesterday. -2 indicates no tourney is active.
Table 8: Game properties

There are several ways to communicate with particular players or all players at once. Such communication is done through using the functions listed below.

NameParametersDescription
AddToNewsstring msgAdd msg to the front page news.
BroadCastTVstring msgBlit a news item to the folks at the bar watching TV.
MailPictureint i_player_id, string urlGiven a player_id and URL, send this player a picture. The picture will appear in the upper right window. Use this with MailContinueButton(). URL's may be absolute or relative to the "public" folder in the FQ server directory.
MailTextint i_player_id, string messageGiven a player_id and string, send this player a mail message.
Table 9: Broadcast messages

There are several functions that aid scripting. These don't directly affect the game or players.

NameParametersDescription
LogMsgstring msgGiven a string, print msg to the FQ server log. This is an excellent debugging tool.
RandomRangeint min, int maxReturn an integer in the inclusive range between min and max.
Returnnonereturn from a function call.
RunScriptNoReturnstring script_nameRun the given script. The path is relative to FQ server root (that is, the directory where fqserver.exe is).
StringExpandstring msgInterpolate any variables in the given string. Return interpolated string.
Table 10: Utility functions

Creating Items (top)

It is possible to add new items to Funeral Quest that players may possess. Every player has 200 "slots" available for inventory. It's better to think of these 200 slots as 200 key-value pairs. The first five slots are already used. They are:

  1. fire alarm
  2. safe
  3. padlock
  4. lockpick
  5. padlock strength

Sometimes, the values of these keys is simply 1 or 0. It's enough to know that the player has the item. Sometimes the value indicates quantity, as it does for lockpicks. Finally, the value can indicate the integerity of an object, as it does for padlock strength. The mod designer is entirely responsible for defining how the item can be possessed by the player and how the player can use the item.

To make this more concrete, let's defined a new item called "body armor" that absorbs damage from opponents. Currently there are no hooks in the scripting engine to control combat. However, one way to implement body armor is to simply boost the player's maximum HP. Recall that FQ already provides a mechanism for permanently improving HP, which is to take phys. ed. classes at Spirit U. To preserve game balance, the body armor either needs to be priced similiarly to the phys. ed. classes or the armor needs to "depreciate" over time and use. The depreciating armor model both provides the better example of the scripting environment and provides another way to part players from their tags.

At first, it seems that there's no way to accurately depreciate the armor's integratity without hooks into the combat system. However, by inspecting the player's daily number of fights, it it is possible to roughly appromixate damage to the armor. When purchased, the armor can have a "strength" value, just like padlocks do. During player maintenance, a script can determine if the player engaged in combat and substract strength points appropriately. When the armor's strength points are gone, the item disappears from the player's inventory.

With this model in place, it's now possible to implement this item. First, two new global integers are defined in scripts/init.c.

       void Main()
       {
          // code removed...

          // Body Armor item requires two inventory slots
          global int C_CUSTOM_BODY_ARMOR          = 10;
          global int C_CUSTOM_BODY_ARMOR_STRENGTH = 11;
       }
       
Listing 1: init.c

The existing lockpick model provides a solid template for how players can acquire body armor at Di-Mart. In scripts/store_supplies.c, the price armor and its strength are defined as script-wide local variables in OnCreate(). The following lines can be added to the bottom of that function.

         void OnCreate()
         {
            // code removed ...
            // Body Armor stuff
            int i_body_armor_price    = 6;
            int i_initial_BA_strength = 3;
         }
       
Listing 2: store_supplies.c

Next, it's time to define the screen that displays the armor and offers the user the chance to buy it.

// Handle the BA acquistion
void DisplayBodyArmor()
{
   loop:

   // Display the wares
   string p;
   string msg = "It's a rough crowd that walks the path of the undertaker.\n";
   msg += "\n`wEffect`y:  Increases your maximum HP.  Wears out over time.\n";

   p = AddCustom(p, "st_url", "flash\\stuff\\body_armor.swf");

   // Let's talk wallet size...
   int curr_gt = GetTags(i_my_id, C_GOLD_TAG);

   // Issue a warning about lack of funds
   if (curr_gt < i_body_armor_price)
   {
      msg += "\nMmm.  I doubt you can afford these threads, Bub.";     
   }
  
   // Report the condition of the existing armor (if any)
   int has_ba = GetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR);

   if (has_ba == 1)
   {
      int ba_str = GetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR_STRENGTH);
      msg += "\nYour armor strength is currently: `w$ba_str$`y\n";
   }

   // Marketing colateral
   p = AddCustom(p, "st_main", msg);

   // Back button
   p = AddButton(p, 1, "Back", 1);

   // Don't bother with the buy button if there's no cash
   curr_gt += 1;  // don't get bitten by the off-by-one bug!
   if (curr_gt > i_body_armor_price) 
   {
      // Buy button
      p = AddButton(p, 2, "Buy it", 2);
   }

   // What does the user want to do?
   SendPacketAndWait(p);

   // Outta here
   if (i_my_result == 1)
   {
      return;
   }

   // Ch-ching! sale!
   if (i_my_result == 2)
   {
       p = "";  // clear out old buttons

       // Debit the account
       ModTags(i_my_id, C_GOLD_TAG, -10);

       // Add BA to inventory
       SetCustomById(i_my_id, C_CUSTOM_BODY_ARMOR, 1);

       // Set the strength
       SetCustomById(i_my_id, C_CUSTOM_BODY_ARMOR_STRENGTH, i_initial_BA_strength);

       // Make the armor take effect
       ModMaxHP(i_my_id, 5);

       // celebrate the sale
       p = AddCustom(p, "st_url", "flash\\stuff\\money_bag_2.swf");
       p = AddUpdateTags(p);  // force client to update the tag count
       p = AddCustom(p, "st_main", "`yYour new `wBody Armor`y makes you feel all manly.");
       p = AddButton(p, 1, "Continue", 1);

       SendPacketAndWait(p); 
   }
 
   goto loop;
}
       
Listing 3: store_supplies.c

This function displays the merchandise with a witty quip and indicates the current strength of the player's armor, if any is owned. If the player doesn't have enough gold to buy the armor, the buy button isn't present. If the user can and does buy the armor, any existing armor and armor bonuses must first be removed. Notice that one can't prepend variables with a minus sign to get a negative value. Instead, create a new variable set to zero and substract the variable from that. This yields the desired negative value. After this housekeeping, the armor is added to the inventory (in the C_CUSTOM_BODY_ARMOR slot), the armor's initial strength is registered (in the C_CUSTOM_BODY_ARMOR_STRENGTH slot) and finally the player's maximum HP is raised appropriately. A congratulatory message is sent to the player to acknowledge the sale.

Now that the armor can be displayed and bought, it's time to add armor to the list of supplies that can be bought. This is done in Main() of script/store_supplies.c. A new line of text (added to st_main) needs to be displayed and a button created that will take the player to the Armor screen (found in DisplayArmor()).

void Main()
{
  ...

  // Add BA to the list of proffered items
  st_items += "`w3`y - Body Armor - Cost: `w$i_body_armor_price$`y gold tags\n";
  st_packet = AddCustom(st_packet, "st_main", st_items);

  //add options
  st_packet = AddButtonStore(st_packet, 1, "Back"); 
  st_packet = AddButton(st_packet, 3, "Lockpick", 1);
  st_packet = AddButton(st_packet, 4, "Armor", 3);

  //send the packet
  SendPacketAndWait(st_packet);

  ...

  if (i_my_result == 3) 
  {
     DisplayBodyArmor();
  }
}
       
Listing 4: store_supplies.c

The player now can buy the armor and enjoy the benefits of his purchase. But what the scripter giveth, the scripter can taketh away.

Every evening, the FQ server runs a maintenance cycle. By adding code to maint_player.c (run for each player), it is possible to determine if this player has body armor and if so, decrement its strength (and the player's MaxHP) by 1 if the player engaged in combat that day. Once the armor strength is reduced to 0, the armor disappears.

void BodyArmorMaint()
{
      int ba_str = GetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR_STRENGTH);
      int fights = GetPlayerFights(i_my_id);

      // any fights will decrement the effectiveness of the armor
      if (fights > 0) 
      {
        string msg;
        ba_str -= 1;

        // Did the armor get destroyed?
        if (ba_str < 1)
        {
            // yes
           SetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR_STRENGTH, 0);
           SetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR, 0);
            
            // Tell the luckless owner
           msg = "Your Body Armor is totaled.  Better it than you.";
        }

        // Depreciate armor and its effect
        if (ba_str > 0)
        {
            SetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR_STRENGTH, ba_str);
            ModMaxHP(i_my_id, -1);
            msg = "Your Body Armor got pretty beat-on.\nArmor Strength: `w$ba_str$`y";
        }

        mailText(i_my_id, msg);
      }
}

void Main()
{
   // Body Armor maint
   int has_ba = GetCustomByID(i_my_id, C_CUSTOM_BODY_ARMOR);

   if (has_ba == 1)
   {
      BodyArmorMaint();
   }
}
       
Listing 5: maint_player.c

It's true that a player who buys armor and never initiates combat will never lose his armor. However players are rewarded for marauding so the purely defensive armor owner is a bit of an edge case.

Scripting Places (top)

As flexible as Funeral Quest is, it's easier to modify the bahavior of some places than others. The church is the easiest place to modify because it is already stubbed out and serves no game purpose now. It is also possible to modify the flash client to enable new locations, like the graveyard, or create new locations entirely (like Dan Wilma's Casino mod). The following example takes the path of least resistance and simply modifies the church to create Bingo night.

Unlike Body Armor, Bingo Night only requires changes to script/places/church.c. The first step is to add a button to the main script for Bingo Night. This is straight forward, as the following listing shows.

void Main()
{
 SetLocation("Church"); 
 
 String st_packet;
 msg  = "Father Smyth greets you warmly, a little too warmly.  ";
 msg += "You wonder if he has anything on under that robe.";
 st_packet = AddCustom(st_packet, "st_generic_info", msg);
 st_packet = AddCustom(st_packet, "st_generic_status", "");

 // new message
 msg  = "`wFather Smyth: `y\"Why hello there, $st_my_name$.  ";
 msg += "Isn't it a wonderfully fantastic day today?  Praise the Lord.\"";
 st_packet = AddCustom(st_packet, "st_main", msg);
  

 st_packet = AddCustom(st_packet, "st_url_bg", "flash/background/background_church.swf");
 st_packet = AddCustom(st_packet, "st_url",    "flash/people/churchguy_h.swf");
 st_packet = AddCustom(st_packet, "st_object", "i_generic"); 

 main_menu:
   // Create the buttons
   st_packet = AddButton(st_packet, 2, "Bingo Night", 2);
   st_packet = AddButtonTownMap(st_packet, 1, "Leave"); 

   //send the packet
   SendPacketAndWait(st_packet); 
   st_packet = ""; //clear it for a new packet 
  
   if (i_my_result == 2)  
   {
      DoBingoNight(); 
   }

 goto main_menu;
}
Listing 6: church.c

Locations need a little bit of setup on the client side. Typically, a location name needs to be set so that folks in the Bar and the FQ admin screen know where the player is. For now, the location is set to "Church" with the SetLocation() call. The text that appears in the upper left hand corner of the flash client, next to the portrait, controlled by setting the st_generic_info variable. All the client variables can be modified with calls to AddCustom(). Text may appear just below the generic_info message. This is controlled by st_general_status. Typically, these are used to display information that doesn't change as frequently as the buttons do. For instance, room descriptions typically go here. The main message section in the client is populated through setting st_main. The portrait that appears in a location can be a composit of two flash files. The background image is set with st_url_bg and the foreground image is set with st_url. The flash client can work with jpeg and gif images too. Finally, two buttons are added to the screen: one for Bingo Night and one to exit the current location entirely. If the user selects the "Bingo Night" button, then DoBingoNight() is called. The listing for that long function is shown below.

void PlayBingo() 
{
   int wtags  = 0;
   int cost   = 0;
   int winner = 0;
   int player = 0;

   SetLocation("Bingo Night");

   play_bingo:
     p    = "";
     msg  = "";
     cost = 0;

     msg = "2 cards = 1 in 6 chance of winning\n";
     msg += "4 cards = 1 in 3 chance of winning\n";
     msg += "6 cards = 1 in 2 chance of winning\n\n";
     msg += "Jackpot is `w12 wood tags`y";

     p = AddCustom(p, "st_generic_info", msg);
     p = AddCustom(p, "st_generic_status", "");

     p = AddCustom(p, "st_url_bg", "flash/background/background_church.swf");
     p = AddCustom(p, "st_url",    "flash/stuff/lotteryticket.swf");
     p = AddCustom(p, "st_object", "i_generic");
  
     wtags = GetTags(i_my_id, C_WOOD_TAG);

     msg  = "\"Ladies and gentlemen, buy your cards now,\" says Father Smyth.\n\n";
     msg += "Each card costs `w1 wood tag`y.\n";
     msg += "You currently have `w$wtags$ wood tags`y.\n";
     p = AddCustom(p, "st_main", msg);
     p = AddButton(p, 1, "Back", 1);

     // beware of off by one issues
     if (wtags > 1)
     {
        p = AddButton(p, 2, "Buy 2",  2);
     }
   
     if (wtags > 3)
     {
        p = AddButton(p, 3, "Buy 4", 3);
     }

     if (wtags > 5)
     {
        p = AddButton(p, 4, "Buy 6", 4); 
     }

     SendPacketAndWait(p);

     if (i_my_result == 1)
     {
        // exit. Mail the results of tonight's winnings
	if (total_winnings > 0)
        {
           msg  = "Look who got lucky at Bingo!  ";
           msg += "Your total winnings are `y$total_winnings$ wood tags`w.";
        }

        if (total_winnings == 0)
        {
           msg  = "Bingo Night was a wash.  ";  
           msg += "Good thing you didn't quit your day job.";
        }

        if (total_winnings < 0)
        {
           int tmp = 0;
           int tmp -= total_winnings;

           msg  = "The bingo cards just weren't talking to you tonight.  ";
	   msg += "You lost `y$tmp$`w wood tags.";         
        }

        MailText(i_my_id, msg);
        return;
     }

     // figure out the cost to the player
     if (i_my_result == 2) 
     {
	player = 2;
     }

     if (i_my_result == 3)
     {
	player = 4;	
     }

     if (i_my_result == 4)
     {
	player = 6;
     }

     cost -= player;
     ModTags(i_my_id, C_WOOD_TAG, cost);
     total_winnings += cost;
     winner = RandomRange(1, 12);

     if (winner > player)
     {
        // select a losing message
        winner = RandomRange(1,3);
        if (winner == 1)
        {
           BingoLoser1();
        }

        if (winner == 2)
        {
           BingoLoser2();
        }

        if (winner == 3)
        {
           BingoLoser3();
        }
        goto play_bingo;
     }

     // Some of the player's cards won, did others?
     winner = RandomRange(1,6);
     if (winner > 2)
     { 
        // Player gets the whole jackpot
	BingoTotalWinner();
	goto play_bingo;
     }

     if (winner == 2)
     {
	// 2-way split
        Bingo2SplitWinner();
	goto play_bingo;
     }

     if (winner == 1)
     {
        Bingo3SplitWinner();
        goto play_bingo;
     }
     
   goto play_bingo;
}
Listing 7: church.c

PlayBingo() is long, but not particularly complex. After several variable housekeeping lines, Bingo Night declares itself to be a new location with SetLocation(). This isn't required at all, but since players will typically spend a few turns here, it seems reasonable to set the location string. The "generic" information is set to give the odds of Bingo to the players and the the initial location portrait is fixed. Existing graphics are recycled. After figuring out the player's current number of tags, a sales pitch proffers bingo cards to the players. Like the Body Armor example, only options that the player can currently afford are rendered.

If the user decides to leave, their total winnings or losses are reported to them through MailText(). Note that total_winnings isn't declared in the this function, but is declared in the yet unseen OnCreate() function (see Listing 8).

Naturally, the number of cards bought affects the amount of wood tags deducted from the player as well as the player's chances for winning. The math logic is slightly twisted here because FQ-C doesn't know how to take the inverse of an integer variable. With the housekeeping done, bingo is ready to be played.

The details of Bingo are completely abstracted into a simple random number choice. Rather than accurately model Bingo, it seemed just as effective to pick a number from 1 - 12, which represent the 12 cards in play. In every round, some player wins (unlike real world bingo). The players buys cards numbered 1, 2, etc up to 6. If the number generated by RandomRange() matches one of the cards the player holds, he wins. Otherwise, the player loses. If the player loses, which will happen frequently, one of three messages is selected to display. This hopefully keeps the player interested in bingo. If the player wins, there are three outcomes. The player may win the whole jackpot because he holds the sole winning card. The player may have to split the pot with other winning card holders. Again, this is to make the bingo play more textured and interesting than the scratch ticket event (which, to be fair, can't be all that complex given the constraints of "unscripted" events). Whatever the outcome, the player is invited to play again.

The are a few file-visible variables needed for Bingo Night. These all appear in OnCreate(), show below.

void OnCreate()
{
   String p           = "";
   String msg         = "";
   int total_winnings = 0;
}
Listing 8: church.c

The next several functions are the lose/win messages. The only complexity they present is that they all call BingoContinue() to paint the message and "Continue" button. This relies on the file-visible string p and string msg.

// Call after the win/lose message
void BingoContinue () 
{
   p = AddCustom(p, "st_url", "flash/stuff/lottery_lose.swf");
   p = AddCustom(p, "st_main", msg);
   p = AddButton(p, 1, "Continue", 1);

   SendPacketAndWait(p);
   return; 
}

// Because losing is so common, there a few loser messages
void BingoLoser1()
{
   p = "";
   msg  = "Ethel Peterson raises her withered hand and shouts \"`wBingo!`y\"\n";
   msg += "\nRats!  You lose.";

   BingoContinue();
}

void BingoLoser2()
{
   p = "";
   msg  = "Frantically you shuffle through your cards, but to no avail. ";
   msg += "None are winners.";
   msg += "\nDag-dab-it!  You lose.";

   BingoContinue();
}

void BingoLoser3()
{
   p = "";
   msg  = "Mrs. Xing jumps up excitedly and exclaims \"`wBingo! Bingo!`y\"\n";
   msg += "\nNo bingo for you!  You lose.";

   BingoContinue();
}

void BingoTotalWinner()
{
   p = "";
   msg  = "Seconds after Father Smyth calls \"`wB-9`y,\" ";
   msg += "you triumphantly announce \"`wBingo!`y\"\n\n";
   msg += "Payout: `w12 wood tags`y";

   // Add winnings to player's total
   ModTags(i_my_id, C_WOOD_TAG, 12);
   total_winnings += 12;

   BingoContinue();
}

void Bingo2SplitWinner()
{
   p = "";
   msg  = "You smile confidently as Father Smyth calls \"`wN-2`y.\" ";
   msg += "Before you can announce your winning card, ";
   msg += "that old witch Ms. Hazel screeches \"`wBingo!`y\"\n";
   msg += "\nYou split the pot with Ms. Hazel.\n";
   msg += "Payout: `w6 wood tags`y";

   // Add winnings to player's total
   ModTags(i_my_id, C_WOOD_TAG, 6);
   total_winnings += 6;

   BingoContinue();
}

void Bingo3SplitWinner()
{
   p = "";
   msg  = "Father Smyth calls \"`wG-4`y.\" ";
   msg += "That firecracker Audry lights and shouts \"`wBingo!`y\" ";
   msg += "A moment later, the spinstress Eileen holds aloft another ";
   msg += "winning card.  To your surpise, you also have a winning card!\n";
   msg += "\nYou split the pot with Audry and Eileen.\n";
   msg += "Payout: `w4 wood tags`y";

   // Add winnings to player's total
   ModTags(i_my_id, C_WOOD_TAG, 4);
   total_winnings += 4;

   BingoContinue();
}
Listing 9: church.c

When appropriate, winnings are added to the player's current wood tag total and total_winnings is updated. It's important to realize that the odds are stacked against the player. Excepting statistical abbhorations, players spending a lot of time at Bingo Night will only make Church rich.

Scripting Events (top)

Before FQ-C was added to Funeral Quest, the game could be locally modified using flat text files. Table 11 lists the filenames and a brief description of their purpose. All the listed files can be found in the Funeral Quest directory.

Name Description
events.txtDefines all the "unscripted" random events that a player can encounter.
hard_sell.txtDefines all the high-priced items players can attempt to sell.
karma.txtThe list of epithets associated with each level of karma.
news_blurbs.txtThe daily news items that often have global implications.
no_customers.txtText that appears when no customers are present.
ratings.txtLabels for player ratings.
risks.txtDefines all the random risks that players may take for a BP bonus.
soft_sell.txtAll the low-ticket items players can use to achieve perfect sales.
swear_words.txtMaps undesirable language to euphemisms.
yangs.txtText that appears when Yang's Cursed Tigers are delivered.
yangs_nice.txtText that appears when Yang's Lucky Sparrows are delivered.
Table 11: Configuration files

Most of the file formats are fairly straight forward and are left as an excercise for the reader. However, the workings of some of these files a less obvious and merit some brief discussion. Experimentation will yield the fastest learning results. In all files, double slashes (//) start comments. All text to the right of them is ignored. Blank lines are generally ignored. Every statement must appear on one line, although some lines below are line-broken for display reasons. When lines are broken, a backslash appears as the last character on the line and the following line is indented with a arrow. Statements that require arguments use a pipe ( | ) to separate the action and each argument.

The events.txt represent random, one-time isolated encounters that players sometimes experience. A typical entry looks like the following:

start_entry
message|An old mortuary science professor bequeaths you his \
 → favorite skeleton, "Morty".
mod_bp|4|8
mod_luck|1|3
set_pic|flash/stuff/battle_lose.swf
       
Listing 10: sample events.txt

Every event entry must begin with a start_entry statement. There is no corresponding end_entry. The next statement identifies the text that appears on the player's screen. Flash client escape sequences my appear here. Next, any bonuses/penalties appear. There are only two awards: mod_bp (adjust burial points) and mod_luck (adjust player's luck attribute). Either one of these may be omitted. Finally, the foreground picture be set with a set_pic action. Recall that the paths are relative to the FQ server root. You will need to reload the files from the FQ admin screen for changes to take effect.

Every game day, a new news item is displayed. News items can provide a one-time award to just one player or to all the players. Below is a typical entry:

start_entry
title|SNAKEHEAD FISH MENACE STRIKES DEATHSVILLE
message|"At long last, our quiet community has been distrupted \
 → by these air-breathing, sword-wielding ninja fish. \
 → God help us all," said the mayor at a recent press conference.
give_random|`wThe phone rings - It's the Hospital.\n\n`$"More fish \
 → fatalities for you, *NAME*."
mod_bp|20|40
mod_luck|1|2
add_log|`7*HOME*'S SUCCESS LINKED TO SNAKEHEAD FISH\n`^*NAME* says \
 → "God bless those carniverous Asian fish."
Listing 11: sample news_blurbs.txt

Just like entries in events.txt, each entry begins with start_entry. The title appears in a different font than the message. Use the give_random action to isolate the benefits/penalties to a random player who will also get whatever message is provided. Additionally, messages can be sent to the server log.

There are two sequences that have special meanings in new items. When "*NAME*" appears, it is replace with the player's name choosen by give_random. "*HOME*" is replaced by the randomed selected player's parlor name.

Risks are those random events that allow players to pit their luck attribute against Dame Fortune for some globally defined amount (the factory default is 50 BP). Risks also have the most complex file structure.

start_message|`$You happen upon an `5ancient scroll `$in the suit \
 → pocket of your most recent client. Its black words instruct 
 → you on how to raise the dead.
start_url|flash/stuff/question.swf
start_url_bg|flash/background/background_1.swf

normal|Burn scroll
risk|Cast spell

success_message|`$After completing an incantation better suited \
 → for inhuman tongues, the ground trembles and cracks! \
 → `4The dead walk again!\n\n`$Coolio! 
success_url|flash/stuff/battle_lose.swf
success_url_bg|flash/background/background_1.swf

fail_message|`4You raised something all right!
fail_url|flash/animals/dog-dragging-hand.swf
fail_url_bg|

normal_message|`$As the scroll disappears into thick, viscous \
 → smoke, you feel pride in your victory over the forces 
 → of darkness.\n\nThen you realize that there are more \
 → scrolls out there... 
normal_url|flash/people/jesus_happy.swf
//normal_url_bg|flash/background/background_1.swf

add_risk|

Listing 12: sample risks.txt

The key to crafting an entertaining risk is to embelish the text -- there isn't much game play in a random roll of the die. Risks comprise four sections: an intro, a success message, a failure message, and a decline message. Unlike the other files discussed, there is no initial directive that begins a risk. The start_message lays out the challenge to the player. Normally, two buttons are presented to the player. The normal action, which takes a button label argument, declines the risk. The risk action, which also takes a button label, accepts the risk. Note that spaces after the pipe will appear in the flash client. The initial portrait that accompanies the risk is specified with start_url and start_url_bg. If the risk is accepted and the player succeeds, the success_* actions occur. A new message and portrait can (and should) accompany a successful risk. On the other hand, risks that fail also have attendent text and graphics specified by the fail_* directives. If the risk is declined, yet another set of messages and graphics are displayed using the normal_* actions. Do not forget to finish the risk block with add_risk. Not properly finishing a risk block, a very common bug, causes weird server problems.

With the advent of FQ-C, events can be made much more interactive through scripting. As an example, the arson event present the player with the opportunity to burn a competitor's palor to the ground. Another interesting use of the scripted events can be used in conjunction with player maintenance to create Extortion Racket event.

In the Extortion Racket event, players are given the "opportunity" to contribute "the olive oil importer's fund for occupation hazzards." A small random amount of tags will be "donated" by the player. So far, this event could be handled with the flat files. However, the tags taken from the players are stored in global variables and every odd game day during maintance, a random player finds an unexpected parcel of tags on his front stoop. What's interesting about this event is that players will find their money directly put into another player's hands. Of course if it were broadcast on the news that the recipient found a big bag of other player's money, it might tend to give game play a certain edge.

Event scripts should be placed in the scripts/events directory. The script, extortion_racket.c, produces the "mysterious stranger" consists only one function, Main(), as seen in Listing 13.

void OnCreate()
{
}

void Main()
{
 LogMsg("Running Extortion event for $st_my_name$.");

 String p;
 String msg;

 int wtags = GetTags(i_my_id, C_WOOD_TAG);
 int stags = GetTags(i_my_id, C_SILVER_TAG);
 int gtags = GetTags(i_my_id, C_GOLD_TAG);

 int scalar = 1;

 msg = "`yA burly gentleman from the local olive oil importers guild ";
 msg += "enters your parlor.\n\n";
 msg += "\"`wI'm collectin' for the Occupational Hazzards fund.  ";
 msg += "Contribute or regret it,`y\" he says omniously.";
 p = AddCustom(p, "st_main", msg);
 p = AddCustom(p, "st_url_bg", "flash/background/background_5.swf");
 p = AddCustom(p, "st_url", "flash/people/m_adult_4.swf");

 loop:
   p = AddButton(p, 2, "Donate", 1); 
   p = AddButton(p, 1, "Refuse", 2); 

   SendPacketAndWait(p); 
 
   if (i_my_result == 1)
   {
      // take fewer tags
      scalar = 3; 
   } 

   if (i_my_result == 2)
   {
      // take all the players cash
      scalar = 1;
   }

   LogMsg("scalar is $scalar$");
   if (scalar < 1) 
   {
      LogMsg("scalar is broken");
      goto loop;
   } 

   // take the right amount of tags
   int _tmp = 0;
   String damage = "";
   if (gtags > 0)
   {
	gtags /= scalar;

        if (gtags > 0)
        {
	   damage += "`w$gtags$`y gold tags";
        }

	_tmp -= gtags;
	ModTags(i_my_id, C_GOLD_TAG, _tmp);
   }

   _tmp = 0;
   if (stags > 0)
   {
        stags /= scalar;
 
        if (stags > 0)
        {
           damage += " `w$stags$`y silver tags";           
        }

        _tmp  -= stags;
        ModTags(i_my_id, C_SILVER_TAG, _tmp);
   }

   _tmp = 0;
   if (wtags > 0)
   {
        wtags /= scalar;
        if (wtags > 0)
        {
           damage += " `w$wtags$`y wood tags";
        } 
        _tmp  -= stags;
        ModTags(i_my_id, C_WOOD_TAG, _tmp);
   }

   // Add tags to the global "Free Parking"
   G_EXTORTION_WTAGS += wtags;
   G_EXTORTION_STAGS += stags;
   G_EXTORTION_GTAGS += gtags;

   msg  = "`7PARLOR VISITED BY ROGUE OLIVE OIL IMPORTERS\n`^";
   msg += "A group of lawless olive oil importers are aggressively ";
   msg += "collecting donations for their \"occupational hazzards\" fund.  ";
   msg += "When questioned, local mortician $st_my_name$ said, ";
   msg += "\"The man was big and burly and ...\".  ";
   msg += "$st_my_name$ then broken down into tears.  ";
   msg += "To date, no one knows where all the money they collect goes.\n\n";

   AddToNews(msg);
   msg  = "mafia coffers swell: $G_EXTORTION_WTAGS$ wt, ";
   msg += "$G_EXTORTION_STAGS$ st, $G_EXTORTION_GTAGS$ gt";
	
   LogMsg(msg);

   p = "";
   p = AddCustom(p, "st_main", "`yOuch!  You \"donated\" $damage$.");
   p = AddButtonHome(p, 1, "Continue"); 

   SendPacketAndWait(p);
   return;
}

void OnKill()
{
}
Listing 13: extortion_racket.c

By now, most of the script should be transparent. First, the portrait and initial message is defined. The player is offered the choice of donating or refusing to cooperate with the thug. In either case, the player is going to lose tags. It's only a question of degree. If the player freely donates tags, the thug takes a third of each type of tag. If the player refuses, all tags are taken. This is a truly painful event.

The player's tags are removed and stored in global variables declared in script/init.c (see Listing 14).

void Main()
{
  /// code snipped ... 

  // Extortion Racket globals
  global int G_EXTORTION_WTAGS = 0;
  global int G_EXTORTION_STAGS = 0;
  global int G_EXTORTION_GTAGS = 0;

  LogMsg("Scripting system initted, ran init.c.");
}
Listing 14: init.c

It's a good idea to begin global variables with 'G_'. Carefully selecting variable names helps other people read unfamiliar code. Note that these globals only exist in RAM. If the game crashes or is shutdown, then these variables disappear. The last step in the extortion racket is to select a player to to be the recipient of the "fund". This happens during game maintenance.

// Pick a player to give the extortion to
void DoExtortion()
{
   int _tmp = RandomRange(1,3);
   if (_tmp < 3)
   {
      return;
   }
   
   // pick a player;
   int _player = GetRandomPlayerID(-1);
   
   string _msg = "`7MORTICIAN FINDS LOOT`^\n";
   _msg += GetNameFromID(_player);
   _msg += " awoke this morning to find ";

   // And if there's a "fund"
   int found = 0;
   if (G_EXTORTION_GTAGS > 0)
   {
      ModTags(_player, C_GOLD_TAG, G_EXTORTION_GTAGS);
      _msg += StringExpand("$G_EXTORTION_GTAGS$ gold tags, ");
      G_EXTORTION_GTAGS = 0;
      found = 1;
   }

   if (G_EXTORTION_STAGS > 0)
   {
      ModTags(_player, C_SILVER_TAG, G_EXTORTION_STAGS);
      _msg += StringExpand("$G_EXTORTION_STAGS$ silver tags, ");
      G_EXTORTION_STAGS = 0;
      found = 1;
   }

   if (G_EXTORTION_WTAGS > 0)
   {
      ModTags(_player, C_WOOD_TAG, G_EXTORTION_WTAGS);
      _msg += StringExpand("$G_EXTORTION_WTAGS$ wood tags, ");
      G_EXTORTION_WTAGS = 0;
      found = 1;
   }

   if (found == 0)
   {
      _msg += "an empty money sack";   
   }

   _msg += " and a whole lot of confusion on his front ";
   _msg += "porch this morning.\n\n";

   AddToNews(_msg);

   return;
}

void Main()
{
  LogMsg("Script maint.c processed.");
  DoExtortion();
}
Listing 15: maint.c

During daily maintenace, DoExtortion() is called. However, the "Free Parking" loot is only distributed 33% of the time. This will let the pot grow and make the harassed players particularly ensanguine with rage. To pick a random player, supply GetRandomPlayer() with an argument of -1. To insure that everyone knows who got the money, a news item message is constructed. Each global variable is checked to see if it has a non-zero amount and if it does, that amount is transfered to the selected player. The news item message includes the exact amount of tags found. The StringExpand() function is needed here because each global variable must be interpolated in the string before it is zeroed out. Otherwise when the news item is send with AddToNews(), the variables will be zero when interpolated. If the "Free Parking" is empty, a stub message is created.

Finally, it's time to add the extortion_racket to list of scripted events. All scripted events must be included in script_event.c file.

void Main()
{
 //pick a random script event

 int i_random = RandomRange(1,3); 

 if (i_random == 1)
  {
    LogMsg("Running arson event");
    RunScriptNoReturn("script\\events\\arson.c"); 
  }

 if (i_random == 2)
 {
    LogMsg("Running bonus_tag event");
    RunScriptNoReturn("script\\events\\bonus_tag.c"); 
 }

 if (i_random == 3)
 {
    RunScriptNoReturn("script/events/extortion_racket.c");
 }

}
Listing: 16: script_event.c

There is no reason that every event needs to be equally likely.

Maintenance Hooks (top)

Every night at midnight server localtime, the Funeral Quest server runs a maintenance cycle. All kinds of game house keeping is done during this time. With FQ-C, it is now possible to define new maintenace tasks on a per player (with script/maint_player.c)or global (with script/maint.c) basis. Read through the Body Armor code for an example of per player maintenance code. Take a look at the Extortion Racket event for a global maintenance example.

Notes about the Flash client (top)

Coming soon.

Escape SequenceDescription
`0light green text
`1dark blue text
`2dark green text
`3cyan text
`4red text
`5purple text
`6brown text
`7light gray text
`8dark gray text
`9light blue text
`wwhite text
`yyellow
Table 12: Flash Client escape sequences