Note: We'd like to thanks Quasar for allowing us to modify the docs of the Eternity Engine to reflect Legacy FraggleScript implementation.
FraggleScript Basics -- Updated 14 August 2001
Contents
Getting Started
To get started using FraggleScript in your DOOM
levels, you'll need to fully understand most or all aspects of DOOM level
editing and WAD file manipulation. If you haven't mastered this basic stuff,
it would probably be wise to check out the Doomworld tutorials section and to
look up a few FAQs. These documents assume you understand basic DOOM editing.
When you first want to create scripts, you should save a blank file from a
suitable text editor as something appropriate, like "map01.fs" The .fs
extension is not required, but its useful for figuring out what and where your
files are later.
When you have the file, you need to place a header in it like this:
[scripts]This tells the game that your mapinfo is declaring a section for scripts. Note that FraggleScript scripts in Legacy reside in the map header along with all other mapinfo information. If you want to define other mapinfo blocks, you can put them before or after this section.
[level info] levelname = The Palace of w00t creator = Quasar [scripts]After the header is in place you can begin defining scripts and variables. When your job is done, you can use the add_fs utility, available with Legacy, to insert your scripts into the level. How to define and use scripts is covered below.
Defining Scripts
Scripts are the basic subprogram unit of
FraggleScript, similar to the functions of C and the procedures of Pascal.
FraggleScript scripts do not take any explicit parameters, however, and
cannot return values, which is quite different from most languages.
Levels can currently have up to 256 scripts, numbered from 0 to 255 (future
expansion of this number is possible to allow for persistent scripts). Scripts
exist only within the level to which they belong, and for the most part only
affect that level, with the exception of hub variables.
To declare a script, follow the syntax of the following example:
[scripts] script 0 { }The script keyword denotes that the script definition is starting, and the number following it is the number for this script. Script numbers should always be unique, one per defined script.
Variables and Data Types
One way in which scripts can accomplish things is
to interact with variables. Variables can be of three natures, explained as
follows:
Built-In -- These variables are always present and are defined by the
FraggleScript runtime. They can be accessed by any script. Their value typically
defaults at the beginning of each level.
Built-in variables, current to Legacy 1.40, include:
int consoleplayer - the player number, 0 to 3
inclusive, of the player attached to the local machine
int displayplayer - the player number of the player
being displayed on the local machine
int zoom - the zoom level for the game camera
mobj trigger - a reference to the mapthing that
started this script. This variable is important and very useful.
Global -- These variables are defined outside of any script, either in a header
file or in the [scripts] section of the mapinfo lump. Any scripts in the current
level can access these types of variables. If global variables are declared with
the const keyword, they are constants, and if they are
declared with the hub keyword, then the current list
of hub variables will be searched by name for a match when the declaration is encountered.
Hub global variables persist between levels and can be accessed and modified by
scripts in any level until the current cluster ends.
Examples:
[scripts] const DOORSPEED = 4; hub int visitedSwamp = 0; int i = 0; script 0 { i = i + 1; }Note that const variables adapt to the default type for their provided literal, while hub global variables require explicit typing.
[scripts] int i = 0; script 0 { int i = 1; print(i); }Note that the print function in this example will print the string "1" and not "0" because local variables always take precedence over any built-in or global variables. This is an important distinction to remember.
int x = 0;fixed (also float) - a 32-bit fixed-point number, somewhat similar to floating-point except that the decimal place is fixed at 16 bits so that the word is evenly divided into integer and decimal parts. fixed numbers must be specified with a decimal point, otherwise the literal will be interpreted as an integer. fixed values are used for high precision interaction with the game world.
fixed f = 2.71828;string - a string of ASCII characters. FraggleScript strings are limited in length to 256 characters. The following escape sequences are supported:
"Hello, World!\n"mobj - an opaque reference to a DOOM mapthing (ie monster, lamp, fireball, player). The values of these references must either be obtained from object spawning functions, or can be specified by use of integer literals, in which case the mobj reference will point to the mapthing numbered by the map editor with that number
// spawn a new dwarf and keep a reference to it in "dwarf" mobj dwarf = spawn(HALIF, 0, 0, 0); // get a reference to the thing numbered 0 at map startup mobj firstthing = 0;Note that using map editor numbers for things has the distinct disadvantage that when the map is edited, the things will be automatically renumbered if any are deleted. It is suggested that the latter form of mobj reference assignment be avoided unless necessary.
mobj halif2 = halif + firstthing;are not, in general, meaningful.
Calling Functions
FraggleScript offers an extensible host of built-in
functions that are implemented in native code. They are the primary means to
manipulate the game and cause things to happen. The FraggleScript Function
Reference is a definitive guide to all functions supported by the Legacy dialect;
this document will provide some basic examples of function use.
Most functions accept a certain number of parameters and expect them to represent
values of specific meaning, such as an integer representing a sector tag, or
an mobj reference to a mapthing to affect.
The function reference lists the parameters that each function expects, but
there are some important things to take note of. As mentioned in the previous
section, type coercions can occur when functions are passed parameters of other
types. An excellent example is the following:
script 0 { startscript("1"); }The startscript function expects an integer value corresponding to the number of a script to run. Here it has been passed a string, "1". Strings will be converted to the integer they represent, if possible, so this string is automatically coerced into the integer value 1, and the script 1 will be started.
script 0 { mobj halif = 0; startscript(halif); }mobj references can be assigned using integer literals, but the rules for coercion from mobj to int state that -1 is always returned for an mobj value (this is because there is *not* a one-to-one mapping between mobj references, which can include objects spawned after map startup which do not have a number, and integers). This statement has the effect of
startscript(-1);and since -1 is not in the domain of startscript, a script error occurs.
script 0 { fixed dist; dist = pointtodist(0, 0, 1, 1); }This places the fixed-point distance between the points (0,0) and (1,1) into the fixed variable dist. It is *not* necessary to capture the return value of a function simply because it has one. For instance,
script 0 { pointtodist(0, 0, 1, 1); }is a valid statement; it is simply useless since pointtodist has no side effects, or things that occur within a function call that affect the program environment. Likewise, it is possible to use function return values without explicitly capturing them in variables, like in the following example:
script 0 { print(spawn(IMP, 0, 0, 0)); }This causes a new imp to be spawned at (0,0,0), a side effect. Since spawn additionally returns a reference to the new object, the mobj reference returned by spawn becomes the parameter to the print function, and the resulting output to the console is "map object". The value returned by spawn "disappears" after the print function has executed and cannot be retrieved or otherwise used. This is useful when you don't need to save the return value of a function beyond using it as a parameter.
[scripts] script 0 { message("Welcome to the Palace of w00t, foolish mortals!"); } startscript(0);In this example, all players would see this message at the beginning of the level. It is not required, but is good style, to put any levelscript function calls after all script definitions.
Control Flow Structures
Control flow structures allow your code to make
decisions and repeat actions over and over. There are several basic control
structures, and each are covered here in full.
while
The while loop is the basic loop control structure. It will continually loop
through its statements until its condition evaluates to 0, or false.
Basic syntax:
while(<condition>) { <statements> }Unlike in C, the braces are required to surround the block statement of a while loop, even if it only contains one statement. An operative example would be the following:
script 0 { int i = 0; while(i < 10) { print(i, "\n"); i = i + 1; } }This code would print the numbers 0 through 9 to the console.
script 0 { while(1) { if(rnd() < 24) { ambientsound("dsbells"); } wait(20); } }This is additionally a fine example of how to do ambient sounds with FraggleScript.
for(<initialization>, <condition>, <iteration>) { <statements> }All three statments may be of any valid form, although typically the initialization statement sets a variable to an initial value, the condition statement checks that that variable is within a certain bound, and the iteration statement increments the variable by a certain amount.
script 0 { int i; for(i=0, i<10, i=i+1) { print(i, "\n"); } }This for loop is equivalent to the while loop example above. while and for are logically equivalent in general, for simply provides a cleaner way to specify the exact loop behavior. for is a rather complex statement form, so if this explanation is insufficient, any decent reference on the C language should have more information that is applicable.
if(<condition>) <statement> if(<condition>) { <statements> }Examples:
if(i > 10) return();
if(i) { print(i, "\n"); return(); }elseif and else are ancilliary clauses that execute when their corresponding if statement was not true. elseif tests its own additional condition, and if it is false, control passes to the next elseif or else, if one exists. An if can have any number of elseif clauses, and one else clause following it. If there is an else clause, it must always be last. When the if statement evaluates to true, no elseif or else clauses will be executed.
script 0 { int i = rnd()%10; if(i == 1) { spawn(IMP, 0, 0, 0); } elseif(i <= 5) { spawn(DEMON, 0, 0, 0); } else { spawn(BARON, 0, 0, 0); } }This example, as you should expect, spawns one enemy at (0,0,0), its type depending on what random value i was assigned.
Script Activation Models
Script activation models are simply the different
ways in which scripts can be started.
Currently supported activation models include the following:
Operators and Operator Precedence
The Legacy dialect of FraggleScript supports the following operators, in
order of precedence from greatest to least. All operators except = are evaluated
from left to right.
script 0 { mobj halif = spawn(DWARF, 0, 0, 0); halif.kill(); }This gives FraggleScript a comfortable object-oriented appearance.
Keyword List
This is a short alphabetical list of keywords in FraggleScript. Variables cannot
be named these words because they are reserved.
const
else
elseif
fixed
float
for
hub
if
int
mobj
script
string
while
Remember that break, continue, return, and goto are defined as special functions,
and not as keywords.
While the names of functions are not reserved words in the strictest sense,
you should additionally avoid naming variables with the same name as functions
since these variables will hide the functions and make them inaccessible. This
will cause parse errors if you attempt to use the function after declaring a
variable with the same name.
Back to Top