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:
- zspecial.acs
- zdefs.acs
- zwvars.acs
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:
- "str"
- "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:
- World-scope
- Map-scope
- 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:
- break
- case
- const
- continue
- default
- define
- do
- open
- print
- printbold
- restart
- script
- special
- str
- suspend
- switch
- terminate
- until
- void
- while
- world
Comments are ignored by the script compiler. There are two forms:
- /*...your comment... */
All information between the first /* and last */ is ignored.
The leading /* and trailing */ are required.
- // 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:
- the continue keyword jumps to the end of the last <statement> in the
iteration-statement. The loop continues.
- the break keyword jumps right out of the 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:
- GAME_SINGLE_PLAYER
- GAME_NET_COOPERATIVE
- GAME_NET_DEATHMATCH
int gameskill(void);
Returns the skill of the game being played:
- SKILL_VERY_EASY
- SKILL_EASY
- SKILL_NORMAL
- SKILL_HARD
- SKILL_VERY_HARD
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>:
- TEXTURE_TOP
- TEXTURE_MIDDLE
- TEXTURE_BOTTOM
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>.