ACS

ACS is the scripting language that was created for Hexen and has been greatly expanded by ZDoom. ACS enables level makers to script events during gameplay, making the creation of interactive environments even in Doom's archaic engine infinitely more open-ended. With very basic commands, 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. One can even use move walls, given that they meet certain criteria (see Polyobject). Textures displayed on floors and walls can be changed. Monsters &mdash; and any other actors for that matter &mdash; can be inserted, removed, monitored, have many of their properties altered, and even pursue "objectives" through a limited form of AI. The use of ACS opens many possibilities for level design, especially if the person using it is talented, patient, and imaginative.

Somewhat more technically, a scripting language is a limited form of programming language; its code takes the form of a list of commands to be interpreted by a specific engine. ACS is structured much like C/C++ (it is even commentable, as shown in the example below). A script, which is composed in a text editor of some sort, contains commands, variable declarations, and possibly even calls to other scripts (as with subroutines in a programming languages). The top-level items to recognize are scripts and their script types, which are the events that trigger the sequence of commands contained in a given script. A script is started by typing something such as the following:

Note that a script is defined somewhat like a function in C. "OPEN", as used in this example, is a script type that tells ZDoom that the script is to be executed upon starting the level.

Variables are dimensioned, as in many programming languages. Arrays, or variables in which many values can be stored with an index to differentiate between them (e.g. ICanMakeArraysLOL[2] = 9), are valid. A variable intended to be available to all sub-scripts must be declared outside all sub-scripts.

Note also that ACS supports conditional statements, and therefore loops made with conditional statements. It supports most (if not all) C/C++ implementations of conditional statements and loops. Looping a script without a delay will cause ZDoom to automatically terminate the script, however, because it won't give anything else in the map a chance to run. When this happens, a message like the following will be generated:

Runaway script 1 terminated

ACS action specials
Once the scripts have been compiled and saved to the BEHAVIOR lumps of the levels, the map designer uses the ACS_Execute action special (#80) to get the contents implemented. The action can be inserted to any sort of linedef, for instance, a switch or a crossable line. Hexen also allows monster kills and item pickups to trigger action specials so such occurrences can also be set to execute scripts. The ACS special includes numeric information such as the identification figure of the script and the map of which script is intended to run. If the target script belongs to a different level than where it was triggered, the system starts to execute the script as soon as the player transports to the map. ACS_LockedExecute (#83) action allows the mapper to lock the trigger function. Consequently, the player needs to hold the right one of the eleven keys before he is able to start the script. The stock maps in Hexen use this solely for spacebar activations but line walkovers, bumps and projectile hits are also supported by the action special. While some of these methods seem to have little to do with key usage, the features do allow the mapper to create imaginative puzzles around the items, especially if the keys are replaced with other artifacts. UsePuzzleItem (#129) is similar to the previous action, as it prevents a script from triggering before the player possesses the correct object among the 17 puzzle items of the game.

Randomization
Among notable features, ACS includes a function that allows the map designer to randomize game events. This enabled a new level of replay value to the series of games using the Doom engine as the game content could potentially change between playthroughs. So far, only Heretic's behavior for Firemace placing and the game's implementation of ambient sounds had provided remarkable randomization.

Drawing a single event
By setting up a script variable and assigning it a random value using the proper function (int random;), the mapper can cause a script to run partially. This is completed by dividing the script into cases with the if conditional statement. In the structure, each case practically has an identification number that matches to the values the variable can receive beforehand. Below is an example of a script where randomization is involved. Once the sequence is activated, one of the defined ambient sounds gets played. SCRIPT 2 (void) {  int Select; // This is a script variable Select = random(1,3); if(Select == 1) {    ambientsound("Ambient2",127); // Plays a cricket chirping sound (with full volume) }  else if(Select == 2) {    ambientsound("Ambient5",127); // Frog croaking }  else if(Select == 3) {    ambientsound("Ambient7",127); // Bird singing }  }

The maps in Hexen contain diverse applications of the function. One can be found during the first visit to the Guardian of Ice. When the player has hit the switch at the northern end of the level, four columns rise behind it, and eventually an area resembling a sword becomes illuminated in front of one. The sword acts like an arrow showing the column plate that works as a teleporter back to the hub map. By stepping onto the other three plates, the player causes their ceiling to plummet which produces severe damage to him. The puzzle has an example of a script that, during its sole run, is set to randomly choose a single case among many to execute only its commands. The drawn map variable subsequently controls the behavior of the column plates that have a different script assigned to them.

Setting a random order for multiple events
Randomization can also be utilized in scripts that include multiple parts, meant to be activated at separate phases of gameplay. Basically, all command cases of the script can be edited to occur in a random order. With the aid of map variables, the mapper is able to set the system to "memorize" which cases have already been run. If the function later draws a figure of which corresponding contents have been executed for once, an additional test can be used to send the script variable for a redraw. When an unused value is finally found, the test is passed and the value's contents are run. A way to implement such a script is demonstrated below.

int Lock; int Lock2; int Lock3; // These three are map variables SCRIPT 2 (void) {  int Select; Select = random(1,3); if((Lock == Select) || (Lock2 == Select) || (Lock3 == Select)) // || means OR  { restart; }  if(Select == 1) {    ambientsound("Ambient2",127); Lock = 1; }  else if(Select == 2) {    ambientsound("Ambient5",127); Lock2 = 2; }  else if(Select == 3) {    ambientsound("Ambient7",127); Lock3 = 3; }  }

This form of randomization has a significant role in the Hypostyle where the central hall has locked doors in all cardinal points. Once the player has eliminated a specific Ettin in the hall, one of the four doors is randomly selected to open. Each door seals an area that has a switch at the end of it. When the player activates such, another door in the hall is chosen to open similarly, revealing a new area and a toggle. Once the switch in the fourth area has been triggered, yet two more doors open in the hall and the player can enter either one to confront Death Wyvern. Although the script that controls the puzzle has a different structure in comparison with the above example (it uses a do while iteration statement in addition to if statements), the common idea is the same and they both result in their events being executed in a random order.

Series of actions
While advancing in Hexen, the player frequently encounters events that require more than just one action to occur. To create such puzzles, the mapper can use the suspend; keyword in a script to interrupt its run of commands. Once the script is called again, the execution continues from where the previous run left. For instance, if the realization of an event requires three separate toggles to be pressed, the following script applies. SCRIPT 3 (void) {  suspend; suspend; // The functions "absorb" the effect of the two first toggles, whichever they are ambientsound("Chat",127); printbold(s:"SEQUENCE COMPLETED!"); }

In case there is a vast amount of switches, it is not necessary to repeat the keyword over and over again. Given that the puzzle includes twelve toggles, a map variable can be assigned to produce a more convenient structure.

int Count; SCRIPT 3 (void) {  Count++; // This increases the variable's value by one each time the script is run (i.e. a new switch is pressed) if(Count == 12) {    ambientsound("Chat",127); printbold(s:"SEQUENCE COMPLETED!"); } }

Multiple series of switches can be met in the Castle of Grief. In the beginning, the player needs to pull two bull switches to raise the drawbridge over the moat. Then, he has to find and activate four moon switches outside the walls of the castle to lower a platform in the tower. Finally, he needs to visit the five guard towers and press a skeleton switch in each one of them to lower a staircase to a Clock Gear. The map variable technique has been utilized in the scripts that control the series.