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 type | Description |
String | One or more characters delimited by double quotes. |
int | an integer value (32-bit signed int) |
float | a real number (32-bit signed float) |
vector3 | 3D 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.
Operator | Description |
= | 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.
Sequence | Description |
\n | Generates 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.
Name | Description |
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.
Name | type | Description |
i_my_id | int |
Contains the ID of the current player. |
i_my_result | int |
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.
Name | Parameters | Description |
GetCustomByID | int i_player_id, int i_custom_id | Given a player_id and an inventory_index, return the amount of this item owned by the player. |
GetHomeNameFromID | int i_player_id | Given a player_id, return the name of the associated Funeral Parlor. |
GetLuck | int i_player_id | Given a player_id, return this player's luck amount. |
GetMaxHP | int i_player_id | Given a player_id, return this player's maximium HP. |
GetNameFromID | int i_player_id | Return the player's "in-game" name for the given player_id. |
GetPlayerFights | int i_player_id | Given a player_id, return the number of fights this player has been in for the current game day. |
GetPolice | int i_player_id | Given a player_id, return 1 if this player has police protection (not eligible for player ambushes) or 0 otherwise. |
GetPsychUpgrades | int i_player_id | Given a player_id, return the number of psychology upgrades this player has received. |
GetRankPercent | int i_player_id | Given 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). |
GetStrength | int i_player_id | Given a player_id, return this player's strength attribute. |
GetTags | int i_player_id, int i_tag_type | Given 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. |
GetTodaysBPs | int i_player_id | Given a player_id, return this player's BP count for the current day's play. |
GetTotalBPs | int i_player_id | Given a player_id, return the total number of Burial Points this player has accumulated in this game or tourney. |
GetTotalBurials | int i_player_id | Given a player_id, return the total number of burials for this player. |
GetTransportationFromID | int i_player_id | Given a player_id, return the text name of the player's transportation. |
GetTurns | int i_player_id | Given a player_id, return the number of turns this player has left. |
IsPlayerOnNow | int i_player_id | Returns 1 if the given player_id is currently online. 0 otherwise. |
ModCustomByID | int i_player_id, int i_custom_id, int i_mod_amount | Given the player_id, the item index and a integer, modify the amount of the given custom item owned by this player. |
ModLuck | int i_player_id, int i_mod_amount | Given a player_id and an integer amount, adjust the player's Luck. Range checking is implement so that luck is always between 1-100. |
ModMaxHP | int i_player_id, int i_mod_amount | Given a player_id and integer amount, adjust the player's HP cap. |
ModPlayerFights | int i_player_id, int i_mod_amount | Given a player_id and an integer amount, adjust the number of fights this player has been in for the current game day. |
ModPsychUpgrades | int i_player_id, int i_mod_amount | Given a player_id and an integer amount, adjust the number of psych upgrades a player has. |
ModStrength | int i_player_id, int i_mod_amount | Given a player_id and integer amount, adjust this player's strength attribute. |
ModTags | int i_player_id, int i_tag_type, int i_mod_amount | Given 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. |
ModTodaysBPs | int i_player_id, int i_mod_amount | Given 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. |
ModTotalBPs | int i_player_id, int i_mod_amount | Given a player_id and an integer amount, adjust this player's total BP count. Most scripts should avoid this function in favor of ModTodaysBPs. |
ModTotalBurials | int i_player_id, int i_mod_amount | Given a player_id and an integer amount, adjust this player's burial count. |
ModTurns | int i_player_id, int i_mod_amount | Given a player_id and integer amount, adjust this player's remaining number of turns. |
SetCustomByID | int i_player_id, int i_custom_id, int i_mod_amount | Given the player_id, the item index and an integer amount, directly set the amount of this item owned by this player. |
SetPolice | int i_player_id, int i_police_status | Given 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). |
SetTags | int i_player_id, int i_tag_type, int i_mod_amount | Given 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.
Name | Parameters | Description |
AddButton | string packet, int button_loc, string label, int value | Into 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. |
AddButtonChangeName | string&nbps;packet, int button_loc, string label | Connects to the internal change name code, used in the scripted parlor status screen. |
AddButtonHome | string packet, int button_loc, string label | Takes player back to their parlor. |
AddButtonStore | string packet, int button_loc, string label | Bring users back to the main Di-Mart store screen. |
AddButtonTownMap | string packet, int button_loc, string label | Takes player to the town map. |
AddCustom | string packet, string name, string value | Directly set a Flash client variable. Particularly useful for those hacking the Flash client code. |
AddUpdateBPs | string packet | Forces client to update daily and global BP totals. |
AddUpdateLuck | string packet | Updates player luck. |
AddUpdateStats | string packet | Updates nearly all player stats. Use sparingly to conserve bandwidth. |
AddUpdateTags | string packet | Refresh the client's tag display. |
AddUpdateTurns | string packet | Refresh the number of turns the player has left. |
MailContinueButton | int player_id | Player must hit a continue button before reading more mail. Useful when sending pictures. |
SendPacketAndWait | string packet | Pause the script until the user has pressed a button. |
SendPacketAndWaitOverlay | string packet | Same as SendPacketAndWait() but creates a pop-up window. Used only for "enter player name". |
SendPacketAndWaitStats | string packet | Used only for the stat screen. |
SetLocation | string label | Set 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.
Name | Parameters | Description |
AddToNews | string msg | Add a news item to the front page |
GetDay | none | Returns the age of the game in days. |
GetRandomPlayerID | int i_not_me | Return 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. |
GetTopPlayerID | none | Return the player_id of the top player. |
GetTourneyDaysLeft | none | Return 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.
Name | Parameters | Description |
AddToNews | string msg | Add msg to the front page news. |
BroadCastTV | string msg | Blit a news item to the folks at the bar watching TV. |
MailPicture | int i_player_id, string url | Given 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. |
MailText | int i_player_id, string message | Given 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.
Name | Parameters | Description |
LogMsg | string msg | Given a string, print msg to the FQ server log. This is an excellent debugging tool. |
RandomRange | int min, int max | Return an integer in the inclusive range between min and max . |
Return | none | return from a function call. |
RunScriptNoReturn | string script_name | Run the given script. The path is relative to FQ server root (that is, the directory where fqserver.exe is). |
StringExpand | string msg | Interpolate 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:
- fire alarm
- safe
- padlock
- lockpick
- 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.txt | Defines all the "unscripted" random events that a player can encounter. |
hard_sell.txt | Defines all the high-priced items players can attempt to sell. |
karma.txt | The list of epithets associated with each level of karma. |
news_blurbs.txt | The daily news items that often have global implications. |
no_customers.txt | Text that appears when no customers are present. |
ratings.txt | Labels for player ratings. |
risks.txt | Defines all the random risks that players may take for a BP bonus. |
soft_sell.txt | All the low-ticket items players can use to achieve perfect sales. |
swear_words.txt | Maps undesirable language to euphemisms. |
yangs.txt | Text that appears when Yang's Cursed Tigers are delivered. |
yangs_nice.txt | Text 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 Sequence | Description |
`0 | light green text |
`1 | dark blue text |
`2 | dark green text |
`3 | cyan text |
`4 | red text |
`5 | purple text |
`6 | brown text |
`7 | light gray text |
`8 | dark gray text |
`9 | light blue text |
`w | white text |
`y | yellow |
Table 12: Flash Client escape sequences
|