Rework UI lifecycle, finish renaming & cleanup

This commit is contained in:
Luke Street
2026-08-19 21:43:35 -06:00
parent fd0e33460f
commit 3ecd785ac2
36 changed files with 536 additions and 526 deletions
+23 -31
View File
@@ -638,14 +638,9 @@ state for the current frame. Register and unregister using `register_camera_oper
### GameModeService (`mods/svc/gamemode.h`)
Allows a mod to register a gamemode that allows the game to designate one form of gameplay (named a gamemode). This
is intended to allow large mods that change large amounts of game logic (such as a randomizer) to have explicit control
over how the game will function at certain points. When a gamemode is registered via the service, it will add an entry
to the pre-launch menu. When selected, the game will use a unique set of savefiles (designated by the `saveName` field)
to store save data while the gamemode is active. Any function pointers registered with the gamemode will be called by
dusklight when their condition is met.
Note: for any gamemode wishing to use the vanilla set of savefiles, use `gczelda2` as the save file name.
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
the vanilla `gczelda2` save.
```cpp
// An example that shows registering a gamemode with function hooks that are scoped to the gamemode being active
@@ -653,18 +648,18 @@ IMPORT_SERVICE(LogService, svc_log);
IMPORT_SERVICE(HookService, svc_hook);
IMPORT_SERVICE(GameModeService, svc_gamemode);
#define MY_GAMEMODE_ID "gamemodeid"
DEFINE_HOOK(fopAcM_createItem, CreateItem);
#define MY_GAME_MODE_ID "game-mode-id"
static HookAction myFunctionHook(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. An example is given below
// 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`.
return HOOK_CONTINUE;
}
DEFINE_HOOK(fopAcM_createItem, CreateItem);
void onGameModeActivated() {
// Setup the gamemode, Add any hooks that are gamemode specific
// Overlay any files that are gamemode specific
@@ -676,7 +671,7 @@ void onGameModeActivated() {
void onGameModeDeactivated() {
// Uninstall any hooks that are gamemode specific
// Remove overlays to any files 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");
@@ -687,27 +682,24 @@ void onSaveLoaded() {
// This function will be invoked by the game as a save is loaded
}
// Register the gamemode when the mod is initialized
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. 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)
// 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 gamemode is active
.onTickFunction = nullptr, // Called every game tick while the game mode is active
};
svc_gamemode->register_gamemode(mod_ctx, &gamemodeDesc);
svc_gamemode->register_game_mode(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.
A game mode can also open UI for per-save settings when creating a new file.
```cpp
IMPORT_SERVICE(GameModeService, svc_gamemode);
@@ -753,13 +745,13 @@ void onNewSaveSelect(bool *out_proceedToNameSelect, bool *out_returnToFileSelect
svc_ui->window_push(mod_ctx, &desc, &windowHandle);
}
const GameModeDesc gamemodeDesc = {
.gamemodeId = "my-gamemode-id",
.fullName = "My GameMode",
.saveName = "my-gamemode-save",
const GameModeDesc gameModeDesc = {
.gameModeId = "my-game-mode-id",
.fullName = "My Game Mode",
.saveName = "my-unique-save",
.onNewSaveSelectFunction = onNewSaveSelect,
};
svc_gamemode->register_gamemode(mod_ctx, &gamemodeDesc);
svc_gamemode->register_game_mode(mod_ctx, &gameModeDesc);
```
+2 -2
View File
@@ -1434,7 +1434,7 @@ set(DUSK_FILES
src/dusk/extras.c
src/dusk/frame_interpolation.cpp
src/dusk/game_clock.cpp
src/dusk/gamemode.cpp
src/dusk/game_mode.cpp
src/dusk/gamepad_color.cpp
src/dusk/globals.cpp
src/dusk/gyro.cpp
@@ -1502,7 +1502,7 @@ set(DUSK_FILES
src/dusk/mods/svc/texture.cpp
src/dusk/mods/svc/ui.cpp
src/dusk/mods/svc/ui.hpp
src/dusk/mods/svc/gamemode.cpp
src/dusk/mods/svc/game_mode.cpp
src/dusk/mods/svc/window.cpp
src/dusk/mods/svc/window.hpp
src/dusk/mods/svc/save.cpp
@@ -8,13 +8,13 @@
#define GAMEMODE_SERVICE_MINOR 0u
typedef struct {
const char* gamemodeId;
const char* gameModeId;
const char* fullName;
const char saveName[32]; // Should be unique. GCI Filenames are limited to 31 characters
void (*onActivatedFunction)(); // Called when the gamemode is selected
void (*onDeactivatedFunction)(); // Called when the gamemode is deselected
const char saveName[32]; // Empty uses default (gczelda2); max 31 chars
void (*onActivatedFunction)(); // Called when the game mode is selected
void (*onDeactivatedFunction)(); // Called when the game mode is deselected
void (*onPlayFunction)(); // Called when play is pressed on the prelaunch menu
void (*onSaveLoadedFunction)(); // Called whenever a savefile is loaded
void (*onSaveLoadedFunction)(); // Called whenever a save file is loaded
void (*onNewSaveFunction)(); // Called when a new save is created
void (*onNewSaveSelectFunction)(bool* out_proceedToNameSelect,
bool* out_returnToFileSelect); // Set out_proceedToNameSelect to true once any UI flows are
@@ -27,7 +27,7 @@ typedef struct GameModeService {
ServiceHeader header;
ModResult (*register_game_mode)(ModContext* ctx, const GameModeDesc* desc);
ModResult (*unregister_game_mode)(ModContext* ctx, const char* id);
ModResult (*is_active)(ModContext* ctx, const char* gamemodeId, bool* out_active);
ModResult (*is_active)(ModContext* ctx, const char* gameModeId, bool* out_active);
} GameModeService;
#ifdef __cplusplus
+1 -1
View File
@@ -24,10 +24,10 @@
#include <cstring>
#ifdef TARGET_PC
#include "dusk/game_mode.hpp"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
#include "dusk/speedrun.h"
#include "dusk/gamemode.hpp"
#include "dusk/version.hpp"
#endif
+2 -2
View File
@@ -13,10 +13,10 @@
#ifdef TARGET_PC
#include <dusk/autosave.h>
#include "dusk/livesplit.h"
#include "dusk/game_mode.hpp"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/livesplit.h"
#include "dusk/speedrun.h"
#include "dusk/gamemode.hpp"
#endif
#include "dusk/version.hpp"
+17 -13
View File
@@ -26,10 +26,10 @@
#include "dusk/version.hpp"
#if TARGET_PC
#include "dusk/game_mode.hpp"
#include "dusk/menu_pointer.h"
#include "helpers/string.hpp"
#include "dusk/mods/svc/save.hpp"
#include "dusk/gamemode.hpp"
#include "helpers/string.hpp"
namespace {
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
@@ -1315,11 +1315,13 @@ void dFile_select_c::selectDataNameMove() {
bool isModoruTxtDisp = modoruTxtDispAnm();
#ifdef TARGET_PC
const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) {
const dusk::gamemode::GameMode* gameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) {
if (mGameModeSaveStartBuildUi) {
gamemode->invokeOnNewSaveSelectFunction(&mGameModeProceedToNameSelect, &mGameModeReturnToFileSelect);
gameMode->invokeOnNewSaveSelectFunction(
&mGameModeProceedToNameSelect, &mGameModeReturnToFileSelect);
mGameModeSaveStartBuildUi = false;
}
if (mGameModeReturnToFileSelect) {
@@ -1430,9 +1432,10 @@ void dFile_select_c::menuSelectStart() {
#if TARGET_PC
dusk::mods::svc::save_slot_loaded(mSelectNum, &mSaveData[mSelectNum]);
const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
gamemode->invokeOnSaveLoadedFunction();
const dusk::gamemode::GameMode* gameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
gameMode->invokeOnSaveLoadedFunction();
}
#endif
} else if (mSelectMenuNum == 0) {
@@ -1787,10 +1790,11 @@ void dFile_select_c::nameInput2() {
mIsSelectEnd = true;
#if TARGET_PC
dusk::mods::svc::save_slot_new(mSelectNum);
const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
gamemode->invokeOnNewSaveFunction();
gamemode->invokeOnSaveLoadedFunction();
const dusk::gamemode::GameMode* gameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
gameMode->invokeOnNewSaveFunction();
gameMode->invokeOnSaveLoadedFunction();
}
#endif
mDataSelProc = DATASELPROC_NEXT_MODE_WAIT;
+5 -4
View File
@@ -24,11 +24,11 @@
#include "JSystem/JUtility/JUTConsole.h"
#ifdef TARGET_PC
#include "dusk/game_mode.hpp"
#include "dusk/language.hpp"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/mods/svc/save.hpp"
#include "dusk/gamemode.hpp"
#include "dusk/version.hpp"
#include "m_Do/m_Do_MemCard.h"
#endif
@@ -809,9 +809,10 @@ void dScnLogo_c::nextSceneChange() {
if (status == 1) {
dusk::mods::svc::save_slot_loaded(
saveSlot, buf + saveSlot * SAVEDATA_SIZE);
const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
gamemode->invokeOnSaveLoadedFunction();
const dusk::gamemode::GameMode* gameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
gameMode->invokeOnSaveLoadedFunction();
}
}
+3 -3
View File
@@ -18,13 +18,13 @@
#include "m_Do/m_Do_mtx.h"
#ifdef TARGET_PC
#include "dusk/autosave.h"
#include "dusk/game_mode.hpp"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/livesplit.h"
#include "dusk/memory.h"
#include "dusk/speedrun.h"
#include "dusk/settings.h"
#include "dusk/autosave.h"
#include "dusk/gamemode.hpp"
#include "dusk/speedrun.h"
#endif
#if TARGET_PC
+2 -2
View File
@@ -28,9 +28,9 @@
#endif
#if TARGET_PC
#include "dusk/settings.h"
#include "dusk/gamemode.hpp"
#include <f_ap/f_ap_game.h>
#include "dusk/game_mode.hpp"
#include "dusk/settings.h"
#include "helpers/string.hpp"
#define strcpy SafeStringCopy
+13 -14
View File
@@ -1,27 +1,26 @@
#include "dusk/achievements.h"
#include "dusk/io.hpp"
#include "dusk/main.h"
#include "d/d_com_inf_game.h"
#include "d/d_item_data.h"
#include "d/d_map_path_fmap.h"
#include "d/d_stage.h"
#include "d/d_menu_fmap.h"
#include "JSystem/JKernel/JKRArchive.h"
#include "d/d_meter2_info.h"
#include "d/actor/d_a_alink.h"
#include "d/actor/d_a_ni.h"
#include "d/actor/d_a_npc4.h"
#include "d/actor/d_a_b_gnd.h"
#include "d/actor/d_a_b_ob.h"
#include "d/actor/d_a_ni.h"
#include "d/actor/d_a_npc4.h"
#include "d/actor/d_a_player.h"
#include "d/d_com_inf_game.h"
#include "d/d_demo.h"
#include "d/d_item_data.h"
#include "d/d_map_path_fmap.h"
#include "d/d_menu_fmap.h"
#include "d/d_meter2_info.h"
#include "d/d_stage.h"
#include "dusk/game_mode.hpp"
#include "dusk/io.hpp"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/speedrun.h"
#include "dusk/ui/ui.hpp"
#include "f_pc/f_pc_name.h"
#include "f_op/f_op_actor_mng.h"
#include "f_pc/f_pc_name.h"
#include "dusk/logging.h"
#include "dusk/gamemode.hpp"
#include "dusk/speedrun.h"
#include <filesystem>
#include <algorithm>
+88
View File
@@ -0,0 +1,88 @@
#include "dusk/game_mode.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "aurora/lib/logging.hpp"
#include "dusk/config.hpp"
#include "dusk/ui/prelaunch.hpp"
#include "m_Do/m_Do_MemCard.h"
namespace dusk::gamemode {
namespace {
aurora::Module Log("dusk::gamemode");
}
GameModeManager g_GameModeManager;
GameModeManager::GameModeManager() {
GameMode vanilla{kVanillaGameModeId, "Vanilla"};
mRegisteredGameModes.emplace(vanilla.getId(), std::move(vanilla));
mCurrentGameModeId = kVanillaGameModeId;
}
void GameModeManager::setGameModeToPrevious() {
// Restore the previously selected game mode if still registered.
GameModeId id = getSettings().game.lastSelectedGameModeId;
if (!mRegisteredGameModes.contains(id)) {
setCurrentGameMode(kVanillaGameModeId);
return;
}
setCurrentGameMode(id);
}
void GameModeManager::registerGameMode(const GameMode& gameMode) {
if (gameMode.getId().empty()) {
Log.fatal("No game mode ID specified in GameModeManager::registerGameMode");
}
if (gameMode.getFullName().empty()) {
Log.fatal("No display name specified for game mode {}", gameMode.getId());
}
if (mRegisteredGameModes.contains(gameMode.getId())) {
Log.warn("Attempting to re-register existing game mode {}", gameMode.getId());
return;
}
mRegisteredGameModes.emplace(gameMode.getId(), gameMode);
ui::Prelaunch::refresh_menu_buttons();
}
void GameModeManager::unregisterGameMode(const GameModeId& gameModeId) {
const auto& it = mRegisteredGameModes.find(gameModeId);
if (it == mRegisteredGameModes.end()) {
Log.warn("Attempting to unregister unknown game mode {}", gameModeId);
return;
}
if (mCurrentGameModeId == gameModeId) {
// Reset to prelaunch before unloading callbacks belonging to the active mod.
ui::prelaunch_state().returnToPrelaunchOnReset = true;
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
setCurrentGameMode(kVanillaGameModeId);
}
mRegisteredGameModes.erase(it);
ui::Prelaunch::refresh_menu_buttons();
}
void GameModeManager::setCurrentGameMode(const GameModeId& id) {
if (mCurrentGameModeId == id) {
return;
}
if (!mRegisteredGameModes.contains(id)) {
Log.warn("Attempting to configure unknown game mode {}", id);
return;
}
const GameMode* currentGameMode = getCurrentGameMode();
if (currentGameMode) {
currentGameMode->invokeOnDeactivatedFunction();
}
mCurrentGameModeId = id;
getSettings().game.lastSelectedGameModeId.setValue(id);
config::save();
currentGameMode = getCurrentGameMode();
if (currentGameMode) {
mDoMemCd_SetFileName(currentGameMode->getSaveName());
currentGameMode->invokeOnActivatedFunction();
}
}
} // namespace dusk::gamemode
@@ -1,21 +1,24 @@
#pragma once
#include "d/d_file_select.h"
#include <functional>
#include <map>
#include "d/d_file_select.h"
#include <string>
#include <utility>
namespace dusk::gamemode {
using GameModeId = std::string;
constexpr const char* kVanillaGameModeId = "vanilla";
constexpr const char* kDefaultGameModeSaveName = "gczelda2";
// This class holds the definition for the gamemode and various function pointers to call
// Holds a game mode definition and its lifecycle callbacks.
class GameMode {
public:
GameMode(const GameModeId& id, const std::string& fullName, const std::string& saveName) {
mId = id;
mFullName = fullName;
mSaveName = saveName;
}
GameMode(GameModeId id, std::string fullName, std::string saveName = {})
: mId{std::move(id)}, mFullName{std::move(fullName)},
mSaveName{saveName.empty() ? kDefaultGameModeSaveName : std::move(saveName)} {}
const GameModeId& getId() const { return mId; }
const std::string& getFullName() const { return mFullName; }
const std::string& getSaveName() const { return mSaveName; }
@@ -53,11 +56,12 @@ public:
mOnNewSaveFunction();
}
}
void invokeOnNewSaveSelectFunction(bool* out_proceedToNameSelect, bool* out_returnToFileSelect) const {
void invokeOnNewSaveSelectFunction(
bool* out_proceedToNameSelect, bool* out_returnToFileSelect) const {
if (mOnNewSaveSelectFunction) {
mOnNewSaveSelectFunction(out_proceedToNameSelect, out_returnToFileSelect);
}else {
} else {
*out_proceedToNameSelect = true;
}
}
@@ -79,7 +83,8 @@ public:
std::function<void()> mOnPlayFunction;
std::function<void()> mOnSaveLoadedFunction;
std::function<void()> mOnNewSaveFunction;
std::function<void(bool* out_proceedToNameSelect, bool* out_returnToFileSelect)> mOnNewSaveSelectFunction;
std::function<void(bool* out_proceedToNameSelect, bool* out_returnToFileSelect)>
mOnNewSaveSelectFunction;
std::function<void()> mOnGameResetFunction;
std::function<void()> mOnTickFunction;
};
@@ -87,16 +92,17 @@ public:
class GameModeManager {
public:
GameModeManager();
void registerGameMode(const GameMode& gamemode);
void unregisterGameMode(const GameModeId& gamemodeId);
void registerGameMode(const GameMode& gameMode);
void unregisterGameMode(const GameModeId& gameModeId);
const GameMode* getCurrentGameMode() const {
const auto& it = mRegisteredGameModes.find(mCurrentGameModeId);
return it != mRegisteredGameModes.end() ? &it->second : &mRegisteredGameModes.at(kVanillaGameModeId);
return it != mRegisteredGameModes.end() ? &it->second :
&mRegisteredGameModes.at(kVanillaGameModeId);
}
bool isCurrentGameMode(const GameModeId& id) const {
const GameMode* gamemode = getCurrentGameMode();
if (gamemode && gamemode->getId() == id) {
const GameMode* gameMode = getCurrentGameMode();
if (gameMode && gameMode->getId() == id) {
return true;
}
return false;
@@ -104,7 +110,9 @@ public:
void setCurrentGameMode(const GameModeId& id);
void setGameModeToPrevious();
std::map<GameModeId, GameMode>& getRegisteredGameModes() { return mRegisteredGameModes; }
const std::map<GameModeId, GameMode>& getRegisteredGameModes() const {
return mRegisteredGameModes;
}
private:
GameModeId mCurrentGameModeId;
@@ -117,4 +125,4 @@ inline GameModeManager& getGameModeManager() {
return g_GameModeManager;
}
}; // namespace dusk::gamemode
} // namespace dusk::gamemode
-92
View File
@@ -1,92 +0,0 @@
#include "dusk/gamemode.hpp"
#include "dusk/config.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "aurora/lib/logging.hpp"
#include "m_Do/m_Do_MemCard.h"
#include "dusk/ui/prelaunch.hpp"
namespace dusk::gamemode {
GameModeManager g_GameModeManager;
aurora::Module DuskGameModeLog("dusk::gamemode");
GameModeManager::GameModeManager() {
registerGameMode(GameMode(kVanillaGameModeId,"Vanilla","gczelda2"));
mCurrentGameModeId = kVanillaGameModeId;
}
void GameModeManager::setGameModeToPrevious() {
// Gets the value from the settings of the last played gamemode id and sets that to the current gamemode (if registered)
GameModeId id = dusk::getSettings().game.lastSelectedGameModeId;
if (mRegisteredGameModes.find(id) == mRegisteredGameModes.end()) {
setCurrentGameMode(kVanillaGameModeId);
return;
}
setCurrentGameMode(id);
}
void GameModeManager::registerGameMode(const GameMode& gamemode) {
if (gamemode.getId().empty()) {
DuskGameModeLog.fatal("No gamemode id specified in GameModeManager::registerGameMode!");
}
if (gamemode.getSaveName().empty()) {
DuskGameModeLog.fatal("No save name provided for gamemode {}", gamemode.getId());
}
if (gamemode.getFullName().empty()) {
DuskGameModeLog.fatal("No Name Specified for gamemode {}", gamemode.getId());
}
if (mRegisteredGameModes.find(gamemode.getId()) != mRegisteredGameModes.end()) {
DuskGameModeLog.warn("Attempting to register gamemode {} when it is already registered!", gamemode.getId());
return;
}
mRegisteredGameModes.emplace(gamemode.getId(),gamemode);
dusk::ui::Prelaunch::rebuild_menu_buttons();
}
void GameModeManager::unregisterGameMode(const GameModeId& gamemodeId) {
const auto& it = mRegisteredGameModes.find(gamemodeId);
if (it == mRegisteredGameModes.end()) {
DuskGameModeLog.warn(
"Attempting to unregister gamemode of id {} that isn't registered!", gamemodeId);
return;
}
if (mCurrentGameModeId == gamemodeId) {
// We need to be careful if we are unregistering a running gamemode, the easiest way is just
// to reset the game back to title as vanilla;
ui::prelaunch_state().showPrelaunchOnReset = true;
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
setCurrentGameMode(kVanillaGameModeId);
}
mRegisteredGameModes.erase(it);
dusk::ui::Prelaunch::rebuild_menu_buttons();
}
void GameModeManager::setCurrentGameMode(const GameModeId& id) {
if (mCurrentGameModeId == id) {
return;
}
const GameMode* currentGameMode = getCurrentGameMode();
if (currentGameMode) {
currentGameMode->invokeOnDeactivatedFunction();
}
if (mRegisteredGameModes.find(id) == mRegisteredGameModes.end()) {
DuskGameModeLog.warn("Attempting to set current game mode to {} when it hasn't been registered!", id);
}
mCurrentGameModeId = id;
dusk::getSettings().game.lastSelectedGameModeId.setValue(id);
dusk::config::save();
currentGameMode = getCurrentGameMode();
if (currentGameMode) {
// Set the loaded save file to our gamemode's save name
mDoMemCd_SetFileName(currentGameMode->mSaveName);
currentGameMode->invokeOnActivatedFunction();
}
}
}; // namespace dusk::gamemode
+2 -2
View File
@@ -9,7 +9,6 @@
#include "imgui.h"
#include <imgui_internal.h>
#include "fmt/format.h"
#include "ImGuiConsole.hpp"
#include "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
@@ -19,14 +18,15 @@
#include "dusk/data.hpp"
#include "dusk/dusk.h"
#include "dusk/frame_interpolation.h"
#include "dusk/game_mode.hpp"
#include "dusk/livesplit.h"
#include "dusk/main.h"
#include "dusk/presentation.hpp"
#include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
#include "dusk/gamemode.hpp"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_name.h"
#include "fmt/format.h"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_main.h"
#include "tracy/Tracy.hpp"
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include <cstdint>
#include "dusk/gamemode.hpp"
#include "dusk/game_mode.hpp"
#include "dusk/speedrun.h"
namespace dusk::speedrun {
+140
View File
@@ -0,0 +1,140 @@
#include "mods/svc/game_mode.h"
#include "dusk/game_mode.hpp"
#include "config.hpp"
#include "registry.hpp"
#include "slot_map.hpp"
#include "aurora/lib/logging.hpp"
#include "dusk/mod_loader.hpp"
#include <algorithm>
#include <cctype>
#include <string>
#include <unordered_map>
#include <vector>
namespace dusk::mods::svc::game_mode_impl {
namespace {
aurora::Module Log("dusk::mods::game_mode");
// Track which gamemodes are registered by which mods, allowing us to automatically unregister them
std::unordered_map<std::string, std::vector<std::string>> s_gameModesByMod;
std::string get_mod_game_mode_id(ModContext* ctx, const std::string& id) {
// Include the mod ID to prevent clashes and normalize to lowercase
std::string fullId = id + "_" + ctx->mod->metadata.id;
std::transform(fullId.begin(), fullId.end(), fullId.begin(),
[](unsigned char c) { return std::tolower(c); });
return fullId;
}
void game_mode_remove_mod(LoadedMod& mod) {
const auto it = s_gameModesByMod.find(mod.metadata.id);
if (it != s_gameModesByMod.end()) {
for (const auto& id : it->second) {
gamemode::getGameModeManager().unregisterGameMode(id);
}
s_gameModesByMod.erase(it);
}
}
} // namespace
ModResult register_game_mode(ModContext* ctx, const GameModeDesc* desc) {
std::string id;
if (!desc->gameModeId) {
Log.error("Attempted to register a game mode with a null ID");
return MOD_ERROR;
}
id = desc->gameModeId;
if (id.empty()) {
Log.error("Attempted to register a game mode with an empty ID");
return MOD_ERROR;
}
id = get_mod_game_mode_id(ctx, id);
std::string fullName;
if (!desc->fullName) {
Log.warn("Game mode {} has no display name; using its ID", id);
fullName = id;
} else {
fullName = desc->fullName;
if (fullName.empty()) {
Log.warn("Game mode {} has an empty display name; using its ID", id);
fullName = id;
}
}
gamemode::GameMode mode{id, fullName, desc->saveName};
if (desc->onActivatedFunction) {
mode.mOnActivatedFunction = desc->onActivatedFunction;
}
if (desc->onDeactivatedFunction) {
mode.mOnDeactivatedFunction = desc->onDeactivatedFunction;
}
if (desc->onPlayFunction) {
mode.mOnPlayFunction = desc->onPlayFunction;
}
if (desc->onSaveLoadedFunction) {
mode.mOnSaveLoadedFunction = desc->onSaveLoadedFunction;
}
if (desc->onNewSaveFunction) {
mode.mOnNewSaveFunction = desc->onNewSaveFunction;
}
if (desc->onNewSaveSelectFunction) {
mode.mOnNewSaveSelectFunction = desc->onNewSaveSelectFunction;
}
if (desc->onGameResetFunction) {
mode.mOnGameResetFunction = desc->onGameResetFunction;
}
if (desc->onTickFunction) {
mode.mOnTickFunction = desc->onTickFunction;
}
gamemode::getGameModeManager().registerGameMode(mode);
s_gameModesByMod[ctx->mod->metadata.id].push_back(id);
return MOD_OK;
}
ModResult unregister_game_mode(ModContext* ctx, const char* id) {
std::string fullId = get_mod_game_mode_id(ctx, id);
gamemode::getGameModeManager().unregisterGameMode(fullId);
// Remove the game mode from the service registered game modes map
auto it = s_gameModesByMod.find(ctx->mod->metadata.id);
if (it != s_gameModesByMod.end()) {
std::erase(it->second, fullId);
}
return MOD_OK;
}
ModResult is_active(ModContext* ctx, const char* gameModeId, bool* out_active) {
*out_active =
gamemode::getGameModeManager().isCurrentGameMode(get_mod_game_mode_id(ctx, gameModeId));
return MOD_OK;
}
} // namespace dusk::mods::svc::game_mode_impl
namespace dusk::mods::svc {
namespace {
constexpr GameModeService s_gamemodeService{
.header = SERVICE_HEADER(GameModeService, GAMEMODE_SERVICE_MAJOR, GAMEMODE_SERVICE_MINOR),
.register_game_mode = game_mode_impl::register_game_mode,
.unregister_game_mode = game_mode_impl::unregister_game_mode,
.is_active = game_mode_impl::is_active,
};
} // namespace
constinit const ServiceModule g_gamemodeModule{
.id = GAMEMODE_SERVICE_ID,
.majorVersion = GAMEMODE_SERVICE_MAJOR,
.minorVersion = GAMEMODE_SERVICE_MINOR,
.service = &s_gamemodeService,
.modDeactivating = game_mode_impl::game_mode_remove_mod,
};
} // namespace dusk::mods::svc
-153
View File
@@ -1,153 +0,0 @@
#include "mods/svc/gamemode.h"
#include "dusk/gamemode.hpp"
#include "config.hpp"
#include "registry.hpp"
#include "slot_map.hpp"
#include "aurora/lib/logging.hpp"
#include "dusk/mod_loader.hpp"
#include <fmt/format.h>
namespace dusk::mods::svc::gamemode_impl {
namespace {
aurora::Module Log("dusk::mods::gamemode");
// These track which gamemodes are registered by which mods, allowing us to automatically unregister them
std::unordered_map<std::string, std::vector<std::string>> s_gamemodesRegisteredToMods;
std::string get_mod_gamemode_id(ModContext* ctx, const std::string& id) {
// Standardize the IDs to include the mod id (to prevent clashes) and set them as lowercase
std::string fullId = id + "_" + ctx->mod->metadata.id;
std::transform(fullId.begin(),fullId.end(),fullId.begin(),[](unsigned char c) {
return std::tolower(c);
});
return fullId;
}
void gamemode_remove_mod(LoadedMod& mod) {
const auto it = s_gamemodesRegisteredToMods.find(mod.metadata.id);
if (it != s_gamemodesRegisteredToMods.end()) {
for (const auto& id : it->second) {
dusk::gamemode::getGameModeManager().unregisterGameMode(id);
}
s_gamemodesRegisteredToMods.erase(it);
}
}
} // namespace
ModResult register_gamemode(ModContext* ctx, const GameModeDesc* desc) {
std::string id;
if (!desc->gamemodeId) {
Log.error("Attempted to register a gamemode with a null id!");
return MOD_ERROR;
}
id = desc->gamemodeId;
if (id.empty()) {
Log.error("Attempted to register a gamemode with an empty id!");
return MOD_ERROR;
}
id = get_mod_gamemode_id(ctx, id); // Append the mod id to the end of the gamemode id to ensure they are unique
std::string fullName;
if (!desc->fullName) {
Log.warn("Attempted to register gamemode {} with a null full name! Defaulting to: ({})",id,id);
fullName = id;
}else{
fullName = desc->fullName;
if (fullName.empty()) {
Log.warn("Attempted to register gamemode {} with an empty full name! Defaulting to: ({})",id,id);
fullName = 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";
}
dusk::gamemode::GameMode gamemode(id, fullName, saveName);
if (desc->onActivatedFunction) {
gamemode.mOnActivatedFunction = desc->onActivatedFunction;
}
if (desc->onDeactivatedFunction) {
gamemode.mOnDeactivatedFunction = desc->onDeactivatedFunction;
}
if (desc->onPlayFunction) {
gamemode.mOnPlayFunction = desc->onPlayFunction;
}
if (desc->onSaveLoadedFunction) {
gamemode.mOnSaveLoadedFunction = desc->onSaveLoadedFunction;
}
if (desc->onNewSaveFunction) {
gamemode.mOnNewSaveFunction = desc->onNewSaveFunction;
}
if (desc->onNewSaveSelectFunction) {
gamemode.mOnNewSaveSelectFunction = desc->onNewSaveSelectFunction;
}
if (desc->onGameResetFunction) {
gamemode.mOnGameResetFunction = desc->onGameResetFunction;
}
if (desc->onTickFunction) {
gamemode.mOnTickFunction = desc->onTickFunction;
}
dusk::gamemode::getGameModeManager().registerGameMode(gamemode);
const auto it = s_gamemodesRegisteredToMods.find(ctx->mod->metadata.id);
if (it == s_gamemodesRegisteredToMods.end()) {
std::vector<std::string> registeredGameModes = {id};
s_gamemodesRegisteredToMods.emplace(ctx->mod->metadata.id, registeredGameModes);
}else {
it->second.push_back(id);
}
return MOD_OK;
}
ModResult unregister_gamemode(ModContext* ctx, const char* id) {
std::string fullId = get_mod_gamemode_id(ctx,id);
dusk::gamemode::getGameModeManager().unregisterGameMode(fullId);
// Remove the gamemode from the service registered gamemodes map
auto it = s_gamemodesRegisteredToMods.find(ctx->mod->metadata.id);
if (it != s_gamemodesRegisteredToMods.end()) {
std::erase(it->second, fullId);
}
return MOD_OK;
}
ModResult is_active(ModContext* ctx, const char* gamemodeId, bool* out_active) {
*out_active = dusk::gamemode::getGameModeManager().isCurrentGameMode(get_mod_gamemode_id(ctx,gamemodeId));
return MOD_OK;
}
}
namespace dusk::mods::svc {
namespace {
constexpr GameModeService s_gamemodeService{
.header = SERVICE_HEADER(GameModeService, GAMEMODE_SERVICE_MAJOR, GAMEMODE_SERVICE_MINOR),
.register_game_mode = gamemode_impl::register_gamemode,
.unregister_game_mode = gamemode_impl::unregister_gamemode,
.is_active = gamemode_impl::is_active
};
}
constinit const ServiceModule g_gamemodeModule{
.id = GAMEMODE_SERVICE_ID,
.majorVersion = GAMEMODE_SERVICE_MAJOR,
.minorVersion = GAMEMODE_SERVICE_MINOR,
.service = &s_gamemodeService,
.modDeactivating = gamemode_impl::gamemode_remove_mod,
};
} // namespace dusk::mods::svc
+3 -4
View File
@@ -424,7 +424,6 @@ public:
}
void close() { pop(); }
void force_close() { Document::hide(true); }
private:
std::function<void()> m_onDestroyed;
@@ -929,7 +928,7 @@ void ui_sync_menu_tabs() {
}
s_menuTabsDirty = false;
if (aurora::rmlui::is_initialized()) {
ui::MenuBar::rebuild();
ui::MenuBar::refresh_tabs();
}
}
@@ -1017,14 +1016,14 @@ void ui_remove_mod(LoadedMod& mod) {
case UiSlotKind::Window: {
auto* window = static_cast<ui::ModWindow*>(slot.document);
if (window != nullptr) {
window->force_close();
window->force_hide(true);
}
break;
}
case UiSlotKind::Dialog: {
auto* dialog = static_cast<ModDialog*>(slot.document);
if (dialog != nullptr) {
dialog->force_close();
dialog->force_hide(true);
}
break;
}
+2 -2
View File
@@ -1,7 +1,7 @@
#include "dusk/settings.h"
#include "dusk/config.hpp"
#include "dusk/gamemode.hpp"
#include <aurora/aurora.h>
#include "dusk/config.hpp"
#include "dusk/game_mode.hpp"
namespace dusk {
+4 -4
View File
@@ -1,10 +1,10 @@
#include "dusk/speedrun.h"
#include "dusk/settings.h"
#include "dusk/config.hpp"
#include "m_Do/m_Do_main.h"
#include <aurora/aurora.h>
#include "dusk/gamemode.hpp"
#include "dusk/config.hpp"
#include "dusk/game_mode.hpp"
#include "dusk/livesplit.h"
#include "dusk/settings.h"
#include "m_Do/m_Do_main.h"
namespace dusk::speedrun {
+1 -2
View File
@@ -1,7 +1,6 @@
#pragma once
#include <aurora/aurora.h>
#include "dusk/gamemode.hpp"
#include "dusk/game_mode.hpp"
namespace dusk::speedrun {
+9
View File
@@ -101,6 +101,15 @@ bool Document::focus() {
return false;
}
bool Document::has_focus() const {
if (mDocument == nullptr) {
return false;
}
auto* context = mDocument->GetContext();
const auto* focused = context != nullptr ? context->GetFocusElement() : nullptr;
return focused != nullptr && focused->GetOwnerDocument() == mDocument;
}
bool Document::set_document_styles(const Rml::String& rcss) {
if (rcss.empty()) {
mDocumentStyleSheets = nullptr;
+5
View File
@@ -20,6 +20,7 @@ public:
virtual void hide(bool close);
virtual void update();
virtual bool focus();
bool has_focus() const;
virtual bool visible() const;
virtual bool active() const;
virtual bool obscures_game() const { return false; }
@@ -64,6 +65,10 @@ public:
hide(true);
uncover_top_document();
}
void force_hide(bool close) {
hide(close);
Document::hide(close);
}
bool closed() const { return mClosed; }
+35 -32
View File
@@ -7,12 +7,13 @@
#include "achievements.hpp"
#include "aurora/rmlui.hpp"
#include "dusk/game_mode.hpp"
#include "dusk/livesplit.h"
#include "dusk/gamemode.hpp"
#include "dusk/main.h"
#include "dusk/mods/svc/ui.hpp"
#include "dusk/settings.h"
#include "dusk/speedrun.h"
#include "dusk/ui/prelaunch.hpp"
#include "editor.hpp"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_name.h"
@@ -21,7 +22,6 @@
#include "mods_window.hpp"
#include "settings.hpp"
#include "ui.hpp"
#include "dusk/ui/prelaunch.hpp"
#include "warp.hpp"
#include "window.hpp"
@@ -56,6 +56,20 @@ MenuBar::MenuBar()
},
.autoSelect = false,
});
// Hide document after transition completion
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
build_tabs();
}
void MenuBar::build_tabs() {
mTabBar->add_tab("Settings", [this] { push(std::make_unique<SettingsWindow>()); });
if (getSettings().backend.enableAdvancedSettings) {
@@ -63,7 +77,7 @@ MenuBar::MenuBar()
mTabBar->add_tab("Editor", [this] { push(std::make_unique<EditorWindow>()); });
}
// Only allow us to access achievements if we are playing on a gamemode that uses them
// Only allow us to access achievements if we are playing on a game mode that uses them
if (dusk::gamemode::getGameModeManager().isCurrentGameMode(dusk::gamemode::kVanillaGameModeId)
|| dusk::gamemode::getGameModeManager().isCurrentGameMode(dusk::speedrun::kSpeedrunGameModeId)) {
mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); });
@@ -101,12 +115,10 @@ MenuBar::MenuBar()
}
dismiss(modal);
if (gamemode::getGameModeManager().getRegisteredGameModes().size() > 1) {
// If we have gamemodes registered, show pre-launch on a menubar reset
prelaunch_state().showPrelaunchOnReset = true;
Document::hide(true);
}else {
hide(false);
// If game modes are registered, return to prelaunch on reset.
prelaunch_state().returnToPrelaunchOnReset = true;
}
hide(false);
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
},
},
@@ -157,28 +169,19 @@ MenuBar::MenuBar()
hide(false);
});
}
// Hide document after transition completion
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
}
void MenuBar::show() {
Document::show();
mRoot->SetAttribute("open", "");
mTabBar->set_active_tab(-1);
if (!mTabBar->focus_tab(mFocusedTabIndex)) {
if (!mTabBar->focus_tab(mFocusedTabTitle)) {
mTabBar->focus();
}
}
void MenuBar::hide(bool close) {
mFocusedTabIndex = mTabBar->focused_tab_index();
mFocusedTabTitle = mTabBar->focused_tab_title();
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
@@ -248,19 +251,19 @@ bool MenuBar::focus() {
return mTabBar->focus();
}
void MenuBar::rebuild() {
for (auto& doc : get_document_stack()) {
if (auto* menuBar = dynamic_cast<MenuBar*>(doc.get())) {
const bool wasVisible = menuBar->visible();
auto next = std::make_unique<MenuBar>();
next->mFocusedTabIndex = menuBar->mFocusedTabIndex;
next->mWasVisible = menuBar->mWasVisible;
doc = std::move(next);
if (wasVisible) {
doc->show();
}
break;
}
void MenuBar::refresh_tabs() {
auto* menuBar = static_cast<MenuBar*>(find_document(DocumentScope::MenuBar));
if (menuBar == nullptr) {
return;
}
const auto focusedTitle = menuBar->mTabBar->focused_tab_title();
if (!focusedTitle.empty()) {
menuBar->mFocusedTabTitle = focusedTitle;
}
menuBar->mTabBar->clear_tabs();
menuBar->build_tabs();
if (menuBar->visible() && !menuBar->mTabBar->focus_tab(menuBar->mFocusedTabTitle)) {
menuBar->mTabBar->focus();
}
}
+3 -2
View File
@@ -21,12 +21,13 @@ public:
bool focus() override;
bool visible() const override;
static void rebuild();
static void refresh_tabs();
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
void build_tabs();
void update_safe_area() noexcept;
Rml::Element* mRoot;
@@ -34,7 +35,7 @@ private:
std::unique_ptr<Button> mCloseButton;
Insets mTabBarPadding;
float mTopMargin = 0.f;
int mFocusedTabIndex = -1;
Rml::String mFocusedTabTitle;
};
} // namespace dusk::ui
-1
View File
@@ -59,7 +59,6 @@ public:
~ModWindow() override;
void update() override;
void force_close() { Document::hide(true); }
private:
Desc mDesc;
+48 -55
View File
@@ -3,7 +3,7 @@
#include "dusk/app_info.hpp"
#include "dusk/config.hpp"
#include "dusk/data.hpp"
#include "dusk/gamemode.hpp"
#include "dusk/game_mode.hpp"
#include "dusk/iso_validate.hpp"
#include "dusk/language.hpp"
#include "dusk/main.h"
@@ -745,18 +745,17 @@ void try_apply_mirrored_layout(Rml::Element* body) {
body->SetClass("mirrored", getSettings().game.enableMirrorMode.getValue());
}
void Prelaunch::rebuild_menu_buttons() {
for (auto& doc : get_document_stack()) {
if (auto* prelaunch = dynamic_cast<Prelaunch*>(doc.get())) {
auto* menuList = prelaunch->mDocument->GetElementById("menu-list");
while (menuList->GetNumChildren() > 0) {
menuList->RemoveChild(menuList->GetChild(0));
}
prelaunch->mMenuButtons.clear();
prelaunch->build_menu_buttons();
break;
}
void Prelaunch::refresh_menu_buttons() {
auto* prelaunch = static_cast<Prelaunch*>(find_document(DocumentScope::Prelaunch));
if (prelaunch == nullptr) {
return;
}
auto* menuList = prelaunch->mDocument->GetElementById("menu-list");
while (menuList->GetNumChildren() > 0) {
menuList->RemoveChild(menuList->GetChild(0));
}
prelaunch->mMenuButtons.clear();
prelaunch->build_menu_buttons();
}
static std::string get_playbutton_text() {
@@ -766,10 +765,10 @@ static std::string get_playbutton_text() {
return "Select Disc Image";
}
std::string playText;
const dusk::gamemode::GameMode* currentGameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
const gamemode::GameMode* currentGameMode =
gamemode::getGameModeManager().getCurrentGameMode();
if (currentGameMode != nullptr) {
return currentGameMode->getId() == dusk::gamemode::kVanillaGameModeId ? "Play" :
return currentGameMode->getId() == gamemode::kVanillaGameModeId ? "Play" :
"Play " + currentGameMode->getFullName();
}
return "Play";
@@ -821,7 +820,7 @@ Prelaunch::Prelaunch() : Document(kDocumentSource, false, DocumentScope::Prelaun
void Prelaunch::build_menu_buttons() {
if (auto* menuList = mDocument->GetElementById("menu-list")) {
// Set the gamemode to the last used before showing the play button
dusk::gamemode::getGameModeManager().setGameModeToPrevious();
gamemode::getGameModeManager().setGameModeToPrevious();
mMenuButtons.push_back(std::make_unique<Button>(menuList, get_playbutton_text()));
mMenuButtons.back()->on_pressed([this] {
@@ -846,66 +845,52 @@ void Prelaunch::build_menu_buttons() {
}
prelaunch_state().firstLaunch = false;
const dusk::gamemode::GameMode* gamemode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
gamemode->invokeOnPlayFunction();
const gamemode::GameMode* gameMode =
gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
gameMode->invokeOnPlayFunction();
}
IsGameLaunched = true;
pop();
// If we deleted the menubar on a previous reset, create it again here
bool menuBarExists = false;
for (auto& doc : dusk::ui::get_document_stack()) {
if (auto* menubar = dynamic_cast<dusk::ui::MenuBar*>(doc.get())) {
menuBarExists = true;
break;
}
}
if (menuBarExists) {
MenuBar::rebuild();
}else{
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
}
hide(true);
MenuBar::refresh_tabs();
});
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
// If we have more gamemodes registered than the default vanilla, show the gamemode
// selection
if (dusk::gamemode::getGameModeManager().getRegisteredGameModes().size() > 1) {
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Select GameMode"));
// Only show selection when game modes are registered (besides vanilla)
if (gamemode::getGameModeManager().getRegisteredGameModes().size() > 1) {
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Select Game Mode"));
mMenuButtons.back()->on_pressed([this] {
std::vector<ModalAction> gamemodeActions;
gamemodeActions.push_back(dusk::ui::ModalAction{
.label = "Vanilla", .onPressed = [this](dusk::ui::Modal& modal) {
std::vector<ModalAction> gameModeActions;
gameModeActions.push_back(ModalAction{
.label = "Vanilla", .onPressed = [this](Modal& modal) {
mDoAud_seStartMenu(kSoundClick);
dusk::gamemode::getGameModeManager().setCurrentGameMode(dusk::gamemode::kVanillaGameModeId);
gamemode::getGameModeManager().setCurrentGameMode(gamemode::kVanillaGameModeId);
modal.pop();
update();
}});
for (const auto& [id, gamemode] :
dusk::gamemode::getGameModeManager().getRegisteredGameModes())
for (const auto& [id, gameMode] :
gamemode::getGameModeManager().getRegisteredGameModes())
{
if (id == dusk::gamemode::kVanillaGameModeId) {
if (id == gamemode::kVanillaGameModeId) {
// Force vanilla to the top
continue;
}
gamemodeActions.push_back(dusk::ui::ModalAction{.label = gamemode.getFullName(),
.onPressed = [this, id](dusk::ui::Modal& modal) {
gameModeActions.push_back(ModalAction{.label = gameMode.getFullName(),
.onPressed = [this, id](Modal& modal) {
mDoAud_seStartMenu(kSoundClick);
dusk::gamemode::getGameModeManager().setCurrentGameMode(id);
gamemode::getGameModeManager().setCurrentGameMode(id);
modal.pop();
update();
}});
}
mRestartSuppressed = false;
push(std::make_unique<dusk::ui::Modal>(dusk::ui::Modal::Props{
push(std::make_unique<Modal>(Modal::Props{
.title = "Play Type",
.bodyRml = "What mode would you like to play?",
.actions = gamemodeActions,
.actions = gameModeActions,
.onDismiss =
[this](dusk::ui::Modal& modal) {
[this](Modal& modal) {
mDoAud_seStartMenu(kSoundWindowClose);
modal.pop();
},
@@ -948,14 +933,14 @@ void Prelaunch::show() {
modal.pop();
};
std::vector<ModalAction> actions;
if constexpr (dusk::SupportsProcessRestart) {
if constexpr (SupportsProcessRestart) {
actions.push_back(ModalAction{
.label = "Restart later",
.onPressed = dismiss,
});
actions.push_back(ModalAction{
.label = "Restart now",
.onPressed = [](Modal&) { dusk::RequestRestart(); },
.onPressed = [](Modal&) { RequestRestart(); },
});
} else {
actions.push_back(ModalAction{
@@ -966,7 +951,7 @@ void Prelaunch::show() {
push(std::make_unique<Modal>(Modal::Props{
.title = "Apply Options",
.bodyRml =
dusk::SupportsProcessRestart ?
SupportsProcessRestart ?
"A restart is required to apply selected options.<br/><br/>Restart now to "
"apply them immediately?" :
"A restart is required to apply selected options.<br/><br/>Close and reopen "
@@ -1126,6 +1111,14 @@ void Prelaunch::update() {
Document::update();
}
void return_to_prelaunch() noexcept {
close_documents_except(DocumentScope::MenuBar);
if (auto* menuBar = find_document(DocumentScope::MenuBar)) {
menuBar->force_hide(false);
}
push_document(std::make_unique<Prelaunch>(), true);
}
bool Prelaunch::focus() {
if (mMenuButtons.empty()) {
return false;
+3 -2
View File
@@ -23,7 +23,7 @@ public:
bool visible() const override;
bool obscures_game() const override { return true; }
static void rebuild_menu_buttons();
static void refresh_menu_buttons();
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
@@ -62,10 +62,11 @@ struct PrelaunchState {
std::string pendingDiscPath;
iso::DiscInfo pendingDiscInfo{};
iso::ValidationError pendingDiscValidation = iso::ValidationError::Unknown;
bool showPrelaunchOnReset = false;
bool returnToPrelaunchOnReset = false;
};
PrelaunchState& prelaunch_state() noexcept;
void return_to_prelaunch() noexcept;
void ensure_initialized() noexcept;
void refresh_configured_disc_state() noexcept;
void open_iso_picker() noexcept;
+14 -14
View File
@@ -1246,7 +1246,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
}
dusk::speedrun::unregisterSpeedrunGameMode();
}
MenuBar::rebuild();
MenuBar::refresh_tabs();
},
});
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
@@ -1363,16 +1363,15 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
"replacements, and other app data.");
});
#endif
leftPane.register_control(
leftPane.add_button("Restart To Main Menu").on_pressed([this] {
mDoAud_seStartMenu(kSoundClick);
pop();
ui::prelaunch_state().showPrelaunchOnReset = true;
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
}),
leftPane.register_control(leftPane.add_button("Restart to Main Menu").on_pressed([this] {
mDoAud_seStartMenu(kSoundClick);
pop();
ui::prelaunch_state().returnToPrelaunchOnReset = true;
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
}),
rightPane, [](Pane& pane) {
pane.add_text(
"Restart Dusklight to the pre-launch menu to change settings, gamemodes, or mods.");
pane.add_text("Restart Dusklight to the pre-launch menu to change settings, game "
"modes, or mods.");
});
leftPane.register_control(
leftPane.add_select_button({
@@ -1462,9 +1461,10 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
config_bool_select(leftPane, rightPane, getSettings().backend.skipPreLaunchUI,
{
.key = "Skip Dusklight Main Menu",
.helpText = "When starting Dusklight, skip the main menu and boot straight into the "
"game if a disc image is available.<br/><br/>Note: If any mods register gamemodes, "
"then this option will be ignored.",
.helpText =
"When starting Dusklight, skip the main menu and boot straight into the "
"game if a disc image is available.<br/><br/>Note: If any mods register game "
"modes, this option will be ignored.",
});
config_bool_select(leftPane, rightPane, getSettings().backend.checkForUpdates,
{
@@ -1493,7 +1493,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.helpText = "Show advanced settings and debugging tools with "
"Shift+F1.<br/><br/><icon class=\"warning\"/> WARNING: Debugging tools "
"can easily break your game. Do not use on a regular save!",
.onChange = [](bool) { MenuBar::rebuild(); },
.onChange = [](bool) { MenuBar::refresh_tabs(); },
.isDisabled = [] { return dusk::speedrun::isActive(); },
});
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewer,
+35 -13
View File
@@ -94,14 +94,14 @@ TabBar::TabBar(Rml::Element* parent, Props props)
bool TabBar::focus() {
if (mProps.selectedTabIndex >= 0 && mProps.selectedTabIndex < mTabs.size()) {
// Try to focus the currently selected tab
if (mTabs[mProps.selectedTabIndex].button.focus()) {
if (mTabs[mProps.selectedTabIndex].button->focus()) {
mLastFocusedTabIndex = mProps.selectedTabIndex;
return true;
}
}
// Otherwise, focus the first enabled tab
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
if (mTabs[i].button.focus()) {
if (mTabs[i].button->focus()) {
mLastFocusedTabIndex = i;
return true;
}
@@ -115,8 +115,8 @@ void TabBar::add_tab(const Rml::String& title, TabCallback callback) {
if (selected && callback) {
callback();
}
auto& button = add_child<Button>(Button::Props{title}, "tab");
button.on_nav_command([this, index](Rml::Event&, NavCommand cmd) {
auto button = std::make_unique<Button>(mRoot, Button::Props{title}, "tab");
button->on_nav_command([this, index](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (mProps.autoSelect) {
mDoAud_seStartMenu(kSoundTabChanged);
@@ -127,7 +127,7 @@ void TabBar::add_tab(const Rml::String& title, TabCallback callback) {
return false;
});
if (selected) {
button.set_selected(true);
button->set_selected(true);
}
if (mEndSpacer != nullptr) {
auto spacer = mRoot->RemoveChild(mEndSpacer);
@@ -135,16 +135,26 @@ void TabBar::add_tab(const Rml::String& title, TabCallback callback) {
}
mTabs.emplace_back(Tab{
.title = title,
.button = button,
.button = std::move(button),
.callback = std::move(callback),
});
}
void TabBar::clear_tabs() {
mProps.selectedTabIndex = -1;
mLastFocusedTabIndex = -1;
while (!mTabs.empty()) {
auto* element = mTabs.back().button->root();
mTabs.pop_back();
mRoot->RemoveChild(element);
}
}
bool TabBar::set_active_tab(int index) {
if (index == -1) {
// Clear currently selected tab
for (auto& tab : mTabs) {
tab.button.set_selected(false);
tab.button->set_selected(false);
}
mProps.selectedTabIndex = -1;
return true;
@@ -154,10 +164,10 @@ bool TabBar::set_active_tab(int index) {
return false;
}
const auto& tab = mTabs[index];
if (tab.button.focus()) {
if (tab.button->focus()) {
mLastFocusedTabIndex = index;
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
mTabs[i].button.set_selected(i == index);
mTabs[i].button->set_selected(i == index);
}
mProps.selectedTabIndex = index;
if (tab.callback) {
@@ -177,24 +187,36 @@ void TabBar::refresh_active_tab() {
}
}
int TabBar::focused_tab_index() const {
return mLastFocusedTabIndex;
Rml::String TabBar::focused_tab_title() const {
if (mLastFocusedTabIndex < 0 || mLastFocusedTabIndex >= static_cast<int>(mTabs.size())) {
return {};
}
return mTabs[mLastFocusedTabIndex].title;
}
bool TabBar::focus_tab(int index) {
if (index < 0 || index >= mTabs.size() || index == mProps.selectedTabIndex) {
return false;
}
if (mTabs[index].button.focus()) {
if (mTabs[index].button->focus()) {
mLastFocusedTabIndex = index;
return true;
}
return false;
}
bool TabBar::focus_tab(const Rml::String& title) {
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
if (mTabs[i].title == title) {
return focus_tab(i);
}
}
return false;
}
int TabBar::tab_containing(Rml::Element* element) const {
for (int i = 0; i < mTabs.size(); ++i) {
if (mTabs[i].button.contains(element)) {
if (mTabs[i].button->contains(element)) {
return i;
}
}
+4 -2
View File
@@ -10,7 +10,7 @@ using TabCallback = std::function<void()>;
struct Tab {
Rml::String title;
Button& button;
std::unique_ptr<Button> button;
TabCallback callback;
};
@@ -27,10 +27,12 @@ public:
bool focus() override;
void add_tab(const Rml::String& title, TabCallback callback);
void clear_tabs();
bool set_active_tab(int index);
void refresh_active_tab();
bool focus_tab(int index);
int focused_tab_index() const;
bool focus_tab(const Rml::String& title);
Rml::String focused_tab_title() const;
bool handle_nav_command(Rml::Event& event, NavCommand cmd);
private:
+21 -10
View File
@@ -267,6 +267,24 @@ void uncover_top_document() noexcept {
input::sync_input_block();
}
Document* find_document(DocumentScope scope) noexcept {
for (auto& doc : std::views::reverse(sDocumentStack)) {
if (!doc->closed() && doc->scope() == scope) {
return doc.get();
}
}
return nullptr;
}
void close_documents_except(DocumentScope scope) noexcept {
for (auto& doc : sDocumentStack) {
if (!doc->closed() && doc->scope() != scope) {
doc->force_hide(true);
}
}
input::sync_input_block();
}
bool any_document_visible() noexcept {
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(),
[](const auto& doc) { return doc && doc->visible(); });
@@ -330,13 +348,10 @@ void update() noexcept {
sPassiveDocuments.erase(first, last);
}
// If no documents have focus, explicitly focus the top one
if (auto* context = aurora::rmlui::get_context();
context != nullptr && (context->GetFocusElement() == nullptr ||
context->GetFocusElement() == context->GetRootElement()))
{
// Keep focus on the highest active document.
if (aurora::rmlui::get_context() != nullptr) {
for (auto& doc : std::views::reverse(sDocumentStack)) {
if (doc->active() && doc->focus()) {
if (doc->active() && (doc->has_focus() || doc->focus())) {
break;
}
}
@@ -462,10 +477,6 @@ void push_toast(Toast toast) noexcept {
sToasts.push_back(std::move(toast));
}
std::vector<std::unique_ptr<Document>>& get_document_stack() noexcept {
return sDocumentStack;
}
std::deque<Toast>& get_toasts() noexcept {
return sToasts;
}
+2 -2
View File
@@ -91,6 +91,8 @@ bool register_scoped_styles(DocumentScope scope, std::string id, const std::stri
void unregister_scoped_styles(DocumentScope scope, std::string_view id) noexcept;
void apply_scoped_styles(Document& doc) noexcept;
void uncover_top_document() noexcept;
Document* find_document(DocumentScope scope) noexcept;
void close_documents_except(DocumentScope scope) noexcept;
bool any_document_visible() noexcept;
bool is_prelaunch_open() noexcept;
bool game_obscured_below(const Document& doc) noexcept;
@@ -104,8 +106,6 @@ Rml::Element* append_text(Rml::Element* parent, const Rml::String& text) noexcep
NavCommand map_nav_event(const Rml::Event& event) noexcept;
Insets safe_area_insets(Rml::Context* context) noexcept;
std::vector<std::unique_ptr<Document>>& get_document_stack() noexcept;
void push_toast(Toast toast) noexcept;
std::deque<Toast>& get_toasts() noexcept;
void show_menu_notification() noexcept;
+7 -6
View File
@@ -27,11 +27,11 @@
#include "m_Do/m_Do_main.h"
#if TARGET_PC
#include "tracy/Tracy.hpp"
#include <dusk/gamepad_color.h>
#include <dusk/autosave.h>
#include <dusk/gamepad_color.h>
#include "dusk/game_mode.hpp"
#include "dusk/menu_pointer.h"
#include "dusk/gamemode.hpp"
#include "tracy/Tracy.hpp"
#endif
fapGm_HIO_c::fapGm_HIO_c() {
@@ -846,9 +846,10 @@ void fapGm_Execute() {
cCt_Counter(0);
#ifdef TARGET_PC
const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
gamemode->invokeOnTickFunction();
const dusk::gamemode::GameMode* gameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
gameMode->invokeOnTickFunction();
}
dusk::AchievementSystem::get().tick();
dusk::menu_pointer::end_game_frame();
+1 -1
View File
@@ -8,8 +8,8 @@
#include "f_pc/f_pc_manager.h"
#ifdef TARGET_PC
#include "dusk/game_mode.hpp"
#include "dusk/speedrun.h"
#include "dusk/gamemode.hpp"
#endif
void fopOvlpReq_SetPeektime(overlap_request_class*, u16);
+8 -30
View File
@@ -20,10 +20,8 @@
#include "os_report.h"
#ifdef TARGET_PC
#include "dusk/game_mode.hpp"
#include "dusk/ui/prelaunch.hpp"
#include "dusk/ui/menu_bar.hpp"
#include "dusk/ui/mods_window.hpp"
#include "dusk/gamemode.hpp"
#endif
static void my_OSCancelAlarmAll() {}
@@ -112,9 +110,10 @@ void checkDiskCallback(s32 result, DVDCommandBlock* block) {
void mDoRst_resetCallBack(int port, void*) {
#ifdef TARGET_PC
const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gamemode) {
gamemode->invokeOnGameResetFunction();
const dusk::gamemode::GameMode* gameMode =
dusk::gamemode::getGameModeManager().getCurrentGameMode();
if (gameMode) {
gameMode->invokeOnGameResetFunction();
}
#endif
@@ -157,31 +156,10 @@ void mDoRst_resetCallBack(int port, void*) {
}
mDoRst::onReset();
#ifdef TARGET_PC
// Show pre-launch only if we have a registered gamemode and are resetting from the menubar
if (dusk::ui::prelaunch_state().showPrelaunchOnReset == false) {
return;
if (dusk::ui::prelaunch_state().returnToPrelaunchOnReset) {
dusk::ui::return_to_prelaunch();
dusk::ui::prelaunch_state().returnToPrelaunchOnReset = false;
}
bool prelaunchExists = false;
for (auto& doc : dusk::ui::get_document_stack()) {
if (auto* menubar = dynamic_cast<dusk::ui::MenuBar*>(doc.get())) {
// Hide the menu bar
menubar->Document::hide(true);
}
if (auto* modwindow = dynamic_cast<dusk::ui::ModsWindow*>(doc.get())) {
// Hide the mod window (if we were disasbling or reloading a mod)
modwindow->pop();
}
if (auto* prelaunch = dynamic_cast<dusk::ui::Prelaunch*>(doc.get())) {
prelaunchExists = true;
prelaunch->focus();
}
}
if (prelaunchExists == false) {
dusk::ui::Prelaunch& prelaunch = static_cast<dusk::ui::Prelaunch&>(dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true));
prelaunch.focus();
}
dusk::ui::prelaunch_state().showPrelaunchOnReset = false;
#endif
}