So let's say you want to make Knuckles for Smash 4. You have the tools to do everything you need, except you don't know how to make him do that cool jump-into-the-air-and-smash-the-ground thing. You have the animations and hitboxes all there, but you don't know how to edit his Neutral Special in MSC to make him do what you want. This tutorial is designed to teach you how to write a character Special Move from scratch. We'll be using pymsc, which decompiles MSC files into Assembly language (ASM).
1. Required Files
- pymsc (jam1garner)
- Python (3.6 recommended)
- Your character’s .mscsb file
pymsc - Program by Jam1garner, used to decompile/compile character MSC files (.mscsb)
ACMD (Animcmd) - The file system responsible for hitboxes, graphic effects and sound effects.
MSC - The file system responsible for move logic.
Action - Dictates what the character will do in a given situation. The character's transitions, cancels, physics, and Subactions are stored into Actions. This is primarily what you'll be editing in MSC.
pre-Action - Scripts called before the Action begins
post-Action - Scripts called when the Action ends
Subaction - Stores hitboxes, graphic effects and sound effects. When a Subaction is called, the associated animation will be called as well. This is primarily what you're editing in ACMD.
Common Scripts - These scripts are the same across all characters. Script_2238 and below are common scripts.
Unique Scripts - Scripts used for character-specific Actions. Commonly used for Special Moves and Final Smashes, but can be used in normal Actions as well for things like resetting variables upon landing on the ground.
Key - Used to register multiple scripts to the same Action.
- Key 0 = pre-Action
- Key 1 = Action
- Key 2 = post-Action
- Key 3 = pre-Action
- Key 4 = Action (looping script)
- Key 5 = Action (looping script)
- Key 6 = post-Action
Var - Variables used for scripting in MSC
Cmd - Short for Command
pushInt. - Pushes the following integer onto the stack
loc - Any label for a block of code used for scripting in MSC
sys (syscall) - Uses parameters from the stack to perform an operation
multf., divf., addf., subf. - Perform a mathematical operation (f indicates float number, i indicates integer)
# - Indicates comment, will not be read by pymsc when recompiling
3. Getting started
First thing you should do is read up on the "Getting Started with MSC" page on Jam1garner's Github. It contains important info on how The Stack works, as I will refer to it in this tutorial. Make sure you download all the Required Files. You can find pymsc via the link or in Jam1garner's GitHub page. Python is the program that pymsc runs off of, so you'll need to install that too. Anything version above python 3 should work, though I'd recommend python 3.6. Lastly, you'll need the character MSC file of your choice. In this tutorial, we're making a Neutral Special for Knuckles, who replaces Sonic, so you'll need to extract sonic.mscsb using Sm4shExplorer, assuming you have it. If you don't, you're probably better off following a "How to Mod Sm4sh" tutorial first.
Once you have everything downloaded, you'll need to decompile sonic.mscsb. Make a folder for your sonic.mscsb. In the folder, hold Shift and right click, and in the menu click "open command window here". In the cmd prompt, type "python", space, then click-drag disasm.py (from your pymsc download) into the cmd window, space, and type "sonic.mscsb", hit enter. If it doesn't work then you may need to click-drag python.exe from your python installation instead of typing "python" in the beginning. It's probably going to be in C:\Users\(your cpu name)\AppData\Local\Programs\Python
After about ~10 seconds it should have all the script files decompiled in an output folder. Now we're ready to get started.
4. Locating scripts
By now you're probably thinking "Stinny, that's too many scripts. Idk if I'm ready for this." Worry not. When editing special moves, the only scripts we'll be looking at are script_2239 and above. Anything below that are common scripts; we're only concerned with the character's unique scripts. Ultimately, we want to change Sonic's Homing Attack into Knuckles' Dive Punch attack, so we're looking for the scripts belonging to Sonic's Neutral B. To make it easier to find these scripts, we're going to need an index of some sort.
Character-specific Actions are always registered to certain scripts. This means when an Action is called, the associated script will be called as well. With that knowledge, we need to find the Action ID for Neutral Special (or in developer terms, SpecialN) to find the scripts we need to edit. Use the MSC Note sheet to find Action IDs. For quick reference, the ID for SpecialNStart is 0x152 (in hex). Now we need to find where 0x152 is registered. As a general rule, script_2243-script_2248 are all used to register Action IDs to scripts. Use Notepad to open up these scripts and use Ctrl+F to find 0x152 (it's at the top of the script in this case but it's good practice anyway). You'll notice that 0x152 appears in script_2243 multiple times. The number in the line above 0x152 is the Key number. In this script, SpecialNStart uses Keys 0x0, 0x1, and 0x2. We're mainly concerned with Key 0x1, since most of the coding for Soinc's Homing Attack takes place in Key 0x1 scripts, and we want to clear all that to make code of our own. Open script_2243:
In the line under 0x152 for Key 0x1 you'll see a "pushInt. script_2282". This is the starting script for the Neutral Special Action, SpecialNStart. Open script_2282, you should see this:
Ctrl+A and delete everything from 2282; we're writing this from scratch.
5. Script Editing
Every script must start with the "begin 0x0, 0x0" Cmd. As for the numbers, just leave them as 0x0 for now since we aren't using Vars at the moment. Now then, time for the fun stuff.
This script we're writing will only be read by the game once; on the 1st frame of Action 0x152. First, we need to tell Knuckles what to do when he uses Neutral Special on the ground. For that we need the "If on Ground" cmd. You can copy/paste it from the MSC Note Sheet (under Complex Cmd's tab) into the next line. This is a conditional. After each conditional, we need an "if loc" in the next line to specify were to go next in the script if the above condition is FALSE. You can name "loc" anything you want, but I personally use loc_(hex number) for the sake of simplicity. If the codition is TRUE, the script continues to the next line. Now, we need an animation for using Neutral Special the ground. Since animations are linked to Subaction IDs, you can find the animation you're looking for by using Sm4shCommand to get the Subaction ID of SpecialNStart from Sonic's ACMD. For quick reference, the Subaction ID for SpecialNStart is 0x211. Knowing this, we can use the Call Subaction cmd from the MSC Note Sheet. Copy/paste the cmd into the next line and change the 0xX to 0x211, 0xY to 0x0 (to start on the first frame), and 0xZ to 1.0f (for normal animation speed).
You'll notice that each cmd that's not a conditional ends with a "sys" followed by two numbers. This is a syscall. The syscall will grab the specified bytes on the stack (usually just the "pushInt." cmds in the lines above) and perform an operation. I.e. for our Edge Slide cmd, the numbers 0x2710 and 0x5 are pushed onto the stack. The 0x2 in "sys 0x2, 0x1a" tells the game to grab the 2 integers most recently pushed onto the stack and use them in the syscall 0x1a. So 0x1a is basically and ID and 0x2 is the number of integers to be used from the stack. Whenever you see a "." after a "sys", the result of the syscall will be pushed onto the stack. We will apply this later in one of our scripts.
Whenever callFunc is used, it needs to be preceded by a "try loc" to specify were to go next in the script given that the above condition is TRUE. If a conditional is TRUE, it returns 1. If FALSE, it returns 0. "if loc" is checking if 0 is returned, and if so, jumps to the "loc". "try loc" isn't really used for checking a conditional, more so as a way to jump to the next block before a "callFunc". By now, your first two cmds should look like this:
Let's Specify what Knuckles does when he uses Neutral Special in the air. Hit Enter twice after "callFunc 0x3" and type the name of the loc specified in the "if" cmd we did earlier, ":", then Enter again and Tab. In this case I used loc_A, so that will be the next block. Use the Edge Slide cmd and specify 0x5 (In air), then the "Set speed to be added" cmd. Change 0xW to 0x3, 0xX to 0x0, and 0xY to 0x0. This will actually set Knuckles' aerial speed to 0 (the cmd might be mislabeled). Next for the Aerial Animation, use the same method we used earlier of finding Subaction ID to Find the ID for SpecialAirNStart. In this case its 0x215. Now use a "try loc" and "Call Subaction" cmd for 0x215. For each "try loc", make sure that it always points to the next block down. Here I use an "else loc" to skip down to the next block after the Subaction is called for each instance. So far you should have something like this:
Lastly, we need a script that checks every frame for Knuckles' animation end. For that we need to make a script loop. Write out one more "try loc" and use the following:
pushInt. script_2283Script_2283 in this case is the script we're going to use as our looping script (we're going to clear out that script too). Script_78 here is grabbing script_2283 and using "callFunc3 0x0" to make the script loop until the Action ends.
One more thing, every script needs to have and "end" cmd. Put one in the last block. The script should look like this:
So that right there is a completed script. Something that's easy to mess up is the ifs and trys pointing to the wrong block. Remember to read your scripts from top to bottom making sure that the conditions and flows make logical sense. Also make sure that the block labels match one another and you have proper formatting. A common mistake I make is forgetting the ":" after the labelled block, which will usually result in an error when trying to recompile in pymsc.
Now that I've explained the basics, I'll sort of skip along and describe each command I use and why in the looping script_2283.
Refer to the screenshot above. This is the looping script. Remember that there is going to be code already in the script so you'll need to clear it out before you start. First, I use a conditional checking for the animation end. If successful, Change Action to SpecialNHit (0x157). Actions can be considered "parts" of Special Moves. Some character Special Moves require more parts, some less. I.e. Mario's Fireball only uses one animation, so it only needs one Action, whereas Knuckles' Dive Punch has three animations, so it needs three Actions. For each animation used in a special move, you will need a new Action. I used 0x157 because it was an Action in Sonic's old Homing Attack, so we won't be replacing anything important. You can also add new Actions without replacing, but I'll explain that later. After a Change Action cmd, you need a "return_7" to tell the script to end. Next block, loc_3E, is used if the animation hasn't ended yet. Here I use a conditional to check if Bit Variable 0x2100000f is set. If you've edited ACMD before, you should recognize this type of variable. In ACMD, I set this particular variable on frame 12, and used MSC to check if it is set for timing purposes. So on frame 12, given that the Bit Variable is set, Knuckles will jump into the air. You can also simply check for animation frame in MSC, but I did it this way so that other people can change the timing of Knuckles' jump into the air more easily through ACMD instead of MSC. Notice the "." in "sys. 0x2, 0x16". Recall that having a "." means the result of the syscall will be pushed onto the stack. In this case, if the Bit Variable is set, 1 will be returned AND be pushed onto the stack for the next "if loc". If Bit is not set, then 0 will be returned. Next, I use another Edge Slide cmd for the air and set the Air/Ground state to 0x2 meaning the character is now considered by the game airborne. After you change Air/Ground status, you'll need to make their physics match the current state. So I use 0xa for a Kinetic Handler which will cause the character's gravity to take effect. In loc_42 I used velocity commands to get the right speed for Knuckles now that he's airborne. With the "Set Speed to be added" cmd (sys 0x4, 0x10) I take advantage of the stack system and push the character's current facing direction onto the stack. When facing right this cmd will return 1, and -1 for facing left. Then I use "multf." to take the two integers on the top of the stack and multiply them, in this case, the value I inputted, 1.0f (f is used to indicate a float number in pymsc), and facing direction return is being multiplied. So it will leave you with either 1 or -1 depending on facing direction. Lastly, a simple Set V velocity cmd is used to set vertical velocity to 3.0f and the Bit Variable is cleared. In the end, Knuckles should jump into the air on frame 12 and wait until his animation is finished, then go to the next Action, SpecialNHit (0x157).
That's pretty much it for this Action. One other thing I should mention before moving on to the next is about editing the pre-Action and post-Action for SpecialNStart (0x152). By looking for where Key 0x0 and Key 0x2 is registered, you can find the scripts used for pre-Action and post-Action respectively. Pre-Action for 0x152 in this case is script_2281. This type of script is read once and will always contain at least 2 pre-Action cmds (sys 0xa, 0x18). You can find its labels in the MSC Note Sheet, but I will mention that a 0xffffffff for the Air/Ground state means that the game will retain whatever Air/Ground state the character is currently in (0x0 for ground, 0x1 for ledge, 0x2 for air).
For post-Action (Key 0x2), the script in this case is 2285. We're actually going to make this script blank since we don't have any use for it in this Action. Blank scripts will still contain a "begin 0x0, 0x0" and "end", so that the game still acknowledges the script as a script.
The rest is stuff I've showed in this tutorial already so I'll just skim through it.
Using script_2243, I locate the next script to edit for Action 0x157 in Key 0x1 (2308, pictured above). This Action is used for Knuckles diving downwards, waiting to hit the ground. Here I called Subaction 0x22d (his diving animation) and set his horizontal and vertical velocity. Script_2309 is the looping script for Action 0x157.
This one is easy; all we need is a cmd to check if he touches the ground and an Action change if he does. Action 0x158 is used next as it is also part of Sonic's old Homing Attack.
Last Action in this Special Move, SpecialNLanding (0x158). Edge slide status and Kinetic Handlers are used to specify his physics for being on the ground, and the corresponding Subaction for SpecialNLanding is called. Now onto script_2303, the looping script.
Since this is the ending part of the Special Move, we want to have him be able to act out of the animation if Allow_Interrupt is used in ACMD or params. First, the cmd to check if interrupt is allowed. Notice here I use a "try loc" with a ".". This will return the result of script_39 and push it onto the stack, in this case, the result will be returned and used in loc_24. Script_39 contains the functions for ground interrupts, such as attacking, walking, jumping, etc., so if a ground interrupt is used, script_39 will return 1. Else, it will return 0. We need to know if an interrupt is used so we can properly end the script. In loc_24, the result of script_39 is checked. If it equals 1, then it pushes 1 onto the stack. "else loc_2E" here will always jump to loc_2E regargless of the result. loc_2E checks if 1 was pushed, and if so, ends the script. So in the end, the script ends if an interrupt was performed. If not, we continue in loc_34.
Next an animation end check is used to put him back into his Idle animation. Here I also checked if he becomes airborne at any point, such as sliding off the edge, and put him in the Fall Action if he does. You'll notice that "pushInt. 0x1" is used, followed by a "return_6". While this will usually end the script and return 1, it achieves the same result as simply using "return_7" in the case of an Action change.
And there you have a completed Neutral Special for Knuckles. Given that the animations and ACMD are there, he should jump into the air, dive downwards with his fist pointed down, and land on the ground, returning to Idle pose. Remember that "loc_XX" can be named anything you want, as long as the labels match. By that I mean make sure that "if Knuckles_stuff" or "try Knuckles_stuff" actually points to the block labeled "Knuckles_stuff". Also looking through scripts and keeping track of things you push onto the stack can help find problems before they happen in game, as you don't usually want anything pushed onto the stack that you won't use. Lastly, formatting is key in pymsc. Make sure the "p" in "pushInt." is lowercase and the "I" is capital, stuff like that.
Once you're ready to test, hold Shift and right click in the output folder, then click "open command window here". Type "python", space, click-drag “asm.py” into the cmd window, space, then type “Scripts”, hit enter. Again, you may need to click-drag python.exe into your cmd window for it to work. It should recompile into "test.mscsb" if everything went well and all the formatting is correct. Rename "test.mscsb" to the character you're editing (in this case, Knuckles is over Sonic, so sonic.mscsb) and replace the corresponding file in Sm4shExplorer. If you get any errors, look over your scripts and double check that your formatting is correct and the cmds are labeled properly. If you tried to solve the problem to no avail, you can ask for help in the #help section of jam1garner's MSC discord server linked here: https://discord.gg/9eTmNTr
7. Using Vars
Now that We're done with the main part of the tutorial, I'd like to just share a few more bits of knowledge before we end here. Vars come in two different forms in MSC scripting. A local Var is used only in that particular script, whereas a global Var is used throughout all the scripts. Local Vars can be used to store values for later use in a script. For example, we can use Vars in one of our scripts from earlier to make it a bit easier on the eye:
Here I multiplied 0.5f by the current facing direction and stored it into Var "0x0, 0x0". The 0x0 on the left indicates whether it is a local Var (0x0) or a global Var (0x1), and the 0x0 on the right indicates the Var ID. We only have 1 Var in this script, so the ID will be 0x0. If you have say 4 local Vars in one script, the 4th one will be "0x0, 0x3". "floatVarSet" is used here since we are storing a float number, whereas "setVar" is used for integers. Regardless, you're always going to use "pushVar." when you want to push your Var onto the stack. Here I pushed my Var in the place of the Horizontal Velocity value so that the cmd uses my Var as the value for H velocity, either 0.5 or -0.5 depending on facing direction. Recall that I mentioned "begin 0x0, 0x0" being used for Vars. Whenever you use a local Var, you must declare how many you're using with the second parameter. So if you're using 3 local Vars in a script, it should look like "begin 0x0, 0x3". If you forget to declare the number of Vars, the game will use 0 as a default value, keep that in mind if you're ever stuck at some point. The first parameter in "begin" is used to declare how many values are being carried over from another script and being stored as Vars into this script. You probably won't be using this too much, so leave it as 0x0.
You've seen earlier the "If on Ground" cmd uses a "pushVar. 0x1, 0x12". This is a global Var, as denoted by the 0x1 in this first parameter. You won't usually be storing values into global Vars since the developers have mostly done that, but you will be using them in the stack quite often. "pushVar. 0x1, 0x12" always contains the current Air/Ground state of the character, so you can use it as a conditional to check if the character is on the ground or not. Global Vars are documented in the MSC Note Sheet, so you can always refer to that if you come across one you don't recognize.
8. Adding new Actions
If you want to add a new Action, there's a few things you need to do first to set it up. First, go to script_2242 and look for a "setVar 0x1, 0x7". Global Var 0x7 stores the maximum number of Actions a character has. This value needs to be greater or equal to the number of Actions the character has. For example, if you want to add more Actions to Mario, who has 0x159 Actions (345 in decimal), you need to increase the value of Global Var 0x7 to the desired number of Actions. So if I want to give Mario 3 more Actions, increase the value 0x159 to 0x15c.
Next, you need to register the Action to scripts using Keys. To do this you can copy/paste the cmds from script_2243 (or 2244, 2245... depending on the character) to get the right formatting, then just change the Action ID to your new ID and the script name to anything you'd like. In this example, I copy/pasted the entry with 0x158 and changed them accordingly:
I chose the next unused Action ID (in Mario's case, 0x159) and a new script named "mario_stuff" for Key 0x1. You will also need to do this for Key 0x0 and 0x2 for pre-Action and post-Action script registration. Now let's fast forward in time and say you've written your pre-Action, Action, and post-Action scripts and you're now proud of yourself. There's one last thing you need to do here, and that's to add the names of your new scripts to the "Scripts" file that appeared when you first decompiled your .mscsb file.
Open up this file with Notepad and scroll all the way to the bottom where the last script is listed and add the names of your new scripts here.
If you miss this step you'll get an error when trying to recompile, so double check the Scripts file if you do.
One last tip for finding Actions: If you’re looking for a character’s particular Special Move Action, you can find the corresponding Subaction ID using Sm4shCommand, then search for the Subaction ID in the MSC scripts. I recommend using the Atom text editor to make the search easier. Once you locate the Subaction ID in MSC, you can trace where each script is called from until you find the script that has an Action ID registered with it. You can use this build of Sm4shCommand to find Subaction IDs.
It was a bit lengthy but hopefully by the end of this tutorial you have the basics of MSC script editing understood and you’re ready to write new Special Moves on your own. If you have any questions don’t be afraid to join the MSC discord server and ask in the #help channel: https://discord.gg/9eTmNTr