ACS scripts

For convenience, section 5 of the official Hexen specs has been reproduced below with some minor editing to account for this being a ZDoom reference.

Scripting with ZDoom is just like scripting for Hexen, and Hexen's acs utility is even used to compile scripts. The only significant difference is that scripts should include "zcommon.acs" instead of "common.acs" so that they can get definitions appropriate for ZDoom instead of Hexen. This file just includes three others:

ZDoom has also added two new built-in functions:

The built-in function setlineblocking has also been enhanced.


The Hexen Script Language is called the "Action Code Script", or ACS.

Each map has an ACS file that contains the scripts specific to that map. The scripts within it are identified using numbers that the general special ACS_Execute uses.

A script itself can call the ACS_Execute special, which will spawn (start) another script that will run concurrently (at the same time) with the rest of the scripts.

A script can also be declared as OPEN, which will make it run automatically upon entering the map. This is used for perpetual type effects, level initialization, etc.

The compiler takes the ACS file and produces and object file that is the last lump in the map WAD (BEHAVIOR).

To create a compiled ACS file from a text script from DOS type:

C:\HEXEN> ACS filename [enter]

Script Shared Structure

Map scripts should start with #include "zcommon.acs", which is just...
#include "zspecial.acs"
#include "zdefs.acs"
#include "zwvars.acs"
The file "zspecial.acs" defines all the general specials. These are used within scripts just like function calls. The file "zdefs.acs" defines a bunch of constants that are used by the scripts. The file "zwvars.acs" defines all the world variables. It needs to be included by all maps so they use consistent indexing.

Variables and their Scope

There is only one data type ACS, a 4 byte integer. Use the keyword int to declare an integer variable. You may also use the keyword str, it is synonymous with int. It's used to indicate that you'll be using the variable as a string. The compiler doesn't use string pointers, it uses string handles, which are just integers.

Declaring a variable

There are two "types" of variables:
  1. "str"
  2. "int"
examples:
str mystring;
int myint;
or:
str texture, sound;
int i, tid;
* Note: You can't assign a variable in its declaration; you must give it a value in a different expression.

The SCOPE of a variable is one of the following:

  1. World-scope
  2. Map-scope
  3. Script-scope

World-scope

World-scope variables are global, and can be accessed in any map. Hexen maintains 64 permanent globals, numbered 0-63. You must assign one of the globals a name in order to access it, like this:
world int 5:Grunt;
This tells Hexen to reference world global number 5 whenever it encounters the name "Grunt".

Map-scope

Map-scope variables are local to the current map. They must be declared outside of any script code, but without the world keyword. These variables can't be accessed in any other map.

Script-scope

Script-scope variables are local to the current script - they can't be accessed by any other script or map.

Here's some code that shows the declaration of all three scopes:

world int 3:DungeonAccess; // World-scope

int mapTimer; // Map-scope

script 4 (void)
{
    int x, y; // Script-scope
    ...
}

Language Structure

Here is a quick reference manual type definition of the language. It ends with a description of all the internal functions.

Keywords

The following identifiers are reserved for use as keywords, and may not be used otherwise:

Comments

Comments are ignored by the script compiler. There are two forms:

  1. /*...your comment... */

    All information between the first /* and last */ is ignored. The leading /* and trailing */ are required.

  2. // your comment

    All information past the // is ignored

examples:

/*
   This is a comment.
*/

int a;   // And this is a comment

World-variable definitions

world int <constant-expression> : <identifier> ;

world int <constant-expression> : <identifier> , ... ;

Map-variable definitions

Declares a variable local to the current map.
int <identifier> ;
str <identifier> ;
int <identifier> , ... ;

Include Directive

Includes the source of the specified file and compiles it. This acts the same as if you have "included" the source in the file it resides in. Use this to make a common reference set of code you use often.
#include <string-literal>
The supplied required includes shown earlier are an illustration:
#include "specials.acs"
#include "defs.acs"
#include "wvars.acs"

Define Directive

