ACS

From DoomWiki.org

Revision as of 08:44, 4 February 2012 by Gez (talk | contribs) (uninitialized ZDoom, it is a name, not a title)


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 — and any other actors for that matter — 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:

// This is a comment
int AvailableToAllScripts = 101;
int ICanMakeArraysLOL[3] = {3,6,9};

SCRIPT 1 OPEN {
    if (AvailableToAllScripts == 101){
        restart;
    }
}

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.

#80 Action parameters
Script number
Map number
Argument 1
Argument 2
Argument 3

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.

#83 Action parameters #129 Action parameters
Script number Item number
Map number Script number
Argument 1 Argument 1
Argument 2 Argument 2
Key number Argument 3

Script arguments

The ACS action specials contain a feature that allows the mapper to design script modules. In other words, if many game effects on a level share an identical sequence of commands, the map designer does not have to create separate scripts for each one of them. Instead of setting unique tag numbers or other values to multiple scripts, the mapper can insert the numeric data of the commands to the ACS_Execute special itself. Thus, he is able to set different target sectors, speeds, heights or such for distinct events while they are based on a single script.

The figures are stored to parameters called arguments. ACS_Execute provides support for three unique values whereas the locked version of the action special is able to hold two of them. Below is an example script and an overhead view of the map where the script is meant to take place.

SCRIPT 2 (int arg0, int arg1) 
// The definitions tell the script to use argument data from the linedef it was triggered
{
  Door_Open(arg0,16);
  tagwait(arg0);
  changefloor(arg1,"F_014");
  Floor_RaiseByValue(arg1,8,48);
} 
"MAP01" of a PWAD

In the instance, the script arguments are used to enable varying tag numbers. With them, the mapper is able to make a single sequence occur on several target sectors. As the script is triggered, a door opens with a speed of 16. The run of actions is suspended until the door has reached its destination height (tagwait). Once the door is fully open, a floor texture is set to change on another sector and the sector is yet commanded to raise by 48 map units with a speed of eight. The two previous commands simulate a bridge rising from fluid, an effect that is fairly common in the worlds of the Doom engine games.

The white figures in the adjacent picture represent the tag numbers of the sectors. The sectors marked with numbers 1-3 have been edited to behave like doors whereas the sectors with tag numbers 4-6 have been prepared to work as bridges. The three sections are accessed by pressing a linedef in front of a door. To realize the contents of the script above, the triggers are set in the following way.

Parameter Left Middle Right
Script number 2 2 2
Map number 1 1 1
Argument 1 1 2 3
Argument 2 4 5 6
Argument 3 0 0 0

Once the player presses the linedefs, their argument numbers replace the arg0 and arg1 (Argument 1 and 2 respectively) spaces of the script. As a result, three different doors and bridges can be activated at separate moments and in any desired order by using only a single script. It is notable, however, that any function that causes a pause (void delay, tagwait, polywait, scriptwait) in a module script prevents other linedefs from starting it again before the defined pause has expired. In the example, the tagwait function has the side effect of locking the triggers on other linedefs until the active door has stopped its movement. If such behavior appears to be adverse for gameplay, the mapper can preclude it by either designing the sequence to be devoid of delay functions or by creating a separate script for each case.

Some original maps in Hexen, such as the Winnowing Hall, have stained glass walls. The breaking of such is one of the effects that utilizes script arguments. Since the effect remains consistent, the levels usually have only one script controlling the breaking event. The script specials have been set to each wall to become activated by an impact. The arguments store information such as the tag of the floor that needs to be lowered in order to remove the blocking feature of the wall. Another important piece of information is the number of the map thing that is used to spawn shreds of glass upon breaking.

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 3 (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 3 (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; control statement 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 4 (void)
{
  suspend;
  suspend; // The statements "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 4 (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 activate two bull switches to raise a drawbridge over the moat. Then he has to find and press 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 push 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.

Source