mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-20 05:16:24 -04:00
+119
@@ -636,6 +636,125 @@ 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/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 `save_name`; leave it empty to use
|
||||
the vanilla `gczelda2` save.
|
||||
|
||||
```cpp
|
||||
IMPORT_SERVICE(LogService, svc_log);
|
||||
IMPORT_SERVICE(HookService, svc_hook);
|
||||
IMPORT_SERVICE(GameModeService, svc_game_mode);
|
||||
|
||||
DEFINE_HOOK(fopAcM_createItem, CreateItem);
|
||||
|
||||
#define MY_GAME_MODE_ID "game-mode-id"
|
||||
|
||||
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_game_mode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`.
|
||||
return HOOK_CONTINUE;
|
||||
}
|
||||
|
||||
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, my_function_hook);
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(outError, result, "failed to install fopAcM_createItem hook");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
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) {
|
||||
return mods::set_error(outError, result, "failed to uninstall fopAcM_createItem hook");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult on_save_loaded(void*, ModError*) {
|
||||
// This function will be invoked by the game as a save is loaded
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
const GameModeDesc gameModeDesc = {
|
||||
.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_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
|
||||
```
|
||||
|
||||
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_game_mode);
|
||||
IMPORT_SERVICE(UiService, svc_ui);
|
||||
|
||||
ModResult on_new_save_select(void*, GameModeNewSaveState* state, ModError* outError) {
|
||||
static GameModeNewSaveState* newSaveState;
|
||||
static UiWindowHandle windowHandle;
|
||||
|
||||
newSaveState = state;
|
||||
|
||||
UiTabDesc tabs[1]{};
|
||||
|
||||
tabs[0].struct_size = sizeof(UiTabDesc);
|
||||
tabs[0].title = "Play";
|
||||
tabs[0].build = [](ModContext* ctx, UiWindowHandle, UiElementHandle leftPane, UiElementHandle rightPane, void*, ModError*) {
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_BUTTON;
|
||||
desc.label = "Play";
|
||||
desc.help_rml = "Play Button";
|
||||
desc.on_pressed = [](ModContext* ctx, void* userdata) {
|
||||
*newSaveState = GAME_MODE_STATE_PROCEED;
|
||||
svc_ui->window_close(ctx, *static_cast<UiWindowHandle*>(userdata));
|
||||
};
|
||||
desc.user_data = &windowHandle;
|
||||
svc_ui->pane_add_control(mod_ctx, leftPane, &desc, nullptr);
|
||||
return MOD_OK;
|
||||
};
|
||||
|
||||
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
|
||||
desc.tabs = tabs;
|
||||
desc.tab_count = 1;
|
||||
desc.on_closed = [](ModContext *, UiWindowHandle, void *userdata) {
|
||||
// If closing the window through backing out, return to file select
|
||||
if (*newSaveState == GAME_MODE_STATE_PENDING) {
|
||||
*newSaveState = GAME_MODE_STATE_RETURN;
|
||||
}
|
||||
};
|
||||
|
||||
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 = {
|
||||
.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_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooking Game Functions
|
||||
|
||||
@@ -1434,6 +1434,7 @@ set(DUSK_FILES
|
||||
src/dusk/extras.c
|
||||
src/dusk/frame_interpolation.cpp
|
||||
src/dusk/game_clock.cpp
|
||||
src/dusk/game_mode.cpp
|
||||
src/dusk/gamepad_color.cpp
|
||||
src/dusk/globals.cpp
|
||||
src/dusk/gyro.cpp
|
||||
@@ -1501,6 +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/game_mode.cpp
|
||||
src/dusk/mods/svc/window.cpp
|
||||
src/dusk/mods/svc/window.hpp
|
||||
src/dusk/mods/svc/save.cpp
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -420,6 +424,13 @@ public:
|
||||
bool pointerMenuSelect();
|
||||
bool pointerCopyDataToSelect();
|
||||
bool pointerYesNoSelect(bool errorSelect);
|
||||
void backToDataSelectMove() {
|
||||
headerTxtSet(0x43, 1, 0);
|
||||
fileRecScaleAnmInitSet2(0.0f, 1.0f);
|
||||
nameMoveAnmInitSet(0xd29, 0xd1f);
|
||||
modoruTxtDispAnmInit(0);
|
||||
mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE;
|
||||
}
|
||||
#endif
|
||||
void _draw();
|
||||
void errorMoveAnmInitSet(int, int);
|
||||
@@ -733,6 +744,8 @@ public:
|
||||
#endif
|
||||
#ifdef TARGET_PC
|
||||
dDlst_FileSelFade_c mFadeDlst;
|
||||
bool mGameModeSaveStartBuildUi = true;
|
||||
GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
|
||||
@@ -112,6 +112,11 @@ public:
|
||||
mSerialNo = serial_no;
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
void setFileName(const std::string& fileName);
|
||||
const char* getFileName();
|
||||
#endif
|
||||
|
||||
/* 0x0000 */ u8 mData[SAVEFILE_SIZE];
|
||||
/* 0x1FBC */ u8 mChannel;
|
||||
/* 0x1FBD */ u8 mCopyToPos;
|
||||
@@ -124,6 +129,11 @@ public:
|
||||
/* 0x1FEC */ s32 mNandState;
|
||||
/* 0x1FF0 */ u64 mSerialNo;
|
||||
/* 0x1FF8 */ u32 mDataVersion;
|
||||
#ifdef TARGET_PC
|
||||
bool mInitialized;
|
||||
std::string mFileName;
|
||||
#endif
|
||||
|
||||
}; // Size: 0x2000
|
||||
|
||||
STATIC_ASSERT(sizeof(mDoMemCd_Ctrl_c) == 8192);
|
||||
@@ -230,4 +240,14 @@ inline s32 mDoMemCd_checkNANDFile() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_PC
|
||||
inline void mDoMemCd_SetFileName(const std::string& fileName) {
|
||||
g_mDoMemCd_control.setFileName(fileName);
|
||||
}
|
||||
|
||||
inline const char* mDoMemCd_GetFileName() {
|
||||
return g_mDoMemCd_control.getFileName();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* M_DO_M_DO_MEMCARD_H */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -519,3 +519,13 @@ progress.verification-progress-bar {
|
||||
flex: 0 0 auto;
|
||||
padding-top: 4dp;
|
||||
}
|
||||
|
||||
.modal-actions-vertical {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.modal-actions-vertical button.modal-btn {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
#include <mods/svc/config.h>
|
||||
|
||||
#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 {
|
||||
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* game_mode_id, bool* out_active);
|
||||
} GameModeService;
|
||||
|
||||
MOD_DECLARE_SERVICE(GameModeService, svc_game_mode, GAME_MODE_SERVICE_ID, GAME_MODE_SERVICE_MAJOR,
|
||||
GAME_MODE_SERVICE_MINOR);
|
||||
@@ -23,10 +23,13 @@
|
||||
#include "d/actor/d_a_npc_tkc.h"
|
||||
#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/version.hpp"
|
||||
#endif
|
||||
|
||||
BOOL daAlink_c::checkEventRun() const {
|
||||
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
|
||||
@@ -4016,9 +4019,9 @@ int daAlink_c::procGanonFinishInit() {
|
||||
onEndResetFlg1(ERFLG1_SHIELD_BACKBONE);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
if (dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::m_speedrunInfo.stopRun();
|
||||
if (dusk::speedrun::isActive()) {
|
||||
if (dusk::speedrun::g_speedrunInfo.m_isRunStarted) {
|
||||
dusk::speedrun::g_speedrunInfo.stopRun();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
#include "JSystem/J2DGraph/J2DScreen.h"
|
||||
#include "JSystem/J2DGraph/J2DTextBox.h"
|
||||
#include "d/d_msg_string.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include <dusk/autosave.h>
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#endif
|
||||
|
||||
#include "dusk/version.hpp"
|
||||
|
||||
@@ -186,15 +190,6 @@ void dBrightCheck_c::modeMove() {
|
||||
if (mDoCPd_c::getTrigA(PAD_1) || mDoCPd_c::getTrigStart(PAD_1)) {
|
||||
mDoAud_seStart(Z2SE_ENTER_GAME, NULL, 0, 0);
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) {
|
||||
// start a new run if a run isn't already in progress
|
||||
if (!dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::resetForSpeedrunMode();
|
||||
dusk::m_speedrunInfo.startRun();
|
||||
dusk::speedrun::start();
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoSave(true);
|
||||
#endif
|
||||
mCompleteCheck = true;
|
||||
|
||||
+45
-2
@@ -26,9 +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 "helpers/string.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
|
||||
@@ -1310,12 +1311,42 @@ void dFile_select_c::selectDataOpenMove() {
|
||||
void dFile_select_c::selectDataNameMove() {
|
||||
bool isHeaderTxtChange = headerTxtChangeAnm();
|
||||
bool isFileRecScale = fileRecScaleAnm2();
|
||||
bool isNameMove = nameMoveAnm();
|
||||
IF_NOT_DUSK(bool isNameMove = nameMoveAnm();)
|
||||
bool isModoruTxtDisp = modoruTxtDispAnm();
|
||||
|
||||
#ifdef TARGET_PC
|
||||
const dusk::gamemode::GameMode* gameMode =
|
||||
dusk::gamemode::getGameModeManager().getCurrentGameMode();
|
||||
if (gameMode) {
|
||||
if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) {
|
||||
if (mGameModeSaveStartBuildUi) {
|
||||
gameMode->invokeOnNewSaveSelectFunction(&mGameModeNewSaveState);
|
||||
mGameModeSaveStartBuildUi = false;
|
||||
}
|
||||
if (mGameModeNewSaveState == GAME_MODE_STATE_RETURN) {
|
||||
backToDataSelectMove();
|
||||
mGameModeSaveStartBuildUi = true;
|
||||
mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
return;
|
||||
}
|
||||
if (mGameModeNewSaveState != GAME_MODE_STATE_PROCEED) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
IF_DUSK(bool isNameMove = nameMoveAnm();)
|
||||
|
||||
if (isHeaderTxtChange == true && isFileRecScale == true && isNameMove == true &&
|
||||
isModoruTxtDisp == true)
|
||||
{
|
||||
#ifdef TARGET_PC
|
||||
mGameModeSaveStartBuildUi = true;
|
||||
mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_NAME_INPUT_WAIT;
|
||||
}
|
||||
}
|
||||
@@ -1397,6 +1428,12 @@ void dFile_select_c::menuSelectStart() {
|
||||
dComIfGs_setDataNum(mSelectNum);
|
||||
#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();
|
||||
}
|
||||
#endif
|
||||
} else if (mSelectMenuNum == 0) {
|
||||
mSelIcon->setAlphaRate(0.0f);
|
||||
@@ -1750,6 +1787,12 @@ 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();
|
||||
}
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_NEXT_MODE_WAIT;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#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"
|
||||
@@ -808,6 +809,11 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
dComIfGs_gameStart();
|
||||
|
||||
+10
-15
@@ -9,11 +9,6 @@
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_meter2_info.h"
|
||||
#include "d/d_s_name.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/memory.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "f_op/f_op_overlap_mng.h"
|
||||
#include "f_op/f_op_scene_mng.h"
|
||||
#include "m_Do/m_Do_Reset.h"
|
||||
@@ -21,7 +16,16 @@
|
||||
#include "m_Do/m_Do_machine.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "m_Do/m_Do_mtx.h"
|
||||
#include <dusk/autosave.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/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
|
||||
@@ -418,15 +422,6 @@ void dScnName_c::changeGameScene() {
|
||||
dComIfGs_setRestartRoomParam(0);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) {
|
||||
// start a new run on file load if a run isn't already in progress
|
||||
if (!dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::resetForSpeedrunMode();
|
||||
dusk::m_speedrunInfo.startRun();
|
||||
dusk::speedrun::start();
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoSave(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
+2
-1
@@ -28,8 +28,9 @@
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/settings.h"
|
||||
#include <f_ap/f_ap_game.h>
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include "helpers/string.hpp"
|
||||
#define strcpy SafeStringCopy
|
||||
|
||||
@@ -509,6 +509,12 @@ BOOL OSJoinThread(OSThread* thread, void** val) {
|
||||
*(s32*)val = (s32)(intptr_t)thread->val;
|
||||
}
|
||||
thread->state = 0;
|
||||
|
||||
{
|
||||
std::lock_guard mapLock(GetThreadDataMutex());
|
||||
GetThreadDataMap().erase(thread);
|
||||
}
|
||||
sActiveThreadCount--;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
+19
-12
@@ -1,25 +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 <filesystem>
|
||||
#include <algorithm>
|
||||
@@ -1302,6 +1303,12 @@ void AchievementSystem::processEntry(Entry& e) {
|
||||
}
|
||||
|
||||
void AchievementSystem::tick() {
|
||||
// Until we implement an AchievementService, achievements will be unavailible in custom gamemodes
|
||||
if (dusk::gamemode::getGameModeManager().isCurrentGameMode(dusk::gamemode::kVanillaGameModeId) == false
|
||||
&& dusk::gamemode::getGameModeManager().isCurrentGameMode(dusk::speedrun::kSpeedrunGameModeId) == false) {
|
||||
m_signals.clear();
|
||||
return;
|
||||
}
|
||||
if (!m_loaded) {
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#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();
|
||||
}
|
||||
|
||||
bool GameModeManager::setCurrentGameMode(const GameModeId& id) {
|
||||
if (mCurrentGameModeId == id) {
|
||||
return true;
|
||||
}
|
||||
if (!mRegisteredGameModes.contains(id)) {
|
||||
Log.warn("Attempting to configure unknown game mode {}", id);
|
||||
return false;
|
||||
}
|
||||
const GameMode* currentGameMode = getCurrentGameMode();
|
||||
if (currentGameMode) {
|
||||
currentGameMode->invokeOnDeactivatedFunction();
|
||||
}
|
||||
mCurrentGameModeId = id;
|
||||
|
||||
currentGameMode = getCurrentGameMode();
|
||||
if (currentGameMode) {
|
||||
mDoMemCd_SetFileName(currentGameMode->getSaveName());
|
||||
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
|
||||
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include "d/d_file_select.h"
|
||||
#include "mods/svc/game_mode.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace dusk::gamemode {
|
||||
using GameModeId = std::string;
|
||||
|
||||
constexpr const char* kVanillaGameModeId = "vanilla";
|
||||
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)} {}
|
||||
const GameModeId& getId() const { return mId; }
|
||||
const std::string& getFullName() const { return mFullName; }
|
||||
const std::string& getSaveName() const { return mSaveName; }
|
||||
|
||||
GameModeId mId;
|
||||
std::string mFullName;
|
||||
std::string mSaveName;
|
||||
|
||||
bool invokeOnActivatedFunction() const {
|
||||
if (mOnActivatedFunction) {
|
||||
return mOnActivatedFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnDeactivatedFunction() const {
|
||||
if (mOnDeactivatedFunction) {
|
||||
return mOnDeactivatedFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnPlayFunction() const {
|
||||
if (mOnPlayFunction) {
|
||||
return mOnPlayFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnSaveLoadedFunction() const {
|
||||
if (mOnSaveLoadedFunction) {
|
||||
return mOnSaveLoadedFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnNewSaveFunction() const {
|
||||
if (mOnNewSaveFunction) {
|
||||
return mOnNewSaveFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnNewSaveSelectFunction(GameModeNewSaveState* state) const {
|
||||
*state = GAME_MODE_STATE_PENDING;
|
||||
if (mOnNewSaveSelectFunction) {
|
||||
if (mOnNewSaveSelectFunction(state)) {
|
||||
return true;
|
||||
}
|
||||
*state = GAME_MODE_STATE_RETURN;
|
||||
return false;
|
||||
}
|
||||
*state = GAME_MODE_STATE_PROCEED;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnGameResetFunction() const {
|
||||
if (mOnGameResetFunction) {
|
||||
return mOnGameResetFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool invokeOnTickFunction() const {
|
||||
if (mOnTickFunction) {
|
||||
return mOnTickFunction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Callback mOnActivatedFunction;
|
||||
Callback mOnDeactivatedFunction;
|
||||
Callback mOnPlayFunction;
|
||||
Callback mOnSaveLoadedFunction;
|
||||
Callback mOnNewSaveFunction;
|
||||
NewSaveSelectCallback mOnNewSaveSelectFunction;
|
||||
Callback mOnGameResetFunction;
|
||||
Callback mOnTickFunction;
|
||||
};
|
||||
|
||||
class GameModeManager {
|
||||
public:
|
||||
GameModeManager();
|
||||
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);
|
||||
}
|
||||
bool isCurrentGameMode(const GameModeId& id) const {
|
||||
const GameMode* gameMode = getCurrentGameMode();
|
||||
if (gameMode && gameMode->getId() == id) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool setCurrentGameMode(const GameModeId& id);
|
||||
void setGameModeToPrevious();
|
||||
|
||||
const std::map<GameModeId, GameMode>& getRegisteredGameModes() const {
|
||||
return mRegisteredGameModes;
|
||||
}
|
||||
|
||||
private:
|
||||
GameModeId mCurrentGameModeId;
|
||||
std::map<GameModeId, GameMode> mRegisteredGameModes;
|
||||
};
|
||||
|
||||
extern GameModeManager g_GameModeManager;
|
||||
|
||||
inline GameModeManager& getGameModeManager() {
|
||||
return g_GameModeManager;
|
||||
}
|
||||
|
||||
} // namespace dusk::gamemode
|
||||
@@ -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,6 +18,7 @@
|
||||
#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"
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "dusk/ui/ui.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"
|
||||
@@ -311,7 +312,7 @@ namespace dusk {
|
||||
|
||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||
m_isLaunchInitialized = true;
|
||||
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
|
||||
if (dusk::speedrun::isActive() && getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::connectLiveSplit();
|
||||
}
|
||||
}
|
||||
@@ -383,7 +384,7 @@ namespace dusk {
|
||||
|
||||
m_menuTools.ShowInputViewer();
|
||||
|
||||
if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) {
|
||||
if (dusk::IsGameLaunched && !dusk::speedrun::isActive()) {
|
||||
m_menuTools.ShowDebugOverlay();
|
||||
m_menuTools.ShowCameraOverlay();
|
||||
m_menuTools.ShowProcessManager();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/dusk.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/os.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
@@ -34,7 +35,7 @@ namespace dusk {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
||||
ImGui::BeginDisabled(dusk::speedrun::isActive());
|
||||
|
||||
ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor);
|
||||
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
|
||||
@@ -56,7 +57,7 @@ namespace dusk {
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Debug")) {
|
||||
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
||||
ImGui::BeginDisabled(dusk::speedrun::isActive());
|
||||
|
||||
bool developmentMode = mDoMain::developmentMode == 1;
|
||||
if (ImGui::Checkbox("Development Mode", &developmentMode)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef DUSK_ISO_VALIDATE_HPP
|
||||
#define DUSK_ISO_VALIDATE_HPP
|
||||
|
||||
#include "dusk/settings.h"
|
||||
#include <borealis/disc.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
+73
-63
@@ -1,46 +1,48 @@
|
||||
#if _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using socket_t = SOCKET;
|
||||
static void closeSocket(socket_t s) {
|
||||
LINGER li{1, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char*>(&li), sizeof(li));
|
||||
closesocket(s);
|
||||
}
|
||||
static int socketError(socket_t s) {
|
||||
int err = 0; int len = sizeof(err);
|
||||
getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&err), &len);
|
||||
return err;
|
||||
}
|
||||
static constexpr int kSendFlags = 0;
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using socket_t = SOCKET;
|
||||
static void closeSocket(socket_t s) {
|
||||
LINGER li{1, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char*>(&li), sizeof(li));
|
||||
closesocket(s);
|
||||
}
|
||||
static int socketError(socket_t s) {
|
||||
int err = 0;
|
||||
int len = sizeof(err);
|
||||
getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&err), &len);
|
||||
return err;
|
||||
}
|
||||
static constexpr int kSendFlags = 0;
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
using socket_t = int;
|
||||
static void closeSocket(socket_t s) {
|
||||
struct linger li{1, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_LINGER, &li, sizeof(li));
|
||||
close(s);
|
||||
}
|
||||
static int socketError(socket_t s) {
|
||||
int err = 0; socklen_t len = sizeof(err);
|
||||
getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len);
|
||||
return err;
|
||||
}
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
using socket_t = int;
|
||||
static void closeSocket(socket_t s) {
|
||||
struct linger li{1, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_LINGER, &li, sizeof(li));
|
||||
close(s);
|
||||
}
|
||||
static int socketError(socket_t s) {
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len);
|
||||
return err;
|
||||
}
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
static constexpr int kSendFlags = 0;
|
||||
#else
|
||||
static constexpr int kSendFlags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
static constexpr int kSendFlags = 0;
|
||||
#else
|
||||
static constexpr int kSendFlags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
@@ -49,18 +51,18 @@
|
||||
|
||||
namespace dusk::speedrun {
|
||||
|
||||
static bool running = false;
|
||||
static bool startPending = false;
|
||||
static uint64_t frameCount = 0;
|
||||
static socket_t sock = INVALID_SOCKET;
|
||||
static bool wasLoading = false;
|
||||
static bool connected = false;
|
||||
static bool connectPending = false;
|
||||
static bool disconnectPending = false;
|
||||
static uint32_t idleProbeCounter = 0;
|
||||
static uint32_t reconnectCounter = 0;
|
||||
static char storedHost[64] = "127.0.0.1";
|
||||
static int storedPort = 16834;
|
||||
static bool running = false;
|
||||
static bool startPending = false;
|
||||
static uint64_t frameCount = 0;
|
||||
static socket_t sock = INVALID_SOCKET;
|
||||
static bool wasLoading = false;
|
||||
static bool connected = false;
|
||||
static bool connectPending = false;
|
||||
static bool disconnectPending = false;
|
||||
static uint32_t idleProbeCounter = 0;
|
||||
static uint32_t reconnectCounter = 0;
|
||||
static char storedHost[64] = "127.0.0.1";
|
||||
static int storedPort = 16834;
|
||||
|
||||
static void sendCmd(const char* cmd) {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
@@ -122,9 +124,11 @@ void onGameFrame() {
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) {
|
||||
if (g_speedrunInfo.m_isRunStarted || running) {
|
||||
return;
|
||||
}
|
||||
resetForSpeedrunMode();
|
||||
g_speedrunInfo.startRun();
|
||||
|
||||
running = true;
|
||||
startPending = true;
|
||||
@@ -214,8 +218,16 @@ void disconnectLiveSplit() {
|
||||
connected = connectPending = disconnectPending = false;
|
||||
}
|
||||
|
||||
bool consumeConnectedEvent() { bool v = connectPending; connectPending = false; return v; }
|
||||
bool consumeDisconnectedEvent() { bool v = disconnectPending; disconnectPending = false; return v; }
|
||||
bool consumeConnectedEvent() {
|
||||
bool v = connectPending;
|
||||
connectPending = false;
|
||||
return v;
|
||||
}
|
||||
bool consumeDisconnectedEvent() {
|
||||
bool v = disconnectPending;
|
||||
disconnectPending = false;
|
||||
return v;
|
||||
}
|
||||
|
||||
void updateLiveSplit() {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
@@ -267,7 +279,8 @@ void updateLiveSplit() {
|
||||
#else
|
||||
|| (r < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
#endif
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (connected) {
|
||||
disconnectPending = true;
|
||||
}
|
||||
@@ -280,15 +293,12 @@ void updateLiveSplit() {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t totalMs = frameCount * 1000 / 30;
|
||||
const uint64_t totalMs = frameCount * 1000 / 30;
|
||||
const uint64_t totalSec = totalMs / 1000;
|
||||
char cmd[32];
|
||||
snprintf(cmd, sizeof(cmd), "setgametime %u:%02u:%02u.%03u",
|
||||
static_cast<uint32_t>(totalSec / 3600),
|
||||
static_cast<uint32_t>((totalSec / 60) % 60),
|
||||
static_cast<uint32_t>(totalSec % 60),
|
||||
static_cast<uint32_t>(totalMs % 1000)
|
||||
);
|
||||
static_cast<uint32_t>(totalSec / 3600), static_cast<uint32_t>((totalSec / 60) % 60),
|
||||
static_cast<uint32_t>(totalSec % 60), static_cast<uint32_t>(totalMs % 1000));
|
||||
sendCmd(cmd);
|
||||
}
|
||||
|
||||
@@ -299,4 +309,4 @@ void shutdown() {
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace dusk::speedrun
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
|
||||
namespace dusk::speedrun {
|
||||
void onGameFrame();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
extern bool IsRunning;
|
||||
@@ -21,8 +20,6 @@ struct StageRequest {
|
||||
};
|
||||
extern StageRequest StageRequested;
|
||||
|
||||
|
||||
|
||||
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
|
||||
(defined(TARGET_OS_TV) && TARGET_OS_TV)
|
||||
inline constexpr bool SupportsProcessRestart = false;
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#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 "dusk/mods/loader/loader.hpp"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods::svc::game_mode_impl {
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::game_mode");
|
||||
|
||||
// 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;
|
||||
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) {
|
||||
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->game_mode_id) {
|
||||
Log.error("Attempted to register a game mode with a null ID");
|
||||
return MOD_ERROR;
|
||||
}
|
||||
id = desc->game_mode_id;
|
||||
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->full_name) {
|
||||
Log.warn("Game mode {} has no display name; using its ID", id);
|
||||
fullName = id;
|
||||
} else {
|
||||
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->save_name};
|
||||
if (desc->on_activated) {
|
||||
mode.mOnActivatedFunction = wrap_callback(
|
||||
*owner, desc->on_activated, desc->user_data, "game mode activation callback");
|
||||
}
|
||||
if (desc->on_deactivated) {
|
||||
mode.mOnDeactivatedFunction = wrap_callback(
|
||||
*owner, desc->on_deactivated, desc->user_data, "game mode deactivation callback");
|
||||
}
|
||||
if (desc->on_play) {
|
||||
mode.mOnPlayFunction =
|
||||
wrap_callback(*owner, desc->on_play, desc->user_data, "game mode play callback");
|
||||
}
|
||||
if (desc->on_save_loaded) {
|
||||
mode.mOnSaveLoadedFunction = wrap_callback(
|
||||
*owner, desc->on_save_loaded, desc->user_data, "game mode save-loaded callback");
|
||||
}
|
||||
if (desc->on_new_save) {
|
||||
mode.mOnNewSaveFunction = wrap_callback(
|
||||
*owner, desc->on_new_save, desc->user_data, "game mode new-save callback");
|
||||
}
|
||||
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->on_game_reset) {
|
||||
mode.mOnGameResetFunction =
|
||||
wrap_callback(*owner, desc->on_game_reset, desc->user_data, "game mode reset callback");
|
||||
}
|
||||
if (desc->on_tick) {
|
||||
mode.mOnTickFunction =
|
||||
wrap_callback(*owner, desc->on_tick, desc->user_data, "game mode tick callback");
|
||||
}
|
||||
|
||||
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 ownership 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, 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,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
constinit const ServiceModule g_gamemodeModule{
|
||||
.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,
|
||||
};
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
@@ -224,6 +224,7 @@ void ModLoader::init_services() {
|
||||
&svc::g_itemModule,
|
||||
&svc::g_flowModule,
|
||||
&svc::g_messageModule,
|
||||
&svc::g_gamemodeModule,
|
||||
})
|
||||
{
|
||||
svc::register_module(*module);
|
||||
|
||||
@@ -82,5 +82,6 @@ extern const ServiceModule g_stageModule;
|
||||
extern const ServiceModule g_itemModule;
|
||||
extern const ServiceModule g_flowModule;
|
||||
extern const ServiceModule g_messageModule;
|
||||
extern const ServiceModule g_gamemodeModule;
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
|
||||
@@ -426,7 +426,6 @@ public:
|
||||
}
|
||||
|
||||
void close() { pop(); }
|
||||
void force_close() { Document::hide(true); }
|
||||
|
||||
private:
|
||||
std::function<void()> m_onDestroyed;
|
||||
@@ -931,7 +930,7 @@ void ui_sync_menu_tabs() {
|
||||
}
|
||||
s_menuTabsDirty = false;
|
||||
if (aurora::rmlui::is_initialized()) {
|
||||
ui::MenuBar::rebuild();
|
||||
ui::MenuBar::refresh_tabs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,14 +1018,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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/config.hpp"
|
||||
#include <aurora/aurora.h>
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/game_mode.hpp"
|
||||
|
||||
namespace dusk {
|
||||
|
||||
@@ -156,7 +157,8 @@ UserSettings g_userSettings = {
|
||||
.recordingMode {"game.recordingMode", false},
|
||||
.removeQuestMapMarkers {"game.removeQuestMapMarkers", false},
|
||||
.showInputViewer {"game.showInputViewer", false},
|
||||
.showInputViewerGyro {"game.showInputViewerGyro", false}
|
||||
.showInputViewerGyro {"game.showInputViewerGyro", false},
|
||||
.lastSelectedGameModeId {"game.lastSelectedGameModeId", gamemode::kVanillaGameModeId}
|
||||
},
|
||||
|
||||
.backend = {
|
||||
@@ -306,6 +308,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.removeQuestMapMarkers);
|
||||
Register(g_userSettings.game.showInputViewer);
|
||||
Register(g_userSettings.game.showInputViewerGyro);
|
||||
Register(g_userSettings.game.lastSelectedGameModeId);
|
||||
Register(g_userSettings.game.fastSpinner);
|
||||
Register(g_userSettings.game.infiniteHearts);
|
||||
Register(g_userSettings.game.infiniteArrows);
|
||||
|
||||
@@ -285,6 +285,8 @@ struct UserSettings {
|
||||
ConfigVar<bool> removeQuestMapMarkers;
|
||||
ConfigVar<bool> showInputViewer;
|
||||
ConfigVar<bool> showInputViewerGyro;
|
||||
|
||||
ConfigVar<std::string> lastSelectedGameModeId;
|
||||
} game;
|
||||
|
||||
struct {
|
||||
|
||||
+47
-9
@@ -1,12 +1,52 @@
|
||||
#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/config.hpp"
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
|
||||
namespace dusk {
|
||||
namespace dusk::speedrun {
|
||||
|
||||
SpeedrunInfo m_speedrunInfo;
|
||||
SpeedrunInfo g_speedrunInfo;
|
||||
|
||||
static void onSpeedrunModeActive() {
|
||||
resetForSpeedrunMode();
|
||||
}
|
||||
|
||||
static void onSpeedrunModeDeactive() {
|
||||
restoreFromSpeedrunMode();
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
}
|
||||
|
||||
void registerSpeedrunGameMode() {
|
||||
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);
|
||||
}
|
||||
|
||||
void unregisterSpeedrunGameMode() {
|
||||
dusk::gamemode::getGameModeManager().unregisterGameMode(kSpeedrunGameModeId);
|
||||
}
|
||||
|
||||
void resetForSpeedrunMode() {
|
||||
mDoMain::developmentMode = -1;
|
||||
@@ -45,9 +85,7 @@ void resetForSpeedrunMode() {
|
||||
}
|
||||
|
||||
static void clearSpeedrunOverrides() {
|
||||
config::EnumerateRegistered([](config::ConfigVarBase& cvar) {
|
||||
cvar.clearSpeedrunOverride();
|
||||
});
|
||||
config::EnumerateRegistered([](config::ConfigVarBase& cvar) { cvar.clearSpeedrunOverride(); });
|
||||
}
|
||||
|
||||
void restoreFromSpeedrunMode() {
|
||||
@@ -55,4 +93,4 @@ void restoreFromSpeedrunMode() {
|
||||
aurora_set_pause_on_focus_lost(getSettings().game.pauseOnFocusLost.getValue());
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
} // namespace dusk::speedrun
|
||||
|
||||
+12
-2
@@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
#include <aurora/aurora.h>
|
||||
#include "dusk/game_mode.hpp"
|
||||
|
||||
namespace dusk {
|
||||
namespace dusk::speedrun {
|
||||
|
||||
constexpr const char* kSpeedrunGameModeId = "vanilla_speedrun";
|
||||
|
||||
struct SpeedrunInfo {
|
||||
|
||||
void startRun() {
|
||||
m_isRunStarted = true;
|
||||
m_rtaStartTimestamp = OSGetNativeTime();
|
||||
@@ -40,9 +44,15 @@ struct SpeedrunInfo {
|
||||
OSTime m_igtTimer = 0;
|
||||
};
|
||||
|
||||
extern SpeedrunInfo m_speedrunInfo;
|
||||
extern SpeedrunInfo g_speedrunInfo;
|
||||
|
||||
void registerSpeedrunGameMode();
|
||||
void unregisterSpeedrunGameMode();
|
||||
void resetForSpeedrunMode();
|
||||
void restoreFromSpeedrunMode();
|
||||
|
||||
inline bool isActive() {
|
||||
return dusk::gamemode::getGameModeManager().isCurrentGameMode(kSpeedrunGameModeId);
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
+43
-28
@@ -7,11 +7,13 @@
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "aurora/rmlui.hpp"
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#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"
|
||||
@@ -54,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) {
|
||||
@@ -61,7 +77,11 @@ MenuBar::MenuBar()
|
||||
mTabBar->add_tab("Editor", [this] { push(std::make_unique<EditorWindow>()); });
|
||||
}
|
||||
|
||||
mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); });
|
||||
// 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>()); });
|
||||
}
|
||||
mTabBar->add_tab("Mods", [this] { push(std::make_unique<ModsWindow>()); });
|
||||
for (auto& tab : mods::svc::ui_mod_menu_tabs()) {
|
||||
mTabBar->add_tab(tab.label, std::move(tab.onSelected));
|
||||
@@ -93,9 +113,13 @@ MenuBar::MenuBar()
|
||||
dismiss(modal);
|
||||
return;
|
||||
}
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
dismiss(modal);
|
||||
if (gamemode::getGameModeManager().getRegisteredGameModes().size() > 1) {
|
||||
// If game modes are registered, return to prelaunch on reset.
|
||||
prelaunch_state().returnToPrelaunchOnReset = true;
|
||||
}
|
||||
hide(false);
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -134,39 +158,30 @@ MenuBar::MenuBar()
|
||||
}));
|
||||
});
|
||||
|
||||
if (getSettings().game.speedrunMode) {
|
||||
if (dusk::speedrun::isActive()) {
|
||||
mTabBar->add_tab("Reset Timer", [this] {
|
||||
mTabBar->set_active_tab(-1);
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
m_speedrunInfo.reset();
|
||||
dusk::speedrun::g_speedrunInfo.reset();
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::reset();
|
||||
}
|
||||
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;
|
||||
@@ -236,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,7 +59,6 @@ public:
|
||||
~ModWindow() override;
|
||||
|
||||
void update() override;
|
||||
void force_close() { Document::hide(true); }
|
||||
|
||||
private:
|
||||
Desc mDesc;
|
||||
|
||||
@@ -25,6 +25,9 @@ Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::mo
|
||||
|
||||
auto* actions = append(mDialog, "div");
|
||||
actions->SetClass("modal-actions", true);
|
||||
if (props.isVertical) {
|
||||
actions->SetClass("modal-actions-vertical", true);
|
||||
}
|
||||
|
||||
for (auto& action : mProps.actions) {
|
||||
add_action(std::move(action));
|
||||
@@ -87,9 +90,11 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
}
|
||||
|
||||
int direction = 0;
|
||||
if (cmd == NavCommand::Left) {
|
||||
NavCommand prevCommand = mProps.isVertical ? NavCommand::Up : NavCommand::Left;
|
||||
NavCommand nextCommand = mProps.isVertical ? NavCommand::Down : NavCommand::Right;
|
||||
if (cmd == prevCommand) {
|
||||
direction = -1;
|
||||
} else if (cmd == NavCommand::Right) {
|
||||
} else if (cmd == nextCommand) {
|
||||
direction = 1;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
std::function<void(Modal&)> onDismiss;
|
||||
Rml::String variant;
|
||||
Rml::String icon = "";
|
||||
bool isVertical;
|
||||
};
|
||||
|
||||
explicit Modal(Props props);
|
||||
|
||||
+13
-13
@@ -301,7 +301,7 @@ void Overlay::update() {
|
||||
update_pipeline_progress();
|
||||
|
||||
#if !(defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST))
|
||||
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
|
||||
if (dusk::speedrun::isActive() && getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::updateLiveSplit();
|
||||
if (dusk::speedrun::consumeConnectedEvent()) {
|
||||
push_toast({.title = "LiveSplit connected", .duration = std::chrono::seconds(3)});
|
||||
@@ -313,33 +313,33 @@ void Overlay::update() {
|
||||
#endif
|
||||
|
||||
if (mSpeedrunTimer != nullptr && mSpeedrunRta != nullptr && mSpeedrunIgt != nullptr) {
|
||||
if (getSettings().game.speedrunMode) {
|
||||
if (dusk::speedrun::isActive()) {
|
||||
// L+R+A+Start to reset timer
|
||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) &&
|
||||
mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1))
|
||||
{
|
||||
m_speedrunInfo.reset();
|
||||
dusk::speedrun::g_speedrunInfo.reset();
|
||||
}
|
||||
|
||||
// L+R+A+Y to manually stop timer
|
||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) &&
|
||||
mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1))
|
||||
{
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
m_speedrunInfo.stopRun();
|
||||
if (speedrun::g_speedrunInfo.m_isRunStarted) {
|
||||
speedrun::g_speedrunInfo.stopRun();
|
||||
}
|
||||
}
|
||||
|
||||
OSTime rtaElapsedTime = 0;
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
rtaElapsedTime = OSGetNativeTime() - m_speedrunInfo.m_rtaStartTimestamp;
|
||||
} else if (m_speedrunInfo.m_rtaTimer != 0) {
|
||||
rtaElapsedTime = m_speedrunInfo.m_rtaTimer;
|
||||
if (speedrun::g_speedrunInfo.m_isRunStarted) {
|
||||
rtaElapsedTime = OSGetNativeTime() - speedrun::g_speedrunInfo.m_rtaStartTimestamp;
|
||||
} else if (speedrun::g_speedrunInfo.m_rtaTimer != 0) {
|
||||
rtaElapsedTime = speedrun::g_speedrunInfo.m_rtaTimer;
|
||||
}
|
||||
|
||||
if (m_speedrunInfo.m_isRunStarted && !m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = OSGetTime() - m_speedrunInfo.m_igtStartTimestamp -
|
||||
m_speedrunInfo.m_totalLoadTime;
|
||||
if (speedrun::g_speedrunInfo.m_isRunStarted && !speedrun::g_speedrunInfo.m_isPauseIGT) {
|
||||
speedrun::g_speedrunInfo.m_igtTimer = OSGetTime() - speedrun::g_speedrunInfo.m_igtStartTimestamp -
|
||||
speedrun::g_speedrunInfo.m_totalLoadTime;
|
||||
}
|
||||
|
||||
mSpeedrunTimer->SetAttribute("open", "");
|
||||
@@ -352,7 +352,7 @@ void Overlay::update() {
|
||||
}
|
||||
|
||||
mSpeedrunIgt->SetInnerRML(
|
||||
escape(fmt::format("IGT {}", FormatElapsedTime(m_speedrunInfo.m_igtTimer))));
|
||||
escape(fmt::format("IGT {}", FormatElapsedTime(speedrun::g_speedrunInfo.m_igtTimer))));
|
||||
} else {
|
||||
mSpeedrunTimer->RemoveAttribute("open");
|
||||
}
|
||||
|
||||
+233
-58
@@ -3,10 +3,12 @@
|
||||
#include "dusk/app_info.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/iso_validate.hpp"
|
||||
#include "dusk/language.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/menu_bar.hpp"
|
||||
#include "modal.hpp"
|
||||
#include "mods_window.hpp"
|
||||
#include "preset.hpp"
|
||||
@@ -35,6 +37,8 @@ namespace dusk::ui {
|
||||
namespace {
|
||||
constexpr borealis::Log PrelaunchLog{"dusk::ui::prelaunch"};
|
||||
|
||||
PrelaunchState sPrelaunchState;
|
||||
|
||||
const Rml::String kDocumentSource = R"RML(
|
||||
<rml>
|
||||
<head>
|
||||
@@ -527,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
|
||||
|
||||
@@ -743,61 +884,25 @@ void try_apply_mirrored_layout(Rml::Element* body) {
|
||||
body->SetClass("mirrored", getSettings().game.enableMirrorMode.getValue());
|
||||
}
|
||||
|
||||
Prelaunch::Prelaunch()
|
||||
: Document(kDocumentSource, false, DocumentScope::Prelaunch),
|
||||
mRoot(mDocument->GetElementById("root")) {
|
||||
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();
|
||||
}
|
||||
|
||||
Prelaunch::Prelaunch() : Document(kDocumentSource, false, DocumentScope::Prelaunch) {
|
||||
mRoot = mDocument->GetElementById("root");
|
||||
ensure_initialized();
|
||||
begin_update_check();
|
||||
|
||||
if (auto* menuList = mDocument->GetElementById("menu-list")) {
|
||||
auto& state = prelaunch_state();
|
||||
const bool activeDiscLoaded = !state.activeDiscPath.empty();
|
||||
mMenuButtons.push_back(
|
||||
std::make_unique<Button>(menuList, activeDiscLoaded ? "Play" : "Select Disc Image"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
if (prelaunch_state().activeDiscPath.empty()) {
|
||||
open_iso_picker();
|
||||
return;
|
||||
}
|
||||
|
||||
mDoAud_seStartMenu(kSoundPlay);
|
||||
show_menu_notification();
|
||||
|
||||
if (getSettings().audio.menuSounds) {
|
||||
JAISoundHandle* handle = g_mEnvSeMgr.field_0x144.getHandle();
|
||||
if (*handle) {
|
||||
(*handle)->stop(60);
|
||||
(*handle)->releaseHandle();
|
||||
}
|
||||
}
|
||||
|
||||
if (g_mDoMemCd_control.mCardCommand == mDoMemCd_Ctrl_c::Command_e::COMM_NONE_e) {
|
||||
mDoMemCd_ThdInit();
|
||||
}
|
||||
|
||||
IsGameLaunched = true;
|
||||
pop();
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Settings"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
mRestartSuppressed = false;
|
||||
push(std::make_unique<SettingsWindow>(true));
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Mods"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
mRestartSuppressed = false;
|
||||
push(std::make_unique<ModsWindow>());
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit"));
|
||||
mMenuButtons.back()->on_pressed([] { IsRunning = false; });
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-4");
|
||||
}
|
||||
build_menu_buttons();
|
||||
|
||||
mDiscStatus = mDocument->GetElementById("disc-status");
|
||||
mDiscDetail = mDocument->GetElementById("disc-version");
|
||||
@@ -835,6 +940,68 @@ Prelaunch::Prelaunch()
|
||||
});
|
||||
}
|
||||
|
||||
void Prelaunch::build_menu_buttons() {
|
||||
if (auto* menuList = mDocument->GetElementById("menu-list")) {
|
||||
// Restore the previously selected game mode before creating the play control.
|
||||
gamemode::getGameModeManager().setGameModeToPrevious();
|
||||
|
||||
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();
|
||||
|
||||
if (getSettings().audio.menuSounds) {
|
||||
JAISoundHandle* handle = g_mEnvSeMgr.field_0x144.getHandle();
|
||||
if (*handle) {
|
||||
(*handle)->stop(60);
|
||||
(*handle)->releaseHandle();
|
||||
}
|
||||
}
|
||||
|
||||
if (g_mDoMemCd_control.mCardCommand == mDoMemCd_Ctrl_c::Command_e::COMM_NONE_e) {
|
||||
mDoMemCd_ThdInit();
|
||||
}
|
||||
|
||||
prelaunch_state().firstLaunch = false;
|
||||
IsGameLaunched = true;
|
||||
hide(true);
|
||||
MenuBar::refresh_tabs();
|
||||
});
|
||||
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] {
|
||||
mRestartSuppressed = false;
|
||||
bool showPrelaunchSettings = prelaunch_state().firstLaunch;
|
||||
push(std::make_unique<SettingsWindow>(showPrelaunchSettings));
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Mods"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
mRestartSuppressed = false;
|
||||
push(std::make_unique<ModsWindow>());
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit"));
|
||||
mMenuButtons.back()->on_pressed([] { IsRunning = false; });
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-4");
|
||||
}
|
||||
}
|
||||
|
||||
void Prelaunch::show() {
|
||||
Document::show();
|
||||
mDocument->SetAttribute("open", "");
|
||||
@@ -846,14 +1013,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{
|
||||
@@ -864,7 +1031,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 "
|
||||
@@ -914,8 +1081,8 @@ void Prelaunch::update() {
|
||||
mEntranceAnimationStarted = true;
|
||||
}
|
||||
|
||||
if (!mMenuButtons.empty()) {
|
||||
mMenuButtons[0]->set_text(activeDiscLoaded ? "Play" : "Select Disc Image");
|
||||
for (const auto& button : mMenuButtons) {
|
||||
button->update();
|
||||
}
|
||||
|
||||
const auto discStatusLabel = mDiscStatus->GetElementById("disc-status-label");
|
||||
@@ -1024,6 +1191,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,6 +3,7 @@
|
||||
#include "button.hpp"
|
||||
#include "document.hpp"
|
||||
#include "dusk/iso_validate.hpp"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -13,6 +14,7 @@ namespace dusk::ui {
|
||||
class Prelaunch : public Document {
|
||||
public:
|
||||
Prelaunch();
|
||||
void build_menu_buttons();
|
||||
|
||||
void show() override;
|
||||
void hide(bool close) override;
|
||||
@@ -21,13 +23,15 @@ public:
|
||||
bool visible() const override;
|
||||
bool obscures_game() const override { return true; }
|
||||
|
||||
static void refresh_menu_buttons();
|
||||
|
||||
protected:
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||
|
||||
private:
|
||||
bool mEntranceAnimationStarted = false;
|
||||
bool mRestartSuppressed = false;
|
||||
std::vector<std::unique_ptr<Button> > mMenuButtons;
|
||||
std::vector<std::unique_ptr<Button>> mMenuButtons;
|
||||
Rml::Element* mRoot = nullptr;
|
||||
Rml::Element* mDiscStatus = nullptr;
|
||||
Rml::Element* mDiscDetail = nullptr;
|
||||
@@ -42,6 +46,7 @@ class PrelaunchOptions;
|
||||
|
||||
struct PrelaunchState {
|
||||
bool initialized = false;
|
||||
bool firstLaunch = true;
|
||||
std::string configuredDiscPath;
|
||||
bool configuredDiscCanLaunch = false;
|
||||
iso::DiscInfo configuredDiscInfo{};
|
||||
@@ -57,9 +62,11 @@ struct PrelaunchState {
|
||||
std::string pendingDiscPath;
|
||||
iso::DiscInfo pendingDiscInfo{};
|
||||
iso::ValidationError pendingDiscValidation = iso::ValidationError::Unknown;
|
||||
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;
|
||||
|
||||
@@ -108,6 +108,7 @@ PresetWindow::PresetWindow() : WindowSmall("modal", "modal-dialog") {
|
||||
getSettings().backend.wasPresetChosen.setValue(true);
|
||||
config::save();
|
||||
hide(true);
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -61,6 +61,7 @@ CrashReportWindow::CrashReportWindow() : WindowSmall("modal", "modal-dialog") {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
apply();
|
||||
hide(true);
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+29
-17
@@ -381,7 +381,7 @@ void add_speedrun_disabled_option(Pane& leftPane, Pane& rightPane, ConfigVar<boo
|
||||
config_bool_select(leftPane, rightPane, var, {
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
|
||||
.isDisabled = [] { return dusk::speedrun::isActive(); },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -702,7 +702,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
{
|
||||
.key = "Pause on Focus Lost",
|
||||
.helpText = "Pause the game when window focus is lost.",
|
||||
.isDisabled = [] { return IsMobile || getSettings().game.speedrunMode; },
|
||||
.isDisabled = [] { return IsMobile || dusk::speedrun::isActive(); },
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
@@ -1063,7 +1063,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
leftPane.add_section("Tools");
|
||||
addOption("Turbo Key", getSettings().game.enableTurboKeybind,
|
||||
"Hold Tab to increase game speed by up to 4x.",
|
||||
[] { return getSettings().game.speedrunMode.getValue(); });
|
||||
[] { return dusk::speedrun::isActive(); });
|
||||
addOption("Reset Key (" + Rml::String{hotkeys::DO_RESET} + ")",
|
||||
getSettings().game.enableResetKeybind,
|
||||
"Press " + Rml::String{hotkeys::DO_RESET} + " to reset the game.");
|
||||
@@ -1176,7 +1176,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
getSettings().game.damageMultiplier.setValue(value);
|
||||
config::save();
|
||||
},
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
|
||||
.isDisabled = [] { return dusk::speedrun::isActive(); },
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.damageMultiplier.getValue() !=
|
||||
@@ -1237,16 +1237,16 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.helpText =
|
||||
"Enables speedrunning options while restricting certain gameplay modifiers.",
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
[this](bool enabled) {
|
||||
if (enabled) {
|
||||
resetForSpeedrunMode();
|
||||
dusk::speedrun::registerSpeedrunGameMode();
|
||||
} else {
|
||||
restoreFromSpeedrunMode();
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
if (dusk::speedrun::isActive()) {
|
||||
pop();
|
||||
}
|
||||
dusk::speedrun::unregisterSpeedrunGameMode();
|
||||
}
|
||||
MenuBar::rebuild();
|
||||
MenuBar::refresh_tabs();
|
||||
},
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
|
||||
@@ -1262,13 +1262,13 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
},
|
||||
.isDisabled = [] { return IsMobile || !getSettings().game.speedrunMode; },
|
||||
.isDisabled = [] { return IsMobile || !dusk::speedrun::isActive(); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.showSpeedrunRTATimer,
|
||||
{
|
||||
.key = "Show RTA",
|
||||
.helpText = "Display the RTA timer. IGT is always visible.",
|
||||
.isDisabled = [] { return !getSettings().game.speedrunMode; },
|
||||
.isDisabled = [] { return !dusk::speedrun::isActive(); },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1317,7 +1317,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
[] {
|
||||
return kMagicArmorModes[static_cast<u8>(getSettings().game.armorRupeeDrain.getValue())];
|
||||
},
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
|
||||
.isDisabled = [] { return dusk::speedrun::isActive(); },
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.armorRupeeDrain.getValue() !=
|
||||
@@ -1363,6 +1363,16 @@ 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().returnToPrelaunchOnReset = true;
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_text("Restart Dusklight to the pre-launch menu to change settings, game "
|
||||
"modes, or mods.");
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Notifications",
|
||||
@@ -1451,8 +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.",
|
||||
.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,
|
||||
{
|
||||
@@ -1481,8 +1493,8 @@ 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(); },
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
|
||||
.onChange = [](bool) { MenuBar::refresh_tabs(); },
|
||||
.isDisabled = [] { return dusk::speedrun::isActive(); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewer,
|
||||
{
|
||||
|
||||
+35
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
+4
-2
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "nav_types.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
|
||||
namespace dusk::ui {
|
||||
class Document;
|
||||
|
||||
@@ -89,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;
|
||||
@@ -102,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;
|
||||
|
||||
@@ -27,10 +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 "tracy/Tracy.hpp"
|
||||
#endif
|
||||
|
||||
fapGm_HIO_c::fapGm_HIO_c() {
|
||||
@@ -845,7 +846,11 @@ void fapGm_Execute() {
|
||||
|
||||
cCt_Counter(0);
|
||||
#ifdef TARGET_PC
|
||||
dusk::speedrun::onGameFrame();
|
||||
const dusk::gamemode::GameMode* gameMode =
|
||||
dusk::gamemode::getGameModeManager().getCurrentGameMode();
|
||||
if (gameMode) {
|
||||
gameMode->invokeOnTickFunction();
|
||||
}
|
||||
dusk::AchievementSystem::get().tick();
|
||||
dusk::menu_pointer::end_game_frame();
|
||||
#endif
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
#include "f_op/f_op_overlap_req.h"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#endif
|
||||
|
||||
void fopOvlpReq_SetPeektime(overlap_request_class*, u16);
|
||||
|
||||
@@ -20,12 +23,12 @@ static int fopOvlpReq_phase_Done(overlap_request_class* i_overlapReq) {
|
||||
i_overlapReq->field_0xc = 0;
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
if (dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::m_speedrunInfo.m_isPauseIGT = false;
|
||||
dusk::m_speedrunInfo.m_totalLoadTime +=
|
||||
OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp;
|
||||
dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime();
|
||||
if (dusk::speedrun::isActive()) {
|
||||
if (dusk::speedrun::g_speedrunInfo.m_isRunStarted) {
|
||||
dusk::speedrun::g_speedrunInfo.m_isPauseIGT = false;
|
||||
dusk::speedrun::g_speedrunInfo.m_totalLoadTime +=
|
||||
OSGetTime() - dusk::speedrun::g_speedrunInfo.m_loadStartTimestamp;
|
||||
dusk::speedrun::g_speedrunInfo.m_loadStartTimestamp = OSGetTime();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -96,9 +99,9 @@ static int fopOvlpReq_phase_Create(overlap_request_class* i_overlapReq) {
|
||||
fpcM_Create(i_overlapReq->procname, NULL, NULL);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::m_speedrunInfo.m_isPauseIGT = true;
|
||||
dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime();
|
||||
if (dusk::speedrun::isActive() && dusk::speedrun::g_speedrunInfo.m_isRunStarted) {
|
||||
dusk::speedrun::g_speedrunInfo.m_isPauseIGT = true;
|
||||
dusk::speedrun::g_speedrunInfo.m_loadStartTimestamp = OSGetTime();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
+104
-48
@@ -3,35 +3,35 @@
|
||||
* Memory Card Control
|
||||
*/
|
||||
|
||||
#include <card.h>
|
||||
#include "m_Do/m_Do_MemCard.h"
|
||||
#include <card.h>
|
||||
#include "JSystem/JKernel/JKRAssertHeap.h"
|
||||
#include "m_Do/m_Do_ext.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/os.h"
|
||||
#include "dusk/version.hpp"
|
||||
#include "m_Do/m_Do_MemCardRWmng.h"
|
||||
#include "m_Do/m_Do_Reset.h"
|
||||
#include "m_Do/m_Do_ext.h"
|
||||
#include "os_report.h"
|
||||
#include "dusk/os.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/version.hpp"
|
||||
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
#include <cstring>
|
||||
#include <revolution/nand.h>
|
||||
#include <revolution/sc.h>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#define SLOT_A 0
|
||||
|
||||
#define CHECKSPACE_RESULT_READY 0
|
||||
#define CHECKSPACE_RESULT_READY 0
|
||||
#define CHECKSPACE_RESULT_INSSPACE 1
|
||||
#define CHECKSPACE_RESULT_NOENT 2
|
||||
#define CHECKSPACE_RESULT_ERROR 3
|
||||
#define CHECKSPACE_RESULT_NOENT 2
|
||||
#define CHECKSPACE_RESULT_ERROR 3
|
||||
|
||||
#if PLATFORM_WII
|
||||
s32 my_CARDOpen(s32 chan, const char* fileName, CARDFileInfo* fileInfo) {
|
||||
CARDStat stat;
|
||||
DVDDiskID* diskID = DVDGetCurrentDiskID();
|
||||
|
||||
|
||||
for (int i = 0; i < CARD_MAX_FILE; i++) {
|
||||
s32 ret = CARDGetStatus(chan, i, &stat);
|
||||
if (ret == CARD_RESULT_READY) {
|
||||
@@ -57,10 +57,10 @@ s32 my_CARDOpen(s32 chan, const char* fileName, CARDFileInfo* fileInfo) {
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WII
|
||||
#define NAND_OPEN NANDSafeOpen
|
||||
#define NAND_OPEN NANDSafeOpen
|
||||
#define NAND_CLOSE NANDSafeClose
|
||||
#elif PLATFORM_SHIELD
|
||||
#define NAND_OPEN NANDSimpleSafeOpen
|
||||
#define NAND_OPEN NANDSimpleSafeOpen
|
||||
#define NAND_CLOSE NANDSimpleSafeClose
|
||||
#endif
|
||||
|
||||
@@ -77,15 +77,20 @@ static u8 MemCardStack[STACK_SIZE];
|
||||
static OSThread MemCardThread;
|
||||
|
||||
void mDoMemCd_Ctrl_c::ThdInit() {
|
||||
#if !PLATFORM_SHIELD
|
||||
#ifdef TARGET_PC
|
||||
if (mInitialized) {
|
||||
return;
|
||||
}
|
||||
CARDSetLoadType((CARDFileType)dusk::getSettings().backend.cardFileType.getValue());
|
||||
#endif
|
||||
|
||||
#if !PLATFORM_SHIELD
|
||||
char version[5] = {};
|
||||
char maker[3] = {};
|
||||
std::memcpy(version, dusk::version::getDiskID().gameName, 4);
|
||||
std::memcpy(maker, dusk::version::getDiskID().company, 2);
|
||||
CARDInit(version, maker);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
mCopyToPos = 0;
|
||||
mProbeStat = 2;
|
||||
@@ -93,6 +98,7 @@ void mDoMemCd_Ctrl_c::ThdInit() {
|
||||
|
||||
#if TARGET_PC
|
||||
mCardCommand = COMM_ATTACH_e;
|
||||
mInitialized = true;
|
||||
#else
|
||||
mCardCommand = COMM_NONE_e;
|
||||
#endif
|
||||
@@ -101,8 +107,9 @@ void mDoMemCd_Ctrl_c::ThdInit() {
|
||||
|
||||
OSInitMutex(&mMutex);
|
||||
OSInitCond(&mCond);
|
||||
OSCreateThread(&MemCardThread, (void*(*)(void*))mDoMemCd_main, NULL, MemCardStack + sizeof(MemCardStack),
|
||||
sizeof(MemCardStack), OSGetThreadPriority(OSGetCurrentThread()) + 1, 1);
|
||||
OSCreateThread(&MemCardThread, (void* (*)(void*))mDoMemCd_main, NULL,
|
||||
MemCardStack + sizeof(MemCardStack), sizeof(MemCardStack),
|
||||
OSGetThreadPriority(OSGetCurrentThread()) + 1, 1);
|
||||
OSResumeThread(&MemCardThread);
|
||||
|
||||
// "Memory Card Thread Init\n"
|
||||
@@ -112,23 +119,26 @@ void mDoMemCd_Ctrl_c::ThdInit() {
|
||||
void mDoMemCd_Ctrl_c::main() {
|
||||
do {
|
||||
OSLockMutex(&mMutex);
|
||||
while (mCardCommand == COMM_NONE_e
|
||||
#ifdef TARGET_PC
|
||||
&& !dusk::IsShuttingDown
|
||||
#endif
|
||||
) {
|
||||
bool shutdownThread = dusk::IsShuttingDown;
|
||||
while (mCardCommand == COMM_NONE_e && !shutdownThread) {
|
||||
OSWaitCond(&mCond, &mMutex);
|
||||
shutdownThread = dusk::IsShuttingDown;
|
||||
}
|
||||
OSUnlockMutex(&mMutex);
|
||||
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::IsShuttingDown) {
|
||||
if (shutdownThread) {
|
||||
break;
|
||||
}
|
||||
#else
|
||||
while (mCardCommand == COMM_NONE_e) {
|
||||
OSWaitCond(&mCond, &mMutex);
|
||||
}
|
||||
OSUnlockMutex(&mMutex);
|
||||
#endif
|
||||
|
||||
switch (mCardCommand) {
|
||||
#if PLATFORM_GCN || PLATFORM_WII
|
||||
#if PLATFORM_GCN || PLATFORM_WII
|
||||
case COMM_RESTORE_e:
|
||||
restore();
|
||||
break;
|
||||
@@ -144,16 +154,16 @@ void mDoMemCd_Ctrl_c::main() {
|
||||
case COMM_DETACH_e:
|
||||
detach();
|
||||
break;
|
||||
#elif PLATFORM_SHIELD
|
||||
#elif PLATFORM_SHIELD
|
||||
case COMM_RESTORE_e:
|
||||
case COMM_STORE_e:
|
||||
case COMM_FORMAT_e:
|
||||
case COMM_ATTACH_e:
|
||||
case COMM_DETACH_e:
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
case COMM_RESTORE_NAND_e:
|
||||
restoreNAND();
|
||||
break;
|
||||
@@ -163,7 +173,7 @@ void mDoMemCd_Ctrl_c::main() {
|
||||
case COMM_STORE_SETUP_NAND_e:
|
||||
storeSetUpNAND();
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
OSLockMutex(&mMutex);
|
||||
@@ -180,7 +190,7 @@ void mDoMemCd_Ctrl_c::update() {
|
||||
OSUnlockMutex(&mMutex);
|
||||
OSSignalCond(&mCond);
|
||||
} else if (getStatus(0) != 14) {
|
||||
#if PLATFORM_GCN || PLATFORM_WII
|
||||
#if PLATFORM_GCN || PLATFORM_WII
|
||||
if (CARDProbe(SLOT_A) && getStatus(0) == 0) {
|
||||
OSLockMutex(&mMutex);
|
||||
mProbeStat = 0;
|
||||
@@ -196,7 +206,7 @@ void mDoMemCd_Ctrl_c::update() {
|
||||
OSUnlockMutex(&mMutex);
|
||||
OSSignalCond(&mCond);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +224,12 @@ void mDoMemCd_Ctrl_c::restore() {
|
||||
CARDFileInfo file;
|
||||
field_0x1fc8 = 0;
|
||||
|
||||
s32 ret = CARD_OPEN(mChannel, "gczelda2", &file);
|
||||
#ifdef TARGET_PC
|
||||
const char* fileName = getFileName();
|
||||
#else
|
||||
const char* fileName = "gczelda2";
|
||||
#endif
|
||||
s32 ret = CARD_OPEN(mChannel, fileName, &file);
|
||||
OS_REPORT("\x1b[43;30mCret=%d\n\x1b[m", ret);
|
||||
if (ret == CARD_RESULT_READY) {
|
||||
s32 ret2 = mDoMemCdRWm_Restore(&file, this, sizeof(mData));
|
||||
@@ -271,12 +286,18 @@ void mDoMemCd_Ctrl_c::store() {
|
||||
s32 ret;
|
||||
field_0x1fc8 = 0;
|
||||
|
||||
#ifdef TARGET_PC
|
||||
const char* fileName = getFileName();
|
||||
#else
|
||||
const char* fileName = "gczelda2";
|
||||
#endif
|
||||
|
||||
if (mCardState == CARD_STATE_NO_FILE_e) {
|
||||
#if PLATFORM_GCN
|
||||
ret = CARDCreate(mChannel, "gczelda2", CARD_FILE_SIZE, &file);
|
||||
#else
|
||||
#if PLATFORM_GCN
|
||||
ret = CARDCreate(mChannel, fileName, CARD_FILE_SIZE, &file);
|
||||
#else
|
||||
ret = CARDCreate(mChannel, "zeldaTp.dat", CARD_FILE_SIZE, &file);
|
||||
#endif
|
||||
#endif
|
||||
if (ret == CARD_RESULT_READY || ret == CARD_RESULT_EXIST) {
|
||||
mCardState = CARD_STATE_READY_e;
|
||||
} else {
|
||||
@@ -285,7 +306,7 @@ void mDoMemCd_Ctrl_c::store() {
|
||||
}
|
||||
|
||||
if (mCardState == CARD_STATE_READY_e) {
|
||||
ret = CARD_OPEN(mChannel, "gczelda2", &file);
|
||||
ret = CARD_OPEN(mChannel, fileName, &file);
|
||||
if (ret == CARD_RESULT_READY) {
|
||||
ret = mDoMemCdRWm_Store(&file, this, sizeof(mData));
|
||||
if (ret != CARD_RESULT_READY) {
|
||||
@@ -527,7 +548,13 @@ s32 mDoMemCd_Ctrl_c::mount() {
|
||||
s32 mDoMemCd_Ctrl_c::loadfile() {
|
||||
CARDFileInfo file;
|
||||
|
||||
s32 ret = CARD_OPEN(mChannel, "gczelda2", &file);
|
||||
#ifdef TARGET_PC
|
||||
const char* fileName = getFileName();
|
||||
#else
|
||||
const char* fileName = "gczelda2";
|
||||
#endif
|
||||
|
||||
s32 ret = CARD_OPEN(mChannel, fileName, &file);
|
||||
if (ret == CARD_RESULT_READY) {
|
||||
CARDClose(&file);
|
||||
return TRUE;
|
||||
@@ -543,7 +570,7 @@ s32 mDoMemCd_Ctrl_c::checkspace() {
|
||||
|
||||
if (result != CARD_RESULT_READY) {
|
||||
setCardState(result);
|
||||
return CHECKSPACE_RESULT_ERROR;
|
||||
return CHECKSPACE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (bytesNotUsed < CARD_FILE_SIZE) {
|
||||
@@ -553,7 +580,7 @@ s32 mDoMemCd_Ctrl_c::checkspace() {
|
||||
if (filesNotUsed < 1) {
|
||||
return CHECKSPACE_RESULT_NOENT;
|
||||
}
|
||||
|
||||
|
||||
return CHECKSPACE_RESULT_READY;
|
||||
}
|
||||
|
||||
@@ -596,7 +623,7 @@ void mDoMemCd_Ctrl_c::restoreNAND() {
|
||||
NANDFileInfo file;
|
||||
s32 ret, ret2;
|
||||
|
||||
field_0x1fc8 = 0;
|
||||
field_0x1fc8 = 0;
|
||||
|
||||
ret = NANDOpen("zeldaTp.dat", &file, NAND_ACCESS_RW);
|
||||
OS_REPORT("\x1b[43;30mCret=%d\n\x1b[m", ret);
|
||||
@@ -668,7 +695,8 @@ void mDoMemCd_Ctrl_c::storeNAND() {
|
||||
ret = NANDCreate("banner.bin", NAND_PERM_RUSR | NAND_PERM_WUSR | NAND_PERM_RGRP, 0);
|
||||
printf("NAND bannerFile Create ret:%d\n", ret);
|
||||
if (ret == NAND_RESULT_OK || ret == NAND_RESULT_EXISTS) {
|
||||
ret = NAND_OPEN("banner.bin", &file, NAND_ACCESS_RW, l_safeCopyBuf, sizeof(l_safeCopyBuf));
|
||||
ret = NAND_OPEN(
|
||||
"banner.bin", &file, NAND_ACCESS_RW, l_safeCopyBuf, sizeof(l_safeCopyBuf));
|
||||
if (ret == NAND_RESULT_OK) {
|
||||
ret = mDoMemCdRWm_StoreBannerNAND(&file);
|
||||
if (ret == NAND_RESULT_OK) {
|
||||
@@ -686,7 +714,8 @@ void mDoMemCd_Ctrl_c::storeNAND() {
|
||||
}
|
||||
|
||||
if (ret == NAND_RESULT_OK) {
|
||||
ret = NAND_OPEN("zeldaTp.dat", &file, NAND_ACCESS_RW, l_safeCopyBuf, sizeof(l_safeCopyBuf));
|
||||
ret = NAND_OPEN(
|
||||
"zeldaTp.dat", &file, NAND_ACCESS_RW, l_safeCopyBuf, sizeof(l_safeCopyBuf));
|
||||
if (ret == NAND_RESULT_OK) {
|
||||
ret = mDoMemCdRWm_StoreNAND(&file, this, sizeof(mData));
|
||||
if (ret == NAND_RESULT_OK) {
|
||||
@@ -732,10 +761,11 @@ s32 mDoMemCd_Ctrl_c::SaveSyncNAND() {
|
||||
|
||||
void mDoMemCd_Ctrl_c::storeSetUpNAND() {
|
||||
field_0x1fc8 = 0;
|
||||
|
||||
while ((int)SCCheckStatus() != 0) {}
|
||||
|
||||
#if PLATFORM_WII
|
||||
while ((int)SCCheckStatus() != 0) {
|
||||
}
|
||||
|
||||
#if PLATFORM_WII
|
||||
if (!SCFlush()) {
|
||||
mNandState = NAND_STATE_WRITE_e;
|
||||
printf("== 本体設定Write OK ==\n");
|
||||
@@ -743,9 +773,9 @@ void mDoMemCd_Ctrl_c::storeSetUpNAND() {
|
||||
mNandState = NAND_STATE_FATAL_ERROR_e;
|
||||
printf("== 本体設定Write ERR ==\n");
|
||||
}
|
||||
#else
|
||||
#else
|
||||
mNandState = NAND_STATE_WRITE_e;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
field_0x1fc8 = 1;
|
||||
}
|
||||
@@ -866,7 +896,7 @@ s32 mDoMemCd_Ctrl_c::checkspaceNAND() {
|
||||
s32 result = NANDCheck(3, 2, &answer);
|
||||
if (result != NAND_RESULT_OK) {
|
||||
setNandState(result);
|
||||
return CHECKSPACE_RESULT_ERROR;
|
||||
return CHECKSPACE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (answer == 0) {
|
||||
@@ -876,11 +906,37 @@ s32 mDoMemCd_Ctrl_c::checkspaceNAND() {
|
||||
} else if (answer & 10) {
|
||||
ret = 2;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_PC
|
||||
void mDoMemCd_Ctrl_c::setFileName(const std::string& fileName) {
|
||||
if (mInitialized == false) {
|
||||
mFileName = fileName;
|
||||
} else {
|
||||
OSLockMutex(&mMutex);
|
||||
mFileName = fileName;
|
||||
OSUnlockMutex(&mMutex);
|
||||
}
|
||||
}
|
||||
|
||||
const char* mDoMemCd_Ctrl_c::getFileName() {
|
||||
const char* fileName = "gczelda2";
|
||||
if (mInitialized) {
|
||||
OSLockMutex(&mMutex);
|
||||
}
|
||||
if (!mFileName.empty()) {
|
||||
fileName = mFileName.c_str();
|
||||
}
|
||||
if (mInitialized) {
|
||||
OSUnlockMutex(&mMutex);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
#endif
|
||||
|
||||
DUSK_GAME_DATA mDoMemCd_Ctrl_c g_mDoMemCd_control;
|
||||
|
||||
static int mDoMemCd_main(void*) {
|
||||
|
||||
+24
-4
@@ -4,21 +4,26 @@
|
||||
*/
|
||||
|
||||
#include "m_Do/m_Do_Reset.h"
|
||||
#include <gx.h>
|
||||
#include "JSystem/JAudio2/JASDvdThread.h"
|
||||
#include "JSystem/JUtility/JUTGamePad.h"
|
||||
#include "JSystem/JUtility/JUTXfb.h"
|
||||
#include "SSystem/SComponent/c_API_controller_pad.h"
|
||||
#include <gx.h>
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include "m_Do/m_Do_DVDError.h"
|
||||
#include "m_Do/m_Do_ext.h"
|
||||
#include "m_Do/m_Do_MemCard.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include "m_Do/m_Do_ext.h"
|
||||
|
||||
#if !PLATFORM_GCN
|
||||
#include <revolution/os.h>
|
||||
#endif
|
||||
#include "os_report.h"
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/ui/prelaunch.hpp"
|
||||
#endif
|
||||
|
||||
static void my_OSCancelAlarmAll() {}
|
||||
|
||||
static void destroyVideo() {
|
||||
@@ -104,6 +109,14 @@ 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();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mDoRst::isReset()) {
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +142,8 @@ void mDoRst_resetCallBack(int port, void*) {
|
||||
#else
|
||||
DVDCommandBlock block;
|
||||
block.userData = (void*)-1;
|
||||
while (DVDCheckDiskAsync(&block, checkDiskCallback));
|
||||
while (DVDCheckDiskAsync(&block, checkDiskCallback))
|
||||
;
|
||||
do {
|
||||
check = (int)block.userData;
|
||||
} while (check == -1);
|
||||
@@ -141,6 +155,12 @@ void mDoRst_resetCallBack(int port, void*) {
|
||||
}
|
||||
}
|
||||
mDoRst::onReset();
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::ui::prelaunch_state().returnToPrelaunchOnReset) {
|
||||
dusk::ui::return_to_prelaunch();
|
||||
dusk::ui::prelaunch_state().returnToPrelaunchOnReset = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void mDoRst_shutdownCallBack() {
|
||||
|
||||
+100
-69
@@ -502,6 +502,56 @@ static void log_build_info() {
|
||||
DuskLog.info("Platform: {}", BOREALIS_PLATFORM_NAME);
|
||||
}
|
||||
|
||||
static void mods_init(const std::filesystem::path& mods_dir) {
|
||||
// Mod search directories, highest priority first: user dir (--mods replaces it), then
|
||||
// mods/ next to the app, then install-bundled mods inside the app bundle.
|
||||
{
|
||||
std::vector<dusk::mods::ModSearchDir> modDirs;
|
||||
modDirs.push_back({.path = mods_dir});
|
||||
#if TARGET_ANDROID
|
||||
// APK-bundled mods are extracted to internal storage
|
||||
// by DuskActivity before SDL_main runs.
|
||||
modDirs.push_back({
|
||||
.path = dusk::CachePath / "bundled_mods",
|
||||
});
|
||||
#elif defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("mods"),
|
||||
.inPlaceNative = true,
|
||||
.nativeLibDir = dusk::data::base_path_relative("Frameworks"),
|
||||
});
|
||||
#else
|
||||
#if defined(__APPLE__)
|
||||
// Base path is Contents/Resources; search up for dev mods
|
||||
// TODO: scope to non-CI builds
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("../../../mods").lexically_normal(),
|
||||
.inPlaceNative = true,
|
||||
});
|
||||
// Contents/Resources/mods
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("mods"),
|
||||
.inPlaceNative = true,
|
||||
});
|
||||
#else
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("mods"),
|
||||
.inPlaceNative = true,
|
||||
});
|
||||
#endif
|
||||
#endif
|
||||
dusk::mods::ModLoader::instance().set_search_dirs(std::move(modDirs));
|
||||
}
|
||||
#if TARGET_ANDROID
|
||||
// A user-relocated data dir can live on external storage, which is mounted noexec.
|
||||
// Native mod libraries must be extracted to internal storage.
|
||||
dusk::mods::ModLoader::instance().set_cache_dir(dusk::CachePath / "mod_cache");
|
||||
#endif
|
||||
|
||||
DuskLog.info("Initializing mods...");
|
||||
dusk::mods::ModLoader::instance().init();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PC ENTRY POINT
|
||||
// =========================================================================
|
||||
@@ -675,12 +725,6 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
dusk::presentation::update_frame_rate_preference();
|
||||
|
||||
// Apply after aurora_initialize: speedrun mode mutates cvars whose change callbacks push
|
||||
// values into aurora.
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
dusk::resetForSpeedrunMode();
|
||||
}
|
||||
|
||||
#if BOREALIS_HAS_DISCORD
|
||||
if (dusk::getSettings().game.enableDiscordPresence) {
|
||||
dusk::discord::initialize();
|
||||
@@ -753,6 +797,8 @@ int game_main(int argc, char* argv[]) {
|
||||
saveConfigBeforePrelaunch = true;
|
||||
}
|
||||
|
||||
bool skipPreLaunchUI = dusk::getSettings().backend.skipPreLaunchUI.getValue();
|
||||
|
||||
std::string dvd_path = dusk::getSettings().backend.isoPath;
|
||||
bool dvd_opened = false;
|
||||
if (parsed_arg_options.count("dvd")) {
|
||||
@@ -769,6 +815,7 @@ int game_main(int argc, char* argv[]) {
|
||||
dusk::DiscVerificationState::Unknown);
|
||||
dusk::config::save();
|
||||
dusk::IsGameLaunched = true;
|
||||
skipPreLaunchUI = true;
|
||||
}
|
||||
} else {
|
||||
DuskLog.warn("DVD image from command line failed validation: {}, opening prelaunch UI", dvd_path);
|
||||
@@ -776,8 +823,6 @@ int game_main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
bool skipPreLaunchUI = dusk::getSettings().backend.skipPreLaunchUI.getValue();
|
||||
|
||||
// If we can't load right into the game, stop requesting to load a stage or save
|
||||
if (forcePreLaunchUI || dvd_path.empty()) {
|
||||
if (dusk::StageRequested.set) {
|
||||
@@ -796,6 +841,16 @@ int game_main(int argc, char* argv[]) {
|
||||
dusk::getSettings().backend.isoPath.getValue(),
|
||||
dusk::getSettings().backend.isoVerification.getValue());
|
||||
|
||||
bool showPrelaunchAfterInit = true;
|
||||
if (!dvd_opened && (dusk::getSettings().backend.isoPath.getValue().empty() || (forcePreLaunchUI && skipPreLaunchUI))) {
|
||||
showPrelaunchAfterInit = false;
|
||||
}
|
||||
|
||||
if (showPrelaunchAfterInit) {
|
||||
// Force launchUILoop to not run, we know that the ISO will be loaded.
|
||||
dusk::IsGameLaunched = true;
|
||||
}
|
||||
|
||||
if (!dvd_opened) {
|
||||
if (dusk::getSettings().backend.isoPath.getValue().empty()) {
|
||||
forcePreLaunchUI = true;
|
||||
@@ -810,7 +865,9 @@ int game_main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (!skipPreLaunchUI) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
|
||||
if (!showPrelaunchAfterInit) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
|
||||
}
|
||||
|
||||
// pre game launch ui main loop
|
||||
if (!launchUILoop()) {
|
||||
@@ -844,16 +901,6 @@ int game_main(int argc, char* argv[]) {
|
||||
dusk::IsGameLaunched = true;
|
||||
}
|
||||
|
||||
#if BOREALIS_HAS_SENTRY
|
||||
if (borealis::sentry::get_consent() == borealis::sentry::Consent::Unknown) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::CrashReportWindow>());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!dusk::getSettings().backend.wasPresetChosen) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::PresetWindow>());
|
||||
}
|
||||
|
||||
dusk::version::init();
|
||||
LanguageInit();
|
||||
|
||||
@@ -872,59 +919,43 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
mDoDvdThd::SyncWidthSound = false;
|
||||
|
||||
// Mod search directories, highest priority first: user dir (--mods replaces it), then
|
||||
// mods/ next to the app, then install-bundled mods inside the app bundle.
|
||||
{
|
||||
std::vector<dusk::mods::ModSearchDir> modDirs;
|
||||
if (parsed_arg_options.contains("mods") &&
|
||||
!parsed_arg_options["mods"].as<std::string>().empty())
|
||||
{
|
||||
modDirs.push_back({.path = parsed_arg_options["mods"].as<std::string>()});
|
||||
} else {
|
||||
modDirs.push_back({.path = dusk::ConfigPath / "mods"});
|
||||
}
|
||||
#if TARGET_ANDROID
|
||||
// APK-bundled mods are extracted to internal storage
|
||||
// by DuskActivity before SDL_main runs.
|
||||
modDirs.push_back({
|
||||
.path = dusk::CachePath / "bundled_mods",
|
||||
});
|
||||
#elif defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("mods"),
|
||||
.inPlaceNative = true,
|
||||
.nativeLibDir = dusk::data::base_path_relative("Frameworks"),
|
||||
});
|
||||
#else
|
||||
#if defined(__APPLE__)
|
||||
// Base path is Contents/Resources; search up for dev mods
|
||||
// TODO: scope to non-CI builds
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("../../../mods").lexically_normal(),
|
||||
.inPlaceNative = true,
|
||||
});
|
||||
// Contents/Resources/mods
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("mods"),
|
||||
.inPlaceNative = true,
|
||||
});
|
||||
#else
|
||||
modDirs.push_back({
|
||||
.path = dusk::data::base_path_relative("mods"),
|
||||
.inPlaceNative = true,
|
||||
});
|
||||
#endif
|
||||
#endif
|
||||
dusk::mods::ModLoader::instance().set_search_dirs(std::move(modDirs));
|
||||
// Apply after aurora_initialize: speedrun mode mutates cvars whose change callbacks push
|
||||
// values into aurora.
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
dusk::speedrun::registerSpeedrunGameMode();
|
||||
}
|
||||
|
||||
if (parsed_arg_options.contains("mods") &&
|
||||
!parsed_arg_options["mods"].as<std::string>().empty())
|
||||
{
|
||||
mods_init(parsed_arg_options["mods"].as<std::string>());
|
||||
} else {
|
||||
mods_init(dusk::ConfigPath / "mods");
|
||||
}
|
||||
|
||||
if (!skipPreLaunchUI && showPrelaunchAfterInit) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
|
||||
}
|
||||
|
||||
if (skipPreLaunchUI == true) {
|
||||
if (dusk::gamemode::getGameModeManager().getRegisteredGameModes().size() > 1 && dusk::getSettings().backend.skipPreLaunchUI.getValue()) {
|
||||
// Force pre-launch if we have registered gamemodes that we need to choose from
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
|
||||
} else {
|
||||
// If we get back to prelaunch later, tell it that we've already started the game
|
||||
dusk::ui::prelaunch_state().firstLaunch = false;
|
||||
}
|
||||
}
|
||||
|
||||
#if BOREALIS_HAS_SENTRY
|
||||
if (borealis::sentry::get_consent() == borealis::sentry::Consent::Unknown) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::CrashReportWindow>());
|
||||
}
|
||||
#if TARGET_ANDROID
|
||||
// A user-relocated data dir can live on external storage, which is mounted noexec.
|
||||
// Native mod libraries must be extracted to internal storage.
|
||||
dusk::mods::ModLoader::instance().set_cache_dir(dusk::CachePath / "mod_cache");
|
||||
#endif
|
||||
|
||||
DuskLog.info("Initializing mods...");
|
||||
dusk::mods::ModLoader::instance().init();
|
||||
if (!dusk::getSettings().backend.wasPresetChosen) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::PresetWindow>());
|
||||
}
|
||||
|
||||
OSReport("Starting main01 (Game Loop)...\n");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user