Use carousel button on prelaunch; more cleanup

This commit is contained in:
Luke Street
2026-08-19 22:58:43 -06:00
parent 3ecd785ac2
commit fd4a5f0cfb
10 changed files with 476 additions and 257 deletions
+45 -45
View File
@@ -636,83 +636,78 @@ first in-game frame. Projection matrices match the renderer's WebGPU clip conven
Camera operators allow overriding the main camera. When an operator callback returns true, its values replace the camera
state for the current frame. Register and unregister using `register_camera_operator` / `unregister_camera_operator`.
### GameModeService (`mods/svc/gamemode.h`)
### GameModeService (`mods/svc/game_mode.h`)
Allows a mod to register a game mode with callbacks for key gameplay and save lifecycle events. Registered game modes
appear in the prelaunch menu. Game modes may use a unique set of saves by configuring `saveName`; leave it empty to use
appear in the prelaunch menu. Game modes may use a unique set of saves by configuring `save_name`; leave it empty to use
the vanilla `gczelda2` save.
```cpp
// An example that shows registering a gamemode with function hooks that are scoped to the gamemode being active
IMPORT_SERVICE(LogService, svc_log);
IMPORT_SERVICE(HookService, svc_hook);
IMPORT_SERVICE(GameModeService, svc_gamemode);
IMPORT_SERVICE(GameModeService, svc_game_mode);
DEFINE_HOOK(fopAcM_createItem, CreateItem);
#define MY_GAME_MODE_ID "game-mode-id"
static HookAction myFunctionHook(ModContext *ctx, void *args, void *, void *) {
static HookAction my_function_hook(ModContext* ctx, void* args, void*, void*) {
// If we wish to have this hook only run while the gamemode is registered, we need to hook the function from the
// gamemode's onActivatedFunction, and uninstall the hook during the onDeactivatedFunction. Example below.
// Alternatively, check with `svc_gamemode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`.
// Alternatively, check with `svc_game_mode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`.
return HOOK_CONTINUE;
}
void onGameModeActivated() {
ModResult on_game_mode_activated(void*, ModError* outError) {
// Setup the gamemode, Add any hooks that are gamemode specific
// Overlay any files that are gamemode specific
ModResult result = mods::hook_add_pre<CreateItem>(svc_hook, myFunctionHook);
ModResult result = mods::hook_add_pre<CreateItem>(svc_hook, my_function_hook);
if (result != MOD_OK) {
svc_log->error(mod_ctx, "failed to install hook to fopAcM_createItem");
}
return mods::set_error(outError, result, "failed to install fopAcM_createItem hook");
}
return MOD_OK;
}
void onGameModeDeactivated() {
ModResult on_game_mode_deactivated(void*, ModError* outError) {
// Uninstall any hooks that are gamemode specific
// Remove any file overlays that are gamemode specific
ModResult result = mods::hook_uninstall<CreateItem>();
if (result != MOD_OK) {
svc_log->error(mod_ctx, "failed to uninstall CreateItem hook");
}
return mods::set_error(outError, result, "failed to uninstall fopAcM_createItem hook");
}
return MOD_OK;
}
void onSaveLoaded() {
ModResult on_save_loaded(void*, ModError*) {
// This function will be invoked by the game as a save is loaded
return MOD_OK;
}
// Register the game mode when the mod is initialized.
const GameModeDesc gameModeDesc = {
.gameModeId = MY_GAME_MODE_ID,
.fullName = "My Game Mode",
.saveName = "my-unique-save", // Custom save names should be unique, max 31 chars
.onActivatedFunction = onGameModeActivated, // Called when the game mode is selected or launched
.onDeactivatedFunction = onGameModeDeactivated, // Called when it is deselected or the mod is disabled
.onPlayFunction = nullptr, // Called when "Play" is pressed on the prelaunch menu
.onSaveLoadedFunction = onSaveLoaded, // Called when a save is loaded
.onNewSaveFunction = nullptr, // Called after a new savefile is created
.onNewSaveSelectFunction = nullptr, // Called during the flow before the file name select is ran (see below)
.onGameResetFunction = nullptr, // Called when the game is reset
.onTickFunction = nullptr, // Called every game tick while the game mode is active
.struct_size = sizeof(GameModeDesc),
.game_mode_id = MY_GAME_MODE_ID,
.full_name = "My Game Mode",
.save_name = "my-unique-save",
.user_data = nullptr,
.on_activated = on_game_mode_activated,
.on_deactivated = on_game_mode_deactivated,
.on_save_loaded = on_save_loaded,
};
svc_gamemode->register_game_mode(mod_ctx, &gameModeDesc);
svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
```
A game mode can also open UI for per-save settings when creating a new file.
A game mode can also open UI for per-save settings when creating a new file. The state begins as
`GAME_MODE_STATE_PENDING` and remains valid until the mod selects `PROCEED` or `RETURN`.
```cpp
IMPORT_SERVICE(GameModeService, svc_gamemode);
IMPORT_SERVICE(GameModeService, svc_game_mode);
IMPORT_SERVICE(UiService, svc_ui);
void onNewSaveSelect(bool *out_proceedToNameSelect, bool *out_returnToFileSelect) {
static bool *proceedToNameSelect;
static bool *returnToFileSelect;
ModResult on_new_save_select(void*, GameModeNewSaveState* state, ModError* outError) {
static GameModeNewSaveState* newSaveState;
static UiWindowHandle windowHandle;
// Used within callbacks as needed
proceedToNameSelect = out_proceedToNameSelect;
returnToFileSelect = out_returnToFileSelect;
newSaveState = state;
UiTabDesc tabs[1]{};
@@ -724,7 +719,7 @@ void onNewSaveSelect(bool *out_proceedToNameSelect, bool *out_returnToFileSelect
desc.label = "Play";
desc.help_rml = "Play Button";
desc.on_pressed = [](ModContext* ctx, void* userdata) {
*proceedToNameSelect = true;
*newSaveState = GAME_MODE_STATE_PROCEED;
svc_ui->window_close(ctx, *static_cast<UiWindowHandle*>(userdata));
};
desc.user_data = &windowHandle;
@@ -737,21 +732,26 @@ void onNewSaveSelect(bool *out_proceedToNameSelect, bool *out_returnToFileSelect
desc.tab_count = 1;
desc.on_closed = [](ModContext *, UiWindowHandle, void *userdata) {
// If closing the window through backing out, return to file select
if (*proceedToNameSelect == false) {
*returnToFileSelect = true;
if (*newSaveState == GAME_MODE_STATE_PENDING) {
*newSaveState = GAME_MODE_STATE_RETURN;
}
};
svc_ui->window_push(mod_ctx, &desc, &windowHandle);
ModResult result = svc_ui->window_push(mod_ctx, &desc, &windowHandle);
if (result != MOD_OK) {
return mods::set_error(outError, result, "failed to open new-save settings");
}
return MOD_OK;
}
const GameModeDesc gameModeDesc = {
.gameModeId = "my-game-mode-id",
.fullName = "My Game Mode",
.saveName = "my-unique-save",
.onNewSaveSelectFunction = onNewSaveSelect,
.struct_size = sizeof(GameModeDesc),
.game_mode_id = "my-game-mode-id",
.full_name = "My Game Mode",
.save_name = "my-unique-save",
.on_new_save_select = on_new_save_select,
};
svc_gamemode->register_game_mode(mod_ctx, &gameModeDesc);
svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
```