-
Notifications
You must be signed in to change notification settings - Fork 3
Moveset Edits: AnimCMD Scripts
AnimCMD controls how each of a character's animations work, and it is split into four categories: game, effect, expression, and sound. AnimCMD is frame-based, and takes care of a lot of the scripting required to make up what we consider a character's moveset. For example, almost all normal hitboxes are defined in a character's game AnimCMD scripts. From here on, "ACMD" and "AnimCMD" will be used interchangeably.
The best resource now for reading AnimCMD functions is ruben_dal's Smash Ultimate Data Viewer. It has all of a character's article's game AnimCMD scripts parsed to look like they originally were written, and also closer to how we'll write code in this framework.
[TODO: Show example of Ruben's viewer and explain it] [TODO: Explain types of functions we can use in ACMD: lua_State, lua_bind]
ACMD functions are completely controlled by frames, so the most important functions are related to frames. In our replacement of Fox's shine, we can see the first method of declaring which frame we are working on:
// Frame 1
acmd.frame(1);
if (acmd.is_excute()) { ... }Another method that is frequently used is wait(x), which means the frame x frames after the previous frame timer. This can be seen in our replacement of Squirtle's uptilt.
// This wait occurred after the previous frame(5)
// Frame 7
acmd.wait(2);
if (acmd.is_excute()) { ... }Download NXTool to convert your character NROs to ELF files. In your terminal, you'd type
./nxtool [path/to/fighter/NRO] --elf=[path/to/output/ELF]
So for example,
./nxtool lua2cpp_pfushigisou.nro --elf=lua2cpp_pfushigisou.elf
Then we can find the function that we want with a program pre-installed with DEVKITPRO.
$DEVKITPRO/devkitA64/bin/aarch64-none-elf-readelf -sW [path/to/character/ELF] | grep "[part of the function name]"
So if I wanted to find HitModule__set_status_all because Ivysaur uses it for her up smash, I'd find:
$DEVKITPRO/devkitA64/bin/aarch64-none-elf-readelf -sW lua2cpp_pfushigisou.elf | grep "HitModule"
122: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZN3app8lua_bind25HitModule__set_whole_implEPNS_26BattleObjectModuleAccessorENS_9HitStatusEi
350: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZN3app8lua_bind30HitModule__set_status_all_implEPNS_26BattleObjectModuleAccessorENS_9HitStatusEiAnd we can see that it's the second one. Now we should add that to our lua_bind namespace for HitModule in acmd_wrapper.hpp. In order to tell the ELF that it should look for the function with this symbol from the main of Ultimate, all we have to do is type asm("symbol") when declaring the function. As for its types, [TODO]. So a declaration of this function would look like:
namespace app::lua_bind
{
// other modules...
namespace HitModule {
void HitModule::set_status_all(u64, int, int) asm("_ZN3app8lua_bind30HitModule__set_status_all_implEPNS_26BattleObjectModuleAccessorENS_9HitStatusEi") LINKABLE;
}
}We'll be working in script_replacement.hpp, specifically replace_scripts(). Notice how the code is already laid out:
void replace_scripts(L2CAgent* l2c_agent, u8 category, int kind) {
// fighter
if (category == BATTLE_OBJECT_CATEGORY_FIGHTER) {
// fox
if (kind == FIGHTER_KIND_FOX) {
l2c_agent->sv_set_function_hash(&shine_replace, hash40("game_speciallwstart"));
l2c_agent->sv_set_function_hash(&shine_replace, hash40("game_specialairlwstart"));
}
// ivysaur
if (kind == FIGHTER_KIND_PFUSHIGISOU) {
l2c_agent->sv_set_function_hash(&ivy_upsmash, hash40("game_attackhi4"));
}
// squirtle
if (kind == FIGHTER_KIND_PZENIGAME) {
l2c_agent->sv_set_function_hash(&squirtle_utilt, hash40("game_attackhi3"));
}
}
}In order to specify which character (or weapon, or ...) we would like to replace an ACMD function for, we can use the BATTLE_OBJECT_CATEGORY and KIND of the battle_object associated to the current L2CAgent. Finding which categories correspond to what type of object and which "KIND"s are associated to which characters, search for "BATTLE_OBJECT_CATEGORY" and "FIGHTER_KIND" here. That link tells you what the actual values of the constants are in-game, but since they are also defined in const_value_table.h we can use them freely.
So we can see that the syntax
l2c_agent->sv_set_function_hash(&shine_replace, hash40("game_speciallwstart"));simply has arguments of our replacement function's address and the hash40 of the animation name. Notice it is prepended by the ACMD type (the others being `"effect_", "expression_", and "sound_").