mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-20 05:16:24 -04:00
Use carousel button on prelaunch; more cleanup
This commit is contained in:
+45
-45
@@ -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);
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "JSystem/J3DGraphLoader/J3DModelLoader.h"
|
||||
#include "JSystem/J3DGraphLoader/J3DAnmLoader.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "mods/svc/game_mode.h"
|
||||
#endif
|
||||
|
||||
class dFile_info_c;
|
||||
class J2DPicture;
|
||||
|
||||
@@ -741,8 +745,7 @@ public:
|
||||
#ifdef TARGET_PC
|
||||
dDlst_FileSelFade_c mFadeDlst;
|
||||
bool mGameModeSaveStartBuildUi = true;
|
||||
bool mGameModeProceedToNameSelect = false;
|
||||
bool mGameModeReturnToFileSelect = false;
|
||||
GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
|
||||
+68
-30
@@ -143,6 +143,74 @@ eyebrow span {
|
||||
transition: decorator color opacity 0.1s linear-in-out;
|
||||
}
|
||||
|
||||
#menu-list button.game-mode-button {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#menu-list game-mode-label-viewport {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 8dp;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#menu-list game-mode-label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 8dp 16dp;
|
||||
opacity: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
transition: opacity 0.24s cubic-in-out;
|
||||
}
|
||||
|
||||
#menu-list game-mode-label.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#menu-list game-mode-previous,
|
||||
#menu-list game-mode-next {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 48dp;
|
||||
height: 54dp;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
font-family: "Material Symbols Rounded";
|
||||
font-weight: normal;
|
||||
font-size: 30dp;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#menu-list game-mode-previous {
|
||||
left: -48dp;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
#menu-list game-mode-next {
|
||||
right: -48dp;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
#menu-list button.game-mode-button.can-cycle game-mode-previous,
|
||||
#menu-list button.game-mode-button.can-cycle game-mode-next {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#menu-list button:hover,
|
||||
#menu-list button:focus-visible {
|
||||
color: black;
|
||||
@@ -366,34 +434,6 @@ body.animate-in .intro-item {
|
||||
transition: opacity transform 0.3s 0.7s cubic-in-out;
|
||||
}
|
||||
|
||||
/* Locks the menu element to not go below the disc status element */
|
||||
@media (max-height: 900dp) {
|
||||
menu {
|
||||
top: auto;
|
||||
bottom: 150dp;
|
||||
transform: none;
|
||||
gap: 24dp;
|
||||
}
|
||||
|
||||
hero {
|
||||
gap: 2dp;
|
||||
max-height: 220dp;
|
||||
}
|
||||
|
||||
hero img {
|
||||
width: 80%;
|
||||
max-height: 180dp;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 750dp) {
|
||||
#menu-list button {
|
||||
font-size: 26dp;
|
||||
height: 45dp;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile layout */
|
||||
@media (max-height: 640dp) {
|
||||
.gradient {
|
||||
@@ -447,8 +487,6 @@ body.animate-in .intro-item {
|
||||
#menu-list button {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
font-size: 23dp;
|
||||
height: 40dp;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,40 +3,48 @@
|
||||
#include <mods/api.h>
|
||||
#include <mods/svc/config.h>
|
||||
|
||||
#define GAMEMODE_SERVICE_ID "dev.twilitrealm.dusklight.gamemode"
|
||||
#define GAMEMODE_SERVICE_MAJOR 1u
|
||||
#define GAMEMODE_SERVICE_MINOR 0u
|
||||
#define GAME_MODE_SERVICE_ID "dev.twilitrealm.dusklight.gamemode"
|
||||
#define GAME_MODE_SERVICE_MAJOR 1u
|
||||
#define GAME_MODE_SERVICE_MINOR 0u
|
||||
|
||||
/* MOD_OK on success; failures disable the mod. */
|
||||
typedef ModResult (*GameModeCallback)(void* user_data, ModError* out_error);
|
||||
|
||||
typedef enum GameModeNewSaveState {
|
||||
GAME_MODE_STATE_PENDING = 0,
|
||||
GAME_MODE_STATE_PROCEED,
|
||||
GAME_MODE_STATE_RETURN,
|
||||
} GameModeNewSaveState;
|
||||
|
||||
/* Set state to PROCEED after custom UI completes, or RETURN to cancel.
|
||||
* State pointer is valid until selection completes. */
|
||||
typedef ModResult (*GameModeNewSaveSelectCallback)(
|
||||
void* user_data, GameModeNewSaveState* state, ModError* out_error);
|
||||
|
||||
typedef struct {
|
||||
const char* gameModeId;
|
||||
const char* fullName;
|
||||
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 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
|
||||
// completed
|
||||
void (*onGameResetFunction)(); // Called when the game is reset
|
||||
void (*onTickFunction)(); // Called on every tick
|
||||
uint32_t struct_size;
|
||||
const char* game_mode_id;
|
||||
const char* full_name;
|
||||
const char save_name[32]; // Empty uses default (gczelda2); max 31 chars
|
||||
void* user_data; // Pointer will be passed to all callbacks
|
||||
GameModeCallback on_activated; // Called when the game mode is selected
|
||||
GameModeCallback on_deactivated; // Called when the game mode is deselected
|
||||
GameModeCallback on_play; // Called when play is pressed
|
||||
GameModeCallback on_save_loaded; // Called whenever a save file is loaded
|
||||
GameModeCallback on_new_save; // Called when a new save is created
|
||||
GameModeNewSaveSelectCallback on_new_save_select;
|
||||
GameModeCallback on_game_reset; // Called when the game is reset
|
||||
GameModeCallback on_tick; // Called on every game tick while active
|
||||
} GameModeDesc;
|
||||
|
||||
#define GAME_MODE_DESC_INIT {sizeof(GameModeDesc)}
|
||||
|
||||
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* game_mode_id, bool* out_active);
|
||||
} GameModeService;
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include "mods/service.hpp"
|
||||
|
||||
template <>
|
||||
struct mods::ServiceTraits<GameModeService> {
|
||||
static constexpr const char* id = GAMEMODE_SERVICE_ID;
|
||||
static constexpr uint16_t major_version = GAMEMODE_SERVICE_MAJOR;
|
||||
static constexpr uint16_t minor_version = GAMEMODE_SERVICE_MINOR;
|
||||
};
|
||||
#endif
|
||||
MOD_DECLARE_SERVICE(GameModeService, svc_game_mode, GAME_MODE_SERVICE_ID, GAME_MODE_SERVICE_MAJOR,
|
||||
GAME_MODE_SERVICE_MINOR);
|
||||
|
||||
@@ -1320,21 +1320,19 @@ void dFile_select_c::selectDataNameMove() {
|
||||
if (gameMode) {
|
||||
if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) {
|
||||
if (mGameModeSaveStartBuildUi) {
|
||||
gameMode->invokeOnNewSaveSelectFunction(
|
||||
&mGameModeProceedToNameSelect, &mGameModeReturnToFileSelect);
|
||||
gameMode->invokeOnNewSaveSelectFunction(&mGameModeNewSaveState);
|
||||
mGameModeSaveStartBuildUi = false;
|
||||
}
|
||||
if (mGameModeReturnToFileSelect) {
|
||||
if (mGameModeNewSaveState == GAME_MODE_STATE_RETURN) {
|
||||
backToDataSelectMove();
|
||||
mGameModeSaveStartBuildUi = true;
|
||||
mGameModeProceedToNameSelect = false;
|
||||
mGameModeReturnToFileSelect = false;
|
||||
mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
return;
|
||||
}
|
||||
if (!mGameModeProceedToNameSelect) {
|
||||
if (mGameModeNewSaveState != GAME_MODE_STATE_PROCEED) {
|
||||
return;
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1347,8 +1345,7 @@ void dFile_select_c::selectDataNameMove() {
|
||||
{
|
||||
#ifdef TARGET_PC
|
||||
mGameModeSaveStartBuildUi = true;
|
||||
mGameModeProceedToNameSelect = false;
|
||||
mGameModeReturnToFileSelect = false;
|
||||
mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_NAME_INPUT_WAIT;
|
||||
}
|
||||
|
||||
+15
-6
@@ -62,27 +62,36 @@ void GameModeManager::unregisterGameMode(const GameModeId& gameModeId) {
|
||||
ui::Prelaunch::refresh_menu_buttons();
|
||||
}
|
||||
|
||||
void GameModeManager::setCurrentGameMode(const GameModeId& id) {
|
||||
bool GameModeManager::setCurrentGameMode(const GameModeId& id) {
|
||||
if (mCurrentGameModeId == id) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
if (!mRegisteredGameModes.contains(id)) {
|
||||
Log.warn("Attempting to configure unknown game mode {}", id);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
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();
|
||||
if (!currentGameMode->invokeOnActivatedFunction()) {
|
||||
mCurrentGameModeId = kVanillaGameModeId;
|
||||
currentGameMode = getCurrentGameMode();
|
||||
mDoMemCd_SetFileName(currentGameMode->getSaveName());
|
||||
currentGameMode->invokeOnActivatedFunction();
|
||||
getSettings().game.lastSelectedGameModeId.setValue(kVanillaGameModeId);
|
||||
config::save();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
getSettings().game.lastSelectedGameModeId.setValue(id);
|
||||
config::save();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dusk::gamemode
|
||||
|
||||
+43
-29
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "d/d_file_select.h"
|
||||
#include "mods/svc/game_mode.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
@@ -16,6 +17,9 @@ constexpr const char* kDefaultGameModeSaveName = "gczelda2";
|
||||
// Holds a game mode definition and its lifecycle callbacks.
|
||||
class GameMode {
|
||||
public:
|
||||
using Callback = std::function<bool()>;
|
||||
using NewSaveSelectCallback = std::function<bool(GameModeNewSaveState* state)>;
|
||||
|
||||
GameMode(GameModeId id, std::string fullName, std::string saveName = {})
|
||||
: mId{std::move(id)}, mFullName{std::move(fullName)},
|
||||
mSaveName{saveName.empty() ? kDefaultGameModeSaveName : std::move(saveName)} {}
|
||||
@@ -27,66 +31,76 @@ public:
|
||||
std::string mFullName;
|
||||
std::string mSaveName;
|
||||
|
||||
void invokeOnActivatedFunction() const {
|
||||
bool invokeOnActivatedFunction() const {
|
||||
if (mOnActivatedFunction) {
|
||||
mOnActivatedFunction();
|
||||
return mOnActivatedFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnDeactivatedFunction() const {
|
||||
bool invokeOnDeactivatedFunction() const {
|
||||
if (mOnDeactivatedFunction) {
|
||||
mOnDeactivatedFunction();
|
||||
return mOnDeactivatedFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnPlayFunction() const {
|
||||
bool invokeOnPlayFunction() const {
|
||||
if (mOnPlayFunction) {
|
||||
mOnPlayFunction();
|
||||
return mOnPlayFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnSaveLoadedFunction() const {
|
||||
bool invokeOnSaveLoadedFunction() const {
|
||||
if (mOnSaveLoadedFunction) {
|
||||
mOnSaveLoadedFunction();
|
||||
return mOnSaveLoadedFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnNewSaveFunction() const {
|
||||
bool invokeOnNewSaveFunction() const {
|
||||
if (mOnNewSaveFunction) {
|
||||
mOnNewSaveFunction();
|
||||
return mOnNewSaveFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnNewSaveSelectFunction(
|
||||
bool* out_proceedToNameSelect, bool* out_returnToFileSelect) const {
|
||||
bool invokeOnNewSaveSelectFunction(GameModeNewSaveState* state) const {
|
||||
*state = GAME_MODE_STATE_PENDING;
|
||||
if (mOnNewSaveSelectFunction) {
|
||||
mOnNewSaveSelectFunction(out_proceedToNameSelect, out_returnToFileSelect);
|
||||
} else {
|
||||
*out_proceedToNameSelect = true;
|
||||
if (mOnNewSaveSelectFunction(state)) {
|
||||
return true;
|
||||
}
|
||||
*state = GAME_MODE_STATE_RETURN;
|
||||
return false;
|
||||
}
|
||||
*state = GAME_MODE_STATE_PROCEED;
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnGameResetFunction() const {
|
||||
bool invokeOnGameResetFunction() const {
|
||||
if (mOnGameResetFunction) {
|
||||
mOnGameResetFunction();
|
||||
return mOnGameResetFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void invokeOnTickFunction() const {
|
||||
bool invokeOnTickFunction() const {
|
||||
if (mOnTickFunction) {
|
||||
mOnTickFunction();
|
||||
return mOnTickFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::function<void()> mOnActivatedFunction;
|
||||
std::function<void()> mOnDeactivatedFunction;
|
||||
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()> mOnGameResetFunction;
|
||||
std::function<void()> mOnTickFunction;
|
||||
Callback mOnActivatedFunction;
|
||||
Callback mOnDeactivatedFunction;
|
||||
Callback mOnPlayFunction;
|
||||
Callback mOnSaveLoadedFunction;
|
||||
Callback mOnNewSaveFunction;
|
||||
NewSaveSelectCallback mOnNewSaveSelectFunction;
|
||||
Callback mOnGameResetFunction;
|
||||
Callback mOnTickFunction;
|
||||
};
|
||||
|
||||
class GameModeManager {
|
||||
@@ -107,7 +121,7 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void setCurrentGameMode(const GameModeId& id);
|
||||
bool setCurrentGameMode(const GameModeId& id);
|
||||
void setGameModeToPrevious();
|
||||
|
||||
const std::map<GameModeId, GameMode>& getRegisteredGameModes() const {
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -19,9 +22,52 @@ namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::game_mode");
|
||||
|
||||
// Track which gamemodes are registered by which mods, allowing us to automatically unregister them
|
||||
// Track ownership of mod ID to game modes
|
||||
std::unordered_map<std::string, std::vector<std::string>> s_gameModesByMod;
|
||||
|
||||
template <typename Fn>
|
||||
bool invoke_mod_callback(LoadedMod& mod, const char* what, Fn&& fn) {
|
||||
if (!mod.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ModError error = MOD_ERROR_INIT;
|
||||
ModResult result = MOD_OK;
|
||||
try {
|
||||
result = fn(&error);
|
||||
} catch (const std::exception& exception) {
|
||||
fail_mod(mod, MOD_ERROR, fmt::format("exception in {}: {}", what, exception.what()));
|
||||
return false;
|
||||
} catch (...) {
|
||||
fail_mod(mod, MOD_ERROR, fmt::format("unknown exception in {}", what));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result != MOD_OK && mod.active) {
|
||||
fail_mod(mod, result,
|
||||
error.message[0] != '\0' ?
|
||||
error.message :
|
||||
fmt::format("{} failed with result {}", what, static_cast<int>(result)));
|
||||
}
|
||||
return result == MOD_OK && mod.active;
|
||||
}
|
||||
|
||||
gamemode::GameMode::Callback wrap_callback(
|
||||
LoadedMod& mod, GameModeCallback callback, void* userData, const char* what) {
|
||||
return [&mod, callback, userData, what] {
|
||||
return invoke_mod_callback(
|
||||
mod, what, [callback, userData](ModError* error) { return callback(userData, error); });
|
||||
};
|
||||
}
|
||||
|
||||
gamemode::GameMode::NewSaveSelectCallback wrap_new_save_select_callback(
|
||||
LoadedMod& mod, GameModeNewSaveSelectCallback callback, void* userData, const char* what) {
|
||||
return [&mod, callback, userData, what](GameModeNewSaveState* state) {
|
||||
return invoke_mod_callback(
|
||||
mod, what, [=](ModError* error) { return callback(userData, state, error); });
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -42,12 +88,17 @@ void game_mode_remove_mod(LoadedMod& mod) {
|
||||
} // namespace
|
||||
|
||||
ModResult register_game_mode(ModContext* ctx, const GameModeDesc* desc) {
|
||||
auto* owner = mod_from_context(ctx);
|
||||
if (owner == nullptr || desc == nullptr || desc->struct_size < sizeof(GameModeDesc)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
if (!desc->gameModeId) {
|
||||
if (!desc->game_mode_id) {
|
||||
Log.error("Attempted to register a game mode with a null ID");
|
||||
return MOD_ERROR;
|
||||
}
|
||||
id = desc->gameModeId;
|
||||
id = desc->game_mode_id;
|
||||
if (id.empty()) {
|
||||
Log.error("Attempted to register a game mode with an empty ID");
|
||||
return MOD_ERROR;
|
||||
@@ -55,41 +106,49 @@ ModResult register_game_mode(ModContext* ctx, const GameModeDesc* desc) {
|
||||
id = get_mod_game_mode_id(ctx, id);
|
||||
|
||||
std::string fullName;
|
||||
if (!desc->fullName) {
|
||||
if (!desc->full_name) {
|
||||
Log.warn("Game mode {} has no display name; using its ID", id);
|
||||
fullName = id;
|
||||
} else {
|
||||
fullName = desc->fullName;
|
||||
fullName = desc->full_name;
|
||||
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;
|
||||
gamemode::GameMode mode{id, fullName, desc->save_name};
|
||||
if (desc->on_activated) {
|
||||
mode.mOnActivatedFunction = wrap_callback(
|
||||
*owner, desc->on_activated, desc->user_data, "game mode activation callback");
|
||||
}
|
||||
if (desc->onDeactivatedFunction) {
|
||||
mode.mOnDeactivatedFunction = desc->onDeactivatedFunction;
|
||||
if (desc->on_deactivated) {
|
||||
mode.mOnDeactivatedFunction = wrap_callback(
|
||||
*owner, desc->on_deactivated, desc->user_data, "game mode deactivation callback");
|
||||
}
|
||||
if (desc->onPlayFunction) {
|
||||
mode.mOnPlayFunction = desc->onPlayFunction;
|
||||
if (desc->on_play) {
|
||||
mode.mOnPlayFunction =
|
||||
wrap_callback(*owner, desc->on_play, desc->user_data, "game mode play callback");
|
||||
}
|
||||
if (desc->onSaveLoadedFunction) {
|
||||
mode.mOnSaveLoadedFunction = desc->onSaveLoadedFunction;
|
||||
if (desc->on_save_loaded) {
|
||||
mode.mOnSaveLoadedFunction = wrap_callback(
|
||||
*owner, desc->on_save_loaded, desc->user_data, "game mode save-loaded callback");
|
||||
}
|
||||
if (desc->onNewSaveFunction) {
|
||||
mode.mOnNewSaveFunction = desc->onNewSaveFunction;
|
||||
if (desc->on_new_save) {
|
||||
mode.mOnNewSaveFunction = wrap_callback(
|
||||
*owner, desc->on_new_save, desc->user_data, "game mode new-save callback");
|
||||
}
|
||||
if (desc->onNewSaveSelectFunction) {
|
||||
mode.mOnNewSaveSelectFunction = desc->onNewSaveSelectFunction;
|
||||
if (desc->on_new_save_select) {
|
||||
mode.mOnNewSaveSelectFunction = wrap_new_save_select_callback(*owner,
|
||||
desc->on_new_save_select, desc->user_data, "game mode new-save selection callback");
|
||||
}
|
||||
if (desc->onGameResetFunction) {
|
||||
mode.mOnGameResetFunction = desc->onGameResetFunction;
|
||||
if (desc->on_game_reset) {
|
||||
mode.mOnGameResetFunction =
|
||||
wrap_callback(*owner, desc->on_game_reset, desc->user_data, "game mode reset callback");
|
||||
}
|
||||
if (desc->onTickFunction) {
|
||||
mode.mOnTickFunction = desc->onTickFunction;
|
||||
if (desc->on_tick) {
|
||||
mode.mOnTickFunction =
|
||||
wrap_callback(*owner, desc->on_tick, desc->user_data, "game mode tick callback");
|
||||
}
|
||||
|
||||
gamemode::getGameModeManager().registerGameMode(mode);
|
||||
@@ -101,7 +160,7 @@ 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
|
||||
// Remove the game mode from the ownership map
|
||||
auto it = s_gameModesByMod.find(ctx->mod->metadata.id);
|
||||
if (it != s_gameModesByMod.end()) {
|
||||
std::erase(it->second, fullId);
|
||||
@@ -121,7 +180,7 @@ namespace dusk::mods::svc {
|
||||
namespace {
|
||||
|
||||
constexpr GameModeService s_gamemodeService{
|
||||
.header = SERVICE_HEADER(GameModeService, GAMEMODE_SERVICE_MAJOR, GAMEMODE_SERVICE_MINOR),
|
||||
.header = SERVICE_HEADER(GameModeService, GAME_MODE_SERVICE_MAJOR, GAME_MODE_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,
|
||||
@@ -130,9 +189,9 @@ constexpr GameModeService s_gamemodeService{
|
||||
} // namespace
|
||||
|
||||
constinit const ServiceModule g_gamemodeModule{
|
||||
.id = GAMEMODE_SERVICE_ID,
|
||||
.majorVersion = GAMEMODE_SERVICE_MAJOR,
|
||||
.minorVersion = GAMEMODE_SERVICE_MINOR,
|
||||
.id = GAME_MODE_SERVICE_ID,
|
||||
.majorVersion = GAME_MODE_SERVICE_MAJOR,
|
||||
.minorVersion = GAME_MODE_SERVICE_MINOR,
|
||||
.service = &s_gamemodeService,
|
||||
.modDeactivating = game_mode_impl::game_mode_remove_mod,
|
||||
};
|
||||
|
||||
+20
-9
@@ -22,11 +22,24 @@ static void onSpeedrunModeDeactive() {
|
||||
}
|
||||
|
||||
void registerSpeedrunGameMode() {
|
||||
dusk::gamemode::GameMode speedrunGameMode(kSpeedrunGameModeId,"Speedrun","gczelda2-speedrun");
|
||||
speedrunGameMode.mOnSaveLoadedFunction = dusk::speedrun::start;
|
||||
speedrunGameMode.mOnActivatedFunction = onSpeedrunModeActive;
|
||||
speedrunGameMode.mOnDeactivatedFunction = onSpeedrunModeDeactive;
|
||||
speedrunGameMode.mOnTickFunction = dusk::speedrun::onGameFrame;
|
||||
dusk::gamemode::GameMode speedrunGameMode{
|
||||
kSpeedrunGameModeId, "Speedrun", "gczelda2-speedrun"};
|
||||
speedrunGameMode.mOnSaveLoadedFunction = [] {
|
||||
dusk::speedrun::start();
|
||||
return true;
|
||||
};
|
||||
speedrunGameMode.mOnActivatedFunction = [] {
|
||||
onSpeedrunModeActive();
|
||||
return true;
|
||||
};
|
||||
speedrunGameMode.mOnDeactivatedFunction = [] {
|
||||
onSpeedrunModeDeactive();
|
||||
return true;
|
||||
};
|
||||
speedrunGameMode.mOnTickFunction = [] {
|
||||
dusk::speedrun::onGameFrame();
|
||||
return true;
|
||||
};
|
||||
|
||||
dusk::gamemode::getGameModeManager().registerGameMode(speedrunGameMode);
|
||||
}
|
||||
@@ -72,9 +85,7 @@ void resetForSpeedrunMode() {
|
||||
}
|
||||
|
||||
static void clearSpeedrunOverrides() {
|
||||
config::EnumerateRegistered([](config::ConfigVarBase& cvar) {
|
||||
cvar.clearSpeedrunOverride();
|
||||
});
|
||||
config::EnumerateRegistered([](config::ConfigVarBase& cvar) { cvar.clearSpeedrunOverride(); });
|
||||
}
|
||||
|
||||
void restoreFromSpeedrunMode() {
|
||||
@@ -82,4 +93,4 @@ void restoreFromSpeedrunMode() {
|
||||
aurora_set_pause_on_focus_lost(getSettings().game.pauseOnFocusLost.getValue());
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
} // namespace dusk::speedrun
|
||||
|
||||
+153
-73
@@ -37,6 +37,8 @@ namespace dusk::ui {
|
||||
namespace {
|
||||
constexpr borealis::Log PrelaunchLog{"dusk::ui::prelaunch"};
|
||||
|
||||
PrelaunchState sPrelaunchState;
|
||||
|
||||
const Rml::String kDocumentSource = R"RML(
|
||||
<rml>
|
||||
<head>
|
||||
@@ -529,7 +531,144 @@ void file_dialog_callback(borealis::file_select::Result result) {
|
||||
begin_disc_verification(result.locations.front());
|
||||
}
|
||||
|
||||
PrelaunchState sPrelaunchState;
|
||||
std::vector<const gamemode::GameMode*> carousel_game_modes() {
|
||||
const auto& registered = gamemode::getGameModeManager().getRegisteredGameModes();
|
||||
std::vector<const gamemode::GameMode*> modes;
|
||||
modes.reserve(registered.size());
|
||||
|
||||
if (const auto vanilla = registered.find(gamemode::kVanillaGameModeId);
|
||||
vanilla != registered.end())
|
||||
{
|
||||
modes.push_back(&vanilla->second);
|
||||
}
|
||||
for (const auto& [id, mode] : registered) {
|
||||
if (id != gamemode::kVanillaGameModeId) {
|
||||
modes.push_back(&mode);
|
||||
}
|
||||
}
|
||||
std::ranges::sort(modes.begin() + std::min<size_t>(1, modes.size()), modes.end(),
|
||||
[](const auto* lhs, const auto* rhs) { return lhs->getFullName() < rhs->getFullName(); });
|
||||
return modes;
|
||||
}
|
||||
|
||||
std::string game_mode_button_text() {
|
||||
if (prelaunch_state().activeDiscPath.empty()) {
|
||||
return "Select Disc Image";
|
||||
}
|
||||
const auto* currentGameMode = gamemode::getGameModeManager().getCurrentGameMode();
|
||||
if (currentGameMode == nullptr || currentGameMode->getId() == gamemode::kVanillaGameModeId) {
|
||||
return "Play";
|
||||
}
|
||||
return currentGameMode->getFullName();
|
||||
}
|
||||
|
||||
class GameModeButton final : public Button {
|
||||
public:
|
||||
GameModeButton(Rml::Element* parent, ButtonCallback onPressed) : Button{parent, ""} {
|
||||
root()->SetClass("game-mode-button", true);
|
||||
mPrevious = append(root(), "game-mode-previous");
|
||||
mLabelViewport = append(root(), "game-mode-label-viewport");
|
||||
for (auto& label : mLabels) {
|
||||
label = append(mLabelViewport, "game-mode-label");
|
||||
}
|
||||
mNext = append(root(), "game-mode-next");
|
||||
|
||||
Component::listen(mPrevious, Rml::EventId::Click, [this](Rml::Event& event) {
|
||||
cycle(-1);
|
||||
event.StopPropagation();
|
||||
});
|
||||
Component::listen(mNext, Rml::EventId::Click, [this](Rml::Event& event) {
|
||||
cycle(1);
|
||||
event.StopPropagation();
|
||||
});
|
||||
Component::listen(root(), Rml::EventId::Keydown, [this](Rml::Event& event) {
|
||||
const auto command = map_nav_event(event);
|
||||
if (command == NavCommand::Left || command == NavCommand::Right) {
|
||||
cycle(command == NavCommand::Left ? -1 : 1);
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
on_pressed(std::move(onPressed));
|
||||
refresh(0);
|
||||
}
|
||||
|
||||
void update() override {
|
||||
refresh(0);
|
||||
Button::update();
|
||||
}
|
||||
|
||||
private:
|
||||
bool can_cycle() const {
|
||||
return !prelaunch_state().activeDiscPath.empty() &&
|
||||
gamemode::getGameModeManager().getRegisteredGameModes().size() > 1;
|
||||
}
|
||||
|
||||
void cycle(int direction) {
|
||||
const auto modes = carousel_game_modes();
|
||||
if (!can_cycle() || modes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* current = gamemode::getGameModeManager().getCurrentGameMode();
|
||||
const auto currentIt = std::ranges::find_if(modes, [current](const auto* mode) {
|
||||
return current != nullptr && mode->getId() == current->getId();
|
||||
});
|
||||
const int currentIndex =
|
||||
currentIt == modes.end() ? 0 : static_cast<int>(currentIt - modes.begin());
|
||||
const int count = static_cast<int>(modes.size());
|
||||
const int nextIndex = ((currentIndex + direction) % count + count) % count;
|
||||
const auto nextId = modes[nextIndex]->getId();
|
||||
if (gamemode::getGameModeManager().setCurrentGameMode(nextId)) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
}
|
||||
refresh(direction);
|
||||
}
|
||||
|
||||
void refresh(int direction) {
|
||||
root()->SetClass("can-cycle", can_cycle());
|
||||
|
||||
const auto text = game_mode_button_text();
|
||||
if (text == mText) {
|
||||
return;
|
||||
}
|
||||
mText = text;
|
||||
if (direction == 0) {
|
||||
mLabels[mActiveLabel]->SetInnerRML(escape(text));
|
||||
mLabels[mActiveLabel]->SetProperty(
|
||||
Rml::PropertyId::Left, Rml::Property{0.0f, Rml::Unit::PERCENT});
|
||||
mLabels[mActiveLabel]->SetClass("active", true);
|
||||
mLabels[1 - mActiveLabel]->SetClass("active", false);
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr float kSlideDistance = 100.0f;
|
||||
constexpr float kSlideDuration = 0.24f;
|
||||
auto* outgoing = mLabels[mActiveLabel];
|
||||
mActiveLabel = 1 - mActiveLabel;
|
||||
auto* incoming = mLabels[mActiveLabel];
|
||||
incoming->SetInnerRML(escape(text));
|
||||
|
||||
const Rml::Property incomingOffset{
|
||||
static_cast<float>(direction) * kSlideDistance, Rml::Unit::PERCENT};
|
||||
const Rml::Property outgoingOffset{
|
||||
static_cast<float>(-direction) * kSlideDistance, Rml::Unit::PERCENT};
|
||||
|
||||
outgoing->Animate(Rml::PropertyId::Left, outgoingOffset, kSlideDuration,
|
||||
Rml::Tween{Rml::Tween::Cubic, Rml::Tween::InOut});
|
||||
incoming->Animate(Rml::PropertyId::Left, Rml::Property{0.0f, Rml::Unit::PERCENT},
|
||||
kSlideDuration, Rml::Tween{Rml::Tween::Cubic, Rml::Tween::InOut}, 1, false, 0.0f,
|
||||
&incomingOffset);
|
||||
outgoing->SetClass("active", false);
|
||||
incoming->SetClass("active", true);
|
||||
}
|
||||
|
||||
Rml::Element* mPrevious = nullptr;
|
||||
Rml::Element* mLabelViewport = nullptr;
|
||||
std::array<Rml::Element*, 2> mLabels{};
|
||||
Rml::Element* mNext = nullptr;
|
||||
Rml::String mText;
|
||||
std::size_t mActiveLabel = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -758,22 +897,6 @@ void Prelaunch::refresh_menu_buttons() {
|
||||
prelaunch->build_menu_buttons();
|
||||
}
|
||||
|
||||
static std::string get_playbutton_text() {
|
||||
auto& state = prelaunch_state();
|
||||
const bool activeDiscLoaded = !state.activeDiscPath.empty();
|
||||
if (activeDiscLoaded == false) {
|
||||
return "Select Disc Image";
|
||||
}
|
||||
std::string playText;
|
||||
const gamemode::GameMode* currentGameMode =
|
||||
gamemode::getGameModeManager().getCurrentGameMode();
|
||||
if (currentGameMode != nullptr) {
|
||||
return currentGameMode->getId() == gamemode::kVanillaGameModeId ? "Play" :
|
||||
"Play " + currentGameMode->getFullName();
|
||||
}
|
||||
return "Play";
|
||||
}
|
||||
|
||||
Prelaunch::Prelaunch() : Document(kDocumentSource, false, DocumentScope::Prelaunch) {
|
||||
mRoot = mDocument->GetElementById("root");
|
||||
ensure_initialized();
|
||||
@@ -819,16 +942,22 @@ 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
|
||||
// Restore the previously selected game mode before creating the play control.
|
||||
gamemode::getGameModeManager().setGameModeToPrevious();
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, get_playbutton_text()));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
auto playButton = std::make_unique<GameModeButton>(menuList, [this] {
|
||||
if (prelaunch_state().activeDiscPath.empty()) {
|
||||
open_iso_picker();
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto* gameMode = gamemode::getGameModeManager().getCurrentGameMode();
|
||||
gameMode != nullptr && !gameMode->invokeOnPlayFunction())
|
||||
{
|
||||
gamemode::getGameModeManager().setCurrentGameMode(gamemode::kVanillaGameModeId);
|
||||
return;
|
||||
}
|
||||
|
||||
mDoAud_seStartMenu(kSoundPlay);
|
||||
show_menu_notification();
|
||||
|
||||
@@ -845,61 +974,12 @@ void Prelaunch::build_menu_buttons() {
|
||||
}
|
||||
|
||||
prelaunch_state().firstLaunch = false;
|
||||
const gamemode::GameMode* gameMode =
|
||||
gamemode::getGameModeManager().getCurrentGameMode();
|
||||
if (gameMode) {
|
||||
gameMode->invokeOnPlayFunction();
|
||||
}
|
||||
|
||||
IsGameLaunched = true;
|
||||
hide(true);
|
||||
MenuBar::refresh_tabs();
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
|
||||
|
||||
// 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(ModalAction{
|
||||
.label = "Vanilla", .onPressed = [this](Modal& modal) {
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
gamemode::getGameModeManager().setCurrentGameMode(gamemode::kVanillaGameModeId);
|
||||
modal.pop();
|
||||
update();
|
||||
}});
|
||||
for (const auto& [id, gameMode] :
|
||||
gamemode::getGameModeManager().getRegisteredGameModes())
|
||||
{
|
||||
if (id == gamemode::kVanillaGameModeId) {
|
||||
// Force vanilla to the top
|
||||
continue;
|
||||
}
|
||||
gameModeActions.push_back(ModalAction{.label = gameMode.getFullName(),
|
||||
.onPressed = [this, id](Modal& modal) {
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
gamemode::getGameModeManager().setCurrentGameMode(id);
|
||||
modal.pop();
|
||||
update();
|
||||
}});
|
||||
}
|
||||
mRestartSuppressed = false;
|
||||
push(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Play Type",
|
||||
.bodyRml = "What mode would you like to play?",
|
||||
.actions = gameModeActions,
|
||||
.onDismiss =
|
||||
[this](Modal& modal) {
|
||||
mDoAud_seStartMenu(kSoundWindowClose);
|
||||
modal.pop();
|
||||
},
|
||||
.icon = "question-mark",
|
||||
.isVertical = true,
|
||||
}));
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
|
||||
}
|
||||
apply_intro_animation(playButton->root(), "delay-1");
|
||||
mMenuButtons.push_back(std::move(playButton));
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Settings"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
@@ -1001,8 +1081,8 @@ void Prelaunch::update() {
|
||||
mEntranceAnimationStarted = true;
|
||||
}
|
||||
|
||||
if (!mMenuButtons.empty()) {
|
||||
mMenuButtons[0]->set_text(get_playbutton_text());
|
||||
for (const auto& button : mMenuButtons) {
|
||||
button->update();
|
||||
}
|
||||
|
||||
const auto discStatusLabel = mDiscStatus->GetElementById("disc-status-label");
|
||||
|
||||
Reference in New Issue
Block a user