This commit is contained in:
EmeraldLockdown 2026-04-04 22:20:28 -04:00 committed by GitHub
commit f8172d239c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 315 additions and 27 deletions

View file

@ -2,7 +2,7 @@
sm64coopdx is an online multiplayer project for the Super Mario 64 PC port that synchronizes all entities and every level for multiple players. The project was started by the Coop Deluxe Team. The purpose is to actively maintain and improve, but also continue sm64ex-coop, created by djoslin0. More features, customization, and power to the Lua API allow modders and players to enjoy Super Mario 64 more than ever!
Feel free to report bugs or contribute to the project.
Feel free to report bugs or contribute to the project.
## Initial Goal (Accomplished)
Create a mod for the PC port where multiple people can play together online.
@ -11,8 +11,9 @@ Unlike previous multiplayer projects, this one synchronizes enemies and events.
Interestingly enough though, the goal of the project has slowly evolved over time from simply just making a Super Mario 64 multiplayer mod to constantly maintaining and improving the project (notably the Lua API.)
## Lua
sm64coopdx is moddable via Lua, similar to Roblox and Garry's Mod's Lua APIs. To get started, click [here](docs/lua/lua.md) to see the Lua documentation.
## Documentation
sm64coopdx is moddable via Lua, similar to Roblox and Garry's Mod's Lua APIs. To get started, click [here](docs/lua/lua.md) to see the Lua documentation. If you want to contribute to the repo, you can view the C documentation [here](docs/c/c.md).
## Wiki
The wiki is made using GitHub's wiki feature, you can go to the wiki tab or click [here](https://github.com/coop-deluxe/sm64coopdx/wiki).

16
docs/c/c.md Normal file
View file

@ -0,0 +1,16 @@
# C Reference
## Naming Convention
- Variable names should use `camelCase`
- Function names should use `snake_case`
- Struct names should use `PascalCase`
- Filenames should use `snake_case`
- Global variables, or variables that can be accessed in any file, should be prefixed with a `g` (i.e. `gGlobalTimer`)
- Static variables, or variables that are only accessed in a single file, should be prefixed with an `s` (i.e. `sOverrideCameraCollision`)
- For creating a pointer, `Type *value;` is the proper convention, not `Type* value;`.
## Sections
- [SMLua](sections/smlua.md)
- [Config File](sections/configfile.md)
- [DJUI](sections/djui.md)

View file

@ -0,0 +1,38 @@
## [:rewind: C Reference](../c.md)
# Config File
The configuration file is where all of the user's settings are stored. It's a decently easy to use system that allows you to add entries to the config file and use it with ease.
## Configuring the config file
Entries for the config file are handled in 2 files, `configfile.c`, and `configfile.h`, both are found in the `src/pc` directory.
To add an entry, at the top of the file there are a bunch of different variables. Go to wherever you deem appropriate and add your entry. A config file has these valid types:
```c
enum ConfigOptionType {
CONFIG_TYPE_BOOL,
CONFIG_TYPE_UINT,
CONFIG_TYPE_FLOAT,
CONFIG_TYPE_BIND,
CONFIG_TYPE_STRING,
CONFIG_TYPE_U64,
CONFIG_TYPE_COLOR,
};
```
In the `options` array, you can add in your config file entry. Go to the same place you deemed appropriate, and add in your entry. An entry consists of a name, this is the key used to find your value. It then takes in a type, which is the type of the value. It lastly takes in that value set. These are the possible types of value:
```c
union {
bool *boolValue;
unsigned int *uintValue;
float *floatValue;
char *stringValue;
u64 *u64Value;
u8 (*colorValue)[3];
};
```
Lastly, head to the header file, or `configfile.h`, and add your variable there.

121
docs/c/sections/djui.md Normal file
View file

