diff --git a/soh/assets/custom/textures/parameter_static/gMoon.rgba32.png b/soh/assets/custom/textures/parameter_static/gMoon.rgba32.png new file mode 100644 index 000000000..9c1afa577 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gMoon.rgba32.png differ diff --git a/soh/assets/custom/textures/parameter_static/gNavi.rgba32.png b/soh/assets/custom/textures/parameter_static/gNavi.rgba32.png new file mode 100644 index 000000000..aae12c339 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gNavi.rgba32.png differ diff --git a/soh/assets/custom/textures/parameter_static/gSun.rgba32.png b/soh/assets/custom/textures/parameter_static/gSun.rgba32.png new file mode 100644 index 000000000..954726e05 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gSun.rgba32.png differ diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h index c0acacf21..568a87963 100644 --- a/soh/assets/soh_assets.h +++ b/soh/assets/soh_assets.h @@ -106,6 +106,15 @@ static const ALIGN_ASSET(2) char gSplitEntranceTex[] = dgSplitEntrance; #define dgBossSoul "__OTR__textures/parameter_static/gBossSoul" static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul; +#define dgMoonIcon "__OTR__textures/parameter_static/gMoon" +static const ALIGN_ASSET(2) char gMoonIconTex[] = dgMoonIcon; + +#define dgSunIcon "__OTR__textures/parameter_static/gSun" +static const ALIGN_ASSET(2) char gSunIconTex[] = dgSunIcon; + +#define dgNaviIcon "__OTR__textures/parameter_static/gNavi" +static const ALIGN_ASSET(2) char gNaviIconTex[] = dgNaviIcon; + #define dgFileSelMQButtonTex "__OTR__textures/title_static/gFileSelMQButtonTex" static const ALIGN_ASSET(2) char gFileSelMQButtonTex[] = dgFileSelMQButtonTex; diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp new file mode 100644 index 000000000..cb9dfbb46 --- /dev/null +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp @@ -0,0 +1,262 @@ +#include "TimeDisplay.h" +#include "soh/Enhancements/gameplaystats.h" +#include + +#include "assets/textures/parameter_static/parameter_static.h" +#include "assets/soh_assets.h" +#include "soh/ImGuiUtils.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +uint64_t GetUnixTimestamp(); +} + +float fontScale = 1.0f; +std::string timeDisplayTime = ""; +ImTextureID textureDisplay = 0; +ImVec4 windowBG = ImVec4(0, 0, 0, 0.5f); +ImVec4 textColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + +// ImVec4 Colors +#define COLOR_WHITE ImVec4(1.0f, 1.0f, 1.0f, 1.0f) +#define COLOR_LIGHT_RED ImVec4(1.0f, 0.05f, 0, 1.0f) +#define COLOR_LIGHT_BLUE ImVec4(0, 0.88f, 1.0f, 1.0f) +#define COLOR_LIGHT_GREEN ImVec4(0.52f, 1.0f, 0.23f, 1.0f) +#define COLOR_GREY ImVec4(0.78f, 0.78f, 0.78f, 1.0f) + +const static std::vector> digitList = { + { "DIGIT_0_TEXTURE", gCounterDigit0Tex }, { "DIGIT_1_TEXTURE", gCounterDigit1Tex }, + { "DIGIT_2_TEXTURE", gCounterDigit2Tex }, { "DIGIT_3_TEXTURE", gCounterDigit3Tex }, + { "DIGIT_4_TEXTURE", gCounterDigit4Tex }, { "DIGIT_5_TEXTURE", gCounterDigit5Tex }, + { "DIGIT_6_TEXTURE", gCounterDigit6Tex }, { "DIGIT_7_TEXTURE", gCounterDigit7Tex }, + { "DIGIT_8_TEXTURE", gCounterDigit8Tex }, { "DIGIT_9_TEXTURE", gCounterDigit9Tex }, + { "COLON_TEXTURE", gCounterColonTex }, +}; + +const std::vector timeDisplayList = { + { DISPLAY_IN_GAME_TIMER, "Display Gameplay Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.InGameTimer") }, + { DISPLAY_TIME_OF_DAY, "Display Time of Day", CVAR_ENHANCEMENT("TimeDisplay.Timers.TimeofDay") }, + { DISPLAY_CONDITIONAL_TIMER, "Display Conditional Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.HotWater") }, + { DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.NaviTimer") } +}; + +static std::vector activeTimers; + +std::string convertDayTime(uint32_t dayTime) { + uint32_t totalSeconds = 24 * 60 * 60; + uint32_t ss = static_cast(static_cast(dayTime) * (totalSeconds - 1) / 65535); + uint32_t hh = ss / 3600; + uint32_t mm = (ss % 3600) / 60; + return fmt::format("{:0>2}:{:0>2}", hh, mm); +} + +std::string convertNaviTime(uint32_t value) { + uint32_t totalSeconds = value * 0.05; + uint32_t ss = totalSeconds % 60; + uint32_t mm = totalSeconds / 60; + return fmt::format("{:0>2}:{:0>2}", mm, ss); +} + +std::string formatHotWaterDisplay(uint32_t value) { + uint32_t ss = value % 60; + uint32_t mm = value / 60; + return fmt::format("{:0>2}:{:0>2}", mm, ss); +} + +std::string formatTimeDisplay(uint32_t value) { + uint32_t sec = value / 10; + uint32_t hh = sec / 3600; + uint32_t mm = (sec - hh * 3600) / 60; + uint32_t ss = sec - hh * 3600 - mm * 60; + uint32_t ds = value % 10; + return fmt::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds); +} + +static void TimeDisplayGetTimer(uint32_t timeID) { + timeDisplayTime = ""; + textureDisplay = 0; + textColor = COLOR_WHITE; + + Player* player = GET_PLAYER(gPlayState); + uint32_t timer1 = gSaveContext.timer1Value; + + switch (timeID) { + case DISPLAY_IN_GAME_TIMER: + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("GAMEPLAY_TIMER"); + timeDisplayTime = formatTimeDisplay(GAMEPLAYSTAT_TOTAL_TIME).c_str(); + break; + case DISPLAY_TIME_OF_DAY: + if (gSaveContext.dayTime >= DAY_BEGINS && gSaveContext.dayTime < NIGHT_BEGINS) { + textureDisplay = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("DAY_TIME_TIMER"); + } else { + textureDisplay = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("NIGHT_TIME_TIMER"); + } + timeDisplayTime = convertDayTime(gSaveContext.dayTime).c_str(); + break; + case DISPLAY_CONDITIONAL_TIMER: + if (gSaveContext.timer1State > 0) { + timeDisplayTime = formatHotWaterDisplay(gSaveContext.timer1Value).c_str(); + textColor = + gSaveContext.timer1State <= 4 + ? (gPlayState->roomCtx.curRoom.behaviorType2 == ROOM_BEHAVIOR_TYPE2_3 ? COLOR_LIGHT_RED + : COLOR_LIGHT_BLUE) + : COLOR_WHITE; + if (gSaveContext.timer1State <= 4) { + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + gPlayState->roomCtx.curRoom.behaviorType2 == ROOM_BEHAVIOR_TYPE2_3 + ? itemMapping[ITEM_TUNIC_GORON].name + : itemMapping[ITEM_TUNIC_ZORA].name); + } + if (gSaveContext.timer1State >= 6) { + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + itemMapping[ITEM_SWORD_MASTER].name); + } + } else { + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + itemMapping[ITEM_TUNIC_KOKIRI].name); + timeDisplayTime = "-:--"; + } + break; + case DISPLAY_NAVI_TIMER: + if (gSaveContext.naviTimer <= NAVI_PREPARE) { + timeDisplayTime = convertNaviTime(NAVI_PREPARE - gSaveContext.naviTimer).c_str(); + } else if (gSaveContext.naviTimer <= NAVI_ACTIVE) { + timeDisplayTime = convertNaviTime(NAVI_ACTIVE - gSaveContext.naviTimer).c_str(); + textColor = COLOR_LIGHT_GREEN; + } else { + timeDisplayTime = convertNaviTime(NAVI_COOLDOWN - gSaveContext.naviTimer).c_str(); + textColor = COLOR_GREY; + } + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("NAVI_TIMER"); + break; + default: + break; + } +} + +void TimeDisplayUpdateDisplayOptions() { + activeTimers.clear(); + for (auto& timer : timeDisplayList) { + if (CVarGetInteger(timer.timeEnable, 0)) { + activeTimers.push_back(timer); + } + } + + //if (pushBack) { + // activeTimers.push_back(timeDisplayList[timeID]); + //} else { + // uint32_t index = 0; + // for (auto& check : activeTimers) { + // if (check.timeID == timeID) { + // activeTimers.erase(activeTimers.begin() + index); + // return; + // } + // index++; + // } + //} +} + +void TimeDisplayWindow::Draw() { + if (!gPlayState) { + return; + } + if (!CVarGetInteger(CVAR_WINDOW("TimeDisplayEnabled"), 0)) { + return; + } + + ImGui::PushStyleColor(ImGuiCol_WindowBg, windowBG); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + + ImGui::Begin("TimerDisplay", nullptr, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + ImGui::SetWindowFontScale(fontScale); + if (activeTimers.size() == 0) { + ImGui::Text("No Enabled Timers..."); + } else { + ImGui::BeginTable("Timer List", 2, ImGuiTableFlags_NoClip); + for (auto& timers : activeTimers) { + ImGui::PushID(timers.timeID); + TimeDisplayGetTimer(timers.timeID); + ImGui::TableNextColumn(); + ImGui::Image(textureDisplay, ImVec2(16.0f * fontScale, 16.0f * fontScale)); + ImGui::TableNextColumn(); + + if (timeDisplayTime != "-:--") { + char* textToDecode = new char[timeDisplayTime.size() + 1]; + textToDecode = std::strcpy(textToDecode, timeDisplayTime.c_str()); + size_t textLength = timeDisplayTime.length(); + uint16_t textureIndex = 0; + + for (size_t i = 0; i < textLength; i++) { + ImVec2 originalCursorPos = ImGui::GetCursorPos(); + if (textToDecode[i] == ':' || textToDecode[i] == '.') { + textureIndex = 10; + } else { + textureIndex = textToDecode[i] - '0'; + } + if (textToDecode[i] == '.') { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (8.0f * fontScale)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + digitList[textureIndex].first), + ImVec2(8.0f * fontScale, 8.0f * fontScale), ImVec2(0, 0.5f), ImVec2(1, 1), + textColor, ImVec4(0, 0, 0, 0)); + } else { + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + digitList[textureIndex].first), + ImVec2(8.0f * fontScale, 16.0f * fontScale), ImVec2(0, 0), ImVec2(1, 1), textColor, + ImVec4(0, 0, 0, 0)); + } + ImGui::SameLine(0, 0); + } + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::End(); + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(1); +} + +void TimeDisplayInitSettings() { + fontScale = CVarGetFloat(CVAR_ENHANCEMENT("TimeDisplay.FontScale"), 1.0f); + if (fontScale < 1.0f) { + fontScale = 1.0f; + } + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"), 0)) { + windowBG = ImVec4(0, 0, 0, 0); + } else { + windowBG = ImVec4(0, 0, 0, 0.5f); + } +} + +static void TimeDisplayInitTimers() { + for (auto& update : timeDisplayList) { + if (CVarGetInteger(update.timeEnable, 0)) { + activeTimers.push_back(update); + } + } +} + +void TimeDisplayWindow::InitElement() { + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("GAMEPLAY_TIMER", gClockIconTex, ImVec4(1, 1, 1, 1)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("DAY_TIME_TIMER", gSunIconTex, ImVec4(1, 1, 1, 1)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("NIGHT_TIME_TIMER", gMoonIconTex, ImVec4(1, 1, 1, 1)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("NAVI_TIMER", gNaviIconTex, ImVec4(1, 1, 1, 1)); + + for (auto& load : digitList) { + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(load.first.c_str(), load.second, ImVec4(1, 1, 1, 1)); + } + + TimeDisplayInitSettings(); + TimeDisplayInitTimers(); +} diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h new file mode 100644 index 000000000..090ac2901 --- /dev/null +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h @@ -0,0 +1,37 @@ +#include + +class TimeDisplayWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override; + void DrawElement() override {}; + void Draw() override; + void UpdateElement() override {}; +}; + +void TimeDisplayUpdateDisplayOptions(); +void TimeDisplayInitSettings(); + +typedef enum { + DISPLAY_IN_GAME_TIMER, + DISPLAY_TIME_OF_DAY, + DISPLAY_CONDITIONAL_TIMER, + DISPLAY_NAVI_TIMER +}; + +typedef enum { + NAVI_PREPARE = 600, + NAVI_ACTIVE = 3000, + NAVI_COOLDOWN = 25800, + DAY_BEGINS = 17759, + NIGHT_BEGINS = 49155 +}; + +typedef struct { + uint32_t timeID; + std::string timeLabel; + const char* timeEnable; +} TimeObject; + +extern const std::vector timeDisplayList; \ No newline at end of file diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 19b8639de..2dbf62a99 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -38,6 +38,7 @@ #include "Enhancements/resolution-editor/ResolutionEditor.h" #include "Enhancements/debugger/MessageViewer.h" #include "soh/Notification/Notification.h" +#include "soh/Enhancements/TimeDisplay/TimeDisplay.h" bool isBetaQuestEnabled = false; @@ -136,6 +137,7 @@ namespace SohGui { std::shared_ptr mAdvancedResolutionSettingsWindow; std::shared_ptr mModalWindow; std::shared_ptr mNotificationWindow; + std::shared_ptr mTimeDisplayWindow; void SetupGuiElements() { auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); @@ -221,6 +223,8 @@ namespace SohGui { mNotificationWindow = std::make_shared(CVAR_WINDOW("Notifications"), "Notifications Window"); gui->AddGuiWindow(mNotificationWindow); mNotificationWindow->Show(); + mTimeDisplayWindow = std::make_shared(CVAR_WINDOW("TimeDisplayEnabled"), "Additional Timers"); + gui->AddGuiWindow(mTimeDisplayWindow); } void Destroy() { @@ -256,6 +260,7 @@ namespace SohGui { mInputViewerSettings = nullptr; mTimeSplitWindow = nullptr; mPlandomizerWindow = nullptr; + mTimeDisplayWindow = nullptr; } void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index cd9948092..a6cbd2d81 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -44,6 +44,7 @@ #include "Enhancements/enemyrandomizer.h" #include "Enhancements/timesplits/TimeSplits.h" #include "Enhancements/randomizer/Plandomizer.h" +#include "Enhancements/TimeDisplay/TimeDisplay.h" // FA icons are kind of wonky, if they worked how I expected them to the "+ 2.0f" wouldn't be needed, but // they don't work how I expect them to so I added that because it looked good when I eyeballed it @@ -607,6 +608,7 @@ extern std::shared_ptr mAudioEditorWindow; extern std::shared_ptr mCosmeticsEditorWindow; extern std::shared_ptr mGameplayStatsWindow; extern std::shared_ptr mTimeSplitWindow; +extern std::shared_ptr mTimeDisplayWindow; void DrawEnhancementsMenu() { if (ImGui::BeginMenu("Enhancements")) @@ -1723,6 +1725,36 @@ void DrawEnhancementsMenu() { mTimeSplitWindow->ToggleVisibility(); } } + + if (mTimeDisplayWindow) { + if (ImGui::Button(GetWindowButtonText("Additional Timers", CVarGetInteger(CVAR_WINDOW("TimeDisplayEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + mTimeDisplayWindow->ToggleVisibility(); + } + } + if (mTimeDisplayWindow->IsVisible()) { + ImGui::SeparatorText("Timer Display Options"); + + if (!gPlayState) { + ImGui::Text("Additional Timer options\n" + "available when a file is\n" + "loaded..."); + } else { + if (UIWidgets::PaddedEnhancementSliderFloat("Font Scale: %.2fx", "##FontScale", CVAR_ENHANCEMENT("TimeDisplay.FontScale"), + 1.0f, 5.0f, "", 1.0f, false, true, false, true)) { + TimeDisplayInitSettings(); + } + if (UIWidgets::PaddedEnhancementCheckbox("Hide Background", CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"), + false, false)) { + TimeDisplayInitSettings(); + } + ImGui::Separator(); + for (auto& timer : timeDisplayList) { + if (UIWidgets::PaddedEnhancementCheckbox(timer.timeLabel.c_str(), timer.timeEnable, false, false)) { + TimeDisplayUpdateDisplayOptions(); + } + } + } + } ImGui::PopStyleVar(3); ImGui::PopStyleColor(1); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 84c71c83b..d2c3e1a26 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -6070,7 +6070,7 @@ void Interface_Draw(PlayState* play) { svar5 = CVarGetInteger(CVAR_COSMETIC("HUD.Timers.PosX"), 0)+204+X_Margins_Timer; } else if (CVarGetInteger(CVAR_COSMETIC("HUD.Timers.PosType"), 0) == 4) {//Hidden svar5 = -9999; - } + } } OVERLAY_DISP =