Momoi 64 Romhacking
This is a WIP document containing some guides for romhacking for Momoi64.
Right now it assumes you have Blender, Fast 64, etc all set up but guides for that may come later.
Some of these guides are a bit old and may be refined in the future but should otherwise still work.
Files
Example Hub
https://files.catbox.moe/ud7xzm.zip
(contains .blend and .lvl files. place .lvl in romhack folder)
Useful Links
Constants
https://github.com/coop-deluxe/sm64coopdx/blob/main/docs/lua/constants.md
Has everything you'll need to reference like level names, models, behaviors, etc
Behaviors
https://sites.google.com/site/jamesskingdom/Home/video-game-secrets-by-james-s/super-mario-64-exposed/sm64-exposed-behaviour-values
Focues entirely on behaviors and what each object can do with different behavior parameters.
Guides
Level and Act Names
Set up a "course.lua" file in your romhack folder (alongside the other scripts like main.lua)
Then paste in the following:
smlua_text_utils_course_acts_replace(COURSE_ID, "LEVEL NAME", "ACT 1", "ACT 2", "ACT 3", "ACT 4", "ACT 5", "ACT 6",
Replace COURSE_ID with the course id of your level. e.g COURSE_BOB for Bob-omb Battlefield. You can find a list of these ids in courses.h of your decomp folder. The rest of the replacements should be self explanatory.
The level name seems to ignore the first word for some reason, so if the first word of your level isn't displaying, that's why. Add some dummy text before it.
Assigning Stars to Acts
Nice and simple one. In your Star's SM64 Object Inspector, you need to edit the behavior parameter. Set it to the following depending on the act.
0x00000000 = Act 1
0x01000000 = Act 2
0x02000000 = Act 3
0x03000000 = Act 4
0x04000000 = Act 5
0x05000000 = Act 6
Dialog
Set up a "dialog.lua" file in your romhack folder (along the other scripts like main.lua)
Then paste in the following:
smlua_text_utils_dialog_replace(DIALOG_00), 1, 4, 95, 200, "Bing")
Replace DIALOG_000 with a unique ID for your dialogue. The Mario 64 limit is up to 169. I tried 170 and the level wouldn't generate. There's probably a way to exceed this limitation but it's not necessary for now.
Next, give the talking NPC a custom behavior and set it to either "id_bhvToadMessage" or "id_bhvMessagePanel". Note that the former seems to overwrite your model choice so only pick that if you're having a mob talk, otherwise pick the latter.
Lastly, make sure "Use Individual Behavior Params" is ticked and change Param 2 to your chosen dialogue ID.
Example:
I have this in dialog.lua
smlua_text_utils_dialog_replace(DIALOG_000, 1, 4, 95, 200, "You are my precious>student. Please respect>my rights, okay?")
So I change Param 2 to DIALOG_000.
Your NPC can now talk. It should be worth making note of what IDs you've claimed for your dialog so it isn't used by anyone else and causes issues.
I don't know what changing "1, 4, 95, 200" does yet...
Custom Music
(This section is very outdated. Will update later. A lot of the problems don't exist anymore.)
Create a "sound" folder inside your romhack folder and put your custom music inside it.
Next, download "custom_music.lua" and place it in your romhack folder: Link: https://files.catbox.moe/2ydo6n.lua
Open up this file and at the top, you will see a table of filenames alongside the course that music will play on. To assign new music, add a new entry to the table and change the course's ID, and then change the music filename to the filename of your song.
Examples:
I want "pixel time.mp3" to play on Jolly Roger's Bay:
[LEVEL_JRB] = "pixel time.mp3",
I want "re-aoharu.mp3" to play on Tick Tock Clock:
[LEVEL_TTC] = "re-aoharu.mp3",
Once you're done, save your changes and your music will play in that respective level.
Currently, this method does not allow you to change special songs like the cap themes, riding a shell, etc.
Additionally, the California Dance script has been updated to account for custom music, meaning that dancing will pause the custom song and finishing the dance will restart the custom song. Replace california-gurls' main.lua with this file: https://files.catbox.moe/swvs2n.lua
Warps
To set up a warp, create an Empty Plain Axis and position it accordingly. Set the Object Type to Object.
For this guide, I will be making a warp pipe. Set the Model to Custom and the Model ID to "E_MODEL_BITS_WARP_PIPE". Next, change the behavior to Custom and the Behavior Name to "id_bhvWarpPipe". Duplicate your warp pipe (Shift + D) and place it somewhere else in the level so we can later connect them.
Next, you need to change the behavior parameter to determine the warp ID. For example, I have changed the first warp pipe to 0x00010000, so this is Warp ID "01". Don't forget it is hexadecimal, so Warp "14" would actually be 0x000E0000 as another example. I have set the second warp pipe's behavior parameter to 0x00020000.
On a side note, 0A (10), F0 (240), and F1 (241) are reserved. 0A is a student's spawn point, while F0 and F1 are used for when your student beats a level or dies. These will be important later, so don't forget. 0A can technically be changed, but the template level file sets this to the spawn point, so best to leave it as it is for consistency.
Next, go to your Area Root object and find the Warp Nodes section. You'll see the aforementioned warp nodes already there. Select "Add Option" to make your new warp. Change the Warp ID to the ID of your made warp, so in this case, 0x01, and then the Destination Node to 0x02. Make another Warp Node and basically do the same in reverse, setting the Warp ID to 0x02 and the Destination Node to 0x01, linking the two pipes. Make sure "Destination Level" is set to the same for both and corresponds to the level you are making. Export your level and the pipes should connect.
If you want your pipe to go to a different level, it's as simple as changing the Destination Level. The Destination Node being set to 0x0A is good if your student is entering a new level and you want her to appear at the spawn point. Basically, if she is entering the level from the hub.
Similarly, you will want to edit the 0xF0 and 0xF1 nodes to make sure they can bring you back to the hub. I set up a hub where Warp 0x02 takes me to my level, so in that level, I will want to change the Destination Nodes of F0 and F1 to 0x02 so they bring me back out of the pipe in the hub.
Lastly, "Destination Area" isn't something I've messed with yet, but I'm assuming you just change that value if there's a different area in your level. e.g Area 2 = 0x02.
Red Coins
To set up a Hidden Star that appears after collecting 8 Red Coins, you just need to create and place the appropriate objects in your level.
-
Create the Hidden Star Object
Start by creating an Empty Plain Axis object. Move it to the position where you want the star to appear.- Set the Object Type to Object
- Leave the Model ID as MODEL_NONE
- From the behavior list, select "Hidden Red Coin Star". Alternatively, choose Custom and set the behavior name to id_bhvHiddenRedCoinStar.
-
(Optional) Add a Red Coin Star Marker
If you want to visually mark the star’s spawn point, create another Empty Plain Axis and place it at the same position as the Hidden Star object you just created.- Set the Model to Transparent Star (E_MODEL_TRANSPARENT_STAR if using the Custom option)
- Set the Behavior to Red Coin Star Marker (id_bhvRedCoinStarMarker)
-
Add the 8 Red Coins
Finally, place 8 Red Coin objects throughout your level.- Create an Empty Plain Axis object. Set the Object Type to Object
- Set the Model to Red Coin (E_MODEL_RED_COIN)
- Set the Behavior to Red Coin (id_bhvRedCoin)
Hidden Stars
This section explains how to add "Hidden Secrets" that trigger a "Hidden Star" to spawn after all of them are found (when you touch them).
The process is very similar to the "Red Coins" section, but with one key difference: you don’t need to assign a model to your objects or include a visual indicator for the star
-
Create the Hidden Star Object
Create an Empty Plain Axis object. Move it to the position where you want the star to appear.- Set the Object Type to Object
- Leave the Model ID as MODEL_NONE
- From the behavior list, select "Hidden Star" (id_bhvHiddenStar).
-
Add the Hidden Star Triggers
You can add as many "Triggers" as you want, note that if you only add one, that trigger alone will make the star to instantly appear. Each trigger activates as soon as your character meets the object's position.- Create an Empty Plain Axis object. Set the Object Type to Object
- Leave the Model ID as MODEL_NONE
- Set the Behavior to Hidden Star Trigger (id_bhvHiddenStarTrigger)
Purple Switch Hidden Boxes
This part is very simple, I'll explain how to add a Purple Switch that spawns Breakable Boxes for a short period of time when pressed.
First you need to create the Purple Switch Object.
Create an Empty Plain Axis object. Then move it to the position where you want your Purple Switch to appear.
With that, the only thing left to do is to set the position of the Breakable Boxes in your level, this process is pretty simple as well.
Create an Empty Plain Axis object. Then move it to any position you want.
You can duplicate the "Hidden Object" as many times as you want to keep adding more Boxes.
Blue Coin Switch
The Blue Coin Switch spawns a specific number of Blue Coins when you ground pound on it.
This Object behaves in a very similar way to the Purple Switch, except the coins are gone forever if your time limit runs out.
Create an Empty Plain Axis object. Then move it to the position where you want your Blue Coin Switch to appear.
Now you just need to set the position of every Blue Coin you want to spawn.
Create an Empty Plain Axis object. Then move it to any position you want.
You can duplicate the "Hidden Blue Coin" Object you just created as many times as you want. Make sure this time you don't leave the "Hidden Blue Coin" Model as MODEL_NONE or else the coins will be invisible.
Alternate Starting Position
This will be kind of a hacky solution, but hey, it works.
Create a Plain Axis in your level and place it where you want your alternate spawn point to be. Keep the Model as "None".
The behavior here is important. It needs to fit the following criteria:
The behavior is something that preferably does nothing.
The behavior has to be a valid one used by SM64.
It must also be a behavior you are not using elsewhere in the level.
For this example, I am using "id_bhvStaticObject"
Next, create a new lua script in your romhack folder. Name it whatever you like, but after your level is probably best.
Copy and paste the following code in your lua script.
function OnLevelInit()
local student = gMarioStates[0]
local level = gNetworkPlayers[0].currLevelNustudent
local act = gNetworkPlayers[0].currActNustudent
if level == LEVEL_NAME and act == X then
local secondSpawn = obj_get_first_with_behavior_id(id_bhvStaticObject)
if secondSpawn ~= nil then
student.pos.x = secondSpawn.oPosX
student.pos.y = secondSpawn.oPosY
student.pos.z = secondSpawn.oPosZ
student.faceAngle.y = 0x4000
end
end
end
hook_event(HOOK_ON_LEVEL_INIT, OnLevelInit)
You need then to replace "LEVEL_NAME" with the name of your level, "X" with the act number you want to assign the alternate spawn to (1-6), and change id_bhvStaticObject if you are using a different behavior.
This code retrieves your student's location, verifies the level and act are correct, retrieves the object with the behavior we assigned, and then sets that student's coordinates to that level. You can also change the angle they'll face if you wish.
This is all done when a level is initialized which is what the last line of code is for.
If you want to have a third, fourth, etc spawn, you can copy and paste the if statement and change the relevant variables. It would not be very efficient, but hey, it'll work. Remember that you'll need a different behavior for your code to look for.
This is all very possible by just assigning coordinates too, but when I tried doing that, it did not give me the results I wanted so I just went with this method.
Students as NPCs
(This section is very outdated. Will update later. A lot of the problems don't exist anymore.)
Want to have your favorite student as an NPC to talk to? You can!
First, create a new .lua file in your romhack folder and call it something like "student_npcs.lua"
Copy and paste this code into the file:
local function SetStudentIdleAnimation()
local student = find_object_with_behavior(get_behavior_from_id(id_bhvMessagePanel))
while student ~= nil do
smlua_anim_util_set_animation(student, "sumire_idle")
student = obj_get_next_with_same_behavior_id(student)
end
end
hook_event(HOOK_ON_LEVEL_INIT, SetStudentIdleAnimation)
In Blender, set your NPC's Model to Custom and the Model ID to E_MODEL_STUDENTNAME, replacing STUDENTNAME with your student. Example: E_MODEL_ALICE
Next, set the Behavior to Custom and set the Behavior Name to id_bhvMessagePanel
You CAN set the behavior to anything you want, theoretically. For example, setting the behavior to id_bhvGoomba WILL make Alice act as a goomba, but the idle animation won't apply and she will look very weird... but that might be funny.
Use this in conjuction with the dialog and have your student tell you that they hate grown-ups like you the most.
This WILL effect all other NPCs with id_bhvMessagePanel and may make them look weird, though chances are they probably already looked weird. The usual sign model is fine though as it doesn't have any animations anyway.
A quirk of this implementation is that the student NPCs share some characteristics with the player student. For example, they'll turn metal if you're metal, they'll blink when you blink, their halos will also move alongside your student's halo, etc. There are certainly ways to fix this, but I don't think it's worth the effort.
General Level Values and Star Positions
In SM64 there's many values you can change using simple Lua scripts to determine the behavior of Objects as well as their time limits and positions.
In this section I'll list the most commonly used as well as an example for each case.
- Replace Star Positions
You can replace the Spawn Position of a specific Star by assigning new values with a single line of code:
vec3f_set(gLevelValues.starPositions.Star Position, X, Y, Z)
Star Position = The Star Position you want to change
X, Y, Z = The x, y, z coordinates in your level.
For example, you can change the position of the Star that spawns after defeating King Bobomb like this:
vec3f_set(gLevelValues.starPositions.KingBobombStarPos, 4792.0, 1882.0, 3985.0)
If you want the new position to be exclusive to a specific level you can do it like this:
function OnLevelInit()
local level = gNetworkPlayers[0].currLevelNumif level == LEVEL_LEVELNAME then
vec3f_set(gLevelValues.starPositions.KingBobombStarPos, 4792.0, 1882.0, 3985.0)
end
endhook_event(HOOK_ON_LEVEL_INIT, OnLevelInit)
LEVELNAME = level ID e.g: BOB for bobomb battlefield.
Make sure include this in a new .lua file inside your Romhack folder or you can add it to an exisiting one. You can name the file whatever you want.
e.g. stars.lua
Here's a complete list of all the Star positions you can change in the game: https://github.com/coop-deluxe/sm64coopdx/blob/main/docs/lua/structs.md#StarPositions
The name usually tells which star it's referring to except for "TuxieMotherStarPos" which means the Star that spawns after returning the Baby Penguin to it's mother.
- Cap Times
You can change the time limit of each individual Cap, including it's duration in their respective Switch Levels.
Here's an example for each, time is in frames. e.g. 900 frames = 30 seconds
gLevelValues.wingCapDuration = 2700
gLevelValues.metalCapDuration = 900
gLevelValues.vanishCapDuration = 900
gLevelValues.wingCapDurationTotwc = 1800
gLevelValues.metalCapDurationCotmc = 900
gLevelValues.vanishCapDurationVcutm = 900
You can include this in any .lua file you want or create a new one exclusive for these values.
- Level Values
You can change the default values that determine where you spawn at the start of the game, when you select "exit castle" or when you skip the credits.
gLevelValues.entryLevel = LEVEL_LEVELNAME
gLevelValues.exitCastleLevel = LEVEL_LEVELNAME
gLevelValues.exitCastleWarpNode = WARPNODE
gLevelValues.skipCreditsAt = LEVEL_LEVELNAME
LEVELNAME= Level ID e.g. JRB for Jolly Roger Bay
WARPNODE = The specific warp node inside your level if you want to include it e.g. 128
- Other Values
First value determines the time limit requirement for the slide minigame Star to spawn, time is in frames so 540 = 18 seconds.
Second value determines King Bobomb's Health, in the example below it requires 4 throws to defeat him.
gLevelValues.pssSlideStarTime = 540
gBehaviorValues.KingBobombHealth = 4
- Star Requirement Values
List below determines how many stars you need to collect for each toad to give you their corresponding Star. You can also change the stars requirement for Mips the Rabbit to appear (1st and 2nd time).
gBehaviorValues.ToadStar1Requirement = 5
gBehaviorValues.ToadStar2Requirement = 15
gBehaviorValues.ToadStar3Requirement = 30
gBehaviorValues.MipsStar1Requirement = 15
gBehaviorValues.MipsStar2Requirement = 80
- Trayectories
You can determine a new trajectory to replace for Koopa The Quick as well as every Special Platform/Object in the game, make sure to edit your trajectory.inc.c inside your level's folder (level folder>area>area number folder) to match the trajectory constant you want to use.
Here's a few examples:
gBehaviorValues.trajectories.KoopaBobTrajectory = get_trajectory('KoopaBoB_path')
gBehaviorValues.trajectories.KoopaThiTrajectory = get_trajectory('KoopaTHI_path')
gBehaviorValues.trajectories.PlatformRrTrajectory = get_trajectory('rr_seg7_trajectory_0702EC3C_RM2C_path')
gBehaviorValues.trajectories.PlatformRr2Trajectory = get_trajectory('rr_seg7_trajectory_0702ECC0_RM2C_path')
gBehaviorValues.trajectories.PlatformCcmTrajectory = get_trajectory('ccm_seg7_trajectory_0701669C_RM2C_path')
gBehaviorValues.trajectories.PlatformBitfsTrajectory = get_trajectory('bitfs_seg7_trajectory_070159AC_RM2C_path')
gBehaviorValues.trajectories.PlatformHmcTrajectory = get_trajectory('hmc_seg7_trajectory_0702B86C_RM2C_path')
gBehaviorValues.trajectories.PlatformLllTrajectory = get_trajectory('lll_seg7_trajectory_0702856C_RM2C_path')
gBehaviorValues.trajectories.PlatformLll2Trajectory = get_trajectory('lll_seg7_trajectory_07028660_RM2C_path')
gBehaviorValues.trajectories.PlatformRr3Trajectory = get_trajectory('rr_seg7_trajectory_0702ED9C_RM2C_path')
gBehaviorValues.trajectories.PlatformRr4Trajectory = get_trajectory('rr_seg7_trajectory_0702EEE0_RM2C_path')
gBehaviorValues.trajectories.RacingPenguinTrajectory = get_trajectory('ccm_seg7_trajectory_penguin_race_RM2C_path')
gBehaviorValues.trajectories.BowlingBallBobTrajectory = get_trajectory('ttc_bowling_ball_path')
gBehaviorValues.trajectories.BowlingBallThiLargeTrajectory = get_trajectory('rr_bowling_ball_path')
Koopa the Quick
Setting up Koopa the Quick
To set up a Koopa the Quick race, you first need to add three objects.
The first object is Koopa the Quick himself, whose model is "E_MODEL_KOOPA_WITH_SHELL" and behavior is "id_bhvKoopa". You must also set the behavior parameter to 0x00020000 (for Bob-omb Battlefield's variant of Koopa the Quick) or 0x00030000 (for Tiny Huge Island's variant). This doesn't mean these Koopa the Quicks are exclusive to these levels or anything. There's just two of them that exist.
The second object is the flag pole, which is just there for visuals and is technically optional, but you'll probably want the player to know where the end is. The model is "E_MODEL_KOOPA_FLAG" and the behavior is "id_bhvKoopaFlag".
The third object is essentially the trigger for the player to finish the race and you'll want it in the same position as your flag pole. There is no need for a model for this one and the behavior is "id_bhvKoopaRaceEndpoint".
Next up, we need to set up a route for Koopa the Quick to follow. I recommend downloading and installing the Indigo Tools Trajectory Exporter plugin for this. Here's a quick link -> https://files.catbox.moe/d02inh.zip
With this plugin, you can begin setting up waypoints for Koopa the Quick to follow. Create as many as you like and place them accordingly. When you are satisfied with your route, export the trajectory. Doing so will give you something to copy and paste, but we need to quickly set up the file to copy and paste to.
Make sure your level has been exported once and then open the exported level folder, then open the area folder that Koopa the Quick belongs to (If he is in Area 1 you open the "area_1" folder). Next, create a "Trajectories.inc.c" file.
Set up the file so it looks like this.
const Trajectory your_trajectory_name[] = {
};
Next, copy and paste your exported trajectory into the table. Here is what mine looks like.
const Trajectory gdd_ktq_path[] = {
TRAJECTORY_POS( 0 , /pos/ 6700, 0, -3600),
TRAJECTORY_POS( 1 , /pos/ 5400, 0, -3600),
TRAJECTORY_POS( 2 , /pos/ 5400, 0, -5500),
TRAJECTORY_POS( 3 , /pos/ 6400, 0, -6700),
TRAJECTORY_POS( 4 , /pos/ 7600, 200, -4800),
TRAJECTORY_POS( 5 , /pos/ 7600, 0, -3600),
TRAJECTORY_POS( 6 , /pos/ 6900, 0, -2400),
TRAJECTORY_END(), // tank Indigo SM64};
With this set up, you just need to let the game know to load this trajectory. Within any lua file in the romhack's directory, add this line.
gBehaviorValues.trajectories.KoopaBobTrajectory = get_trajectory('your_trajectory_name')
If you used the Tiny-Huge Island variant, use this line instead.
gBehaviorValues.trajectories.KoopaBobTrajectory = get_trajectory('your_trajectory_name')
Re-export your level and Koopa the Quick should spawn and follow your new route.
Students as Koopa the Quick
Firstly, the "Blue Archive NPCs" mod has been edited for this, so it's best to integrate it directly into the romhack if it isn't already there. Here is a download link -> https://files.catbox.moe/r6v4zp.zip
Once extracted, open up blue_archive_npcs.lua. Similar to custom music, you need to assign a model for Koopa the Quick depending on your level. There is already an example that sets Momoi to be the racer on Snowman's Land. As another example, if you want to race against Yuzu on a level that replaces Bob-omb Battlefield, here is what you add.
[LEVEL_BOB] = smlua_model_util_get_id("yuzu_geo"),
Save the changes and your intended student will appear. Koopa the Quick defaults to Sumire, so if this happens, make sure you've typed everything in correctly.
Troubleshooting
NPCs appear as Error Models
If your NPC appears with an error model, you need to change their model to Custom and then find their model name directly in model_ids.h of your decomp.
You then need to preface the model name with "E_"
Examples:
E_MODEL_PEACH
E_MODEL_GOOMBA
E_MODEL_HEAVE_HO
You may need to also do something similar with the behavior. If your NPC isn't appearing, add Custom behavior and find the intended behavior in behavior_data.c in your decomp. You then need to preface the behavior with "id_"
Examples:
id_bhvGoomba
id_bhvHeaveHo