Use carousel button on prelaunch; more cleanup

This commit is contained in:
Luke Street
2026-08-19 22:58:43 -06:00
parent 3ecd785ac2
commit fd4a5f0cfb
10 changed files with 476 additions and 257 deletions
+6 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+86 -27
View File
@@ -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
View File
@@ -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
View File
@@ -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");