Skip to content

Actions

Actions trigger game functionality from json scripts.

They can do useful things such as spawning objects, triggering visual effects, changing the size of the arena, and more.

They can be run from stages, units, bullets, patterns, status effects, and even guns.

State Machine Control

These actions are used to handle logic and flow through a behaviour state machine.

Wait

Wait for an amount of time before continuing.

1
2
3
"state 0": [
    { "action": "Wait", "time":0.5, },  
],

Tip

To wait indefinitely, don't provide a value for time.

1
2
3
"state_0": [
    { "action": "Wait", },  
],

Warning

For best practices, only use Wait actions in behaviour state machines, not in handlers like onHitPixel, onPlayerHit, etc.

Condition

Use Condition actions like if-else statements. Based on whether the condition is true or false, call certain other actions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"state_1":[
    { "action": "Condition", "condition": "playerPos.y > 0f",
        "true": [
            { "action": "CallMethod", "target": "player", "method": "Vibrate", "params": { "strength": 1.5, "time": 0.25, }},
            { "action": "Goto", "state": "state_2" },
        ],
        "false":[
            { "action": "Goto", "state": "state_3" },
        ]},
],

Tip

You can omit the false branch if it's not needed (or even omit the true branch).

1
2
3
4
5
6
7
8
"5b":[
    { "action": "Condition", "condition": "player.GetStatusLevel('status/passive/shield') > 0",
        "true": [
            { "action": "CallMethod", "target": "stage", "method": "SpawnPattern", "params": { "path": "octopus/pattern/bubbleGrid.1", }},
            { "action": "CallMethod", "target":"player", "method": "Vibrate", "params": { "strength":0.1, "time":1, }},
            { "action": "CallMethod", "target":"stage", "method": "ShakeCamera", "params": { "strength":0.25, "time":0.1, }},
        ],},
],

Tip

Conditions can be nested as much as you want.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"state 0": [
    { "action": "Condition", "condition": "rand.Float(0f, 1f) < 0.25f",
        "true": [
            { "action": "Condition", "condition": "playerPos.y < 0f",
                "true": [{ "action": "Goto", "state": "state 1" },],
                "false":[{ "action": "Goto", "state": "state 2" },]},
        ],
        "false":[
            { "action": "Condition", "condition": "playerPos.x < 0f",
                "false": [{ "action": "Goto", "state": "state 3" },],},
        ]},
],

Switch

Use a Switch action when your branching logic has too many conditions.

The value parameter should be an integer variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "properties": {
        "attack_num": { "type": "Int", "value":1 },
    },

    // ...

    "fsm": {
        "form0": {
            "choose_next_attack":[
                { "action": "Switch", "value": "attack_num", "cases": {
                    "0": [{ "action": "Log", "message": "Shoot small attack!", },],
                    "1": [{ "action": "Log", "message": "Shoot medium attack!!", },],
                    "2": [
                        { "action": "Log", "message": "Shoot big attack!!!", },
                        { "action": "CallMethod", "target":"stage", "method": "PlaySfx", "params": { "sfxType":"BombExplode", "pos":"unitPos", }},                
                    ],
                }},
            ],
        }
    },
}

Repeat

Use the Repeat action to loop actions multiple times.

The count parameter controls the number of times to loop the actions.
The delay parameter is the time to wait after each loop.
The inner parameter is the list of actions to loop.

1
2
3
4
5
"state_6":[
    { "action": "Repeat", "count": "6 + min(loopNum, 4)", "delay": "map(loopNum, 0, 6, 1.5f, 1.25f, 'QuadIn')", "inner": [
        { "action": "CallMethod", "method": "ChargePattern", "params": { "path": "claw/pattern/orbiter.1", "partType": "core", "chargeTime":1, "dir":"(playerPos + playerVel * 2f) - partPos", }},
    ]},
],

Expanded for clarity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
"state_6":[
    { "action": "Repeat", 
        "count": "6 + min(loopNum, 4)", 
        "delay": "map(loopNum, 0, 6, 1.5f, 1.25f, 'QuadIn')", 
        "inner": [
            { "action": "CallMethod", "method": "ChargePattern", 
                "params": { 
                    "path": "claw/pattern/orbiter.1", 
                    "partType": "core", 
                    "chargeTime":1, 
                    "dir":"(playerPos + playerVel * 2f) - partPos", 
                }
            },
        ]},
],

Goto

The Goto action lets you change states.