@ -0,0 +1,121 @@
## [:rewind: C Reference](../c.md)
# DJUI
DJUI, or djoslin0 User Interface, is a custom UI framework for making panels, buttons, and other elements easy to work with.
## How it works
Before we get into a list of elements, first we need to clear something up:
- The DJUI found in Lua is not necessarily the same you will be working with in C. Lua is much more barebones, whereas C mainly uses panels. This documentation does not apply to Lua.
Now getting back on track, DJUI panels and elements aren't used directly, they're used via DJUI functions. There's quite the list of them, with each element having it's own unique list of functions if necessary. Thankfully, DJUI removes a lot of headache by unifying all elements to contain a `DjuiBase`.
Every DJUI element has a `DjuiBase`. This base contains properties such as the width, height, position, anchor, color, and more. You can configure a base with it's list of functions. A list of functions can be found in `djui_base.h`, which is found in `src/pc/djui`, along with all the other DJUI files, you'll see this functions pop up.
There's a few key constants to keep in mind.
- `DjuiScreenValueType` is a size type. You can make it relative, `DJUI_SVT_RELATIVE`, so a multiplier of the parent's size, or absolute, `DJUI_SVT_ABSOLUTE`, so it's the pixel size set.
- `DjuiHAlign` and `DjuiVAlign`, which is the alignment of a base.
```c
enum DjuiScreenValueType { DJUI_SVT_ABSOLUTE, DJUI_SVT_RELATIVE, DJUI_SVT_ASPECT_RATIO };
enum DjuiHAlign { DJUI_HALIGN_LEFT, DJUI_HALIGN_CENTER, DJUI_HALIGN_RIGHT };
enum DjuiVAlign { DJUI_VALIGN_TOP, DJUI_VALIGN_CENTER, DJUI_VALIGN_BOTTOM };
```
## DJUI Elements
Working with DJUI elements is easy. Let's take `DjuiText` as an example.
```c
struct DjuiText {
struct DjuiBase base;
char* message;
const struct DjuiFont* font;
f32 fontScale;
struct DjuiColor dropShadow;
enum DjuiHAlign textHAlign;
enum DjuiVAlign textVAlign;
};
```
Every DJUI element always has a `DjuiBase`, and it's always the first field of the element. In this element there are multiple custom options, but most of it is still configured with functions.
```c
...
void djui_text_set_text(struct DjuiText* text, const char* message);
void djui_text_set_font(struct DjuiText* text, const struct DjuiFont* font);
void djui_text_set_font_scale(struct DjuiText* text, f32 fontScale);
void djui_text_set_drop_shadow(struct DjuiText* text, f32 r, f32 g, f32 b, f32 a);
void djui_text_set_alignment(struct DjuiText* text, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign);
...
struct DjuiText* djui_text_create(struct DjuiBase* parent, const char* message);
```
I didn't show all the functions, but as you can see an element can have quite the amount of functions to configure itself.
Each element has a dedicated creation function. For text, it's `djui_text_create`. A create function allocates memory for the element and adds it's base as a child of the base it was created from. Parent and children management is done through bases. All create functions take in a parent, along with element specific parameters.
Let's analyze one of these panels.
```c
static void djui_panel_sound_value_change(UNUSED struct DjuiBase* caller) {
audio_custom_update_volume();
}
void djui_panel_sound_create(struct DjuiBase* caller) {
struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(SOUND, SOUND), false);
struct DjuiBase* body = djui_three_panel_get_body(panel);
{
djui_slider_create(body, DLANG(SOUND, MASTER_VOLUME), &configMasterVolume, 0, 127, djui_panel_sound_value_change);
djui_slider_create(body, DLANG(SOUND, MUSIC_VOLUME), &configMusicVolume, 0, 127, djui_panel_sound_value_change);
djui_slider_create(body, DLANG(SOUND, SFX_VOLUME), &configSfxVolume, 0, 127, djui_panel_sound_value_change);
djui_slider_create(body, DLANG(SOUND, ENV_VOLUME), &configEnvVolume, 0, 127, djui_panel_sound_value_change);
djui_checkbox_create(body, DLANG(SOUND, FADEOUT), &configFadeoutDistantSounds, NULL);
djui_checkbox_create(body, DLANG(SOUND, MUTE_FOCUS_LOSS), &configMuteFocusLoss, NULL);
djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back);
}
djui_panel_add(caller, panel, NULL);
}
```
Let's look at the `djui_panel_sound_create`.
- First, a three panel is created by using the `djui_panel_menu_create` function. This is a custom create function specifically designed for creating panels in the main menu or the pause menu. Not all panels require it, some use `djui_three_panel_create`, which is the original panel create function.
- `DjuiThreePanel` is a panel containing 3 sections, the header, the body, and the footer. Most of the time the footer is unused, but the header and body are almost always used.
- We get the body that is created from the three panel using the `djui_three_panel_get_body` function. This gets the body section of the three panel.
- Multiple different elements are created.
- `djui_slider_create` takes in it's parent, the name of the slider, a reference to the value (unsigned int) so it can be changed, which typically points to somewhere in the config, the ranges, so a minimum range and a maximum range, and a function for what happens when it's changed.
- The on change functions can always be `NULL`, but they are very useful. In this scenario, `djui_panel_sound_value_change` is called, which calls `audio_custom_update_volume`, so the volume is updated to match the slider.
- `DLANG` is the language system, it takes in a header key and a normal key. You can see a list of language keys and values in `lang/English.ini`, or any language of your choosing.
- `djui_checkbox_create` takes in it's parent, the name of the checkbox, a reference to a boolean, and an on change function call.
- As you can see, the on change function call is set to `NULL`, since there's no need for one.
- `djui_button_create` takes in it's parent, a name, the style of the button, and a on press function.
- To add a panel to the pause menu the `djui_panel_add` function is used, the caller being the previous panel, panel being the current panel, and the last argument being the base that the cursor should go to by default.
This gets a lot of the basics out of the way, but you can configure a base even more using the base functions. Let's go over some important flow elements.
### Flow Layout
`DjuiFlowLayout` allows you to decide how new elements are added. It's actually used by the panel created using `djui_panel_menu_create` internally. A flow layout handles margins, and decides how elements flow. You can have elements flow up, down, left, right, any direction, and you can define the exact size of the margin.
```c
enum DjuiFlowDirection { DJUI_FLOW_DIR_DOWN, DJUI_FLOW_DIR_UP, DJUI_FLOW_DIR_RIGHT, DJUI_FLOW_DIR_LEFT };
void djui_flow_layout_set_flow_direction(struct DjuiFlowLayout* layout, enum DjuiFlowDirection flowDirection);
void djui_flow_layout_set_margin(struct DjuiFlowLayout* layout, f32 margin);
void djui_flow_layout_set_margin_type(struct DjuiFlowLayout* layout, enum DjuiScreenValueType marginType);
struct DjuiFlowLayout* djui_flow_layout_create(struct DjuiBase* parent);
```
You can configure it's size and position via it's base, like any other element.
When configuring children inside the flow layout, if you scale the base by the relative, only the amount of space currently available is up for sale. So if you create 2 side by side in a flow layout, you would need to calculate it expecting that room not being available. So the first button would take up half the space, or 0.5, and then the next button would need to take up the rest of that space, or 1.0.
### Rect Container
A rect container is just an invisible `DjuiRect`. It's just an empty container, so unlike a flow layout, it's children need to behave, and it doesn't hold your hand. The space is not shared between them, so if you use a relative size and tell 2 buttons to use half the space, the full space will be used.

