User:Hayaunderscore/ACS

From SRB2 Wiki
Jump to navigation Jump to search
This article or section is incomplete. It doesn't have all of the necessary core information on this topic. Please help the SRB2 Wiki by finishing this article.
To do
  1. Add the function glossary
  2. Add the script type glossary
For more information on this article, visit the ACS page on the Doom Wiki.

ACS (Action Code Script) is the scripting language that was originally created for Hexen by Raven Software and has been greatly expanded by ZDoom. SRB2 itself doesn't use ACS, but Dr. Robotnik's Ring Racers uses an implementation to power specific level interactions and events.

Introduction

ACS enables level makers to script events during gameplay, making interactive environments much easier to create compared to linedef executors. Using basic functions, an author can modify the structure of a level in ways such as raising and lowering floors separately, simultaneously, in the same or opposite directions, and to any height or depth. Textures displayed on floors and walls can be changed. Map objects and any actor for that matter can have their properties altered, etc. ACS is a very possibility-opening scripting implementation, especially if the person using it is talented, patient, and imaginative.

Somewhat more technically defined, a script is something a person writes in a text editor of some sort, that contains individual scripts (kind of like subroutines), commands, variable declarations, and so on. ACS is its very own miniature programming language, structured much like C/C++. The top-level items to recognize are scripts and their script types, which are simply put the events that trigger the sequence of commands contained in a given script. A script is started by typing something such as the following:

// This is a comment
/* This is also a comment */

#include "rrcommon.acs" // You'll probably need this.

int AvailableToAllScripts = 201;
 
script "OpenScript" OPEN
{
    int ScriptVariable = 0;
    if (ScriptVariable == 0 && AvailableToAllScripts == 201)
        Log("Hello World!");
}

Variables are dimensioned like in many programming languages. If you want a variable to be available to all scripts ("global" in scope), declare/define it outside any script declarations (by tradition, above them).

Note that a script is defined sort of like a function in C, including the fact that it does not have to be terminated after the bracket with a semicolon like statements inside scripts. "OPEN" as used in this example is a script type that tells the ACS VM that the script is to be executed upon starting the level.

Note also that ACS supports conditional ("if") statements, and therefore loops made with conditional statements. It supports most (if not all) C/C++ implementations of conditional statements and loops.

ACS needs to be compiled before it can be used in a map. RR's map editor, High Voltage Ring, a fork of Ultimate Zone Builder, can compile ACS directly via it's script editor, and therefore also has it's own copy of GDCC-ACC, for usage in other programs, (like SLADE).

A quick beginner's guide to ACS

To do
Finish this !!!!!! reference zdoom article for dis

If you are new to ACS scripting, or if you just need help with basic stuff, this section will hopefully solve your issues. It is assumed that you know something about mapping.

A basic script

This lesson will guide you into making your very first script.

#include "rrcommon.acs"

script "OpenScript" OPEN
{
    Log("Hello world!");
}


This script will print the string (a data type that can hold pretty much anything, but cannot be used in any operation except strings without conversion) "Hello World!" upon entering the level.

O.K. Now for the interactive part of this lesson, assuming you're making a map for Ring Racers:

Open up High Voltage Ring and create a new map (with all the boilerplate required), give it a sector and a Player 1 start.

Press F10. This will bring up the Script Editor window, which is also found on the main toolbar. Now go ahead and copy the code you see above. You can simply save via Ctrl+S or by clicking on "Compile". Now test your map, and see what happens!

Now, you will learn about the structure of a script.

The structure of a script

#include "rrcommon.acs"

script "OpenScript" OPEN
{
    Log("Hello world!");
}

Each script requires a name. This is script "OpenScript".