In the following example, normally the state would loop from "1" back to the beginning state "initial delay". Instead it's told to go back to state "0".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"fsm": {
    "form0": {
        "initial delay":[
            // do this only one time
        ],
        "0":[
            // do this every time
        ],
        "1":[
            { "action": "Goto", "state":"0" },
        ],
    }
},

Tip

You can also be more specific and pass in a state of "form0.0" (or even "form1.0", if that exists).

SetValue

Set the value of a custom variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
    "properties":[
        // ...
        "lastTauntTime": { "type": "Float", "value":-60, },
    ],

    // when the player takes damage, boss should consider taunting them if it's been long enough since last taunt
    "baseForm": [
        "onHitPlayer":[
            { "action": "Condition", "condition": "(stageTime - lastTauntTime) > 5f",
                "true": [
                    // display a speech bubble taunt here

                    // set value of lastTauntTime to current time
                    { "action": "SetValue", "name": "lastTauntTime", "value": "stageTime" },
                ],},
        ],
    ],
}

When setting the value of another object's property, you must specify the target object and the type of the property.

1
{ "action": "SetValue", "target":"stage", "name": "cloudFlashGlowPercent", "type": "Float", "value": "clamp01(cloudFlashGlowPercent + 0.75f)" },

Expanded for clarity:

1
2
3
4
5
6
7
8
9
[
    { 
        "action": "SetValue", 
        "target":"stage", 
        "name": "cloudFlashGlowPercent", 
        "type": "Float", 
        "value": "clamp01(cloudFlashGlowPercent + 0.75f)" 
    },
],

CallMethod

The CallMethod action is used to run whitelisted C# methods.

Specifying the target is only necessary if you are calling a method on a different json object.

1
{ "action": "CallMethod", "target":"stage", "method": "AddTimeScale", "params": { "scale": 0.33, "time": 0.25, "easingType": "CubicIn" }},


Here are some of the most useful methods:

Stage Methods

SpawnUnit
AddTimeScale
LerpBounds
ShakeCamera
SpawnPattern
AffectBulletsInRadius
AffectTouchingBullets
AffectUnitsInRadius
AffectTouchingUnits
AddPattern
DespawnAddedPatterns

Player Methods

AddForce
AddPhysicsForce
Vibrate
ReplaceMainGun
RestoreDefaultMainGun
AddStatus
GetStatusLevel

Pattern Methods

AddBullet
AffectBullets

Bullet Methods

AddPattern
Flash
Redirect
Reflect
AffectTouchingBullets
AffectTouchingUnits
SetFloatVar
SetVectorVar
SetIntVar
Transform

Unit Methods

AddForce
Respawn
Flash
DoesPartExist
Shake
SetHidden
AimTowards

Unit Part Methods

ChargePattern
AddPattern
Flash
Look
SpawnBubble
StopChargingPatterns
StopPatterns
SetCore

Pixel Methods

SplashDamage
TransformPixelLine
TransformPixelCircle
Spark
ApplyDamage

Status Effect Methods

AddBullet
DespawnAddedBullets
AddGun
DespawnGuns
SetCharge
Execute
LevelUp
LevelDown
Disable

Return Values

When using CallMethod, or when calling a whitelisted method directly from a scriptfunc, it's possible to store the return value (output) of the method.

For example, SpawnUnit has a return value of type Unit (which will be equal to the unit spawned if successful, or null if unsuccessful).

1
2
3
4
5
6
7
{ "action": "CallMethod", "target":"stage", "method": "SpawnUnit", 
    "params": { 
        "name": "egg", 
        "pos": "vec2(0f, 0f)" 
    }, 
    "return":"eggUnit",
},

In that example, egg refers to the name of the unit in the stage's units property...

1
2
3
4
"units": {
    // ...
    "egg": { "config": "misc/unit/eggShield", "count":1, },
},

...and eggUnit refers to the custom variable of type Unit defined in the stage's properties:

1
2
3
4
"properties": {
    // ...
    "eggUnit": { "type": "Unit" },
},

By setting the return value to eggUnit, the variable can now be used to call actions on the unit we just spawned.

1
2
3
4
5
{ "action": "CallMethod", "target":"stage", "method": "SpawnUnit", "params": { "name": "egg", "pos": "vec2(0f, 0f)" }, "return":"eggUnit" },
{ "action": "Condition", "condition": "eggUnit != null",
    "true": [
        { "action": "CallMethod", "target":"eggUnit", "method": "Shake", "params": { "strength":2, "time":0.5, }},
    ],},

