update timer impl

This commit is contained in:
TakaRikka
2026-04-25 21:12:51 -07:00
parent 17949e526b
commit 8e121a7e51
13 changed files with 215 additions and 118 deletions
+1
View File
@@ -1461,6 +1461,7 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp
src/dusk/iso_validate.cpp
src/dusk/livesplit.cpp
src/dusk/offset_ptr.cpp
src/dusk/OSContext.cpp
src/dusk/OSThread.cpp
+1 -2
View File
@@ -119,8 +119,7 @@ struct UserSettings {
ConfigVar<bool> enableTurboKeybind;
// Tools
ConfigVar<bool> speedrunTimer;
ConfigVar<bool> speedrunTimerOverlay;
ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled;
} game;
+10
View File
@@ -23,6 +23,7 @@
#include "d/actor/d_a_npc_tkc.h"
#include <cstring>
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
BOOL daAlink_c::checkEventRun() const {
@@ -4005,6 +4006,15 @@ int daAlink_c::procGanonFinishInit() {
field_0x37c8 = current.pos;
onEndResetFlg1(ERFLG1_SHIELD_BACKBONE);
#if TARGET_PC
if (dusk::getSettings().game.speedrunMode) {
if (dusk::g_imguiConsole.isSpeedrunStart()) {
dusk::g_imguiConsole.stopSpeedrun();
}
}
#endif
return 1;
}
+8
View File
@@ -10,6 +10,7 @@
#include "JSystem/J2DGraph/J2DTextBox.h"
#include "d/d_msg_string.h"
#include "dusk/livesplit.h"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "m_Do/m_Do_controller_pad.h"
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
@@ -141,6 +142,13 @@ void dBrightCheck_c::modeMove() {
mDoAud_seStart(Z2SE_ENTER_GAME, NULL, 0, 0);
#ifdef TARGET_PC
dusk::speedrun::start();
if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) {
// start a new run if a run isn't already in progress
if (!dusk::g_imguiConsole.isSpeedrunStart()) {
dusk::g_imguiConsole.startSpeedrun();
}
}
#endif
mCompleteCheck = true;
mMode = MODE_WAIT_e;
+15 -5
View File
@@ -5,19 +5,20 @@
#include "d/dolzel.h" // IWYU pragma: keep
#include "d/d_s_name.h"
#include "JSystem/JKernel/JKRExpHeap.h"
#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/memory.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"
#include "m_Do/m_Do_graphic.h"
#include "m_Do/m_Do_machine.h"
#include "m_Do/m_Do_mtx.h"
#include "m_Do/m_Do_main.h"
#include "f_op/f_op_overlap_mng.h"
#include "dusk/memory.h"
#include "dusk/settings.h"
#include "m_Do/m_Do_mtx.h"
#if TARGET_PC
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
@@ -412,6 +413,15 @@ void dScnName_c::changeGameScene() {
dKy_clear_game_init();
dComIfGs_resetDan();
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::g_imguiConsole.isSpeedrunStart()) {
dusk::g_imguiConsole.startSpeedrun();
}
}
#endif
}
}
+4 -5
View File
@@ -334,7 +334,6 @@ namespace dusk {
if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuTools.draw();
m_menuSpeedrunTimer.draw();
const auto fpsLabel =
fmt::format(FMT_STRING("FPS: {:.2f}\n"), ImGui::GetIO().Framerate);
@@ -366,12 +365,11 @@ namespace dusk {
}
}
m_menuSpeedrunTimer.drawOverlay();
UpdateDragScroll();
m_menuGame.windowControllerConfig();
m_menuGame.windowInputViewer();
m_menuGame.drawSpeedrunTimerOverlay();
if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::updateLiveSplit();
@@ -381,7 +379,7 @@ namespace dusk {
AddToast("LiveSplit disconnected");
}
if (dusk::IsGameLaunched) {
if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) {
m_menuTools.ShowDebugOverlay();
m_menuTools.ShowCameraOverlay();
m_menuTools.ShowProcessManager();
@@ -392,8 +390,9 @@ namespace dusk {
m_menuTools.ShowPlayerInfo();
m_menuTools.ShowAudioDebug();
m_menuTools.ShowSaveEditor();
m_menuTools.ShowStateShare();
}
m_menuTools.ShowStateShare();
DuskDebugPad(); // temporary, remove later
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
+5 -2
View File
@@ -10,7 +10,6 @@
#include "ImGuiFirstRunPreset.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuSpeedrunTimer.hpp"
#include "ImGuiMenuTools.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "imgui.h"
@@ -30,6 +29,11 @@ public:
static bool CheckMenuViewToggle(ImGuiKey key, bool& active);
void AddToast(std::string_view message, float duration = 3.f);
bool isSpeedrunStart() const { return m_menuGame.isRunStarted(); }
void startSpeedrun() { m_menuGame.startRun(); }
void stopSpeedrun() { m_menuGame.stopRun(); }
void incSpeedrunTotalLoadTime(OSTime time) { m_menuGame.incTotalLoadTime(time); }
private:
struct Toast {
std::string message;
@@ -53,7 +57,6 @@ private:
ImGuiFirstRunPreset m_firstRunPreset;
ImGuiMenuGame m_menuGame;
ImGuiMenuSpeedrunTimer m_menuSpeedrunTimer;
ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last
+119 -2
View File
@@ -12,12 +12,15 @@
#include "dusk/main.h"
#include "dusk/hotkeys.h"
#include "dusk/settings.h"
#include "dusk/livesplit.h"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_graphic.h"
#include <aurora/gfx.h>
#include <SDL3/SDL_gamepad.h>
#include "m_Do/m_Do_main.h"
namespace {
constexpr int kInternalResolutionScaleMax = 12;
} // namespace
@@ -199,6 +202,7 @@ namespace dusk {
ImGui::SeparatorText("Difficulty");
ImGui::BeginDisabled(getSettings().game.speedrunMode);
config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d");
config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath);
@@ -211,6 +215,7 @@ namespace dusk {
ImGui::SetTooltip("Hearts will never drop from enemies,\n"
"pots and various other places.");
}
ImGui::EndDisabled();
ImGui::SeparatorText("Quality of Life");
@@ -280,12 +285,39 @@ namespace dusk {
ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously.");
}
ImGui::SeparatorText("Speedrunning");
if (config::ImGuiCheckbox("Speedrun Mode", getSettings().game.speedrunMode)) {
resetForSpeedrunMode();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables Speedrunning options, while restricting certain gameplay modifiers.");
}
ImGui::BeginDisabled(!getSettings().game.speedrunMode);
bool prevLiveSplit = getSettings().game.liveSplitEnabled;
config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834.");
}
ImGui::EndDisabled();
if ((bool)getSettings().game.liveSplitEnabled != prevLiveSplit) {
if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit();
} else {
dusk::speedrun::disconnectLiveSplit();
DuskToast("LiveSplit disconnected", 3.f);
}
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawCheatsMenu() {
if (ImGui::BeginMenu("Cheats")) {
ImGui::BeginDisabled(getSettings().game.speedrunMode);
ImGui::SeparatorText("Resources");
config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts);
config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows);
@@ -293,8 +325,8 @@ namespace dusk {
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
config::ImGuiCheckbox("Items Don't Despawn", getSettings().game.enableIndefiniteItemDrops);
ImGui::SetItemTooltip("Items Don't Despawn Unless You Load A Different Room In Which Case They Do But Even Under Some Circumstances They Don't, It Is Quite Rare Though");
config::ImGuiCheckbox("No Item Timer", getSettings().game.enableIndefiniteItemDrops);
ImGui::SetItemTooltip("Item drops such as Rupees, Hearts, etc. will never disappear after they drop.");
ImGui::SeparatorText("Abilities");
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
@@ -317,6 +349,8 @@ namespace dusk {
ImGui::SetTooltip("Makes the magic armor work without rupees.");
}
ImGui::EndDisabled();
ImGui::EndMenu();
}
}
@@ -431,10 +465,12 @@ namespace dusk {
ImGui::SeparatorText("Tools");
ImGui::BeginDisabled(getSettings().game.speedrunMode);
config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x.");
}
ImGui::EndDisabled();
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
@@ -971,4 +1007,85 @@ namespace dusk {
ImGui::End();
}
static std::string GetFormattedTime(OSTime ticks) {
OSCalendarTime time;
OSTicksToCalendarTime(ticks, &time);
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", time.hour, time.min, time.sec, time.msec);
}
void ImGuiMenuGame::resetForSpeedrunMode() {
// reset settings that should be off for speedrun mode
mDoMain::developmentMode = -1;
getSettings().game.damageMultiplier.setValue(1);
getSettings().game.instantDeath.setValue(false);
getSettings().game.noHeartDrops.setValue(false);
getSettings().game.infiniteHearts.setValue(false);
getSettings().game.infiniteArrows.setValue(false);
getSettings().game.infiniteBombs.setValue(false);
getSettings().game.infiniteOil.setValue(false);
getSettings().game.infiniteOxygen.setValue(false);
getSettings().game.infiniteRupees.setValue(false);
getSettings().game.enableIndefiniteItemDrops.setValue(false);
getSettings().game.moonJump.setValue(false);
getSettings().game.superClawshot.setValue(false);
getSettings().game.alwaysGreatspin.setValue(false);
getSettings().game.enableFastIronBoots.setValue(false);
getSettings().game.canTransformAnywhere.setValue(false);
getSettings().game.fastSpinner.setValue(false);
getSettings().game.freeMagicArmor.setValue(false);
getSettings().game.enableTurboKeybind.setValue(false);
}
void ImGuiMenuGame::drawSpeedrunTimerOverlay() {
if (!getSettings().game.speedrunMode) {
return;
}
// 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::getTrigStart(PAD_1)) {
m_speedrunInfo.m_endTimestamp = 0;
m_speedrunInfo.m_startTimestamp = 0;
m_speedrunInfo.m_totalLoadTime = 0;
m_speedrunInfo.m_isRunStarted = false;
}
// L+R+A+Z to manually stop timer
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) {
if (m_speedrunInfo.m_isRunStarted) {
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
m_speedrunInfo.m_isRunStarted = false;
}
}
ImGui::SetNextWindowBgAlpha(0.65f);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoDocking
| ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoScrollbar;
if (ImGui::Begin("##SpeedrunTimerWindow", nullptr, flags)) {
OSTime elapsedTime = 0;
if (m_speedrunInfo.m_isRunStarted) {
elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp;
} else if (m_speedrunInfo.m_endTimestamp != 0) {
elapsedTime = m_speedrunInfo.m_endTimestamp;
}
ImGui::Text("RTA");
ImGui::SameLine(60.0f);
ImGuiStringViewText(GetFormattedTime(elapsedTime));
ImGui::Text("IGT");
ImGui::SameLine(60.0f);
ImGuiStringViewText(GetFormattedTime(elapsedTime - m_speedrunInfo.m_totalLoadTime));
}
ImGui::End();
}
}
+22
View File
@@ -15,9 +15,23 @@ namespace dusk {
void windowInputViewer();
void windowControllerConfig();
void drawSpeedrunTimerOverlay();
static void ToggleFullscreen();
static void resetForSpeedrunMode();
bool isRunStarted() const { return m_speedrunInfo.m_isRunStarted; }
void startRun() {
resetForSpeedrunMode();
m_speedrunInfo.m_isRunStarted = true;
m_speedrunInfo.m_startTimestamp = OSGetTime();
}
void stopRun() {
m_speedrunInfo.m_isRunStarted = false;
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
}
void incTotalLoadTime(OSTime time) { m_speedrunInfo.m_totalLoadTime += time; }
private:
void drawAudioMenu();
void drawInputMenu();
@@ -40,6 +54,14 @@ namespace dusk {
bool m_showInputViewerGyro = false;
int m_inputOverlayCorner = 3;
std::string m_controllerName;
struct {
bool m_showTimerWindow = false;
bool m_isRunStarted = false;
OSTime m_startTimestamp = 0;
OSTime m_endTimestamp = 0;
OSTime m_totalLoadTime = 0;
} m_speedrunInfo;
};
}
-96
View File
@@ -1,96 +0,0 @@
#include "fmt/format.h"
#include "imgui.h"
#include "ImGuiMenuSpeedrunTimer.hpp"
#include "ImGuiConfig.hpp"
#include "ImGuiConsole.hpp"
#include "dusk/livesplit.h"
#include "dusk/settings.h"
namespace dusk {
static auto formatTime(uint64_t frames) {
const uint64_t totalSec = frames / 30;
return fmt::format(
FMT_STRING("{:d}:{:02d}:{:02d}.{:02d}"),
totalSec / 3600,
(totalSec / 60) % 60,
totalSec % 60,
(int)(((f32)(frames % 30) / 30.0f) * 100.0f)
);
}
void ImGuiMenuSpeedrunTimer::draw() {
if (!getSettings().game.speedrunTimer) return;
const uint64_t frames = dusk::speedrun::getFrameCount();
if (ImGui::BeginMenu("Timer##speedrun_timer")) {
ImGui::TextUnformatted(formatTime(frames).c_str());
ImGui::Separator();
if (ImGui::MenuItem("Reset")) {
dusk::speedrun::reset();
}
config::ImGuiCheckbox("Show Overlay", getSettings().game.speedrunTimerOverlay);
bool prevLiveSplit = getSettings().game.liveSplitEnabled;
config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834.");
}
if ((bool)getSettings().game.liveSplitEnabled != prevLiveSplit) {
if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit();
} else {
dusk::speedrun::disconnectLiveSplit();
DuskToast("LiveSplit disconnected", 3.f);
}
}
ImGui::EndMenu();
}
}
void ImGuiMenuSpeedrunTimer::drawOverlay() {
if (!getSettings().game.speedrunTimer || !getSettings().game.speedrunTimerOverlay) {
return;
}
const uint64_t frames = dusk::speedrun::getFrameCount();
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float padding = 10.f;
ImGui::SetNextWindowPos(
ImVec2(
viewport->WorkPos.x + viewport->WorkSize.x - padding,
viewport->WorkPos.y + viewport->WorkSize.y - padding
),
ImGuiCond_Always, ImVec2(1.f, 1.f)
);
ImGui::SetNextWindowBgAlpha(0.65f);
const float fixedWidth = ImGui::CalcTextSize("9:59:59.99").x;
ImGui::SetNextWindowContentSize(ImVec2(fixedWidth, 0.f));
if (
ImGui::Begin("##speedrun_overlay", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav
)
) {
ImGui::TextUnformatted(formatTime(frames).c_str());
}
ImGui::End();
}
}
+9
View File
@@ -50,10 +50,14 @@ namespace dusk {
ImGui::BeginDisabled();
}
ImGui::BeginDisabled(getSettings().game.speedrunMode);
ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor);
ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader);
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
ImGui::EndDisabled();
if (!dusk::IsGameLaunched) {
ImGui::EndDisabled();
}
@@ -69,6 +73,8 @@ namespace dusk {
}
if (ImGui::BeginMenu("Debug")) {
ImGui::BeginDisabled(getSettings().game.speedrunMode);
bool developmentMode = mDoMain::developmentMode == 1;
if (ImGui::Checkbox("Development Mode", &developmentMode)) {
mDoMain::developmentMode = developmentMode ? 1 : -1;
@@ -117,6 +123,9 @@ namespace dusk {
}
ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable);
ImGui::EndDisabled();
ImGui::EndMenu();
}
}
+2 -4
View File
@@ -92,8 +92,7 @@ UserSettings g_userSettings = {
.enableTurboKeybind {"game.enableTurboKeybind", false},
// Tools
.speedrunTimer {"game.speedrunTimer", false},
.speedrunTimerOverlay {"game.speedrunTimerOverlay", false},
.speedrunMode {"game.speedrunMode", false},
.liveSplitEnabled {"game.liveSplitEnabled", false}
},
@@ -162,8 +161,7 @@ void registerSettings() {
Register(g_userSettings.game.noLowHpSound);
Register(g_userSettings.game.midnasLamentNonStop);
Register(g_userSettings.game.enableTurboKeybind);
Register(g_userSettings.game.speedrunTimer);
Register(g_userSettings.game.speedrunTimerOverlay);
Register(g_userSettings.game.speedrunMode);
Register(g_userSettings.game.liveSplitEnabled);
Register(g_userSettings.game.fastSpinner);
Register(g_userSettings.game.infiniteHearts);
+19 -2
View File
@@ -4,13 +4,14 @@
*/
#include "f_op/f_op_scene_req.h"
#include <cstdio>
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/logging.h"
#include "f_op/f_op_overlap_mng.h"
#include "f_op/f_op_scene.h"
#include "f_op/f_op_scene_pause.h"
#include "f_pc/f_pc_executor.h"
#include "f_pc/f_pc_manager.h"
#include <cstdio>
#include "dusk/logging.h"
static cPhs_Step fopScnRq_phase_ClearOverlap(scene_request_class* i_sceneReq) {
return fopOvlpM_ClearOfReq() == 1 ? cPhs_NEXT_e : cPhs_INIT_e;
@@ -39,6 +40,10 @@ static cPhs_Step fopScnRq_phase_IsDoneOverlap(scene_request_class* i_sceneReq) {
static BOOL l_fopScnRq_IsUsingOfOverlap;
#if TARGET_PC
static OSTime l_fopScnRq_StartTime = 0;
#endif
static cPhs_Step fopScnRq_phase_Done(scene_request_class* i_sceneReq) {
if (i_sceneReq->create_request.parameters != 1) {
@@ -48,6 +53,14 @@ static cPhs_Step fopScnRq_phase_Done(scene_request_class* i_sceneReq) {
}
l_fopScnRq_IsUsingOfOverlap = FALSE;
#if TARGET_PC
if (dusk::getSettings().game.speedrunMode) {
if (dusk::g_imguiConsole.isSpeedrunStart()) {
dusk::g_imguiConsole.incSpeedrunTotalLoadTime(OSGetTime() - l_fopScnRq_StartTime);
}
}
#endif
return cPhs_NEXT_e;
}
@@ -88,6 +101,10 @@ static scene_request_class* fopScnRq_FadeRequest(s16 i_procname, u16 i_peektime)
req = fopOvlpM_Request(i_procname, i_peektime);
if (req != NULL) {
l_fopScnRq_IsUsingOfOverlap = TRUE;
#if TARGET_PC
l_fopScnRq_StartTime = OSGetTime();
#endif
}
}