Replaces an identifier with a constant expression.
#define <identifier> <constant-expression>
Whenever "identifier" is used in the source, the "constant-expression" is substituted. This is similar to a macro or keyboard short-cut.

Constant Expressions

<integer-constant>:

decimal (200)

hexadecimal (0x00a0, 0x00A0)

fixed point (32.0, 0.5, 103.329)

any radix <radix>_digits :

binary (2_01001010)

octal (8_072310)

decimal (10_50025)

hexadecimal (16_00a03f2)

String Literals

<string-literal>:

"string"

Example: "Hello there"

Script Definitions

To define a script:

<script-definition>:

script <constant-expression> ( <arglist> ) { <statement> }

script <constant-expression> OPEN { <statement> }

For example:

script 10 (void) { ... }

script 5 OPEN { ... }
* Note that OPEN scripts do not take arguments.

Statements

<statement>:

<declaration-statement>

<assignment-statement>

<compound-statement>

<switch-statement>

<jump-statement>

<selection-statement>

<iteration-statement>

<function-statement>

<linespecial-statement>

<print-statement>

<control-statement>

Declaration Statements

Declaration statements create script variables.

<declaration-statement>:

int <variable> ;

int <variable> , <variable> , ... ;

Assignment Statements

Assigns an expression to a variable.

<assignment-statement>:

<variable> <assignment-operator> <expression> ;

<assignment-operator>:

=

+=

-=

*=

/=

%=

* Note: An assignment of the form V <op>= E is equivalent to V = V <op> E. For example:

A += 5;
is the same as
A = A + 5;

Compound Statements

<compound-statement>:

{ <statement-list> }

<statement-list>:

<statement>

<statement-list> <statement>

Switch Statements

A switch statement evaluates an integral expression and passes control to the code following the matched case.

<switch-statement>:

switch ( <expression> ) { <labeled-statement-list> }

<labeled-statement>:

case <constant-expression> : <statement>

default : <statement>

Example:

switch (a)
{
    case 1:       // when a == 1
        b = 1;    // .. this is executed,
        break;    // and this breaks out of the switch().
    case 2:       // when a == 2
        b = 8;    // .. this is executed,
		          // but there is no break, so it continues to the next
		          // case, even though a != 3.
    case 3:       // when a == 3
        b = 666;  // .. this is executed,
        break;    // and this breaks out of the switch().
    default:      // when none of the other cases match,
        b = 777;  // .. this is executed.
}
Note for C users:

While C only allows integral expressions in a switch statement, ACS allows full expressions such as "a + 10".

Jump Statements

A jump statement passes control to another portion of the script.

<jump-statement>:

continue;

break;

restart;

Iteration Statements

<iteration-statement>:

while ( <expression> ) <statement>

until ( <expression> ) <statement>

do <statement> while ( <expression> ) ;

do <statement> until ( <expression> ) ;

for ( <assignment-statement> ; <expression> ; <assignment-statement> ) <statement>

The continue, break and restart keywords can be used in an iteration statement:

Function Statements

A function statement calls a Hexen internal-function, or a Hexen linespecial-function.

<function-statement>:

<internal-function> | <linespecial-statement>

<internal-function>:

<identifier> ( <expression> , ... ) ;

<identifier> ( const : <constant-expression> , ... ) ;

<linespecial-statement>:

<linespecial> ( <expression> , ... ) ;

<linespecial> ( const : <constant-expression> , ... ) ;

Print Statements

<print-statement>:

print ( <print-type> : <expression> , ... ) ;

printbold ( <print-type> : <expression> , ... ) ;

<print-type>:

s (string)

d (decimal)

c (constant)

Note: Some combinations of text cause playing errors. The text appear to be hard coded in the .EXE. If you have a strange error and have a "print" statement, remove the statement and see if the error still occurs. If it now works, change your text.

Selection Statements

<selection-statement>:

if ( <expression> ) <statement>

if ( <expression> ) <statement> else <statement>

Control Statements

<control-statement>:

suspend; // suspends the script

terminate; // terminates the script

Internal Functions

