Skip to content

Action assigned ACS scripts

Evghenii edited this page Sep 4, 2022 · 4 revisions

Action assigned ACS scripts

In Q-Zandronum it is possible to execute ACS scripts by pressing general action buttons like Move forward, Jump or Fire. These scripts will be executed every tic as long as the action button is pressed.

To do so you first need to create a script. We will be creating kind of a jetpack that thrusts the player upward if he holds the jump key:

Script "Example_Jetpack" (int predicting, int justPressed, int justReleased, int buttons)
{
    // The script also executes when the button was released.
    // For this example, we don't want to do anything when the button is not pressed.
    if (!justReleased)
    {
        // Thrust the player upward
        ThrustThingZ(0, 10, 0, 1);
    }
}

Then, we need to assign that script to the jump action:

In Decorate:

Player.ActionScript "jump" "Example_Jetpack"

Or in ACS:

// SetActionScript(int tid, int action_button, str script_name)
SetActionScript(0, BT_JUMP, "Example_Jetpack");

And that's it!

All possible actions are below:

Decorate ACS Description
"attack" BT_ATTACK Script will execute when the player holds the attack button
"altattack" BT_ALTATTACK Script will execute when the player holds the alt attack button
"reload" BT_RELOAD Script will execute when the player holds the reload button
"use" BT_USE Script will execute when the player holds the use button
"speed" BT_SPEED Script will execute when the player is walking. In Q-Zandronum speed is always considered pressed when the player is walking and unpressed when the player is running, no matter whether he has cl_run ON or OFF and whether he presses the run button or not.
"jump" BT_JUMP Script will execute when the player holds the jump button
"crouch" BT_CROUCH Script will execute when the player holds the crouch button
"forward" BT_FORWARD Script will execute when the player holds the move forward button
"back" BT_BACK Script will execute when the player holds the move backward button
"moveleft" BT_MOVELEFT Script will execute when the player holds the strafe left button
"moveright" BT_MOVERIFHT Script will execute when the player holds the strafe right button
"moveup" BT_MOVEUP Script will execute when the player holds the move up button
"movedown" BT_MOVEDOWN Script will execute when the player holds the move down button
"strafe" BT_STRAFE Script will execute when the player holds the strafe button
"turn180" BT_TURN180 Script will execute when the player holds the turn 180 button
"zoom" BT_ZOOM Script will execute when the player holds the zoom button
"left" BT_LEFT Script will execute when the player holds the look left button
"right" BT_RIGHT Script will execute when the player holds the look right button
"lookup" BT_LOOKUP Script will execute when the player holds the look up button
"lookdown" BT_LOOKDOWN Script will execute when the player holds the look down button
"showscores" BT_SHOWSCORES Script will execute when the player holds the show scores button
"user1" BT_USER1 Script will execute when the player holds the user1 button
"user2" BT_USER2 Script will execute when the player holds the user2 button
"user3" BT_USER3 Script will execute when the player holds the user3 button
"user4" BT_USER4 Script will execute when the player holds the user4 button
"always" 0 Script will execute all the time, no matter what buttons the player presses
  • NOTE: Make sure to not mark the action script as NET or CLIENTSIDE, cause that will break prediction.
  • NOTE: Keep in mind that action scripts are executed AFTER the engine calculated player movement.
  • NOTE: Keep in mind that action scripts are executed in the same order as above, aka attack, then altattack, then reload and so on.

Client predictable

The main reason the above exists is to give modders a way to create unlagged custom movement, i.e. movement that doesn't depend on player's ping. You might have noticed the (int predicting, int justPressed, int justReleased, int buttons) parameters in the script example. We'll get to that later, but first we need to explain how does the engine compensate ping for built-in movement.

Zandronum (and Q-Zandronum) has a feature called client prediction. Please make sure to read How to make your mod feel lagless in multiplayer about that.

Client predictable action assigned ACS scripts bring that to ACS. Now, let's say that we want our jetpack to consume fuel and, if the fuel is out, it will not work.

First, we want to refill our jetpack, for example, by picking up an item, and the jetpack can work for up to 5 seconds, e.g. for 175 tics. And we also want the engine to remember the fuel amount and tell us how much fuel is left during client prediction.

We will need these two functions:

SetPredictableValue(int tid, int val_index, int value) - set a value for client prediction

GetPredictableValue(int tid, int val_index) - read the client predictable value

In other words, you set a value via the SetPredictableValue and the engine will remember the value for that specific tick. Then, during actual prediction, the GetPredictableValue will return the value you set before for that tick

So we need to execute this function on item pickup:

// This will tell the engine to write 175 into predictable variable 1 for the activator (tid 0)
SetPredictableValue(0, 1, 175);
  • NOTE: You can only store up to 20 predictable values, aka 0 <= value index <= 19

Now we need to update our jetpack script to use our fuel. The engine also tells us whether it is currently predicting or not. When it is predicting we only need to thrust the player, otherwise we need to thrust and lower fuel amount:

Script "Example_Jetpack" (int predicting, int justPressed, int justReleased, int buttons)
{
    // The script also executes when the button was released.
    // For this example, we don't want to do anything when the button is not pressed.
    if (value1 > 0 && !justReleased)
    {
        // Thrust the player upward
        ThrustThingZ(0, 10, 0, 1);

        // "predicting" variable is 0 when not predicting and 1 when predicting
        if (predicting == 0)
        {
            // Read the amount of fuel we currently have
            int fuel = GetPredictableValue(0, 1);

            // Reduce the fuel amount by 1 and write it back
            SetPredictableValue(0, 1, fuel - 1);
        }
    }
}

And now you have a jetpack script that doesn't lag and doesn't cause jitter in multiplayer!

Limitations

Avoid using the delay() function inside the predictable part of the script. The reason is that when you use the delay() function, the engine will pause it and put it in queue for future ticks. This will take the script out of client prediction.