131
docs/c/sections/smlua.md Normal file
View file

@ -0,0 +1,131 @@
## [:rewind: C Reference](../c.md)
# SMLua
SMLua is what allows Lua to communicate with SM64. It's the backbone of the modding api, and it contains multiple features that makes development in C convenient.
## Autogen
Autogen is the system in place to allow C functions to be generated into Lua functions automatically.
Autogen can be ran by running `autogen/autogen.sh` in the root directory of your project.
Autogen needs to be ran for a variety of reasons:
- Anytime a function's name, parameter, or description is changed
- Anytime a struct's name, or variable is changed
- Anytime a enum's name, or contents is changed
- Anytime a new function or struct is added
- Anytime a hook event is added or modified
- And much more
## Adding functions, structs, and constants to autogen
Constants, functions, and structs are each handled in their own files.
- `convert_constants.py`
- `convert_functions.py`
- `convert_structs.py`
Each file is laid out differently to account for their own needs, the only thing that's the same in each is `in_files`, which tells autogen to look for functions, constants, or structs in a specific file
### Constants
Constants is the least used one, so it has the least to go over.
- `exclude_constants` tells autogen to ignore specific constants. For instance, in `djui_console.h`, we don't want to include `CONSOLE_MAX_TMP_BUFFER`, Lua has no need for that constant, so we exclude it.
- `include_constants` tells autogen to add specific constants, and ignore every other constant in that file. For instance, in `mod_storage.h`, there's a bunch of constants we don't want exposed to Lua, so instead of excluding them, we include the few we actually need.
### Functions
Functions have quite a bit of options, so let's go over it:
- `override_allowed_functions` is similar to `include_constants`, it tells autogen to add specific functions and ignore all other functions in the file. It proves especially useful for functions since frequently files contains tons of functions that shouldn't be exposed to Lua.
- `override_disallowed_functions` is similar to `exclude_constants`, it tells autogen to ignore specific functions.
- `override_hide_functions` tells autogen to not document the function. It still exists in SMLua, and Lua can call it, but it can't see it. This is typically used for deprecated functions.
- `override_function_version_excludes` excludes functions from a specific version of the game. It doesn't have too much of a use anymore, so you can ignore it.
Functions have a unique feature of being able to be documented. In a header file where autogen reads the function and generates documentation and generates an SMLua implementation, you can define a description for the function above it. An example of it and the syntax is as the following:
```c
/* |description|Behavior init function for NPC Toad|descriptionEnd| */
void bhv_toad_message_init(void);
```
It can also be multiline:
```c
/* |description|
Checks if Mario's current animation has reached its final frame (i.e., the last valid frame in the animation).
Useful for deciding when to transition out of an animation-driven action
|descriptionEnd| */
s32 is_anim_at_end(struct MarioState *m);
```
### Structs
Structs have the most options, so let's go through it:
- `override_field_types` changes the type of a field in a struct to something else. It pretty much lies to lua about what it actually is. This usually isn't useful, but in specific scenarios it can be.
- `override_field_mutable` tells autogen to make a specific field mutable and also make every other field immutable.
- `override_field_invisible` tells autogen to hide a field from Lua. Unlike `override_hide_functions`, it makes the value not accessible at all.
- `override_field_deprecated` tells autogen to mark certain fields as deprecated.
- `override_field_immutable` tells autogen to make certain fields immutable.
- `override_field_version_excludes` tells autogen to exclude specific fields depending on your version. Similarly to `override_function_version_excludes`, it doesn't have too much of a use anymore, so you can ignore it.
- `override_allowed_structs` tells autogen to make specific structs visible and tangible to Lua, but remove all other structs from Lua.
## Hook Events
Hook events are partially done through autogen and manual C code. Documentation for hooks is manually written out in [hooks.md](../../lua/guides/hooks.md), and sometimes the implementation of a hook event is done manually.
### Defining a hook event
Hook events are defined in 2 places, `smlua_hooks.h`, found in `src/pc/lua`, and `smlua_hook_events.inl`, also found in `src/pc/lua`.
If you want to create a hook, first go to `smlua_hooks.h` and find the `LuaHookedEventType` enum.
Scroll down to the bottom until you find `HOOK_MAX`. `HOOK_MAX` should always be at the very bottom of the enum, so move your custom hook one spot above it. Add your custom hook there.
Next, go to `smlua_hook_events.inl`. This is where the magic happens.
You can define your custom hook with `SMLUA_EVENT_HOOK`. Let's go over how it works and what the parameters are:
- The very first parameter is your hook defined in `smlua_hook.h`
- The next parameter is the hook event return type, here are the options:
```c
enum LuaHookedEventReturn {
HOOK_RETURN_NEVER, // Never returns before calling all hooks for a given event, returns true if there is at least one successful callback call
HOOK_RETURN_ON_SUCCESSFUL_CALL, // Returns true on first successful callback call, skipping next hooks for a given event
HOOK_RETURN_ON_OUTPUT_SET, // Returns true on output set after a successful call, skipping next hooks for a given event
};
```
- To reiterate, `HOOK_RETURN_NEVER` should be used if your hook should always go to every single mod. If you don't use `HOOK_RETURN_NEVER`, your hook may only be ran for one instance.
- `HOOK_RETURN_ON_SUCCESSFUL_CALL` should be used if you don't want any other mods to use a hook call if the call succeeds for the first mod handling it. It's only used a few times, but it can come in handy.
- `HOOK_RETURN_ON_OUTPUT_SET` should be used if you don't want any mod to access a hook that had it's output set by Lua.
- If these 3 hook return types don't cover what you are looking for, you need to make a custom implementation, mark this parameter with an `_` if you want to do a custom implementation.
- Every argument after is the parameters and return values for Lua, and they are optional. Parameters come first, insert the parameters you want Lua to receive, for instance, `struct MarioState* m` to allow Lua to receive a mario state in the hook event call.
- After parameters comes output, or return values. This is optional. To define an output parameter, use the `OUTPUT` macro.
- Here is an example hook showcasing this:
```c
// Never end the hook early, so use HOOK_RETURN_NEVER, pass in the current mario state and the hazard types as parameters to lua. Request a boolean from Lua to decide if the hazard should be registered or not.
SMLUA_EVENT_HOOK(HOOK_ALLOW_HAZARD_SURFACE, HOOK_RETURN_NEVER, struct MarioState *m, s32 hazardType, OUTPUT bool *allowHazard)
```
### Calling a hook event from C
Hook events can be called via the `smlua_call_event_hooks`. The best way to explain this function is with an example. Let's use that same hook, `HOOK_ALLOW_HAZARD_SURFACE`.
```c
bool allowHazard = true;
smlua_call_event_hooks(HOOK_ALLOW_HAZARD_SURFACE, m, HAZARD_TYPE_LAVA_WALL, &allowHazard);
```
- First, the hook name defined in the `LuaHookedEventType` is inserted.
- Each parameter that comes after that follows the same layout as defined in the `SMLUA_EVENT_HOOK` call, including the output.
- The output should be a reference so the function can properly set the variable.
- If Lua doesn't return anything, the output passed into the function stays as what it was originally, so it serves as a default value. That's why `allowHazard` is set to true.
After all these changes, remember to rerun autogen. Once that's done, you should have your hook into the game, test it and make sure everything works!

