A Quick Beginners Guide to ACS
From ZDoom Wiki
If you are new to ACS Scripting, or if you just need help with basic stuff, this thread will hopefully solve your issues. It is assumed that you know something about DOOM Mapping, and that you are using Doom Builder (Doom in Hexen Format).
Contents |
A Basic Script
Congratulations on choosing ACS Scripting! Anyways, this lesson will guide you into making your very first script.
#include "zcommon.acs"
script 1 ENTER {
print(s:"Hello World!");
}
This script will print the string (a variable that can hold pretty much anything, but cannot be used in any operation but strings without conversion) "Hello World!" upon entering the level.
O.K. Now for the interactive part of this lesson:
Open up Doom Builder and create a new map (call it MAP01) in ZDoom (Doom in Hexen) format. Create 1 sector and put whatever textures you want (unless you want HOM) and a Player 1 Start.
Done? Good. Now go to the "Scripts" Menu and choose "Edit BEHAVIOR Lump". You should see a button that says "Make New Script". Go ahead and click on that. Now go ahead and copy the code you see above. When finished, click on "Compile" (if there are any errors, send Pure Hellspawn a PM). Then, click on the "Test Map" button and see what happens!
Now, you will learn about the structure of a script.
The Structure Of A Script
#include "zcommon.acs"
script 1 ENTER {
print(s:"Hello World!");
}
Each script has a number. This is script 1. RULE: ALL SCRIPTS MUST BE FROM 1 to 999 so don't come crying if you have an error because you have script 1337 and it doesn't compile.
After the script number comes the "type" of script. Probably the most common type is (void). BE AWARE (void) MUST BE IN PARENTHESIS! You might use scripts with arguments, but those will be discussed later.
To activate a (void) script, you must either have a thing (I hope you know what this is) with it's thing special set to 80 (monster dies, health/weapon/powerup/key picked up), a line who's special is set to 80 (line crossed, player presses use, shot hits or goes past), or activated by another script (you will learn about that later).
It is very common among mappers to place linedefs that you cannot detect and will not block your path. These lines are used for scripting.
For Special Script Types: Script types
After declaring the type, you must have a { then a } at the end
Valid
script 1 ENTER {
print(s:"Hello World!");
}
script 2 (void)
{
print(s:"Bye World!");
}
Invalid
script 1 ENTER
print(s:"Hello World!");
{
}
So you know about script types? Good.
The Print Statement
In the script you saw above that prints "Hello World!", a Print statement was used. This is a very basic statement that enables you to communicate text information to the player.
The print statement's structure is like this:
print(?:);
The ? resembles the letter s (for string), d (for decimal), and some extras (these two are the most basic, but this lesson will only teach about the string).
#include "zcommon.acs"
script 1 (void) {
print(s:"Hi.");
print(s:"lol");
}
This makes the message "Hi." appear, and then the message "lol" appear.
script 1 (void) {
#include "zcommon.acs"
print(s:"Hi.\nlol");
}
The "\n" means new line. This will cause the message "Hi." to appear on one line, and "lol" to appear on another.
For more information, goto Print
Variables
This lesson will teach you about variables and basic usage.
script 2 (void) {
int a = 9;
print(s:"a is " , d: a);
}
This script does two things. First, it declares a to be an integer and setting it's value to 9. Then, it prints the string "a is " followed by the value of a. In this case, the output would be "a is 9".
You can declare other variables, such as bool (boolean, which returns either true or false), string (which can hold pretty much anything, but cannot be used in operations besides strings, incorrect usage can and probably will cause errors).
With integers, you can do basic math (and a little advanced math if programmed correctly).
script 2 (void) {
int a = 9;
int b = 17;
print(d: a + b);
}
This is basic addition. This code declares an integer a and sets it's value to 9, declares another integer b and sets it's value to 17, and prints out the value of a + b. Since a is 9 and b is 17, it will print out the value of 9 + 17, which is 26. If you activated the script while playing, you would see the number 26.
If you want to print "9 + 17", then do so like this:
script 2 (void) {
print(s:"9 + 17");
}
Other useful functions with integers: -, *, / (BE CAREFUL WITH THIS ONE), %, ++, --.
(in these examples, a is still 9 and b is still 17)
- - is minus. a - b = -8
- * is multiplication. a * b = 153
- / is division. a / b = 0 (Not 0.5294, integers will always round down to the nearest whole number)
- % is the modulus operator. a % b = 8 (After dividing a and b, what is left over? )
- ++ simply adds 1 to a variable. a++ = 10
- -- simply subtracts 1 from a variable. a-- = 8.
This concludes the basic introductory lesson to variables.
Script Actions and Parameters
Surely you want to do other stuff besides printing text, right? Well, you have come to the right place. Here, you will learn about scripts that do actions besides print, and parameters.
Suppose we put a red key in the map. In vanilla Doom, picking it up gives you a red key, enabling you to open red doors or activate swithces that require the red key.
Well, with ACS Scripting, you can make any object do any DOOM relating thing you want! You can have a red key kill the player, give him the BFG9000, do absolutely nothing, or raise dead monsters! If you have two red keys, you can make each one do something completely different!
Create a sector, and put a player start, and a red key. You'll then need to give each thing an identification number. Double click on the thing, then click on the "effects" tab, then click on the "Next Unused" button and write down the number and what thing is being edited (player or key).
Now, go back to the key and give it a thing action of 80 - H Execute Script with a script number of 1.
Type the following code (Be aware this code won't compile because of a missing parameter (intentional))
#include "zcommon.acs"
script 1 (void) {
Thing_Damage();
}
Now, for the Thing_Damage statement, we need some parameters. What thing do we want to damage? How much damage? If the thing dies, what message appears?
The first parameter is who we want to damage. Since we want to damage the player, we put the players identification number (make sure it is 1; set it before doing the key)
With an identification of 1:
Thing_Damage(1);
How much damage? Let's do weak damage (with a power of 2). To move from one parameter to another, put a comma after the end of the parameter
Like This:
Thing_Damage(1,2);
If the player dies, we just want the obituary to say "%o died" (%o is your name).
We can do that like this:
Thing_Damage(1,2,0);
Now, replace Thing_Damage() with what you see above. Your code should look like this:
#include "zcommon.acs"
script 1 void {
Thing_Damage(1,2,0);
}
Test your level, pick up the red key, and watch your health. It will drop when you pick up the red key.
Look at this page for more action statements
Using functions to make simple changes to a map
90% of beginers don't need to know how to declare variables or how to use program control statements like 'if' statements. So this quick tutorial is ment to show you how to use the built in functions and line actions to make simple changes to a map. Start by opening doombuilder and making a new map in zdoom(doom in hexen) format.
We are going to make a simple pool first. So create a sector thats roughly a 512 square should work. Any textures and flats you want should be fine. I would make the floor height 0 and the ceiling height 128 and make the brightness somewheres between 160 and 192. Be sure to add a player one start inside this sector as well. Then create a second sector inside the first one. A 64 square should be good. Make the ceiling height the same as the first one and make the floor height 64 units lower then the first one. That should -64 if you follow my suggestions. Give this sector a tag, for simplicity sake lets say 1. Now create a third sector outside from the other two and I will call this the control sector. Size doesn't matter but I would suggest a small triangle. Make the ceiling hieght the same as the ceiling of the inside sector, in our case it should be 128, and the floor should be the same as the outside sector, in our case 0, and lets give it a tag 2. To make the pool look realistic I suggest giving the upper flat of the inside sector and the lower flat of the control sector the fwater1 flat. Next double click on any of the control sectors lines. We want to give it line action 209:transfer_heights. It takes two arguments. The first is the tag of the inside sector, which I gave as 1, the other is called 'when'. It is an advanced option and depending on the value can create a number of different effects. We will give it the value of 8 because that creates a swimable effect otherwise you generally will just use 0.
Now comes the ACS. Click on the scripts tab and create a new script. On the first line you want to add this:
#include "zcommon.acs"
What this does is tell the compiler that you want to include this file. This comes with your doombuilder download and contains information about the different built-in functions and other stuff. Next we want to creat a script that adds color and fog to the control sector. For this we need two functions: sector_setcolor() and sector_setfade(). Both take the same arguments: the sector tag of the sector you want this to effect, the red value, the green value, and the blue value. Valid values are from 0-255, where 0 means none of that color and 255 is all of that color. What we get is this
script 1 open
{
//we want to use the sector tag of the control sector
sector_setcolor(2, 0, 205, 0); //this creates a blue color
sector_setfade(2,0,205,0); //this creates a blue fog effect
}
And that's it. Notice the use of the 'open' script type, this means that the script will run when the map loads. Run it and you should see a pool that you can go into and 'swim' in. For more information on transfer_heights since its such a complicated line action check here: Transfer_Heights
As seen above, both sector_setfade and sector_setcolor are both line actions but you can use them in ACS just like a built in function. In this example we will create a door that opens when you kill a monster.
To do this create a new map and make two rooms connected by a door. The first room should contain a monster, lets say a zombie, and the player 1 start. In the other room can be some random goodie or whatever. The door connecting the two should not have any line actions so that you can open the door but should have a tag, lets go with 1 agian. Lets create the script. We are going to have the same first line as befiore and as a matter of habit this should always be your first line unless you have a comment header.
#include "zcommon.acs"
Then we are going to use the print() and the door_open() functions. The print function is a complicated function but we are just going to use its most basic functionality as describe by Pure Hellspawn. Feal free to check out the wiki for more information on it. The door_open() fucntion takes three arguments: the sector tag, in our case 1, the speed to open the door, I find 64 to be good fast speed, and the light tag, this is a more advanced feature so I don't describe or use it so we will set it to one but you can check out its usage by looking on the wiki. This gives us this code:
script 1 (void)
{
print(s:"You killed a zombieman!"); //prints "You killed a zombieman" onto the screen
door_open(1,64,0); //let's open the door
}
Now, in order for this to work we give the zombie a thing special. In this case we will use 80:acs_execute(). It basically runs a script. It takes five arguments: the script number we want to execute, in this case 1, the map of the script you want to execute, you can have a script from another map called from a different map and when you enter that map it will run the script on startup, we will use 0 because that denotes the current map. The next three arguments are values you can pass to a script if your script allows. Ours doesn't so we set all of them to 0.
That should be everything. Notice I use the 'void' type script, also known as the closed script, because we set up a way for the script to run. Now just run it and kill the zombieman the message should print and the door should open. If you want to learn more I suggest the wiki and trial and error are you best routes, thats how I learned.
Flow Control
Conditional execution (if / else)
Otherwise known as if/else, this is a data comparison. Simply, this means that if a condition is true/false, the script (or parts of it) will/won't run. It's composed like this:
script # type
{
if(condition)
{
command1;
}
else if(condition)
{
command2;
}
else
{
command3;
}
}
"condition" can be something like x==y (variable x is equal to variable y) or GameType()!=GAME_SINGLEPLAYER (the game type does not equal to single player (meaning there's more than more player in the map). The "else" parts are not required if you only want things to happen on one condition.
Conditional repeating loops (while)
A "while" loop will do the same thing as an if comparison, except it will restart when finished. The script will stop repeating when the condition(s) no longer are met.
NOTE ABOUT SCRIPT LOOPS:
A portion of a script intended to loop MUST contain at least one "delay" function with a value over 1. If not, ZDoom will terminate the script for it's own good (a looping script that won't take a break between restarts will go on in eternity without giving other things the ability to do anything). If your script is terminated, ZDoom will let you know through a message like "Runaway script # terminated" at the top of your screen.
NOTE ABOUT UNTIL LOOPS:
To do an until loop, simply add a ! (not) sign at the beginning (eg. while(!(i>0))
script # type
{
int i=5;
while(i>0)
{
command or series of commands that change i;
}
}
"int i=5;" means that the compiler makes a variable "i" with a numerical value of 5. This while loop will run as long as the variable i is of a value greater than 0.
If you want to create a while loop that never ends (perhaps to create an ongoing effect in the map), you can use an expression that never returns false to do this:
script # type
{
while (true) // Or while(1), or while(4 == 4), etc
{
commands
}
}
Count-controlled loops (for)
A "for" loop will loop a portion of a script a defined number of times. Such a loop looks like this:
script # type
{
for(int i=0; i<10; i++)
{
command;
}
}
This script will do whatever commands are nested inside it ten times.
- "int i;" means that the compiler should identify a new variable, "i" with no value.
- "i<10" means the loop condition, or when the loop should continue, in this case, when "i" is less than 10.
- "i++" means that every time the loop is finished, "i" will add one to it's value. After the first time, "i" will be 1, after the next, "i" is 2, etc.