After the script number comes the "type" of script. Probably the most common type is (void). Remember, (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 475 (can be used only by thing types 4096 and 5000, the radius action and ball switch, respectively), a line who's special is set to 475 (line crossed, by the player or by any other thing), 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.

After declaring the type, you must have a { then a } at the end

Valid

script "OpenScript" OPEN
{
    Log("Hello world!");
}

script "CalledScript" (void)
{
    Log("Bye world!");
}

Invalid

script "OpenScript" OPEN
Log("Hello world!");
{
}

So you know about script types? Good.

The Log statement

In the script you saw above that logs "Hello World!", a Log statement was used. This is a very basic statement that enables you to send information via the console. (Print is a function in Ring Racers, though it's more in your face compared to ZDoom's.)

The Log statement's structure is like this:

Log(?:s);

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). You can omit this and the colon if you're just printing a string. The s resembles the string you want to log to the console, so something like "chicken butt". Make sure your string is enclosed with double quotes!

script "CalledScript" (void)
{
    Log("I would like to");
    Log("make an announcement");
}

This makes the message "I would like to" appear, and then the message "make an announcement" appear.

script "CalledScript" (void)
{
    Log("this is a very long message\nI have to break it down !!!!!!!!");
}

The "\n" means new line. This will cause the message "this is a very long message" to appear on one line, and "I have to break it down !!!!!!!!" to appear on another.

Variables

This lesson will teach you about variables and basic usage.

script "aiswhat" (void) 
{
    int a = 9;
    Log(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 "anbiswhat" (void) 
{
    int a = 9;
    int b = 17;
    Log(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 "whats9plus17" (void) 
{
    Log(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 = 9 (After dividing 9 and 17, 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 have a ball switch in the map. By default, it does nothing even when activated. But with ACS scripting, you can make it do anything you want! You can have a switch call dialogue, set the player's properties, or move parts of the level geometry! This also means you can have two different ball switches do different things!

Create a sector, and put a player start, and a ball switch. Now, give the switch a special action of 475 - Call ACS Script, with a name. Keep the name in mind, as that will be your identifier on what script the switch will use. The example will use "Die" as the script name.

Type the following code (Be aware this code won't compile because of missing parameters (intentional))

#include "rrcommon.acs"

script "Die" (void)
{
	SetThingProperty();
}

Now for the SetThingProperty statement, we need some parameters. What thing do you want to change the property of? What property are you gonna change? What will be the value of the changed property?

The first parameter is who we want to change the property of. This takes in a thing tag, but we can use the activator's TID directly here via the function ActivatorTID(), which is, in this case, the player.

SetThingProperty(ActivatorTID()); // don't forget the parentheses! ActivatorTID is a function that returns the id of the activator activating the script, not a variable!

What property should we change? Let's change the health of the activator. To move from one parameter to another, put a comma after the end of the parameter Use the THING_PROP_ enums for these.

SetThingProperty(ActivatorTID(), THING_PROP_HEALTH);

What will be the final value of the property? Let's set it to 0, which will forcibly respawn the player.

SetThingProperty(ActivatorTID(), THING_PROP_HEALTH, 0);

Now, replace SetThingProperty() with what you see above. Your code should look like this:

#include "rrcommon.acs"

script "Die" (void)
{
	SetThingProperty(ActivatorTID(), THING_PROP_HEALTH, 0);
}

Test your level, charge an insta-whip into the ball switch, and see what happens! You will be forcibly respawned after activating the switch.

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 name type
{
    if(condition)
    {
        // commands
    }
    else if(condition)
    {
        // commands
    }
    else
    {
        // commands
    }
}

"condition" can be something like x==y (variable x is equal to variable y) or IsNetworkGame() (the game is a netgame, as opposed to match race or grand prix). 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 UNTIL LOOPS:
To do an until loop, simply add a ! (not) sign at the beginning (eg. while(!(i>0))

script name 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 name type
{
    while ({{const|TRUE}}) // Or while(1), or while(4 == 4), etc.
    {
        // commands. ditto.
    }
}

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 "ForLoop" (void)
{
    for (int i=0; i<10; i++)
    {
        Log(i:i);
    }
}

This script will print 0 through 9 in the log.

ACS uses what's known as a three-expression for loop, so called for the three expressions: the initializer (i=0), the loop-test (i<10), and the counter (i++). With the first pass of the loop is i = 0 which satisfies the loop test of i < 10 and then proceeds to the commands nested within the loop. Int i is then incremented by 1 and the next iteration of the loop begins. This continues until i = 10, at which point the loop-test of i < 10 is no longer satisfied, and the nested commands are not executed.