mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-23 06:34:15 -04:00
improved speedrun mode
This commit is contained in:
+2
-3
@@ -1435,6 +1435,7 @@ set(DUSK_FILES
|
||||
src/dusk/layout.cpp
|
||||
src/dusk/logging.cpp
|
||||
src/dusk/settings.cpp
|
||||
src/dusk/speedrun.cpp
|
||||
src/dusk/stubs.cpp
|
||||
src/dusk/update_check.cpp
|
||||
src/dusk/update_check.hpp
|
||||
@@ -1444,9 +1445,7 @@ set(DUSK_FILES
|
||||
src/dusk/imgui/ImGuiConsole.cpp
|
||||
src/dusk/imgui/ImGuiEngine.cpp
|
||||
src/dusk/imgui/ImGuiEngine.hpp
|
||||
src/dusk/imgui/ImGuiMenuGame.cpp
|
||||
src/dusk/imgui/ImGuiMenuGame.hpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.hpp
|
||||
src/dusk/imgui/ImGuiMenuTools.cpp
|
||||
src/dusk/imgui/ImGuiMenuTools.hpp
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef DUSK_CONFIG_HPP
|
||||
#define DUSK_CONFIG_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "config_var.hpp"
|
||||
@@ -111,6 +112,11 @@ void Save();
|
||||
*/
|
||||
ConfigVarBase* GetConfigVar(std::string_view name);
|
||||
|
||||
/**
|
||||
* \brief Call a function on every registered CVar.
|
||||
*/
|
||||
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
|
||||
|
||||
template <ConfigValue T>
|
||||
const ConfigImplBase* GetConfigImpl() {
|
||||
static ConfigImpl<T> config;
|
||||
|
||||
@@ -48,6 +48,13 @@ enum class ConfigVarLayer : u8 {
|
||||
* Will not get saved to config.
|
||||
*/
|
||||
Override,
|
||||
|
||||
/**
|
||||
* The CVar is temporarily overridden by speedrun mode.
|
||||
* Will not get saved to config. Cleared when speedrun mode is disabled.
|
||||
* Lower priority than Override, so launch args still win.
|
||||
*/
|
||||
Speedrun,
|
||||
};
|
||||
|
||||
class ConfigImplBase;
|
||||
@@ -113,6 +120,12 @@ public:
|
||||
* This is necessary to make it legal to access.
|
||||
*/
|
||||
void markRegistered();
|
||||
|
||||
/**
|
||||
* Clear a speedrun-mode override if one is active on this CVar.
|
||||
* Safe to call on any CVar, no-op if not at the Speedrun layer.
|
||||
*/
|
||||
virtual void clearSpeedrunOverride() {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -189,6 +202,7 @@ public:
|
||||
case ConfigVarLayer::Value:
|
||||
return value;
|
||||
case ConfigVarLayer::Override:
|
||||
case ConfigVarLayer::Speedrun:
|
||||
return overrideValue;
|
||||
default:
|
||||
abort();
|
||||
@@ -239,6 +253,38 @@ public:
|
||||
overrideValue = std::move(newValue);
|
||||
layer = ConfigVarLayer::Override;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Give a CVar a speedrun-mode override value.
|
||||
*
|
||||
* Lower priority than a launch-arg override. Cleared when speedrun mode is disabled.
|
||||
* The overridden value will not get saved to config.
|
||||
*
|
||||
* @param newValue The new value the CVar will get.
|
||||
*/
|
||||
void setSpeedrunValue(T newValue) {
|
||||
checkRegistered();
|
||||
if (layer != ConfigVarLayer::Override) {
|
||||
overrideValue = std::move(newValue);
|
||||
layer = ConfigVarLayer::Speedrun;
|
||||
}
|
||||
}
|
||||
|
||||
void clearOverride() {
|
||||
checkRegistered();
|
||||
if (layer == ConfigVarLayer::Override) {
|
||||
overrideValue = {};
|
||||
layer = ConfigVarLayer::Value;
|
||||
}
|
||||
}
|
||||
|
||||
void clearSpeedrunOverride() override {
|
||||
checkRegistered();
|
||||
if (layer == ConfigVarLayer::Speedrun) {
|
||||
overrideValue = {};
|
||||
layer = ConfigVarLayer::Value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ struct UserSettings {
|
||||
// Tools
|
||||
ConfigVar<bool> speedrunMode;
|
||||
ConfigVar<bool> liveSplitEnabled;
|
||||
ConfigVar<bool> showSpeedrunRTATimer;
|
||||
ConfigVar<bool> recordingMode;
|
||||
} game;
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
struct SpeedrunInfo {
|
||||
void startRun() {
|
||||
m_isRunStarted = true;
|
||||
m_startTimestamp = OSGetTime();
|
||||
}
|
||||
|
||||
void stopRun() {
|
||||
m_isRunStarted = false;
|
||||
m_endTimestamp = OSGetTime() - m_startTimestamp;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_isRunStarted = false;
|
||||
m_startTimestamp = 0;
|
||||
m_endTimestamp = 0;
|
||||
m_isPauseIGT = false;
|
||||
m_loadStartTimestamp = 0;
|
||||
m_totalLoadTime = 0;
|
||||
m_igtTimer = 0;
|
||||
}
|
||||
|
||||
bool m_isRunStarted = false;
|
||||
OSTime m_startTimestamp = 0;
|
||||
OSTime m_endTimestamp = 0;
|
||||
|
||||
bool m_isPauseIGT = false;
|
||||
OSTime m_loadStartTimestamp = 0;
|
||||
OSTime m_totalLoadTime = 0;
|
||||
OSTime m_igtTimer = 0;
|
||||
};
|
||||
|
||||
extern SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
void resetForSpeedrunMode();
|
||||
|
||||
} // namespace dusk
|
||||
@@ -201,6 +201,37 @@ fps {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
speedrun-timer {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
background-color: rgba(0, 0, 0, 65%);
|
||||
padding: 2dp 4dp;
|
||||
pointer-events: none;
|
||||
font-family: "Noto Mono";
|
||||
font-size: 16dp;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
speedrun-timer[open] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
speedrun-rta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
speedrun-rta[open] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
speedrun-igt {
|
||||
display: block;
|
||||
}
|
||||
|
||||
fps[open] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
|
||||
BOOL daAlink_c::checkEventRun() const {
|
||||
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#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"
|
||||
|
||||
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
|
||||
@@ -146,7 +147,7 @@ void dBrightCheck_c::modeMove() {
|
||||
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::ImGuiMenuGame::resetForSpeedrunMode();
|
||||
dusk::resetForSpeedrunMode();
|
||||
dusk::m_speedrunInfo.startRun();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@
|
||||
#include "d/d_s_name.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#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"
|
||||
@@ -418,7 +419,7 @@ void dScnName_c::changeGameScene() {
|
||||
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::ImGuiMenuGame::resetForSpeedrunMode();
|
||||
dusk::resetForSpeedrunMode();
|
||||
dusk::m_speedrunInfo.startRun();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,3 +264,9 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void dusk::config::EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) {
|
||||
for (auto& pair : RegisteredConfigVars) {
|
||||
callback(*pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,6 @@ namespace dusk {
|
||||
// so make the window bg fully transparent temporarily
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
if (showMenu && ImGui::BeginMainMenuBar()) {
|
||||
m_menuGame.draw();
|
||||
m_menuTools.draw();
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
@@ -282,7 +281,7 @@ namespace dusk {
|
||||
|
||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||
m_isLaunchInitialized = true;
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::connectLiveSplit();
|
||||
}
|
||||
}
|
||||
@@ -353,15 +352,6 @@ namespace dusk {
|
||||
}
|
||||
|
||||
m_menuTools.ShowInputViewer();
|
||||
m_menuGame.drawSpeedrunTimerOverlay();
|
||||
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::updateLiveSplit();
|
||||
if (dusk::speedrun::consumeConnectedEvent())
|
||||
AddToast("LiveSplit connected");
|
||||
else if (dusk::speedrun::consumeDisconnectedEvent())
|
||||
AddToast("LiveSplit disconnected");
|
||||
}
|
||||
|
||||
if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) {
|
||||
m_menuTools.ShowDebugOverlay();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
#include "ImGuiMenuGame.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "imgui.h"
|
||||
@@ -44,8 +43,6 @@ private:
|
||||
ImVec2 m_dragScrollLastMousePos = {};
|
||||
std::deque<Toast> m_toasts;
|
||||
|
||||
ImGuiMenuGame m_menuGame;
|
||||
|
||||
// Keep always last
|
||||
ImGuiMenuTools m_menuTools;
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
#include "fmt/format.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "ImGuiConfig.hpp"
|
||||
|
||||
#include "dusk/main.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
|
||||
namespace dusk {
|
||||
ImGuiMenuGame::ImGuiMenuGame() {}
|
||||
|
||||
void ImGuiMenuGame::draw() {}
|
||||
|
||||
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);
|
||||
getSettings().game.debugFlyCam.setValue(false);
|
||||
getSettings().game.autoSave.setValue(false);
|
||||
}
|
||||
|
||||
SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
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::getTrigZ(PAD_1)) {
|
||||
m_speedrunInfo.reset();
|
||||
}
|
||||
|
||||
// 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::getTrigY(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));
|
||||
|
||||
if (!m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
|
||||
}
|
||||
|
||||
ImGui::Text("IGT");
|
||||
ImGui::SameLine(60.0f);
|
||||
ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer));
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#ifndef DUSK_IMGUI_MENUGAME_HPP
|
||||
#define DUSK_IMGUI_MENUGAME_HPP
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <pad.h>
|
||||
#include <string>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dusk {
|
||||
struct SpeedrunInfo {
|
||||
void startRun() {
|
||||
m_isRunStarted = true;
|
||||
m_startTimestamp = OSGetTime();
|
||||
}
|
||||
|
||||
void stopRun() {
|
||||
m_isRunStarted = false;
|
||||
m_endTimestamp = OSGetTime() - m_startTimestamp;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_isRunStarted = false;
|
||||
m_startTimestamp = 0;
|
||||
m_endTimestamp = 0;
|
||||
m_isPauseIGT = false;
|
||||
m_loadStartTimestamp = 0;
|
||||
m_totalLoadTime = 0;
|
||||
m_igtTimer = 0;
|
||||
}
|
||||
|
||||
bool m_isRunStarted = false;
|
||||
OSTime m_startTimestamp = 0;
|
||||
OSTime m_endTimestamp = 0;
|
||||
|
||||
bool m_isPauseIGT = false;
|
||||
OSTime m_loadStartTimestamp = 0;
|
||||
OSTime m_totalLoadTime = 0;
|
||||
OSTime m_igtTimer = 0;
|
||||
};
|
||||
|
||||
extern SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
class ImGuiMenuGame {
|
||||
public:
|
||||
ImGuiMenuGame();
|
||||
void draw();
|
||||
|
||||
void drawSpeedrunTimerOverlay();
|
||||
|
||||
static void resetForSpeedrunMode();
|
||||
|
||||
private:
|
||||
bool m_showTimerWindow = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // DUSK_IMGUI_MENUGAME_HPP
|
||||
+151
-32
@@ -2,19 +2,45 @@
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using socket_t = SOCKET;
|
||||
static void closeSocket(socket_t s) { closesocket(s); }
|
||||
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) { close(s); }
|
||||
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
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
@@ -24,12 +50,17 @@
|
||||
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 void sendCmd(const char* cmd) {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
@@ -37,18 +68,20 @@ static void sendCmd(const char* cmd) {
|
||||
}
|
||||
|
||||
char msg[64];
|
||||
int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd);
|
||||
const int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd);
|
||||
if (len <= 0 || len >= static_cast<int>(sizeof(msg))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (send(sock, msg, len, 0) >= 0) {
|
||||
if (send(sock, msg, len, kSendFlags) >= 0) {
|
||||
if (!connected) {
|
||||
connected = connectPending = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
int err = WSAGetLastError();
|
||||
const int err = WSAGetLastError();
|
||||
if (err == WSAEWOULDBLOCK || err == WSAENOTCONN) {
|
||||
return;
|
||||
}
|
||||
@@ -58,10 +91,13 @@ static void sendCmd(const char* cmd) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (connected) disconnectPending = true;
|
||||
if (connected) {
|
||||
disconnectPending = true;
|
||||
}
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = connectPending = false;
|
||||
reconnectCounter = 0;
|
||||
}
|
||||
|
||||
uint64_t getFrameCount() {
|
||||
@@ -89,57 +125,93 @@ void start() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
running = true;
|
||||
startPending = true;
|
||||
frameCount = 0;
|
||||
wasLoading = false;
|
||||
sendCmd("initgametime");
|
||||
sendCmd("reset");
|
||||
sendCmd("starttimer");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
running = false;
|
||||
startPending = false;
|
||||
frameCount = 0;
|
||||
wasLoading = false;
|
||||
sendCmd("reset");
|
||||
}
|
||||
|
||||
void connectLiveSplit(const char* host, int port) {
|
||||
#if _WIN32
|
||||
WSADATA wd{}; WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
|
||||
static void reconnect() {
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closeSocket(sock); sock = INVALID_SOCKET;
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
connected = connectPending = false;
|
||||
|
||||
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
u_long nb = 1;
|
||||
ioctlsocket(sock, FIONBIO, &nb);
|
||||
if (ioctlsocket(sock, FIONBIO, &nb) != 0) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
|
||||
const int fl = fcntl(sock, F_GETFL, 0);
|
||||
if (fl < 0 || fcntl(sock, F_SETFL, fl | O_NONBLOCK) < 0) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
sockaddr_in addr{}; addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons((uint16_t)port);
|
||||
inet_pton(AF_INET, host, &addr.sin_addr);
|
||||
connect(sock, (sockaddr*)&addr, sizeof(addr));
|
||||
sendCmd("initgametime");
|
||||
#if defined(__APPLE__)
|
||||
{
|
||||
int opt = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
|
||||
}
|
||||
#endif
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(storedPort));
|
||||
if (inet_pton(AF_INET, storedHost, &addr.sin_addr) != 1) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
return;
|
||||
}
|
||||
|
||||
const int cr = connect(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
|
||||
#if _WIN32
|
||||
const bool connectPending_ = cr < 0 && WSAGetLastError() == WSAEWOULDBLOCK;
|
||||
#else
|
||||
const bool connectPending_ = cr < 0 && errno == EINPROGRESS;
|
||||
#endif
|
||||
if (cr != 0 && !connectPending_) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void connectLiveSplit(const char* host, int port) {
|
||||
#if _WIN32
|
||||
WSADATA wd{};
|
||||
WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
snprintf(storedHost, sizeof(storedHost), "%s", host);
|
||||
storedPort = port;
|
||||
reconnect();
|
||||
}
|
||||
|
||||
void disconnectLiveSplit() {
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = false;
|
||||
}
|
||||
connected = connectPending = disconnectPending = false;
|
||||
}
|
||||
|
||||
bool consumeConnectedEvent() { bool v = connectPending; connectPending = false; return v; }
|
||||
@@ -147,29 +219,76 @@ bool consumeDisconnectedEvent() { bool v = disconnectPending; disconnectPending
|
||||
|
||||
void updateLiveSplit() {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
if ((reconnectCounter++ % 30) == 0) {
|
||||
reconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
fd_set writefds, errorfds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_ZERO(&errorfds);
|
||||
FD_SET(sock, &writefds);
|
||||
FD_SET(sock, &errorfds);
|
||||
timeval tv{0, 0};
|
||||
#if _WIN32
|
||||
const int r = select(0, nullptr, &writefds, &errorfds, &tv);
|
||||
#else
|
||||
const int r = select(sock + 1, nullptr, &writefds, &errorfds, &tv);
|
||||
#endif
|
||||
if (r < 0 || FD_ISSET(sock, &errorfds) || socketError(sock) != 0) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
reconnectCounter = 0;
|
||||
return;
|
||||
}
|
||||
if (!FD_ISSET(sock, &writefds)) {
|
||||
return;
|
||||
}
|
||||
sendCmd("initgametime");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startPending) {
|
||||
startPending = false;
|
||||
sendCmd("initgametime");
|
||||
sendCmd("reset");
|
||||
sendCmd("starttimer");
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
if ((idleProbeCounter++ % 60) == 0) {
|
||||
char buf;
|
||||
const int r = recv(sock, &buf, 1, 0);
|
||||
if (r == 0
|
||||
#if _WIN32
|
||||
|| (r < 0 && WSAGetLastError() != WSAEWOULDBLOCK)
|
||||
#else
|
||||
|| (r < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
#endif
|
||||
) {
|
||||
if (connected) {
|
||||
disconnectPending = true;
|
||||
}
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = connectPending = false;
|
||||
reconnectCounter = 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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",
|
||||
(uint32_t)(totalSec / 3600),
|
||||
(uint32_t)((totalSec / 60) % 60),
|
||||
(uint32_t)(totalSec % 60),
|
||||
(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);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ UserSettings g_userSettings = {
|
||||
// Tools
|
||||
.speedrunMode {"game.speedrunMode", false},
|
||||
.liveSplitEnabled {"game.liveSplitEnabled", false},
|
||||
.showSpeedrunRTATimer {"game.showSpeedrunRTATimer", true},
|
||||
.recordingMode {"game.recordingMode", false}
|
||||
},
|
||||
|
||||
@@ -197,6 +198,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.enableTurboKeybind);
|
||||
Register(g_userSettings.game.speedrunMode);
|
||||
Register(g_userSettings.game.liveSplitEnabled);
|
||||
Register(g_userSettings.game.showSpeedrunRTATimer);
|
||||
Register(g_userSettings.game.recordingMode);
|
||||
Register(g_userSettings.game.fastSpinner);
|
||||
Register(g_userSettings.game.infiniteHearts);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
void resetForSpeedrunMode() {
|
||||
mDoMain::developmentMode = -1;
|
||||
|
||||
getSettings().game.enableTurboKeybind.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.damageMultiplier.setSpeedrunValue(1);
|
||||
getSettings().game.instantDeath.setSpeedrunValue(false);
|
||||
getSettings().game.noHeartDrops.setSpeedrunValue(false);
|
||||
getSettings().game.autoSave.setSpeedrunValue(false);
|
||||
getSettings().game.sunsSong.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.infiniteHearts.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteArrows.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteBombs.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOil.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOxygen.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteRupees.setSpeedrunValue(false);
|
||||
getSettings().game.enableIndefiniteItemDrops.setSpeedrunValue(false);
|
||||
getSettings().game.moonJump.setSpeedrunValue(false);
|
||||
getSettings().game.superClawshot.setSpeedrunValue(false);
|
||||
getSettings().game.alwaysGreatspin.setSpeedrunValue(false);
|
||||
getSettings().game.enableFastIronBoots.setSpeedrunValue(false);
|
||||
getSettings().game.canTransformAnywhere.setSpeedrunValue(false);
|
||||
getSettings().game.fastSpinner.setSpeedrunValue(false);
|
||||
getSettings().game.freeMagicArmor.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
|
||||
aurora_set_pause_on_focus_lost(false);
|
||||
|
||||
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
|
||||
getSettings().game.recordingMode.setSpeedrunValue(false);
|
||||
getSettings().game.debugFlyCam.setSpeedrunValue(false);
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "aurora/rmlui.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "editor.hpp"
|
||||
@@ -58,6 +60,8 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
}
|
||||
|
||||
mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); });
|
||||
|
||||
|
||||
mTabBar->add_tab("Reset", [this] {
|
||||
mTabBar->set_active_tab(-1);
|
||||
const auto dismiss = [](Modal& modal) { modal.pop(); };
|
||||
@@ -125,6 +129,18 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
}));
|
||||
});
|
||||
|
||||
if (getSettings().game.speedrunMode) {
|
||||
mTabBar->add_tab("Reset Timer", [this] {
|
||||
mTabBar->set_active_tab(-1);
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
m_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") &&
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "fmt/format.h"
|
||||
#include "magic_enum.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
@@ -9,6 +12,7 @@
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
#include <dolphin/pad.h>
|
||||
#include <m_Do/m_Do_main.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
@@ -25,6 +29,10 @@ const Rml::String kDocumentSource = R"RML(
|
||||
</head>
|
||||
<body>
|
||||
<fps id="fps" />
|
||||
<speedrun-timer id="speedrun-timer">
|
||||
<speedrun-rta id="speedrun-rta" />
|
||||
<speedrun-igt id="speedrun-igt" />
|
||||
</speedrun-timer>
|
||||
</body>
|
||||
</rml>
|
||||
)RML";
|
||||
@@ -204,8 +212,17 @@ void Overlay::advance_fps_counter(float& outFps, Uint64 perfFreq) {
|
||||
outFps = static_cast<float>(1.0 / avgSeconds);
|
||||
}
|
||||
|
||||
static std::string FormatTime(OSTime ticks) {
|
||||
OSCalendarTime t;
|
||||
OSTicksToCalendarTime(ticks, &t);
|
||||
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec);
|
||||
}
|
||||
|
||||
Overlay::Overlay() : Document(kDocumentSource) {
|
||||
mFpsCounter = mDocument->GetElementById("fps");
|
||||
mSpeedrunTimer = mDocument->GetElementById("speedrun-timer");
|
||||
mSpeedrunRta = mDocument->GetElementById("speedrun-rta");
|
||||
mSpeedrunIgt = mDocument->GetElementById("speedrun-igt");
|
||||
|
||||
listen(mDocument, Rml::EventId::Focus, [](Rml::Event&) { Log.warn("Overlay received focus"); });
|
||||
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
||||
@@ -268,6 +285,63 @@ void Overlay::update() {
|
||||
}
|
||||
}
|
||||
|
||||
#if !(defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST))
|
||||
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::updateLiveSplit();
|
||||
if (dusk::speedrun::consumeConnectedEvent()) {
|
||||
push_toast({.title = "LiveSplit connected", .duration = std::chrono::seconds(3)});
|
||||
}
|
||||
if (dusk::speedrun::consumeDisconnectedEvent()) {
|
||||
push_toast({.title = "LiveSplit disconnected", .duration = std::chrono::seconds(3)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mSpeedrunTimer != nullptr && mSpeedrunRta != nullptr && mSpeedrunIgt != nullptr) {
|
||||
if (getSettings().game.speedrunMode) {
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
m_speedrunInfo.m_isRunStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
|
||||
}
|
||||
|
||||
mSpeedrunTimer->SetAttribute("open", "");
|
||||
|
||||
if (getSettings().game.showSpeedrunRTATimer) {
|
||||
mSpeedrunRta->SetAttribute("open", "");
|
||||
mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatTime(elapsedTime))));
|
||||
} else {
|
||||
mSpeedrunRta->RemoveAttribute("open");
|
||||
}
|
||||
|
||||
mSpeedrunIgt->SetInnerRML(escape(fmt::format("IGT {}", FormatTime(m_speedrunInfo.m_igtTimer))));
|
||||
} else {
|
||||
mSpeedrunTimer->RemoveAttribute("open");
|
||||
}
|
||||
}
|
||||
|
||||
const bool showControllerWarning = PADGetIndexForPort(PAD_CHAN0) < 0 &&
|
||||
PADGetKeyButtonBindings(PAD_CHAN0, nullptr) == nullptr &&
|
||||
dynamic_cast<Window*>(top_document()) == nullptr &&
|
||||
|
||||
@@ -21,6 +21,9 @@ protected:
|
||||
Rml::Element* mCurrentToast = nullptr;
|
||||
Rml::Element* mControllerWarning = nullptr;
|
||||
Rml::Element* mMenuNotification = nullptr;
|
||||
Rml::Element* mSpeedrunTimer = nullptr;
|
||||
Rml::Element* mSpeedrunRta = nullptr;
|
||||
Rml::Element* mSpeedrunIgt = nullptr;
|
||||
clock::time_point mCurrentToastStartTime;
|
||||
clock::time_point mMenuNotificationStartTime;
|
||||
|
||||
|
||||
+84
-54
@@ -166,29 +166,46 @@ AuroraBackend configured_backend() {
|
||||
void reset_for_speedrun_mode() {
|
||||
mDoMain::developmentMode = -1;
|
||||
|
||||
getSettings().game.damageMultiplier.setValue(1);
|
||||
getSettings().game.instantDeath.setValue(false);
|
||||
getSettings().game.noHeartDrops.setValue(false);
|
||||
getSettings().game.enableTurboKeybind.setSpeedrunValue(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.damageMultiplier.setSpeedrunValue(1);
|
||||
getSettings().game.instantDeath.setSpeedrunValue(false);
|
||||
getSettings().game.noHeartDrops.setSpeedrunValue(false);
|
||||
getSettings().game.autoSave.setSpeedrunValue(false);
|
||||
getSettings().game.sunsSong.setSpeedrunValue(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.infiniteHearts.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteArrows.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteBombs.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOil.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOxygen.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteRupees.setSpeedrunValue(false);
|
||||
getSettings().game.enableIndefiniteItemDrops.setSpeedrunValue(false);
|
||||
getSettings().game.moonJump.setSpeedrunValue(false);
|
||||
getSettings().game.superClawshot.setSpeedrunValue(false);
|
||||
getSettings().game.alwaysGreatspin.setSpeedrunValue(false);
|
||||
getSettings().game.enableFastIronBoots.setSpeedrunValue(false);
|
||||
getSettings().game.canTransformAnywhere.setSpeedrunValue(false);
|
||||
getSettings().game.fastSpinner.setSpeedrunValue(false);
|
||||
getSettings().game.freeMagicArmor.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.enableTurboKeybind.setValue(false);
|
||||
getSettings().game.debugFlyCam.setValue(false);
|
||||
getSettings().game.autoSave.setValue(false);
|
||||
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
|
||||
aurora_set_pause_on_focus_lost(false);
|
||||
|
||||
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
|
||||
getSettings().game.recordingMode.setSpeedrunValue(false);
|
||||
getSettings().game.debugFlyCam.setSpeedrunValue(false);
|
||||
}
|
||||
|
||||
void clear_speedrun_overrides() {
|
||||
config::EnumerateRegistered([](config::ConfigVarBase& cvar) {
|
||||
cvar.clearSpeedrunOverride();
|
||||
});
|
||||
}
|
||||
|
||||
void restore_from_speedrun_mode() {
|
||||
clear_speedrun_overrides();
|
||||
aurora_set_pause_on_focus_lost(getSettings().game.pauseOnFocusLost.getValue());
|
||||
}
|
||||
|
||||
const Rml::String kInternalResolutionHelpText =
|
||||
@@ -252,6 +269,15 @@ SelectButton& config_bool_select(
|
||||
return button;
|
||||
}
|
||||
|
||||
void add_speedrun_disabled_option(Pane& leftPane, Pane& rightPane, ConfigVar<bool>& var,
|
||||
const Rml::String& key, const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, var, {
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
}
|
||||
|
||||
SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<float>& var,
|
||||
Rml::String key, Rml::String helpText, int min, int max, int step = 5,
|
||||
std::function<bool()> isDisabled = {}) {
|
||||
@@ -492,7 +518,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
|
||||
{
|
||||
.key = "Pause on Focus Lost",
|
||||
.isDisabled = [] { return IsMobile; },
|
||||
.helpText = "Pause the game when window focus is lost.",
|
||||
.onChange = [](bool value) { aurora_set_pause_on_focus_lost(value); },
|
||||
.isDisabled = [] { return IsMobile || getSettings().game.speedrunMode; },
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
@@ -801,12 +829,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
};
|
||||
auto addSpeedrunDisabledOption = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
add_speedrun_disabled_option(leftPane, rightPane, value, key, helpText);
|
||||
};
|
||||
|
||||
leftPane.add_section("General");
|
||||
@@ -858,12 +881,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"Quicker climbing on ladders and vines like the HD version.");
|
||||
addOption("Faster Tears of Light", getSettings().game.fastTears,
|
||||
"Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.autoSave,
|
||||
{
|
||||
.key = "Autosave",
|
||||
.helpText = "Autosaves the game when going to a new area, opening a dungeon door, "
|
||||
"or getting a new item.",
|
||||
});
|
||||
addSpeedrunDisabledOption("Autosave", getSettings().game.autoSave,
|
||||
"Autosaves the game when going to a new area, opening a dungeon door, "
|
||||
"or getting a new item.");
|
||||
addOption("Instant Saves", getSettings().game.instantSaves,
|
||||
"Skips the delay when writing to the Memory Card.");
|
||||
addOption("Hold B for Instant Text", getSettings().game.instantText,
|
||||
@@ -877,7 +897,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"Link will not recoil when his sword hits walls.");
|
||||
addOption("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat,
|
||||
"Skip needing to catch a second fish for Sera's cat.");
|
||||
addOption("Sun's Song (R+X)", getSettings().game.sunsSong,
|
||||
addSpeedrunDisabledOption("Sun's Song (R+X)", getSettings().game.sunsSong,
|
||||
"Allows Wolf Link to howl and change the time of day.");
|
||||
addOption("Quick Transform (R+Y)", getSettings().game.enableQuickTransform,
|
||||
"Transform instantly by pressing R and Y simultaneously.");
|
||||
@@ -888,12 +908,29 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.key = "Speedrun Mode",
|
||||
.helpText =
|
||||
"Enables speedrunning options while restricting certain gameplay modifiers.",
|
||||
.onChange = [](bool) { reset_for_speedrun_mode(); },
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
if (enabled) {
|
||||
reset_for_speedrun_mode();
|
||||
} else {
|
||||
restore_from_speedrun_mode();
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
}
|
||||
for (auto& doc : get_document_stack()) {
|
||||
if (dynamic_cast<MenuBar*>(doc.get())) {
|
||||
doc = std::make_unique<MenuBar>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
|
||||
{
|
||||
.key = "LiveSplit Connection",
|
||||
.helpText = "Connect to LiveSplit server on localhost:16834.",
|
||||
.helpText = "Connect to LiveSplit server on localhost:16834. For this to work you must right click LiveSplit, and turn on Control -> Start TCP Server."
|
||||
" To see IGT in LiveSplit you must change your comparison to Game Time.",
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
if (enabled) {
|
||||
@@ -902,6 +939,12 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
},
|
||||
.isDisabled = [] { return IsMobile || !getSettings().game.speedrunMode; },
|
||||
});
|
||||
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; },
|
||||
});
|
||||
});
|
||||
@@ -912,12 +955,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
|
||||
auto addCheat = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
add_speedrun_disabled_option(leftPane, rightPane, value, key, helpText);
|
||||
};
|
||||
|
||||
leftPane.add_section("Resources");
|
||||
@@ -1068,12 +1106,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.helpText = "Checks GitHub releases for a new Dusk version on startup.<br/><br/>"
|
||||
"No personal information is transmitted or collected.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
|
||||
{
|
||||
.key = "Pause On Focus Lost",
|
||||
.helpText = "Pause the game when window focus is lost.",
|
||||
.onChange = [](bool value) { aurora_set_pause_on_focus_lost(value); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.enableAdvancedSettings,
|
||||
{
|
||||
.key = "Enable Advanced Settings",
|
||||
@@ -1090,6 +1122,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
}
|
||||
}
|
||||
},
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
|
||||
leftPane.add_section("Game");
|
||||
@@ -1098,12 +1131,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.key = "Skip TV Settings Screen",
|
||||
.helpText = "Skips the TV calibration screen shown when loading a save.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.recordingMode,
|
||||
{
|
||||
.key = "Recording Mode",
|
||||
.helpText = "Disables the game HUD and all background music.<br/><br/>Useful for "
|
||||
"recording footage.",
|
||||
});
|
||||
add_speedrun_disabled_option(leftPane, rightPane, getSettings().game.recordingMode,
|
||||
"Recording Mode",
|
||||
"Disables the game HUD and all background music.<br/><br/>Useful for recording footage.");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ bool initialize() noexcept {
|
||||
load_font("AlegreyaSC-Regular.ttf");
|
||||
load_font("AlegreyaSC-Bold.ttf");
|
||||
load_font("MaterialSymbolsRounded-Regular.ttf");
|
||||
load_font("NotoMono-Regular.ttf");
|
||||
|
||||
sInitialized = true;
|
||||
return true;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "f_op/f_op_overlap_req.h"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
|
||||
#include "dusk/imgui/ImGuiMenuGame.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
|
||||
void fopOvlpReq_SetPeektime(overlap_request_class*, u16);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user