diff --git a/docs/modding.md b/docs/modding.md index 89a395ac5d..4c6f654df2 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -692,19 +692,85 @@ const GamemodeDesc gamemodeDesc = { .gamemodeId = MY_GAMEMODE_ID, .fullName = "Gamemode Name", // The save name should be something that other gamemodes will not try to use, so appending your name to it - // is reccomended - .saveName = "my-unique-save-name_developer-name", + // is reccomended. Note: it is limited to 31 characters long + .saveName = "my-unique-save_developer-name", .onActivatedFunction = onGamemodeActivated, // Called when the gamemode is selected on the prelaunch menu (or is launched) .onDeactivatedFunction = onGamemodeDeactivated, // Called when the gamemode is deselected on the prelaunch menu (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 while 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 gamemode is active }; svc_gamemode->register_gamemode(mod_ctx, &gamemodeDesc); ``` +Within the gamemode service, a gamemode can also request to load a UI for per-save file settings when the button to +create a new file is pressed. + +```cpp +#include "d/d_file_select.h" // Requires game feature +IMPORT_SERVICE(GamemodeService, svc_gamemode); +IMPORT_SERVICE(UiService, svc_ui); + +bool proceedUI = false; +UiWindowHandle windowHandle; + +bool onNewSaveSelect(dFile_select_c *fileSelect) { + if (fileSelect->mGamemodeSaveStartBuildUi) { + proceedUI = false; + UiTabDesc tabs[1]{}; + + tabs[0].struct_size = sizeof(UiTabDesc); + tabs[0].title = "Play"; + tabs[0].build = [](ModContext* ctx, UiWindowHandle, UiElementHandle leftPane, UiElementHandle rightPane, void*, ModError*) { + UiControlDesc desc = UI_CONTROL_DESC_INIT; + desc.kind = UI_CONTROL_BUTTON; + desc.label = "Play"; + desc.help_rml = "Play Button"; + desc.on_pressed = [](ModContext* ctx, void* userdata) { + proceedUI = true; + svc_ui->window_close(ctx, *static_cast(userdata)); + }; + desc.user_data = &windowHandle; + svc_ui->pane_add_control(mod_ctx, leftPane, &desc, &windowHandle); + return MOD_OK; + }; + + UiWindowDesc desc = UI_WINDOW_DESC_INIT; + desc.tabs = tabs; + desc.tab_count = 1; + desc.user_data = fileSelect; + desc.on_closed = [](ModContext *, UiWindowHandle, void *userdata) { + dFile_select_c *i_this = static_cast(userdata); + + // if closing the window through backing out, return to file select + if (!proceedUI) { + i_this->backToDataSelectMove(); + } + }; + + svc_ui->window_push(mod_ctx, &desc, &windowHandle); + } + + // When we hit play in the UI, return true (tells the file select that we can proceed) + if (proceedUI) { + return true; + } + return false; +} + +const GamemodeDesc gamemodeDesc = { + .gamemodeId = "my-gamemode-id", + .fullName = "My Gamemode", + .saveName = "my-gamemode-save", + .onNewSaveSelectFunction = onNewSaveSelect, +}; +svc_gamemode->register_gamemode(mod_ctx, &gamemodeDesc); + +``` + --- ## Hooking Game Functions diff --git a/include/d/d_file_select.h b/include/d/d_file_select.h index 634c0db352..0a0772e807 100644 --- a/include/d/d_file_select.h +++ b/include/d/d_file_select.h @@ -420,6 +420,14 @@ public: bool pointerMenuSelect(); bool pointerCopyDataToSelect(); bool pointerYesNoSelect(bool errorSelect); + void backToDataSelectMove() { + headerTxtSet(0x43, 1, 0); + fileRecScaleAnmInitSet2(0.0f, 1.0f); + nameMoveAnmInitSet(0xd29, 0xd1f); + modoruTxtDispAnmInit(0); + mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE; + mGamemodeSaveStartBuildUi = true; + } #endif void _draw(); void errorMoveAnmInitSet(int, int); @@ -733,6 +741,7 @@ public: #endif #ifdef TARGET_PC dDlst_FileSelFade_c mFadeDlst; + bool mGamemodeSaveStartBuildUi = true; #endif #if PLATFORM_WII || PLATFORM_SHIELD diff --git a/sdk/include/mods/svc/gamemode.h b/sdk/include/mods/svc/gamemode.h index 2132002578..0586fe6fab 100644 --- a/sdk/include/mods/svc/gamemode.h +++ b/sdk/include/mods/svc/gamemode.h @@ -7,15 +7,18 @@ #define GAMEMODE_SERVICE_MAJOR 1u #define GAMEMODE_SERVICE_MINOR 0u +struct dFile_select_c; + typedef struct { const char* gamemodeId; const char* fullName; - const char* saveName; + const char saveName[32]; // GCI Filenames are limited to 31 characters void (*onActivatedFunction)(); void (*onDeactivatedFunction)(); void (*onPlayFunction)(); void (*onSaveLoadedFunction)(); void (*onNewSaveFunction)(); + bool (*onNewSaveSelectFunction)(dFile_select_c* fileSelect); // Should return true when any custom options flows are finished void (*onGameResetFunction)(); void (*onTickFunction)(); } GamemodeDesc; diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index 936a368f81..5945b2ef5a 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -29,6 +29,7 @@ #include "dusk/menu_pointer.h" #include "helpers/string.hpp" #include "dusk/mods/svc/save.hpp" +#include "dusk/gamemode.hpp" namespace { constexpr u8 pointer_target(u8 group, u8 index) noexcept { @@ -1313,6 +1314,24 @@ void dFile_select_c::selectDataNameMove() { bool isNameMove = nameMoveAnm(); bool isModoruTxtDisp = modoruTxtDispAnm(); +#ifdef TARGET_PC + const dusk::gamemode::Gamemode* gamemode = dusk::gamemode::getGamemodeManager().getCurrentGamemode(); + if (gamemode) { + if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) { + bool gamemodeFlowClosed = gamemode->invokeOnNewSaveSelectFunction(this); + if (mGamemodeSaveStartBuildUi == true) { + mGamemodeSaveStartBuildUi = false; + } + if (!gamemodeFlowClosed) { + return; + } + }else { + return; + } + } + mGamemodeSaveStartBuildUi = true; +#endif + if (isHeaderTxtChange == true && isFileRecScale == true && isNameMove == true && isModoruTxtDisp == true) { diff --git a/src/dusk/gamemode.hpp b/src/dusk/gamemode.hpp index 9aa39ebe59..3f9324107a 100644 --- a/src/dusk/gamemode.hpp +++ b/src/dusk/gamemode.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include "d/d_file_select.h" namespace dusk::gamemode { using GamemodeId = std::string; @@ -50,6 +51,13 @@ public: mOnNewSaveFunction(); } } + + bool invokeOnNewSaveSelectFunction(dFile_select_c* fileSelect) const { + if (mOnNewSaveSelectFunction) { + return mOnNewSaveSelectFunction(fileSelect); + } + return true; + } void invokeOnGameResetFunction() const { if (mOnGameResetFunction) { @@ -68,6 +76,7 @@ public: std::function mOnPlayFunction; std::function mOnSaveLoadedFunction; std::function mOnNewSaveFunction; + std::function mOnNewSaveSelectFunction; std::function mOnGameResetFunction; std::function mOnTickFunction; }; diff --git a/src/dusk/mods/svc/gamemode.cpp b/src/dusk/mods/svc/gamemode.cpp index 96d8368c46..a009f13de3 100644 --- a/src/dusk/mods/svc/gamemode.cpp +++ b/src/dusk/mods/svc/gamemode.cpp @@ -65,16 +65,10 @@ ModResult register_gamemode(ModContext* ctx, const GamemodeDesc* desc) { } } - std::string saveName; - if (!desc->saveName) { - Log.warn("Attempted to register gamemode {} with a null save name! Defaulting to: (gczelda2)",id); + std::string saveName = desc->saveName; + if (saveName.empty()) { + Log.warn("Attempted to register gamemode {} with an empty save name! Defaulting to: (gczelda2)",id); saveName = "gczelda2"; - }else{ - saveName = desc->saveName; - if (saveName.empty()) { - Log.warn("Attempted to register gamemode {} with an empty save name! Defaulting to: (gczelda2)",id); - saveName = "gczelda2"; - } } dusk::gamemode::Gamemode gamemode(id, fullName, saveName); @@ -94,6 +88,9 @@ ModResult register_gamemode(ModContext* ctx, const GamemodeDesc* desc) { if (desc->onNewSaveFunction) { gamemode.mOnNewSaveFunction = desc->onNewSaveFunction; } + if (desc->onNewSaveSelectFunction) { + gamemode.mOnNewSaveSelectFunction = desc->onNewSaveSelectFunction; + } if (desc->onGameResetFunction) { gamemode.mOnGameResetFunction = desc->onGameResetFunction; }