View file

@ -1,18 +1,5 @@
-- name: Matrix Code
-- description: Run /matrix and a builtin texture name to replace with the digital rain
-- deluxe: true
if SM64COOPDX_VERSION == nil then
local first = false
hook_event(HOOK_ON_LEVEL_INIT, function()
if not first then
first = true
play_sound(SOUND_MENU_CAMERA_BUZZ, gMarioStates[0].marioObj.header.gfx.cameraToObject)
djui_chat_message_create("\\#ff7f7f\\Matrix Code is not supported with sm64ex-coop\nas it uses sm64coopdx exclusive Lua functionality.\n\\#dcdcdc\\To use this mod, try out sm64coopdx at\n\\#7f7fff\\https://sm64coopdx.com")
end
end)
return
end
local sMatrixFrames = {}
for i = 0, 10 do

View file

@ -1,18 +1,12 @@
# Lua Reference
The Lua scripting API is in early development.
Expect many more things to be supported in the future.
<br />
## How to install Lua mods
Lua scripts you make can be placed either the `mods` folder in the base directory, or in `<SAVE FILE LOCATION>/mods`
Lua scripts you make can be placed either the `mods` folder in the base directory, or in `<SAVE FILE LOCATION>/mods`. You can also drag and drop mods into the window to install them.
Save file locations:
- Windows: `%appdata%/sm64ex-coop`
- Linux: `~/.local/share/sm64ex-coop`
- MacOS: `~/Library/Application Support/sm64ex-coop`
- Windows: `%appdata%/sm64coopdx`
- Linux: `~/.local/share/sm64coopdx`
- MacOS: `~/Library/Application Support/sm64coopdx`
<br />
@ -30,7 +24,7 @@ Save file locations:
- [Structs](structs.md)
### Guides
- [Setting up Visual Studio Code](guides/vs-code-setup.md)
- [Setting up Visual Studio Code](guides/vs-code-setup.md)
- [Hooks](guides/hooks.md)
- [gMarioStates](guides/mario-state.md)
- [Behavior Object Lists](guides/object-lists.md)