ACMD - HDR-Development/smashline GitHub Wiki

ACMD Scripts

What are ACMD Scripts?

ACMD (Animation Command, also called AnimCMD) scripts are short declaritive scripts that perform consistent actions on objects.

ACMD scripts typically don't have any logic in them, their primary purpose is to perform actions based on a frame timer without having conditional logic such as if frame > 14.0 && frame < 16.0 { ATTACK(...); }.

An example ACMD script outline could look like:

A[Script Start]-->B[Wait until frame 10]
B-->C[Create 3 hitboxes]
C-->D[Wait for 6 frames]
D-->E[Clear all hitboxes]
E-->F[Script end]

Technical: How do ACMD scripts work?

ACMD scripts were initially programmed in Lua, a general purpose easy-to-integrate scripting language that comes with good support from Nintendo (presumably) on the Nintendo Switch.

In Lua, there are concepts called "coroutines", which is basically a function that can be run in parallel with other functions on the same thread. They aren't truly run in parallel, but moreso can yield control when they need to wait on some other condition that is outside the control of the running function.

For example, if I need to wait until frame 15, I can tell whatever is managing me that I can't really do anything until frame 15 comes along, so they can go ahead and do other things. That's what it means to "yield control".

This allows a very simple declarative control flow when designing scripts:

function game_AttackAirHi()
    frame(15.0)
    if is_excute() then
        ATTACK(...)
    end

    wait(6.0)
    if is_excute() then
        AttackModule.clear_all()
    end
end

This function will take about 21 frames to complete, since it yields until frame 15 and then waits another 6.

However, fighter code is not run from Lua. Their scripts were initially transpiled to C++ and then compiled into machine code, which makes their runtime a little bit more complex. They make use of something called "fibers", which are identical to coroutines in concept and similar in implementation. These functions will still yield control.

When an ACMD script gets interrupted, the coroutine gets cancelled, which in Lua means cleaning up the lua stack and stopping execution. In machine code, it means throwing an exception and needing to unwind the stack.

Writing an ACMD Script

In Rust, our modding programming language of choice, we write our ACMD scripts as functions, like this:

pub unsafe extern "C" fn game_attackairn(fighter: &mut L2CAgentBase) {
    // Implementation
}

pub fn install() {
    Agent::new("mario")
        .game_acmd("game_attackairn", game_attackairn, Priority::Default)
        .install();
}

I'm not going to break down how functions work in programming languages, if you are that new to programming I'd recommend finding some beginner guides on YouTube or reading the Rust book (google it).

We take a fighter: &mut L2CAgentBase into this function as it has all of the context required in order to execute nearly all actions required.

I omitted most of the actual script, but you can see plenty of script examples in open source mods or in the dumped script repo.