mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-12 04:57:06 -04:00
speedrun timer
This commit is contained in:
@@ -216,6 +216,10 @@ set(GAME_INCLUDE_DIRS
|
||||
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
|
||||
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient)
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND GAME_LIBS Ws2_32)
|
||||
endif ()
|
||||
|
||||
if (DUSK_MOVIE_SUPPORT)
|
||||
if (TARGET libjpeg-turbo::turbojpeg-static)
|
||||
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
|
||||
|
||||
@@ -1361,6 +1361,8 @@ set(DUSK_FILES
|
||||
src/dusk/imgui/ImGuiMenuTools.hpp
|
||||
src/dusk/imgui/ImGuiMenuEnhancements.cpp
|
||||
src/dusk/imgui/ImGuiMenuEnhancements.hpp
|
||||
src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp
|
||||
src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp
|
||||
src/dusk/imgui/ImGuiPreLaunchWindow.cpp
|
||||
src/dusk/imgui/ImGuiPreLaunchWindow.hpp
|
||||
src/dusk/imgui/ImGuiFirstRunPreset.hpp
|
||||
@@ -1373,6 +1375,7 @@ set(DUSK_FILES
|
||||
src/dusk/imgui/ImGuiStubLog.cpp
|
||||
src/dusk/imgui/ImGuiMapLoader.cpp
|
||||
src/dusk/imgui/ImGuiSaveEditor.cpp
|
||||
src/dusk/livesplit.cpp
|
||||
src/dusk/offset_ptr.cpp
|
||||
src/dusk/OSContext.cpp
|
||||
src/dusk/OSThread.cpp
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace dusk::speedrun {
|
||||
void onGameFrame();
|
||||
uint64_t getFrameCount();
|
||||
void start();
|
||||
void reset();
|
||||
void connectLiveSplit(const char* host = "127.0.0.1", int port = 16834);
|
||||
void disconnectLiveSplit();
|
||||
bool consumeConnectedEvent();
|
||||
bool consumeDisconnectedEvent();
|
||||
void updateLiveSplit();
|
||||
void shutdown();
|
||||
}
|
||||
@@ -79,6 +79,11 @@ struct UserSettings {
|
||||
|
||||
// Controls
|
||||
ConfigVar<bool> enableTurboKeybind;
|
||||
|
||||
// Tools
|
||||
ConfigVar<bool> speedrunTimer;
|
||||
ConfigVar<bool> speedrunTimerOverlay;
|
||||
ConfigVar<bool> liveSplitEnabled;
|
||||
} game;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "JSystem/J2DGraph/J2DScreen.h"
|
||||
#include "JSystem/J2DGraph/J2DTextBox.h"
|
||||
#include "d/d_msg_string.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
|
||||
@@ -138,6 +139,9 @@ void dBrightCheck_c::modeWait() {}
|
||||
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
|
||||
dusk::speedrun::start();
|
||||
#endif
|
||||
mCompleteCheck = true;
|
||||
mMode = MODE_WAIT_e;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
@@ -38,6 +39,10 @@ namespace dusk {
|
||||
ImGui::TextUnformatted(text.data(), text.data() + text.size());
|
||||
}
|
||||
|
||||
void DuskToast(std::string_view message, float duration) {
|
||||
g_imguiConsole.AddToast(message, duration);
|
||||
}
|
||||
|
||||
void ImGuiTextCenter(std::string_view text) {
|
||||
ImGui::NewLine();
|
||||
float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x;
|
||||
@@ -235,6 +240,8 @@ namespace dusk {
|
||||
m_menuEnhancements.draw();
|
||||
m_menuTools.draw();
|
||||
|
||||
m_menuSpeedrunTimer.draw();
|
||||
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale());
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate));
|
||||
@@ -252,12 +259,26 @@ namespace dusk {
|
||||
}
|
||||
|
||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||
m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f);
|
||||
DuskToast("Press F1 to toggle menu"s);
|
||||
m_isLaunchInitialized = true;
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::connectLiveSplit();
|
||||
}
|
||||
}
|
||||
|
||||
m_menuSpeedrunTimer.drawOverlay();
|
||||
|
||||
m_menuGame.windowControllerConfig();
|
||||
m_menuGame.windowInputViewer();
|
||||
|
||||
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) {
|
||||
m_menuTools.ShowDebugOverlay();
|
||||
m_menuTools.ShowCameraOverlay();
|
||||
@@ -383,6 +404,10 @@ namespace dusk {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImGuiConsole::AddToast(std::string_view message, float duration) {
|
||||
m_toasts.emplace_back(std::string(message), duration);
|
||||
}
|
||||
|
||||
void ImGuiConsole::ShowToasts() {
|
||||
if (m_toasts.empty()) {
|
||||
return;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "ImGuiFirstRunPreset.hpp"
|
||||
#include "ImGuiMenuEnhancements.hpp"
|
||||
#include "ImGuiMenuGame.hpp"
|
||||
#include "ImGuiMenuSpeedrunTimer.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "ImGuiPreLaunchWindow.hpp"
|
||||
#include "imgui.h"
|
||||
@@ -22,6 +23,7 @@ public:
|
||||
void PostDraw();
|
||||
|
||||
static bool CheckMenuViewToggle(ImGuiKey key, bool& active);
|
||||
void AddToast(std::string_view message, float duration = 3.f);
|
||||
|
||||
private:
|
||||
struct Toast {
|
||||
@@ -39,6 +41,7 @@ private:
|
||||
ImGuiFirstRunPreset m_firstRunPreset;
|
||||
ImGuiMenuGame m_menuGame;
|
||||
ImGuiMenuEnhancements m_menuEnhancements;
|
||||
ImGuiMenuSpeedrunTimer m_menuSpeedrunTimer;
|
||||
ImGuiPreLaunchWindow m_preLaunchWindow;
|
||||
|
||||
// Keep always last
|
||||
@@ -57,6 +60,7 @@ std::string BytesToString(size_t bytes);
|
||||
void SetOverlayWindowLocation(int corner);
|
||||
bool ShowCornerContextMenu(int& corner, int avoidCorner);
|
||||
void ImGuiStringViewText(std::string_view text);
|
||||
void DuskToast(std::string_view message, float duration = 3.f);
|
||||
void ImGuiBeginGroupPanel(const char* name, const ImVec2& size);
|
||||
void ImGuiEndGroupPanel();
|
||||
void ImGuiTextCenter(std::string_view text);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "ImGuiMenuEnhancements.hpp"
|
||||
#include "ImGuiConfig.hpp"
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
namespace dusk {
|
||||
@@ -181,6 +183,22 @@ namespace dusk {
|
||||
"This will not work with the \"Unlock Framerate\" enhancement.");
|
||||
}
|
||||
|
||||
bool prevSpeedrunTimer = getSettings().game.speedrunTimer;
|
||||
config::ImGuiCheckbox("Speedrun Timer", getSettings().game.speedrunTimer);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Shows a speedrun timer in the menu bar.");
|
||||
}
|
||||
if ((bool)getSettings().game.speedrunTimer != prevSpeedrunTimer) {
|
||||
if (!getSettings().game.speedrunTimer) {
|
||||
getSettings().game.speedrunTimerOverlay.setValue(false);
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
getSettings().game.liveSplitEnabled.setValue(false);
|
||||
dusk::speedrun::disconnectLiveSplit();
|
||||
DuskToast("LiveSplit disconnected", 3.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP
|
||||
#define DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP
|
||||
|
||||
namespace dusk {
|
||||
class ImGuiMenuSpeedrunTimer {
|
||||
public:
|
||||
void draw();
|
||||
void drawOverlay();
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
#endif // DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP
|
||||
@@ -0,0 +1,183 @@
|
||||
#if _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using socket_t = SOCKET;
|
||||
static void closeSocket(socket_t s) { closesocket(s); }
|
||||
#else
|
||||
#include <sys/socket.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); }
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include "dusk/livesplit.h"
|
||||
#include "f_op/f_op_overlap_mng.h"
|
||||
|
||||
namespace dusk::speedrun {
|
||||
|
||||
static bool running = 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 void sendCmd(const char* cmd) {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
char msg[64];
|
||||
int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd);
|
||||
|
||||
if (send(sock, msg, len, 0) >= 0) {
|
||||
if (!connected) {
|
||||
connected = connectPending = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
int err = WSAGetLastError();
|
||||
if (err == WSAEWOULDBLOCK || err == WSAENOTCONN) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (connected) disconnectPending = true;
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = connectPending = false;
|
||||
}
|
||||
|
||||
uint64_t getFrameCount() {
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
void onGameFrame() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool loading = fopOvlpM_IsDoingReq() != 0;
|
||||
|
||||
if (loading != wasLoading) {
|
||||
sendCmd(loading ? "pausegametime" : "unpausegametime");
|
||||
wasLoading = loading;
|
||||
}
|
||||
|
||||
if (!loading) {
|
||||
++frameCount;
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
frameCount = 0;
|
||||
wasLoading = false;
|
||||
sendCmd("initgametime");
|
||||
sendCmd("reset");
|
||||
sendCmd("starttimer");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
running = false;
|
||||
frameCount = 0;
|
||||
wasLoading = false;
|
||||
sendCmd("reset");
|
||||
}
|
||||
|
||||
void connectLiveSplit(const char* host, int port) {
|
||||
#if _WIN32
|
||||
WSADATA wd{}; WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closeSocket(sock); sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
u_long nb = 1;
|
||||
ioctlsocket(sock, FIONBIO, &nb);
|
||||
#else
|
||||
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
|
||||
#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");
|
||||
}
|
||||
|
||||
void disconnectLiveSplit() {
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool consumeConnectedEvent() { bool v = connectPending; connectPending = false; return v; }
|
||||
bool consumeDisconnectedEvent() { bool v = disconnectPending; disconnectPending = false; return v; }
|
||||
|
||||
void updateLiveSplit() {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
sendCmd("initgametime");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
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)
|
||||
);
|
||||
|
||||
sendCmd(cmd);
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
disconnectLiveSplit();
|
||||
#if _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,7 +66,12 @@ UserSettings g_userSettings = {
|
||||
.restoreWiiGlitches {"game.restoreWiiGlitches", false},
|
||||
|
||||
// Controls
|
||||
.enableTurboKeybind {"game.enableTurboKeybind", false}
|
||||
.enableTurboKeybind {"game.enableTurboKeybind", false},
|
||||
|
||||
// Tools
|
||||
.speedrunTimer {"game.speedrunTimer", false},
|
||||
.speedrunTimerOverlay {"game.speedrunTimerOverlay", false},
|
||||
.liveSplitEnabled {"game.liveSplitEnabled", false}
|
||||
},
|
||||
|
||||
.backend = {
|
||||
@@ -123,6 +128,9 @@ 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.liveSplitEnabled);
|
||||
Register(g_userSettings.game.fastSpinner);
|
||||
Register(g_userSettings.game.enableFrameInterpolation);
|
||||
Register(g_userSettings.game.enableGyroAim);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "d/d_model.h"
|
||||
#include "d/d_tresure.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/logging.h"
|
||||
#include "f_op/f_op_camera_mng.h"
|
||||
#include "f_op/f_op_draw_tag.h"
|
||||
@@ -771,6 +772,9 @@ void fapGm_Execute() {
|
||||
fpcM_ManagementFunc(NULL, fapGm_After);
|
||||
#endif
|
||||
cCt_Counter(0);
|
||||
#ifdef TARGET_PC
|
||||
dusk::speedrun::onGameFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
fapGm_HIO_c g_HIO;
|
||||
|
||||
Reference in New Issue
Block a user