void tagwait(int tag);
The current script is suspended until all sectors marked with <tag> are inactive.
void polywait(int po);
The current script is suspended until the polyobj marked with <po> is incactive.
void scriptwait(int script);
The current script is suspended until the script specified by <script> has terminated.
void delay(int ticks);
The current script is suspended for a time specified by <ticks>. A tick represents one cycle from a 35Hz timer.
void changefloor(int tag, str flatname);
The floor flat for all sectors marked with <tag> is changed to <flatname>.
void changeceiling(int tag, str flatname);
The ceiling flat for all sectors marked with <tag> is changed to <flatname>.
int random(int low, int high);
Returns a random number between <low> and <high>, inclusive. The values for <low> and <high> range from 0 to 255.
int lineside(void);
Returns the side of the line the script was activated from. Use the macros LINE_FRONT and LINE_BACK, defined in "defs.acs".
void clearlinespecial(void);
The special of the line that activated the script is cleared.
int playercount(void);
Returns the number of active players.
int gametype(void);
Returns the type of game being played:
int gameskill(void);
Returns the skill of the game being played: Example:
int a;
a = gameskill();

switch( gameskill() )
{
    case SKILL_VERY_EASY:
    ...
    case SKILL_VERY_HARD:
    ...
}
int timer(void);
Returns the current leveltime in ticks.
void sectorsound(str name, int volume);
Plays a sound in the sector the line is facing. <volume> has the range 0 to 127.
void thingsound(int tid, str name, int volume);
Plays a sound at all things marked with <tid>. <volume> has the range 0 to 127.
void activatorsound(str name, int volume);
Causes the activator of the script to play a sound. <volume> has the range 0 to 127.
void ambientsound(str name, int volume);
Plays a sound that all players hear at the same volume. <volume> has the range 0 to 127.
void localambientsound(str name, int volume);
Plays a sound that only the player who activated the script can hear. <volume> has the range 0 to 127.
void soundsequence(str name);
Plays a sound sequence in the sector the line is facing.
int thingcount(int type, int tid);
Returns a count of things in the world. Use the thing type definitions in defs.acs for <type>. Both <type> and <tid> can be 0 to force the counting to ignore that information. Examples:
// Count all ettins that are marked with TID 28:

c = thingcount(T_ETTIN, 28);

// Count all ettins, no matter what their TID is:

c = thingcount(T_ETTIN, 0);

// Count all things with TID 28, no matter what their type is:

c = thingcount(0, 28);
void setlinetexture(int line, int side, int position, str texturename);
Sets a texture on all lines identified by <line>. A line is identified by giving it the special Line_SetIdentification in a map editor.

<side>:

<position>: Examples:
setlinetexture(14, SIDE_FRONT, TEXTURE_MIDDLE, "ice01");
setlinetexture(3, SIDE_BACK, TEXTURE_TOP, "forest03");
void setlineblocking(int line, int blocking);
Sets the blocking (impassable) flag on all lines identified by <line>.

<blocking>:

BLOCK_NOTHING
The line blocks nothing.
BLOCK_CREATURES
The line blocks monsters and players, but not projectiles.
BLOCK_EVERYTHING
The line blocks everything that tries to cross it.

For compatibility with Hexen and older versions of ZDoom, the following can also be used:

ON
Same as BLOCK_CREATURES
OFF
Same as BLOCK_NOTHING

Example:

setlineblocking(22, BLOCK_NOTHING);
void setlinemonsterblocking(int line, int blocking);
This function is like setlineblocking, except that it controls whether monsters can cross a line. This setting is only relevant if the line's regular blocking has been set to BLOCK_NOTHING. Otherwise, a monster will never be able to cross the line.

<blocking>:

ON
Monsters can never cross the line.
OFF
If the line's regular blocking is BLOCK_NOTHING, monsters can cross the line. Otherwise, they cannot.
void setlinespecial(int line, int special, int arg1, int arg2, int arg3, int arg4, int arg5);
Sets the line special and args on all lines identified by <line>.