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.
Example:
[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.

Back to Top

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.

The script above is valid, but it is not very interesting because it does nothing, and a script alone cannot run without first being called. Scripts may be invoked in several manners, which is covered in the Script Activation Models section.

Back to Top

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.

Local -- These variables are declared inside a script. They can only be accessed within the script itself, and are destroyed when the script completes execution.

Example:
[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.

Variable names may be of arbitrary length, but should not be named with any FraggleScript reserved word.

FraggleScript has four primary data types as follows:

int - 32-bit signed integer. Only decimal integer literals are accepted.

Example:
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.

Example:
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:

\n - line break
\\ - a literal \ character
\" - a literal " character
\? - a literal ? character
\a - bell character - causes the console to play a sound
\t - tab
\0 - write white text Strings must be delimited by quotation marks as follows:

"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

Examples:
// 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 references are very powerful and allow a large number of effects and fine control not available in other languages such as ACS.

Also note that although integer literals can be used to assign mobj reference values, mobj and int are not interchangeable, and statements such as
mobj halif2 = halif + firstthing;
are not, in general, meaningful.

Type Coercions
     FraggleScript is a weakly typed language, and as such, coercions are made freely between all data types. These coercions follow the rules below:

Conversion to int from:

string - the string will be passed to the ANSI C function atoi()

fixed - the fixed-point value will be chopped to its integer portion with no rounding

mobj - coercion is not meaningful, -1 is always returned

Conversion to fixed from:

string - the string will be passed to the ANSI C function atof(), and then the resulting double value is renormalized and chopped into fixed precision (this process incurs unavoidable round-off error).

int - the integer value is renormalized as a fixed value (overflow is possible if the integer is greater than 32767 or less than -32768, and is not checked).

mobj - coercion is not meaningful, -1*FRACUNIT is returned (FRACUNIT is equal to 1<<16 and represents the value 1 in the fixed-point number system).

Conversion to string from:

int - the value is converted to a string via the rules of ANSI C's stdio functions

fixed - the value is renormalized as an IEEE double floating-point number and then converted to string via the rules of ANSI C's stdio functions (this process incurs some unavoidable round-off error)

mobj - coercion is not meaningful, the string "map object" is always returned

Conversion to mobj from:

int - the level is checked for a mapthing numbered by the value of this integer and if it exists, a reference to it is returned -- if it does not exist, a script error will occur

string - the string will be passed to atoi() and the resulting integer value will be cast via the rules above

fixed - the fixed value is chopped to its integer portion and then the resulting integer value will be cast via the rules above

Coercion is an automatic process that takes place when a variable is assigned the value of a variable of a different type, when values are passed to functions that do not match the specified parameter types, and when operands of multiple types are used with some operators. Some functions may perform more strict type checking at their own volition, so beware that script errors may occur if meaningless values are passed to some functions.

Back to Top

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.

An example of a coercion that is NOT meaningful in the intended manner would be the following:
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.

Parameter coercions of this type should be avoided for purposes of clarity and maintainability of your code. When coercions are convenient or necessary, be certain that the value you obtain through coercion will always be meaningful.

Note that some functions, like print, can take a variable number of parameters. These types of functions generally treat all their parameters in a like manner, and can accept up to 128 of them. See the reference to get a better idea about these types of functions and what they do.

Functions may return values, and in fact, most useful functions do. To capture the return value of a function, you simply assign it to a variable.

Example:
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.

If a function is listed as being of return type void, this means that it does not return a meaningful value and that it operates by side effect only, like a procedure in some languages. However, unlike in C, void functions in FraggleScript, will return the integer value 0, rather than causing an error when used in assignments. In practice, it is best not to assign variables the null return value from a void function even though it is allowed, since this is confusing to see in code and useless anyways.

Note that it is possible to make limited function calls outside of any script, in the surrounding program environment, which in FraggleScript is referred to as the levelscript. Function calls placed in the levelscript will be executed once (and only once) at the beginning of the level. This is commonly used to start scripts to perform certain actions.

Example:
[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.

Back to Top

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.

The continue() and break() functions are capable of modifying the behavior of the while loop. continue() causes the loop to return to the beginning and run the next iteration, while break() causes the loop to exit completely, returning control to the surrounding script.

A while loop can run forever if its condition is never false, but if you write a loop like this, be sure to call one of the wait functions inside the body of the loop, or else the game will wait on the script to finish forever, effectively forcing you to reboot!

Example:
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

The for loop is a more sophisticated loop structure that takes three parameters. Note that unlike C, the FraggleScript for parameters are separated by commas, not by semicolons, and all 3 are required to be present.

Basic syntax:
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.

Braces are again required, and the continue() and break() statements work the same way here as they do for while(), with the exception that a continuing for loop will check its condition and perform its iteration, whereas a while loop ignorantly repeats.

An operative example:
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, elseif, else

if tests its condition, and if the condition evaluates to any non-zero value, or true, the statements inside its body are executed. Note that unlike while and for, braces are not required for an if statement UNLESS you either intend for the if body to contain more than one statement, OR you intend to place an elseif or else clause after the if.

Basic syntax:
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.

Example:
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.

Note that elseif and else are new to the Eternity and DOOM Legacy dialects of FraggleScript and that they are not currently supported by SMMU.

Back to Top

Script Activation Models

     Script activation models are simply the different ways in which scripts can be started.

Currently supported activation models include the following: Back to Top

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. Back to Top

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