Tip

You can also use the return value of methods called within scriptfuncs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"properties":{
    // ...
    "pixel": { "type": "PixelData", },
},

// ...

"baseForm":{
    // ...
    "onSpawn": [
        { "action": "SetValue", "name": "pixel", "value": "unit.GetRandomPixel()" },
    ],
},

Subroutines

Subroutines allow you to create your own functions (essentially, a sequence of actions).

The following example calls the speech.sub subroutine when the player gets hit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
    "onHitPlayer":[
        { "action": "Condition", "condition": "(stageTime - lastTauntTime) > 5f",
            "true": [
                { "action": "CallSubroutine", "target":"unit", "path": ".speech.sub", "params": { "message": "#octopus.boss.player_hit_pixel", "chance":"map(stageTime - lastTauntTime, 0f, 60f, 0f, 0.45f, 'QuadIn') * map(stageTime - lastSpeechTime, 0f, 4f, 0f, 1f, 'ExpoIn')", }},
                { "action": "SetValue", "name": "lastTauntTime", "value": "stageTime" },
            ],},
    ],

    "speech.sub": {
        "parameters": {
            "this": { "type": "Unit" },
            "message": { "type": "String" },
            "chance": { "type": "Float" },
        },

        "actions": [
            { "action": "Condition", "condition": "rand.Float(0f, 1f) < chance * map(stageTime - lastSpeechTime, 0f, 4f, 0f, 1f, 'ExpoIn') && !isImploding",
                "true": [ 
                    { "action": "CallMethod", "target":"unit.GetPart('core')", "method": "SpawnBubble", "ignoreNullRef": true, "params": { "text": "${message}", "partType":"core", "lifetime": 4,
                        "fillColor": "color(0.2f, 0.1f, fastSin(stageTime * PI * 2f) * 0.2f + 0.5f, 0.8f)", 
                        "borderColor": "color(0.75f, 0.75f, fastSin((stageTime + 0.5f) * PI * 2f) * 0.05f + 0.95f, 0.66f)", 
                        "borderWidth": "fastSin(stageTime * PI * 3f) * 2f + 4f",
                        "textColor":"lerp(color(1f, 1f, 1f), color(0.75f, 0.75f, 1f), 0.5f + fastSin(stageTime * PI * 12f) * 0.5f)",
                        "fontSize":33,
                    }},
                    { "action": "CallMethod", "target":"stage", "method": "PlaySfx", "params": { "sfxType": "OctopusBossSpeech", "pos":"unitPos", }},
                    { "action": "SetValue", "name": "lastSpeechTime", "value": "stageTime" },
                ],},
        ]
    },
},

When calling the subroutine, you should specify the target, even if you're calling the subroutine from an object on itself.

1
2
3
4
5
6
7
8
9
{   
    "action": "CallSubroutine", 
    "target":"unit", 
    "path": ".speech.sub", 
    "params": { 
        "message": "#octopus.boss.player_hit_pixel", 
        "chance":"map(stageTime - lastTauntTime, 0f, 60f, 0f, 0.45f, 'QuadIn') * map(stageTime - lastSpeechTime, 0f, 4f, 0f, 1f, 'ExpoIn')", 
    },
},

The subroutine definition should include a this parameter, set to the type of object the subroutine will be run on.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
    "tauntSpeech.sub": {
        "parameters": {
            "this": { "type": "Unit" },
        },

        "actions": [
            { "action": "Condition", "condition": "(stageTime - lastTauntTime) > 10f && rand.Float(0f, 1f) < map(stageTime - lastTauntTime, 0f, 60f, 0f, 0.35f, 'QuadIn') * map(stageTime - lastSpeechTime, 0f, 4f, 0f, 1f, 'ExpoIn')",
                "true": [
                    { "action": "Condition", "condition": "player.GetStatusLevel('status/passive/shield') < 1",
                        "true": [{ "action": "CallSubroutine", "target":"unit", "path": ".speech.sub", "params": { "message": "#octopus.boss.player_hit_bullet_no_shield", "chance":1, }},],
                        "false":[{ "action": "CallSubroutine", "target":"unit", "path": ".speech.sub", "params": { "message": "#octopus.boss.player_hit_bullet", "chance":1, }},  ]},

                    { "action": "SetValue", "name": "lastTauntTime", "value": "stageTime" },
                ],},
        ]
    },
}

Tip

The name of subroutines don't need to include .sub, we just do it for clarity.