diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index 2409b53b27..9fb2986a64 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -253,15 +253,6 @@ TimestampInfo itemTimestampDisplay[TIMESTAMP_MAX]; TimestampInfo sceneTimestampDisplay[8191]; // std::vector sceneTimestampDisplay; -std::string formatTimestampGameplayStat(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 spdlog::fmt_lib::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds); -} - std::string formatIntGameplayStat(uint32_t value) { return spdlog::fmt_lib::format("{}", value); } @@ -275,7 +266,7 @@ std::string formatHexOnlyGameplayStat(uint32_t value) { } extern "C" char* GameplayStats_GetCurrentTime() { - std::string timeString = formatTimestampGameplayStat(static_cast(GAMEPLAYSTAT_TOTAL_TIME)).c_str(); + std::string timeString = Ship_FormatTimeDisplay(static_cast(GAMEPLAYSTAT_TOTAL_TIME)).c_str(); const size_t stringLength = timeString.length(); char* timeChar = (char*)malloc(stringLength + 1); // We need to use malloc so we can free this from a C file. strcpy(timeChar, timeString.c_str()); @@ -449,20 +440,19 @@ void DrawGameplayStatsHeader() { GameplayStatsRow("Build Version:", (char*)gBuildVersion); } if (gSaveContext.ship.stats.rtaTiming) { - GameplayStatsRow("Total Time (RTA):", formatTimestampGameplayStat(static_cast(GAMEPLAYSTAT_TOTAL_TIME)), + GameplayStatsRow("Total Time (RTA):", Ship_FormatTimeDisplay(static_cast(GAMEPLAYSTAT_TOTAL_TIME)), gSaveContext.ship.stats.gameComplete ? COLOR_GREEN : COLOR_WHITE); } else { - GameplayStatsRow("Total Game Time:", formatTimestampGameplayStat(static_cast(GAMEPLAYSTAT_TOTAL_TIME)), + GameplayStatsRow("Total Game Time:", Ship_FormatTimeDisplay(static_cast(GAMEPLAYSTAT_TOTAL_TIME)), gSaveContext.ship.stats.gameComplete ? COLOR_GREEN : COLOR_WHITE); } if (CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowAdditionalTimers"), 0)) { // !Only display total game time - GameplayStatsRow("Gameplay Time:", formatTimestampGameplayStat(gSaveContext.ship.stats.playTimer / 2), + GameplayStatsRow("Gameplay Time:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.playTimer / 2), COLOR_GREY); + GameplayStatsRow("Pause Menu Time:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.pauseTimer / 3), COLOR_GREY); - GameplayStatsRow("Pause Menu Time:", formatTimestampGameplayStat(gSaveContext.ship.stats.pauseTimer / 3), - COLOR_GREY); - GameplayStatsRow("Time in scene:", formatTimestampGameplayStat(gSaveContext.ship.stats.sceneTimer / 2), + GameplayStatsRow("Time in scene:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.sceneTimer / 2), COLOR_LIGHT_BLUE); - GameplayStatsRow("Time in room:", formatTimestampGameplayStat(gSaveContext.ship.stats.roomTimer / 2), + GameplayStatsRow("Time in room:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.roomTimer / 2), COLOR_LIGHT_BLUE); } if (gPlayState != NULL && CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowDebugInfo"), 0)) { // && display debug info @@ -494,7 +484,7 @@ void DrawGameplayStatsTimestampsTab() { for (int i = 0; i < TIMESTAMP_MAX; i++) { // To be shown, the entry must have a non-zero time and a string for its display name if (itemTimestampDisplay[i].time > 0 && strnlen(itemTimestampDisplay[i].name, 24) > 1) { - GameplayStatsRow(itemTimestampDisplay[i].name, formatTimestampGameplayStat(itemTimestampDisplay[i].time), + GameplayStatsRow(itemTimestampDisplay[i].name, Ship_FormatTimeDisplay(itemTimestampDisplay[i].time), itemTimestampDisplay[i].color); } } @@ -562,7 +552,7 @@ void DrawGameplayStatsCountsTab() { // If using MM Bunny Hood enhancement, show how long it's been equipped (not counting pause time) if (Ship_GetBunnyHoodMode() != BUNNY_HOOD_VANILLA || gSaveContext.ship.stats.count[COUNT_TIME_BUNNY_HOOD] > 0) { GameplayStatsRow("Bunny Hood Time:", - formatTimestampGameplayStat(gSaveContext.ship.stats.count[COUNT_TIME_BUNNY_HOOD] / 2)); + Ship_FormatTimeDisplay(gSaveContext.ship.stats.count[COUNT_TIME_BUNNY_HOOD] / 2)); } GameplayStatsRow("Rolls:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_ROLLS])); GameplayStatsRow("Bonks:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_BONKS])); @@ -613,7 +603,7 @@ void DrawGameplayStatsBreakdownTab() { TimestampInfo tsInfo = sceneTimestampDisplay[i]; bool canShow = !tsInfo.isRoom || CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0); if (tsInfo.time > 0 && strnlen(tsInfo.name, 40) > 1 && canShow) { - GameplayStatsRow(tsInfo.name, formatTimestampGameplayStat(tsInfo.time), tsInfo.color); + GameplayStatsRow(tsInfo.name, Ship_FormatTimeDisplay(tsInfo.time), tsInfo.color); } } std::string toPass; @@ -624,7 +614,7 @@ void DrawGameplayStatsBreakdownTab() { } else { toPass = ResolveSceneID(gSaveContext.ship.stats.sceneNum, gSaveContext.ship.stats.roomNum); } - GameplayStatsRow(toPass.c_str(), formatTimestampGameplayStat(CURRENT_MODE_TIMER / 2)); + GameplayStatsRow(toPass.c_str(), Ship_FormatTimeDisplay(CURRENT_MODE_TIMER / 2)); ImGui::EndTable(); ImGui::PopStyleVar(1); } @@ -843,6 +833,7 @@ void SetupDisplayNames() { strcpy(itemTimestampDisplayName[TIMESTAMP_BOSSRUSH_FINISH], "Boss Rush Finished: "); strcpy(itemTimestampDisplayName[TIMESTAMP_FOUND_GREG], "Greg Found: "); strcpy(itemTimestampDisplayName[TIMESTAMP_TRIFORCE_COMPLETED], "Triforce Completed: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_TIMESPLITS_COMPLETED], "Timesplits Completed: "); // Rando items strcpy(itemTimestampDisplayName[TIMESTAMP_FOUND_GOHMA_SOUL], "Gohma's Soul: "); @@ -959,6 +950,7 @@ void SetupDisplayColors() { case TIMESTAMP_DEFEAT_GANONDORF: case TIMESTAMP_DEFEAT_GANON: case TIMESTAMP_TRIFORCE_COMPLETED: + case TIMESTAMP_TIMESPLITS_COMPLETED: itemTimestampDisplayColor[i] = COLOR_YELLOW; break; case ITEM_SONG_STORMS: diff --git a/soh/soh/Enhancements/gameplaystats.h b/soh/soh/Enhancements/gameplaystats.h index 2a07775cab..414ffe689f 100644 --- a/soh/soh/Enhancements/gameplaystats.h +++ b/soh/soh/Enhancements/gameplaystats.h @@ -19,15 +19,17 @@ char* GameplayStats_GetCurrentTime(); // Total gameplay time is tracked in tenths of seconds // I.E. game time counts frames at 20fps/2, pause time counts frames at 30fps/3 // Frame counts in z_play.c and z_kaleido_scope_call.c -#define GAMEPLAYSTAT_TOTAL_TIME \ - (gSaveContext.ship.stats.rtaTiming \ - ? (!gSaveContext.ship.stats.gameComplete \ - ? (!gSaveContext.ship.stats.firstInput \ - ? 0 \ - : ((GetUnixTimestamp() - gSaveContext.ship.stats.firstInput) / 100)) \ - : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ - ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ - : gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED])) \ +#define GAMEPLAYSTAT_TOTAL_TIME \ + (gSaveContext.ship.stats.rtaTiming \ + ? (!gSaveContext.ship.stats.gameComplete \ + ? (!gSaveContext.ship.stats.firstInput \ + ? 0 \ + : ((GetUnixTimestamp() - gSaveContext.ship.stats.firstInput) / 100)) \ + : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ + ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \ + : (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \ + ? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \ + : gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TIMESPLITS_COMPLETED]))) \ : (gSaveContext.ship.stats.playTimer / 2 + gSaveContext.ship.stats.pauseTimer / 3)) #define CURRENT_MODE_TIMER \ (CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0) ? gSaveContext.ship.stats.roomTimer \ @@ -118,6 +120,7 @@ typedef enum { /* 0xEC */ TIMESTAMP_FOUND_ZR_BEAN_SOUL, /* 0xED */ TIMESTAMP_FOUND_SKELETON_KEY, /* 0xEE */ TIMESTAMP_FOUND_ROCS_FEATHER, + /* 0xEF */ TIMESTAMP_TIMESPLITS_COMPLETED, /* 0xF0 */ TIMESTAMP_MAX } GameplayStatTimestamp; diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index f0f78e0de5..d73e899e9a 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -1,305 +1,144 @@ -#include -#include - -#include -#include - #include "TimeSplits.h" -#include "soh/Enhancements/gameplaystats.h" -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include -#include "soh/SohGui/UIWidgets.hpp" +#include +#include "fast/Fast3dGui.h" + +#include "soh/ShipUtils.h" extern "C" { -#include "soh_assets.h" -#include "z64item.h" -#include "macros.h" -extern SaveContext gSaveContext; +#include "variables.h" extern PlayState* gPlayState; +uint64_t GetUnixTimestamp(); } -using namespace UIWidgets; +namespace TimeSplits { + +#define BLANK_SPLIT "--:--:--.-" // ImVec4 Colors #define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f) -#define COLOR_LIGHT_RED ImVec4(1.0f, 0.05f, 0.0f, 1.0f) -#define COLOR_RED ImVec4(1.00f, 0.00f, 0.00f, 1.00f) -#define COLOR_LIGHT_GREEN ImVec4(0.52f, 1.0f, 0.23f, 1.0f) -#define COLOR_GREEN ImVec4(0.10f, 1.00f, 0.10f, 1.00f) -#define COLOR_BLUE ImVec4(0.00f, 0.33f, 1.00f, 1.00f) -#define COLOR_PURPLE ImVec4(0.54f, 0.19f, 0.89f, 1.00f) -#define COLOR_YELLOW ImVec4(1.00f, 1.00f, 0.00f, 1.00f) -#define COLOR_ORANGE ImVec4(1.00f, 0.67f, 0.11f, 1.00f) -#define COLOR_LIGHT_BLUE ImVec4(0.00f, 0.88f, 1.00f, 1.00f) #define COLOR_GREY ImVec4(0.78f, 0.78f, 0.78f, 1.00f) +#define COLOR_GREEN ImVec4(0.10f, 1.00f, 0.10f, 1.00f) +#define COLOR_RED ImVec4(1.00f, 0.00f, 0.00f, 1.00f) +#define COLOR_LIGHTGREEN ImVec4(0.42f, 0.86f, 0.38f, 1.0f) +#define COLOR_LIGHTRED ImVec4(0.87f, 0.40f, 0.40f, 1.0f) -using json = nlohmann::json; +std::vector splitList; +std::vector comparisonList; +ImGuiTableFlags tableColumnFlags = ImGuiTableColumnFlags_None; +ImVec4 splitOpacity = { 0, 0, 0, 0.5f }; -static uint32_t splitBestTimeDisplay; -static int32_t popupID = -1; -static int32_t removeIndex = -1; -static uint32_t tableSize = 0; -static int skullTokenCount = 0; -static float timeSplitsWindowSize = 1.0f; +uint32_t GetTotalTime() { + return (uint32_t)GAMEPLAYSTAT_TOTAL_TIME; +} -static ImVec4 windowColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); -static ImVec4 splitStatusColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); -static ImVec4 splitTimeColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); -static ImVec4 activeSplitHighlight = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); +SplitTextObject GetCurrentTimeTextDisplay(TimesplitObject split) { + SplitTextObject textDisplay; -static ImVec2 imageSize = ImVec2(38.0f, 38.0f); -static float imagePadding = 2.0f; + switch (split.splitStatus) { + case SPLIT_INACTIVE: + case SPLIT_SKIPPED: + textDisplay.timeDisplay = 0; + textDisplay.colorDisplay = COLOR_GREY; + return textDisplay; + case SPLIT_ACTIVE: + textDisplay.timeDisplay = GetTotalTime(); + textDisplay.colorDisplay = COLOR_WHITE; + return textDisplay; + case SPLIT_COMPLETE: + textDisplay.timeDisplay = split.splitCurrentTime; + if (split.splitCurrentTime > split.splitPreviousBest) { + textDisplay.colorDisplay = COLOR_RED; + } else if (split.splitCurrentTime == split.splitPreviousBest) { + textDisplay.colorDisplay = COLOR_WHITE; + } else if (split.splitCurrentTime < split.splitPreviousBest) { + textDisplay.colorDisplay = COLOR_GREEN; + } + if (split.splitPreviousBest == 0) { + textDisplay.colorDisplay = COLOR_WHITE; + } + return textDisplay; + default: + return textDisplay; + } +} -std::vector keys; +SplitTextObject GetComparisonTimeTextDisplay(TimesplitObject split, TimesplitObject splitCompare) { + SplitTextObject textDisplay; -char listNameBuf[25]; -int dragSourceIndex = -1; -int dragTargetIndex = -1; - -std::vector splitList; -std::vector emptyList; - -std::vector splitObjectList = { - // clang-format off - { SPLIT_TYPE_ITEM, ITEM_STICK, "Deku Stick", "ITEM_STICK", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_NUT, "Deku Nut", "ITEM_NUT", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BOMB, "Bomb", "ITEM_BOMB", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BOW, "Fairy Bow", "ITEM_BOW", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_ARROW_FIRE, "Fire Arrow", "ITEM_ARROW_FIRE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_DINS_FIRE, "Din's Fire", "ITEM_DINS_FIRE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_SLINGSHOT, "Fairy Slingshot", "ITEM_SLINGSHOT", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_OCARINA_FAIRY, "Fairy Ocarina", "ITEM_OCARINA_FAIRY", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_OCARINA_TIME, "Ocarina of Time", "ITEM_OCARINA_TIME", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BOMBCHU, "Bombchu", "ITEM_BOMBCHU", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_HOOKSHOT, "Hookshot", "ITEM_HOOKSHOT", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_LONGSHOT, "Longshot", "ITEM_LONGSHOT", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_ARROW_ICE, "Ice Arrow", "ITEM_ARROW_ICE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_FARORES_WIND, "Farore's Wind", "ITEM_FARORES_WIND", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BOOMERANG, "Boomerang", "ITEM_BOOMERANG", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_LENS, "Lens of Truth", "ITEM_LENS", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BEAN, "Magic Bean", "ITEM_BEAN", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_HAMMER, "Megaton Hammer", "ITEM_HAMMER", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_ARROW_LIGHT, "Light Arrow", "ITEM_ARROW_LIGHT", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_NAYRUS_LOVE, "Nayru's Love", "ITEM_NAYRUS_LOVE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BOTTLE, "Empty Bottle", "ITEM_BOTTLE", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_POTION_RED, "Red Potion", "ITEM_POTION_RED", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_POTION_GREEN, "Green Potion", "ITEM_POTION_GREEN", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_POTION_BLUE, "Blue Potion", "ITEM_POTION_BLUE", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_FAIRY, "Bottled Fairy", "ITEM_FAIRY", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_FISH, "Fish", "ITEM_FISH", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MILK_BOTTLE, "Milk", "ITEM_MILK_BOTTLE", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_LETTER_RUTO, "Ruto's Letter", "ITEM_LETTER_RUTO", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BLUE_FIRE, "Blue Fire", "ITEM_BLUE_FIRE", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BUG, "Bug", "ITEM_BUG", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BIG_POE, "Big Poe", "ITEM_BIG_POE", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_POE, "Poe", "ITEM_POE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_WEIRD_EGG, "Weird Egg", "ITEM_WEIRD_EGG", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_CHICKEN, "Chicken", "ITEM_CHICKEN", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_LETTER_ZELDA, "Zelda's Letter", "ITEM_LETTER_ZELDA", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_KEATON, "Keaton Mask", "ITEM_MASK_KEATON", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_SKULL, "Skull Mask", "ITEM_MASK_SKULL", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_SPOOKY, "Spooky Mask", "ITEM_MASK_SPOOKY", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_BUNNY, "Bunny Hood", "ITEM_MASK_BUNNY", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_GORON, "Goron Mask", "ITEM_MASK_GORON", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_ZORA, "Zora Mask", "ITEM_MASK_ZORA", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_GERUDO, "Gerudo Mask", "ITEM_MASK_GERUDO", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_MASK_TRUTH, "Mask of Truth", "ITEM_MASK_TRUTH", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_POCKET_EGG, "Pocket Egg", "ITEM_POCKET_EGG", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_POCKET_CUCCO, "Pocket Cucco", "ITEM_POCKET_CUCCO", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_COJIRO, "Cojiro", "ITEM_COJIRO", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_ODD_MUSHROOM, "Odd Mushroom", "ITEM_ODD_MUSHROOM", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_ODD_POTION, "Odd Potion", "ITEM_ODD_POTION", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_SAW, "Poacher's Saw", "ITEM_SAW", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_SWORD_BROKEN, "Goron's Sword (Broken)", "ITEM_SWORD_BROKEN", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_PRESCRIPTION, "Prescription", "ITEM_PRESCRIPTION", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_FROG, "Eyeball Frog", "ITEM_FROG", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_EYEDROPS, "Eye Drops", "ITEM_EYEDROPS", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_CLAIM_CHECK, "Claim Check", "ITEM_CLAIM_CHECK", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_SWORD_KOKIRI, "Kokiri Sword", "ITEM_SWORD_KOKIRI", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_SWORD_MASTER, "Master Sword", "ITEM_SWORD_MASTER", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_SWORD_BGS, "Giant's Knife & Biggoron's Sword", "ITEM_SWORD_BGS", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_SHIELD_DEKU, "Deku Shield", "ITEM_SHIELD_DEKU", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_SHIELD_HYLIAN, "Hylian Shield", "ITEM_SHIELD_HYLIAN", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_SHIELD_MIRROR, "Mirror Shield", "ITEM_SHIELD_MIRROR", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_TUNIC_GORON, "Goron Tunic", "ITEM_TUNIC_GORON", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_TUNIC_ZORA, "Zora Tunic", "ITEM_TUNIC_ZORA", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_BOOTS_IRON, "Iron Boots", "ITEM_BOOTS_IRON", COLOR_WHITE }, - { SPLIT_TYPE_EQUIPMENT, ITEM_BOOTS_HOVER, "Hover Boots", "ITEM_BOOTS_HOVER", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BULLET_BAG_30, "Bullet Bag (30)", "ITEM_BULLET_BAG_30", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BULLET_BAG_40, "Bullet Bag (40)", "ITEM_BULLET_BAG_40", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BULLET_BAG_50, "Bullet Bag (50)", "ITEM_BULLET_BAG_50", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_QUIVER_30, "Quiver (30)", "ITEM_QUIVER_30", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_QUIVER_40, "Big Quiver (40)", "ITEM_QUIVER_40", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_QUIVER_50, "Biggest Quiver (50)", "ITEM_QUIVER_50", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BOMB_BAG_20, "Bomb Bag (20)", "ITEM_BOMB_BAG_20", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BOMB_BAG_30, "Big Bomb Bag (30)", "ITEM_BOMB_BAG_30", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_BOMB_BAG_40, "Biggest Bomb Bag (40)", "ITEM_BOMB_BAG_40", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_BRACELET, "Goron's Bracelet", "ITEM_BRACELET", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_GAUNTLETS_SILVER, "Silver Gauntlets", "ITEM_GAUNTLETS_SILVER", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_GAUNTLETS_GOLD, "Golden Gauntlets", "ITEM_GAUNTLETS_GOLD", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_SCALE_SILVER, "Silver Scale", "ITEM_SCALE_SILVER", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_SCALE_GOLDEN, "Golden Scale", "ITEM_SCALE_GOLDEN", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_SWORD_KNIFE, "Giant's Knife (Broken)", "ITEM_SWORD_KNIFE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_WALLET_ADULT, "Adult's Wallet", "ITEM_WALLET_ADULT", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_WALLET_GIANT, "Giant's Wallet", "ITEM_WALLET_GIANT", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_FISHING_POLE, "Fishing Pole", "ITEM_FISHING_POLE", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_MINUET, "Minuet of Forest", "QUEST_SONG_MINUET", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_BOLERO, "Bolero of Fire", "QUEST_SONG_BOLERO", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_SERENADE, "Serenade of Water", "QUEST_SONG_SERENADE", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_REQUIEM, "Requiem of Spirit", "QUEST_SONG_REQUIEM", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_NOCTURNE, "Nocturne of Shadow", "QUEST_SONG_NOCTURNE", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_PRELUDE, "Prelude of Light", "QUEST_SONG_PRELUDE", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_LULLABY, "Zelda's Lullaby", "QUEST_SONG_LULLABY", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_EPONA, "Epona's Song", "QUEST_SONG_EPONA", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_SARIA, "Saria's Song", "QUEST_SONG_SARIA", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_SUN, "Sun's Song", "QUEST_SONG_SUN", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_TIME, "Song of Time", "QUEST_SONG_TIME", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SONG_STORMS, "Song of Storms", "QUEST_SONG_STORMS", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_MEDALLION_FOREST, "Forest Medallion", "QUEST_MEDALLION_FOREST", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_MEDALLION_FIRE, "Fire Medallion", "QUEST_MEDALLION_FIRE", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_MEDALLION_WATER, "Water Medallion", "QUEST_MEDALLION_WATER", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_MEDALLION_SPIRIT, "Spirit Medallion", "QUEST_MEDALLION_SPIRIT", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_MEDALLION_SHADOW, "Shadow Medallion", "QUEST_MEDALLION_SHADOW", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_MEDALLION_LIGHT, "Light Medallion", "QUEST_MEDALLION_LIGHT", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_KOKIRI_EMERALD, "Kokiri's Emerald", "QUEST_KOKIRI_EMERALD", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_GORON_RUBY, "Goron's Ruby", "QUEST_GORON_RUBY", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_ZORA_SAPPHIRE, "Zora's Sapphire", "QUEST_ZORA_SAPPHIRE", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_STONE_OF_AGONY, "Stone of Agony", "QUEST_STONE_OF_AGONY", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_GERUDO_CARD, "Gerudo's Card", "QUEST_GERUDO_CARD", COLOR_WHITE }, - { SPLIT_TYPE_QUEST, ITEM_SKULL_TOKEN, "Skulltula Token", "QUEST_SKULL_TOKEN", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_SINGLE_MAGIC, "Magic Meter", "ITEM_MAGIC_SMALL", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_DOUBLE_MAGIC, "Double Magic", "ITEM_MAGIC_LARGE", COLOR_WHITE }, - { SPLIT_TYPE_ITEM, ITEM_DOUBLE_DEFENSE, "Double Defense", "ITEM_HEART_CONTAINER", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_STICK_UPGRADE_20, "Deku Stick Upgrade (20)", "ITEM_STICK", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_STICK_UPGRADE_30, "Deku Stick Upgrade (30)", "ITEM_STICK", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_NUT_UPGRADE_30, "Deku Nut Upgrade (30)", "ITEM_NUT", COLOR_WHITE }, - { SPLIT_TYPE_UPGRADE, ITEM_NUT_UPGRADE_40, "Deku Nut Upgrade (40)", "ITEM_NUT", COLOR_WHITE }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_GOMA, "Queen Gohma", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_LIGHT_GREEN }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_DODONGO, "King Dodongo", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_LIGHT_RED }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_VA, "Barinade", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_LIGHT_BLUE }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANONDROF, "Phantom Ganon", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_GREEN }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_FD2, "Volvagia", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_RED }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_MO, "Morpha", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_BLUE }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_SST, "Bongo Bongo", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_PURPLE }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_TW, "Twinrova", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_ORANGE }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANON, "Ganondorf", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_GREY }, - { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANON2, "Ganon", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_YELLOW }, - { SPLIT_TYPE_ENTRANCE, SCENE_DEKU_TREE, "Enter Deku Tree", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_DODONGOS_CAVERN, "Enter Dodongos Cavern", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_JABU_JABU, "Enter Jabu Jabu's Belly", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_FOREST_TEMPLE, "Enter Forest Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_FIRE_TEMPLE, "Enter Fire Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_WATER_TEMPLE, "Enter Water Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_SPIRIT_TEMPLE, "Enter Spirit Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_SHADOW_TEMPLE, "Enter Shadow Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_BOTTOM_OF_THE_WELL, "Enter Bottom of the Well", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_ICE_CAVERN, "Enter Ice Cavern", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_GANONS_TOWER, "Enter Ganons Tower", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_GERUDO_TRAINING_GROUND, "Enter Gerudo Training Ground", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_THIEVES_HIDEOUT, "Enter Thieves Hideout", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_INSIDE_GANONS_CASTLE, "Enter Ganons Castle", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_GANONS_TOWER_COLLAPSE_INTERIOR, "Enter Tower Collapse Interior", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_ENTRANCE, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, "Enter Ganons Castle Collapse", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_MISC, SCENE_ZORAS_RIVER, "Lost Woods Escape", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_MISC, SCENE_LOST_WOODS, "Forest Escape", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - { SPLIT_TYPE_MISC, SCENE_KAKARIKO_VILLAGE, "Watchtower Death", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, - - // clang-format on -}; - -std::map> popupList = { - // clang-format off - { ITEM_STICK, { ITEM_STICK, ITEM_STICK_UPGRADE_20, ITEM_STICK_UPGRADE_30 } }, - { ITEM_NUT, { ITEM_NUT, ITEM_NUT_UPGRADE_30, ITEM_NUT_UPGRADE_40 } }, - { ITEM_BOMB, { ITEM_BOMB_BAG_20, ITEM_BOMB_BAG_30, ITEM_BOMB_BAG_40 } }, - { ITEM_BOW, { ITEM_QUIVER_30, ITEM_QUIVER_40, ITEM_QUIVER_50 } }, - { ITEM_SLINGSHOT, { ITEM_BULLET_BAG_30, ITEM_BULLET_BAG_40, ITEM_BULLET_BAG_50 } }, - { ITEM_OCARINA_FAIRY, { ITEM_OCARINA_FAIRY, ITEM_OCARINA_TIME } }, - { ITEM_HOOKSHOT, { ITEM_HOOKSHOT, ITEM_LONGSHOT } }, - { ITEM_BOTTLE, { ITEM_BOTTLE, ITEM_POTION_RED, ITEM_POTION_GREEN, ITEM_POTION_BLUE, - ITEM_FAIRY, ITEM_FISH, ITEM_MILK_BOTTLE, ITEM_LETTER_RUTO, - ITEM_BLUE_FIRE, ITEM_BUG, ITEM_BIG_POE, ITEM_POE } }, - { ITEM_WEIRD_EGG, { ITEM_WEIRD_EGG, ITEM_CHICKEN, ITEM_LETTER_ZELDA, ITEM_MASK_KEATON, - ITEM_MASK_SKULL, ITEM_MASK_SPOOKY, ITEM_MASK_BUNNY, ITEM_MASK_GORON, - ITEM_MASK_ZORA, ITEM_MASK_GERUDO, ITEM_MASK_TRUTH } }, - { ITEM_POCKET_EGG, { ITEM_POCKET_EGG, ITEM_POCKET_CUCCO, ITEM_COJIRO, ITEM_ODD_MUSHROOM, - ITEM_ODD_POTION, ITEM_SAW, ITEM_SWORD_BROKEN, ITEM_PRESCRIPTION, - ITEM_FROG, ITEM_EYEDROPS, ITEM_CLAIM_CHECK } }, - { ITEM_BRACELET, { ITEM_BRACELET, ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_GOLD } }, - { ITEM_SCALE_SILVER, { ITEM_SCALE_SILVER, ITEM_SCALE_GOLDEN } }, - { ITEM_WALLET_ADULT, { ITEM_WALLET_ADULT, ITEM_WALLET_GIANT } }, - { ITEM_SINGLE_MAGIC, { ITEM_SINGLE_MAGIC, ITEM_DOUBLE_MAGIC } }, - { ITEM_SKULL_TOKEN, { } } - - // clang-format on -}; - -std::string removeSpecialCharacters(const std::string& str) { - std::string result; - for (char ch : str) { - // Only keep alphanumeric characters (letters and digits) - if (std::isalnum(static_cast(ch))) { - result += ch; + textDisplay.timeDisplay = splitCompare.splitPreviousBest; + if (splitCompare.splitPreviousBest == 0) { + textDisplay.timeDisplay = splitCompare.splitPreviousBest; + textDisplay.colorDisplay = COLOR_GREY; + } else { + if (split.splitStatus == SPLIT_ACTIVE) { + if (GetTotalTime() <= splitCompare.splitPreviousBest) { + textDisplay.colorDisplay = COLOR_LIGHTGREEN; + } else { + textDisplay.colorDisplay = COLOR_LIGHTRED; + } + } else { + if (split.splitCurrentTime <= splitCompare.splitPreviousBest) { + textDisplay.colorDisplay = COLOR_LIGHTRED; + } else { + textDisplay.colorDisplay = COLOR_LIGHTGREEN; + } } } - return result; + + return textDisplay; } -std::string formatTimestampTimeSplit(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 spdlog::fmt_lib::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds); -} +SplitTextObject GetTimeDiffTextDisplay(TimesplitObject split) { + SplitTextObject textDisplay; -nlohmann::json ImVec4_to_json(const ImVec4& vec) { - return nlohmann::json{ { "x", vec.x }, { "y", vec.y }, { "z", vec.z }, { "w", vec.w } }; -} - -ImVec4 json_to_ImVec4(const nlohmann::json& jsonVec) { - return ImVec4(jsonVec["x"], jsonVec["y"], jsonVec["z"], jsonVec["w"]); -} - -nlohmann::json SplitObject_to_json(const SplitObject& split) { - return nlohmann::json{ { "splitType", split.splitType }, - { "splitID", split.splitID }, - { "splitName", split.splitName }, - { "splitImage", split.splitImage }, - { "splitTint", ImVec4_to_json(split.splitTint) }, - { "splitTimeCurrent", split.splitTimeCurrent }, - { "splitTimeBest", split.splitTimeBest }, - { "splitTimePreviousBest", split.splitTimePreviousBest }, - { "splitTimeStatus", SPLIT_STATUS_INACTIVE }, - { "splitSkullTokenCount", split.splitSkullTokenCount } }; -} - -SplitObject json_to_SplitObject(const nlohmann::json& jsonSplit) { - SplitObject split; - split.splitType = jsonSplit["splitType"]; - split.splitID = jsonSplit["splitID"]; - split.splitName = jsonSplit["splitName"].get(); - split.splitImage = jsonSplit["splitImage"].get(); - split.splitTint = json_to_ImVec4(jsonSplit["splitTint"]); - split.splitTimeCurrent = jsonSplit["splitTimeCurrent"]; - split.splitTimeBest = jsonSplit["splitTimeBest"]; - split.splitTimePreviousBest = jsonSplit["splitTimePreviousBest"]; - split.splitTimeStatus = jsonSplit["splitTimeStatus"]; - split.splitSkullTokenCount = jsonSplit["splitSkullTokenCount"]; - return split; -} - -void TimeSplitsGetImageSize(uint32_t item) { - if (item >= ITEM_SONG_MINUET && item <= ITEM_SONG_STORMS) { - imageSize = ImVec2(30.0f, 38.0f); - imagePadding = 6.0f; - } else { - imageSize = ImVec2(38.0f, 38.0f); - imagePadding = 2.0f; + switch (split.splitStatus) { + case SPLIT_INACTIVE: + case SPLIT_SKIPPED: + textDisplay.timeDisplay = split.splitPreviousBest; + textDisplay.colorDisplay = COLOR_GREY; + return textDisplay; + case SPLIT_ACTIVE: + if (GetTotalTime() > split.splitPreviousBest) { + textDisplay.timeDisplay = GetTotalTime() - split.splitPreviousBest; + textDisplay.colorDisplay = COLOR_RED; + } else if (GetTotalTime() == split.splitPreviousBest) { + textDisplay.timeDisplay = GetTotalTime(); + textDisplay.colorDisplay = COLOR_WHITE; + } else if (GetTotalTime() < split.splitPreviousBest) { + textDisplay.timeDisplay = split.splitPreviousBest - GetTotalTime(); + textDisplay.colorDisplay = COLOR_GREEN; + } + if (split.splitPreviousBest == 0) { + textDisplay.colorDisplay = COLOR_WHITE; + } + return textDisplay; + case SPLIT_COMPLETE: + if (split.splitCurrentTime > split.splitPreviousBest) { + textDisplay.timeDisplay = split.splitCurrentTime - split.splitPreviousBest; + textDisplay.colorDisplay = COLOR_RED; + } else if (split.splitCurrentTime == split.splitPreviousBest) { + textDisplay.timeDisplay = split.splitCurrentTime; + textDisplay.colorDisplay = COLOR_WHITE; + } else if (split.splitCurrentTime < split.splitPreviousBest) { + textDisplay.timeDisplay = split.splitPreviousBest - split.splitCurrentTime; + textDisplay.colorDisplay = COLOR_GREEN; + } + if (split.splitPreviousBest == 0) { + textDisplay.colorDisplay = COLOR_WHITE; + } + return textDisplay; + default: + return textDisplay; } } +void TableCellCenteredText(ImVec4 color, const char* text) { + float textHeight = ImGui::GetTextLineHeight(); + float offsetY = (32.0f - textHeight + 10.0f) * 0.5f; + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + offsetY); + ImGui::TextColored(color, "%s", text); +} + void SplitsPushImageButtonStyle() { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); @@ -310,722 +149,147 @@ void SplitsPopImageButtonStyle() { ImGui::PopStyleColor(3); } -void TimeSplitsUpdateSplitStatus() { - uint32_t index = 0; - for (auto& data : splitList) { - if (data.splitTimeStatus == SPLIT_STATUS_INACTIVE || data.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - data.splitTimeStatus = SPLIT_STATUS_ACTIVE; - break; - } - index++; - } - for (size_t i = index; i < splitList.size(); i++) { - if (splitList[i].splitTimeStatus != SPLIT_STATUS_ACTIVE && - splitList[i].splitTimeStatus != SPLIT_STATUS_COLLECTED) { - splitList[i].splitTimeStatus = SPLIT_STATUS_INACTIVE; - } - } -} +void DrawSplitsList(bool isMain) { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + float columnSizeMultiplier = isMain ? 1.0f : 1.5f; + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 7.0f); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10, 0)); -void HandleDragAndDrop(std::vector& objectList, int targetIndex, const std::string& itemName, - ImGuiDragDropFlags flags = ImGuiDragDropFlags_None) { - if (ImGui::BeginDragDropSource(flags)) { - ImGui::SetDragDropPayload("DragMove", &targetIndex, sizeof(uint32_t)); - ImGui::Text("Move %s", itemName.c_str()); - ImGui::EndDragDropSource(); + if (isMain) { + ImGui::Begin("Timesplits", nullptr, + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoScrollbar); } - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DragMove")) { - IM_ASSERT(payload->DataSize == sizeof(uint32_t)); - dragSourceIndex = *(const int*)payload->Data; - dragTargetIndex = targetIndex; - } - ImGui::EndDragDropTarget(); - } -} - -void TimeSplitCompleteSplits() { - gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = static_cast(GAMEPLAYSTAT_TOTAL_TIME); - gSaveContext.ship.stats.gameComplete = true; -} - -void TimeSplitsSkipSplit(uint32_t index) { - splitList[index].splitTimeStatus = SPLIT_STATUS_SKIPPED; - if (index + 1 == splitList.size()) { - TimeSplitCompleteSplits(); - } else { - TimeSplitsUpdateSplitStatus(); - } -} - -void TimeSplitsFileManagement(uint32_t action, const char* listEntry, std::vector listData) { - std::string filename = Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json"); - json saveFile; - json listArray = nlohmann::json::array(); - - std::ifstream inputFile(filename); - if (inputFile.is_open()) { - inputFile >> saveFile; - inputFile.close(); - } - - if (action == SPLIT_ACTION_SAVE) { - for (auto& data : listData) { - listArray.push_back(SplitObject_to_json(data)); - } - saveFile[listEntry] = listArray; - - // Update Save File on Disk - std::ofstream outputFile(filename); - if (outputFile.is_open()) { - outputFile << saveFile.dump(4); - outputFile.close(); - } - } - - if (action == SPLIT_ACTION_LOAD) { - if (saveFile.contains(listEntry)) { - listArray = saveFile[listEntry]; - splitList.clear(); - - for (auto& data : listArray) { - splitList.push_back(json_to_SplitObject(data)); + if (ImGui::BeginChild("SplitChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar)) { + if (ImGui::BeginTable("Splits", 5, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable)) { + ImGui::TableSetupColumn("Item Image", + ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, + (28.0f * columnSizeMultiplier)); + ImGui::TableSetupColumn("Item Name"); + ImGui::TableSetupColumn("Current Time"); + ImGui::TableSetupColumn("+/-"); + ImGui::TableSetupColumn("Previous Best "); + if (tableColumnFlags != ImGuiTableColumnFlags_None) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); } - splitList[0].splitTimeStatus = SPLIT_STATUS_ACTIVE; - } - } - if (action == SPLIT_ACTION_UPDATE) { - for (auto& update : listData) { - if (update.splitTimeBest < update.splitTimePreviousBest) { - update.splitTimePreviousBest = update.splitTimeBest; - } - } - } + for (uint32_t i = 0; i < splitList.size(); i++) { + ImGui::PushID(i); - if (action == SPLIT_ACTION_COLLECT) { - keys.clear(); - for (auto& data : saveFile.items()) { - keys.push_back(data.key()); - } - if (keys.size() == 0) { - keys.push_back("No Saved Lists"); - } - } + // Item Image Column + ImGui::TableNextColumn(); - if (action == SPLIT_ACTION_DELETE) { - if (saveFile.contains(listEntry)) { - saveFile.erase(listEntry); - - std::ofstream outputFile(filename); - if (outputFile.is_open()) { - outputFile << saveFile.dump(4); - outputFile.close(); - } - } - } -} - -void TimeSplitsPopUpContext() { - if ((popupID != -1) && ImGui::BeginPopup("TimeSplitsPopUp")) { - auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); - if (popupID == ITEM_SKULL_TOKEN) { - ImGui::BeginTable("Token Table", 2); - ImGui::TableNextColumn(); - SplitsPushImageButtonStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 2.0f)); - ImGui::ImageButton("QUEST_SKULL_TOKEN", gui->GetTextureByName("QUEST_SKULL_TOKEN"), ImVec2(32.0f, 32.0f), - ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0)); - ImGui::PopStyleVar(); - ImGui::TableNextColumn(); - SplitsPopImageButtonStyle(); - ImGui::PushItemWidth(150.0f); - - ImGui::BeginGroup(); - std::string MinusBTNName = " - ##Set Tokens"; - ImGui::SameLine(); - if (ImGui::Button(MinusBTNName.c_str()) && skullTokenCount > 0) { - skullTokenCount--; - } - ImGui::SameLine(); - ImGui::SliderInt("##count", &skullTokenCount, 0, 100, "%d Tokens"); - std::string PlusBTNName = " + ##Set Tokens"; - ImGui::SameLine(); - if (ImGui::Button(PlusBTNName.c_str()) && skullTokenCount < 100) { - skullTokenCount++; - } - ImGui::EndGroup(); - - ImGui::PopItemWidth(); - if (ImGui::Button("Set Tokens")) { - auto findID = std::find_if(splitObjectList.begin(), splitObjectList.end(), - [&](const SplitObject& obj) { return obj.splitID == ITEM_SKULL_TOKEN; }); - SplitObject& buildTokenObject = *findID; - std::string tokenStr = " (" + std::to_string(skullTokenCount) + ")"; - buildTokenObject.splitName += tokenStr.c_str(); - buildTokenObject.splitSkullTokenCount = skullTokenCount; - - splitList.push_back(buildTokenObject); - TimeSplitsUpdateSplitStatus(); - ImGui::CloseCurrentPopup(); - popupID = -1; - } - ImGui::EndTable(); - } else { - int rowIndex = 0; - SplitsPushImageButtonStyle(); - for (auto item : popupList[popupID]) { - auto findID = std::find_if(splitObjectList.begin(), splitObjectList.end(), - [&](const SplitObject& obj) { return obj.splitID == item; }); - if (findID == splitObjectList.end()) { - continue; + if (CVarGetInteger("gSettings.TimeSplits.Highlight", 0) && splitList[i].splitStatus == SPLIT_ACTIVE) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); } - SplitObject& popupObject = *findID; - ImGui::BeginGroup(); - ImGui::PushID(popupObject.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 2.0f)); - auto ret = ImGui::ImageButton(popupObject.splitImage.c_str(), - gui->GetTextureByName(popupObject.splitImage), ImVec2(32.0f, 32.0f), - ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), popupObject.splitTint); - ImGui::PopStyleVar(); - if (ret) { - splitList.push_back(popupObject); - if (splitList.size() == 1) { - splitList[0].splitTimeStatus = SPLIT_STATUS_ACTIVE; - } else { - splitList[splitList.size() - 1].splitTimeStatus = SPLIT_STATUS_INACTIVE; - } - ImGui::CloseCurrentPopup(); - popupID = -1; + SplitsPushImageButtonStyle(); + if (ImGui::ImageButton(std::to_string(i).c_str(), gui->GetTextureByName(GetSplitImage(splitList[i])), + GetSplitImageSize(splitList[i]), ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), + GetSplitColor(splitList[i]))) { + SkipSplitEntry(i); + }; + SplitsPopImageButtonStyle(); + + // Item Name Column + ImGui::TableNextColumn(); + TableCellCenteredText(COLOR_WHITE, splitList[i].splitName.c_str()); + + // Current Time Column + ImGui::TableNextColumn(); + TableCellCenteredText( + GetCurrentTimeTextDisplay(splitList[i]).colorDisplay, + !gPlayState ? BLANK_SPLIT + : Ship_FormatTimeDisplay(GetCurrentTimeTextDisplay(splitList[i]).timeDisplay).c_str()); + + // +/- Column + ImGui::TableNextColumn(); + TableCellCenteredText( + GetTimeDiffTextDisplay(splitList[i]).colorDisplay, + !gPlayState ? BLANK_SPLIT + : Ship_FormatTimeDisplay(GetTimeDiffTextDisplay(splitList[i]).timeDisplay).c_str()); + if (CVarGetInteger("gSettings.TimeSplits.Compare", 0) && comparisonList.size() != 0) { + !gPlayState ? ImGui::TextColored(COLOR_WHITE, BLANK_SPLIT) + : i < comparisonList.size() + ? ImGui::TextColored( + GetComparisonTimeTextDisplay(splitList[i], comparisonList[i]).colorDisplay, "%s", + Ship_FormatTimeDisplay( + GetComparisonTimeTextDisplay(splitList[i], comparisonList[i]).timeDisplay) + .c_str()) + : ImGui::TextColored(COLOR_GREY, "No Data"); } + + // Previous Best Column + ImGui::TableNextColumn(); + TableCellCenteredText(COLOR_WHITE, Ship_FormatTimeDisplay(splitList[i].splitPreviousBest).c_str()); + if (CVarGetInteger("gSettings.TimeSplits.Compare", 0) && comparisonList.size() != 0) { + ImGui::TextColored(COLOR_GREY, "%s", + i < comparisonList.size() + ? Ship_FormatTimeDisplay(comparisonList[i].splitPreviousBest).c_str() + : "No Data"); + } + ImGui::PopID(); - if (popupObject.splitType == SPLIT_TYPE_UPGRADE) { - if (popupID <= ITEM_SLINGSHOT && popupID != -1) { - ImVec2 imageMax = ImGui::GetItemRectMax(); - ImVec2 textPos = ImVec2(imageMax.x - ImGui::CalcTextSize("00").x - 5, - imageMax.y - ImGui::CalcTextSize("00").y - 5); - - ImGui::SetCursorScreenPos(textPos); - std::string upgSubstr = popupObject.splitName.substr(popupObject.splitName.size() - 4); - std::string upgOutput = removeSpecialCharacters(upgSubstr); - ImGui::Text("%s", upgOutput.c_str()); - } - } - ImGui::EndGroup(); - if (rowIndex != 5) { - ImGui::SameLine(); - } - rowIndex++; - } - SplitsPopImageButtonStyle(); - } - ImGui::EndPopup(); - } -} - -void TimeSplitsPostDragAndDrop() { - if (dragTargetIndex != -1) { - SplitObject tempSourceSplitObject = splitList[dragSourceIndex]; - if (tempSourceSplitObject.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - tempSourceSplitObject.splitTimeStatus = SPLIT_STATUS_INACTIVE; - } - if (splitList[dragTargetIndex].splitTimeStatus == SPLIT_STATUS_ACTIVE) { - splitList[dragTargetIndex].splitTimeStatus = SPLIT_STATUS_INACTIVE; - } - - splitList.erase(splitList.begin() + dragSourceIndex); - splitList.insert(splitList.begin() + dragTargetIndex, tempSourceSplitObject); - dragTargetIndex = -1; - dragSourceIndex = -1; - - TimeSplitsUpdateSplitStatus(); - } -} - -void TimeSplitsItemSplitEvent(uint32_t type, u8 item) { - uint32_t index = 0; - if (type <= SPLIT_TYPE_QUEST) { - if (item == ITEM_NUTS_5 || item == ITEM_NUTS_10) { - item = ITEM_NUT; - } else if (item == ITEM_STICKS_5 || item == ITEM_STICKS_10) { - item = ITEM_STICK; - } - if (item == ITEM_SKULL_TOKEN) { - auto it = std::find_if(splitList.begin(), splitList.end(), [item](const SplitObject& split) { - if (split.splitSkullTokenCount == static_cast(gSaveContext.inventory.gsTokens)) { - return split.splitID == item; - } else { - return split.splitID == ITEM_NONE; - } - }); - if (it == splitList.end()) { - return; - } - } - } - if (type == SPLIT_TYPE_ENTRANCE) { - if ((item == SCENE_ZORAS_RIVER && gSaveContext.entranceIndex == ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT) || - (item == SCENE_LOST_WOODS && (gSaveContext.entranceIndex == ENTR_LOST_WOODS_BRIDGE_EAST_EXIT || - gSaveContext.entranceIndex == ENTR_LOST_WOODS_SOUTH_EXIT))) { - type = SPLIT_TYPE_MISC; - } - } - - for (auto& split : splitList) { - if (split.splitType == type) { - if (item == split.splitID) { - if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - split.splitTimeCurrent = static_cast(GAMEPLAYSTAT_TOTAL_TIME); - split.splitTimeStatus = SPLIT_STATUS_COLLECTED; - if (split.splitTimeBest > GAMEPLAYSTAT_TOTAL_TIME || split.splitTimeBest == 0) { - split.splitTimeBest = static_cast(GAMEPLAYSTAT_TOTAL_TIME); - } - if (split.splitTimePreviousBest == 0) { - split.splitTimePreviousBest = static_cast(GAMEPLAYSTAT_TOTAL_TIME); - } - if (index == splitList.size() - 1) { - TimeSplitCompleteSplits(); - } else { - splitList[index + 1].splitTimeStatus = SPLIT_STATUS_ACTIVE; - } + if (CVarGetInteger("gSettings.TimeSplits.Follow", 0) && splitList[i].splitStatus == SPLIT_ACTIVE) { + ImGui::SetScrollHereY(); } } - } - index++; - } -} -void TimeSplitsSplitBestTimeDisplay(SplitObject split) { - activeSplitHighlight = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); - if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - if (GAMEPLAYSTAT_TOTAL_TIME > split.splitTimePreviousBest) { - splitTimeColor = COLOR_RED; - splitBestTimeDisplay = (static_cast(GAMEPLAYSTAT_TOTAL_TIME) - split.splitTimePreviousBest); - } - if (GAMEPLAYSTAT_TOTAL_TIME == split.splitTimePreviousBest) { - splitTimeColor = COLOR_WHITE; - splitBestTimeDisplay = static_cast(GAMEPLAYSTAT_TOTAL_TIME); - } - if (GAMEPLAYSTAT_TOTAL_TIME < split.splitTimePreviousBest) { - splitTimeColor = COLOR_GREEN; - splitBestTimeDisplay = (split.splitTimePreviousBest - static_cast(GAMEPLAYSTAT_TOTAL_TIME)); - } - activeSplitHighlight = COLOR_LIGHT_BLUE; - } - if (split.splitTimeStatus == SPLIT_STATUS_INACTIVE) { - splitTimeColor = COLOR_WHITE; - splitBestTimeDisplay = split.splitTimeBest; - } - if (split.splitTimeStatus == SPLIT_STATUS_COLLECTED) { - if (split.splitTimeCurrent > split.splitTimePreviousBest) { - splitTimeColor = COLOR_RED; - splitBestTimeDisplay = (split.splitTimeCurrent - split.splitTimePreviousBest); - } - if (split.splitTimeCurrent == split.splitTimePreviousBest) { - splitTimeColor = COLOR_WHITE; - splitBestTimeDisplay = split.splitTimeCurrent; - } - if (split.splitTimeCurrent < split.splitTimePreviousBest) { - splitTimeColor = COLOR_GREEN; - splitBestTimeDisplay = (split.splitTimePreviousBest - split.splitTimeCurrent); + ImGui::EndTable(); } } -} - -void TimeSplitsDrawSplitsList() { - uint32_t dragIndex = 0; - ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); - if (ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable)) { - auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); - ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, - 34.0f); - ImGui::TableSetupColumn("Item Name"); - ImGui::TableSetupColumn("Current Time"); - ImGui::TableSetupColumn("+/-"); - ImGui::TableSetupColumn("Prev. Best"); - ImGui::TableHeadersRow(); - - SplitsPushImageButtonStyle(); - for (auto& split : splitList) { - ImGui::TableNextColumn(); - TimeSplitsSplitBestTimeDisplay(split); - - ImGui::PushID(split.splitID); - if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); - } - TimeSplitsGetImageSize(split.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = ImGui::ImageButton(split.splitImage.c_str(), gui->GetTextureByName(split.splitImage), imageSize, - ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), split.splitTint); - ImGui::PopStyleVar(); - if (ret) { - TimeSplitsSkipSplit(dragIndex); - } - HandleDragAndDrop(splitList, dragIndex, split.splitName); - ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); - ImGui::AlignTextToFramePadding(); - ImGui::TextWrapped("%s", split.splitName.c_str()); - ImGui::TableNextColumn(); - // Current Time - ImGui::Text("%s", (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) - ? formatTimestampTimeSplit(static_cast(GAMEPLAYSTAT_TOTAL_TIME)).c_str() - : (split.splitTimeStatus == SPLIT_STATUS_COLLECTED) - ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() - : "--:--:-"); - ImGui::TableNextColumn(); - // +/- Difference - ImGui::TextColored(splitTimeColor, "%s", formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); - ImGui::TableNextColumn(); - // Previous Best - ImGui::Text("%s", (split.splitTimePreviousBest != 0) - ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() - : "--:--:-"); - ImGui::PopID(); - ImGui::PopStyleVar(1); - - dragIndex++; - } - SplitsPopImageButtonStyle(); - - TimeSplitsPostDragAndDrop(); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); ImGui::EndChild(); + + if (isMain) { + ImGui::End(); + } + + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(1); } -void TimeSplitsGetTableSize(uint32_t type) { - switch (type) { - case SPLIT_TYPE_ITEM: - case SPLIT_TYPE_QUEST: - tableSize = 6; +void UpdateSplitSettings(uint32_t settingName) { + switch (settingName) { + case SPLIT_HEADERS: + tableColumnFlags = CVarGetInteger("gSettings.TimeSplits.ShowHeaders", 0) + ? ImGuiTableColumnFlags_NoHeaderLabel + : ImGuiTableColumnFlags_None; break; - case SPLIT_TYPE_EQUIPMENT: - tableSize = 3; + case SPLIT_OPACITY: + splitOpacity.w = CVarGetInteger("gSettings.TimeSplits.Opacity", 0) ? 0 : 0.5f; + break; + case SPLIT_COMPARE: + if (CVarGetInteger("gSettings.TimeSplits.Compare", 0)) { + SplitSaveFileAction(SPLIT_RETRIEVE, ""); + if (savedLists.size() != 0) { + SplitLoadComparisonList(); + } + } break; default: - tableSize = 2; break; } } -void TimeSplitsDrawItemList(uint32_t type) { - TimeSplitsGetTableSize(type); - - ImGui::BeginChild("Item Child"); - ImGui::BeginTable("Item List", tableSize); - for (size_t i = 0; i < tableSize; i++) { - if (i == 0) { - ImGui::TableSetupColumn("Item Image", - ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 39.0f); - } else { - if (type > SPLIT_TYPE_QUEST) { - ImGui::TableSetupColumn("Item Name"); - } else { - ImGui::TableSetupColumn(std::to_string(i).c_str(), - ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 39.0f); - } - } +void TimesplitsWindow::Draw() { + if (!CVarGetInteger("gSettings.TimeSplits.Enable", 0)) { + return; } - - for (auto& split : splitObjectList) { - if (split.splitType == type) { - auto gui = - std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); - ImGui::TableNextColumn(); - ImGui::PushID(split.splitID); - TimeSplitsGetImageSize(split.splitID); - SplitsPushImageButtonStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = ImGui::ImageButton(split.splitImage.c_str(), gui->GetTextureByName(split.splitImage), imageSize, - ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), split.splitTint); - ImGui::PopStyleVar(); - if (ret) { - if (popupList.contains(split.splitID) && (split.splitType < SPLIT_TYPE_BOSS)) { - popupID = split.splitID; - ImGui::OpenPopup("TimeSplitsPopUp"); - } else { - splitList.push_back(split); - - if (splitList.size() == 1) { - splitList[0].splitTimeStatus = SPLIT_STATUS_ACTIVE; - } else { - splitList[splitList.size() - 1].splitTimeStatus = SPLIT_STATUS_INACTIVE; - } - } - } - SplitsPopImageButtonStyle(); - - TimeSplitsPopUpContext(); - ImGui::PopID(); - - if (type > SPLIT_TYPE_QUEST) { - ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 7.0f)); - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", split.splitName.c_str()); - ImGui::PopStyleVar(1); - } - } - } - ImGui::EndTable(); - ImGui::EndChild(); -} - -void TimeSplitsUpdateWindowSize() { - timeSplitsWindowSize = CVarGetFloat(CVAR_ENHANCEMENT("TimeSplits.WindowScale"), 0); - if (timeSplitsWindowSize < 1.0f) { - timeSplitsWindowSize = 1.0f; - } -} - -void TimeSplitsDrawOptionsMenu() { - ImGui::SeparatorText("Window Options"); - Color_RGBA8 defaultColor = { 0, 0, 0, 255 }; - if (CVarColorPicker("Background Color", CVAR_ENHANCEMENT("TimeSplits.WindowColor"), defaultColor, true, 0, - THEME_COLOR)) { - windowColor = VecFromRGBA8(CVarGetColor(CVAR_ENHANCEMENT("TimeSplits.WindowColor.Value"), defaultColor)); - } - - if (CVarSliderFloat("Window Scale", CVAR_ENHANCEMENT("TimeSplits.WindowScale"), - FloatSliderOptions() - .Min(1.0f) - .Max(3.0f) - .DefaultValue(1.0f) - .Format("%.1fx") - .Size({ 300.0f, 0.0f }) - .Step(0.1f) - .Color(THEME_COLOR))) { - TimeSplitsUpdateWindowSize(); - } - - ImGui::SeparatorText("Split List Management"); - - ImGui::Text("New List Name: "); - ImGui::PushItemWidth(150.0f); - PushStyleInput(THEME_COLOR); - ImGui::InputText("##listName", listNameBuf, 25); - PopStyleInput(); - ImGui::PopItemWidth(); - ImGui::SameLine(); - if (Button("Create List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { - TimeSplitsFileManagement(SPLIT_ACTION_SAVE, listNameBuf, splitList); - } - UIWidgets::PaddedSeparator(); - - TimeSplitsFileManagement(SPLIT_ACTION_COLLECT, "", emptyList); - static uint32_t selectedItem = 0; - ImGui::Text("Select List to Load: "); - ImGui::PushItemWidth(150.0f); - Combobox("", &selectedItem, keys, ComboboxOptions().Color(THEME_COLOR).LabelPosition(LabelPositions::Near)); - ImGui::PopItemWidth(); - ImGui::SameLine(); - if (Button("Load List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { - TimeSplitsFileManagement(SPLIT_ACTION_LOAD, keys[selectedItem].c_str(), emptyList); - } - ImGui::SameLine(); - if (Button("Save List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { - TimeSplitsFileManagement(SPLIT_ACTION_SAVE, keys[selectedItem].c_str(), splitList); - } - ImGui::SameLine(); - if (Button("Delete List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { - TimeSplitsFileManagement(SPLIT_ACTION_DELETE, keys[selectedItem].c_str(), emptyList); - } - UIWidgets::Separator(true, true, ImGui::GetStyle().ItemSpacing.y, ImGui::GetStyle().ItemSpacing.y); - - if (Button("New Attempt", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { - for (auto& data : splitList) { - data.splitTimeStatus = SPLIT_STATUS_INACTIVE; - } - splitList[0].splitTimeStatus = SPLIT_STATUS_ACTIVE; - } - ImGui::SameLine(); - if (Button("Update Splits", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { - TimeSplitsFileManagement(SPLIT_ACTION_UPDATE, keys[selectedItem].c_str(), splitList); - } -} - -void TimeSplitsRemoveSplitEntry(uint32_t index) { - if (removeIndex != -1) { - splitList.erase(splitList.begin() + index); - removeIndex = -1; - } -} - -void TimeSplitsDrawManageList() { - uint32_t index = 0; - ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); - if (ImGui::BeginTable("List Management", 2, ImGuiTableFlags_BordersInnerV)) { - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Options", ImGuiTableColumnFlags_NoHeaderLabel); - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); - - ImGui::TableNextColumn(); - ImGui::BeginTabBar("List Preview"); - if (ImGui::BeginTabItem("Preview")) { - auto gui = - std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); - ImGui::BeginChild("PreviewChild"); - for (auto& data : splitList) { - float availableWidth = ImGui::GetContentRegionAvail().x; - float imageWidth = 38.0f; // Width of your image button - float offsetX = (availableWidth - imageWidth) * 0.5f; // Centering offset - - if (offsetX > 0.0f) { - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); // Apply the offset to center - } - TimeSplitsGetImageSize(data.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = - ImGui::ImageButton(data.splitImage.c_str(), gui->GetTextureByName(data.splitImage), imageSize, - ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), data.splitTint); - ImGui::PopStyleVar(); - if (ret) { - removeIndex = index; - } - HandleDragAndDrop(splitList, index, splitList[index].splitName); - index++; - } - TimeSplitsRemoveSplitEntry(removeIndex); - ImGui::EndChild(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - - ImGui::PopStyleColor(3); - ImGui::TableNextColumn(); - ImGui::BeginTabBar("List Options"); - if (ImGui::BeginTabItem("Equipment")) { - TimeSplitsDrawItemList(SPLIT_TYPE_EQUIPMENT); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Inventory")) { - TimeSplitsDrawItemList(SPLIT_TYPE_ITEM); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Quest")) { - TimeSplitsDrawItemList(SPLIT_TYPE_QUEST); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Entrances")) { - TimeSplitsDrawItemList(SPLIT_TYPE_ENTRANCE); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Bosses")) { - TimeSplitsDrawItemList(SPLIT_TYPE_BOSS); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Miscellaneous")) { - TimeSplitsDrawItemList(SPLIT_TYPE_MISC); - ImGui::EndTabItem(); - } - - TimeSplitsPostDragAndDrop(); - - ImGui::EndTabBar(); - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - ImGui::EndChild(); -} - -void TimeSplitWindow::Draw() { - ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor); - GuiWindow::Draw(); + ImGui::PushStyleColor(ImGuiCol_WindowBg, splitOpacity); + DrawSplitsList(true); ImGui::PopStyleColor(); } -static bool initialized = false; - -void TimeSplitWindow::DrawElement() { - ImGui::SetWindowFontScale(timeSplitsWindowSize); - - PushStyleTabs(THEME_COLOR); - if (ImGui::BeginTabBar("Split Tabs")) { - if (ImGui::BeginTabItem("Splits")) { - TimeSplitsDrawSplitsList(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Manage List")) { - TimeSplitsDrawManageList(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Options")) { - TimeSplitsDrawOptionsMenu(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - PopStyleTabs(); +void TimesplitsWindow::InitElement() { + UpdateSplitSettings(SPLIT_HEADERS); + UpdateSplitSettings(SPLIT_OPACITY); + UpdateSplitSettings(SPLIT_COMPARE); } -void TimeSplitWindow::InitElement() { - auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); - TimeSplitsUpdateWindowSize(); - - gui->LoadGuiTexture("SPECIAL_TRIFORCE_PIECE_WHITE", gWTriforcePieceTex, "", ImVec4(1, 1, 1, 1)); - gui->LoadGuiTexture("SPECIAL_SPLIT_ENTRANCE", gSplitEntranceTex, "", ImVec4(1, 1, 1, 1)); - Color_RGBA8 defaultColour = { 0, 0, 0, 255 }; - windowColor = VecFromRGBA8(CVarGetColor(CVAR_ENHANCEMENT("TimeSplits.WindowColor.Value"), defaultColour)); - - GameInteractor::Instance->RegisterGameHook([](u8 item) { - if (item != ITEM_SKULL_TOKEN) { - uint32_t tempType = SPLIT_TYPE_ITEM; - for (auto& data : splitList) { - if (data.splitID == item) { - tempType = data.splitType; - break; - } - } - TimeSplitsItemSplitEvent(tempType, item); - } - }); - - GameInteractor::Instance->RegisterGameHook([](GetItemEntry itemEntry) { - if (itemEntry.itemId == ITEM_SKULL_TOKEN || itemEntry.itemId == ITEM_BOTTLE || itemEntry.itemId == ITEM_POE || - itemEntry.itemId == ITEM_BIG_POE) { - uint32_t tempType = SPLIT_TYPE_ITEM; - for (auto& data : splitList) { - if (data.splitID == itemEntry.itemId) { - tempType = data.splitType; - break; - } - } - TimeSplitsItemSplitEvent(tempType, static_cast(itemEntry.itemId)); - } - }); - - GameInteractor::Instance->RegisterGameHook( - [](int16_t contents) { TimeSplitsItemSplitEvent(SPLIT_TYPE_UPGRADE, static_cast(contents)); }); - - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - Actor* bossActor = (Actor*)refActor; - TimeSplitsItemSplitEvent(SPLIT_TYPE_BOSS, static_cast(bossActor->id)); - }); - - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { - if (gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { - TimeSplitsItemSplitEvent(SPLIT_TYPE_ENTRANCE, static_cast(sceneNum)); - } - }); - - GameInteractor::Instance->RegisterGameHook([](int16_t amount) { - if (gPlayState->sceneNum == SCENE_KAKARIKO_VILLAGE) { - Player* player = GET_PLAYER(gPlayState); - if (player->fallDistance > 500 && gSaveContext.health <= 0) { - TimeSplitsItemSplitEvent(SPLIT_TYPE_MISC, static_cast(gPlayState->sceneNum)); - } - } - }); -} +} // namespace TimeSplits \ No newline at end of file diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.h b/soh/soh/Enhancements/timesplits/TimeSplits.h index 4a4828a373..d41eec2160 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.h +++ b/soh/soh/Enhancements/timesplits/TimeSplits.h @@ -2,53 +2,95 @@ #include +#include +#include + #ifdef __cplusplus -class TimeSplitWindow final : public Ship::GuiWindow { +namespace TimeSplits { + +class TimesplitsWindow : public Ship::GuiWindow { public: using GuiWindow::GuiWindow; void InitElement() override; + void DrawElement() override{}; void Draw() override; - void DrawElement() override; void UpdateElement() override{}; }; -typedef enum SplitAction { - SPLIT_ACTION_SAVE, - SPLIT_ACTION_LOAD, - SPLIT_ACTION_UPDATE, - SPLIT_ACTION_COLLECT, - SPLIT_ACTION_DELETE -} SplitAction; +typedef enum { + SPLIT_HEADERS, + SPLIT_OPACITY, + SPLIT_HIGHLIGHT, + SPLIT_COMPARE, +} SplitSettings; -typedef enum SplitStatus { - SPLIT_STATUS_ACTIVE, - SPLIT_STATUS_INACTIVE, - SPLIT_STATUS_COLLECTED, - SPLIT_STATUS_SKIPPED, -} SplitStatus; +typedef enum { + SPLIT_SAVE, + SPLIT_LOAD, + SPLIT_RETRIEVE, + SPLIT_CONVERT, +} SplitFileActions; -typedef enum SplitType { - SPLIT_TYPE_ITEM, - SPLIT_TYPE_UPGRADE, - SPLIT_TYPE_EQUIPMENT, - SPLIT_TYPE_QUEST, - SPLIT_TYPE_BOSS, - SPLIT_TYPE_ENTRANCE, - SPLIT_TYPE_MISC -} SplitType; +typedef enum { SPLIT_INACTIVE, SPLIT_ACTIVE, SPLIT_COMPLETE, SPLIT_SKIPPED } SplitStatus; + +// Split ids come from several enums, the type says which one an id belongs to. +typedef enum { + SPLIT_TYPE_ITEM, // ITEM_* + SPLIT_TYPE_SCENE, // SCENE_* + SPLIT_TYPE_BOSS, // ACTOR_* of the boss +} SplitTypes; typedef struct { - uint32_t splitType; - uint32_t splitID; + uint32_t splitId; std::string splitName; - std::string splitImage; - ImVec4 splitTint; - uint32_t splitTimeCurrent; - uint32_t splitTimeBest; - uint32_t splitTimePreviousBest; - uint32_t splitTimeStatus; - uint32_t splitSkullTokenCount; -} SplitObject; + uint32_t splitCurrentTime; + uint32_t splitPreviousBest; + uint8_t splitStatus; + uint32_t splitType; +} TimesplitObject; + +#define SPLIT_BOSS(actorId, name) \ + { actorId, name, 0, 0, SPLIT_INACTIVE, SPLIT_TYPE_BOSS } + +typedef struct { + uint32_t timeDisplay; + ImVec4 colorDisplay; +} SplitTextObject; + +typedef struct { + uint32_t startIndex; + uint32_t endIndex; +} IndexRangeObject; + +extern std::vector splitList; +extern std::vector comparisonList; +extern std::vector splitObjectList; +extern std::vector sceneObjectList; +extern std::vector savedLists; +extern std::map> itemSubMenuList; +extern uint32_t comparedIndex; +extern bool shouldPopUpOpen; +extern uint32_t GetTotalTime(); +extern ImVec4 GetSplitColor(const TimesplitObject& split); +extern TimesplitObject GetSplitObject(uint32_t splitType, uint32_t splitId); +extern ImVec2 GetSplitImageSize(const TimesplitObject& split); +extern void TableCellCenteredText(ImVec4 color, const char* text); +extern const char* GetSplitImage(const TimesplitObject& split); +extern void SplitsPushImageButtonStyle(); +extern void SplitsPopImageButtonStyle(); +extern void HandlePopUpContext(uint32_t popupId); +extern void HandleDragAndDrop(size_t i); +extern void UpdateSplitBests(); +extern void UpdateSplitSettings(uint32_t settingName); +extern void SkipSplitEntry(uint32_t index); +extern void AddSplitEntryBySceneId(uint32_t sceneId); +extern void AddSplitEntry(uint32_t splitType, uint32_t splitId); +extern void RemoveSplitEntry(uint32_t index); +extern void SplitSaveFileAction(uint32_t action, std::string listName); +extern void DrawSplitsList(bool isMain); +extern void SplitLoadComparisonList(); + +} // namespace TimeSplits #endif diff --git a/soh/soh/Enhancements/timesplits/TimeSplitsActions.cpp b/soh/soh/Enhancements/timesplits/TimeSplitsActions.cpp new file mode 100644 index 0000000000..b614e7dacd --- /dev/null +++ b/soh/soh/Enhancements/timesplits/TimeSplitsActions.cpp @@ -0,0 +1,432 @@ +#include "TimeSplits.h" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "fast/Fast3dGui.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include +#include +#include + +using json = nlohmann::json; + +extern "C" { +#include "variables.h" +uint64_t GetUnixTimestamp(); +} + +#define CVAR_NAME "gSettings.TimeSplits.Enable" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +namespace TimeSplits { + +typedef struct { + uint32_t splitType; + uint32_t splitId; +} LegacySplitId; + +// Bosses and the magic upgrades used to be ids 256+ in the item space, saved lists still hold those. +#define LEGACY_SPLIT_ID_BASE 256 +static const LegacySplitId legacySplitIds[] = { + { SPLIT_TYPE_BOSS, ACTOR_BOSS_GOMA }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_DODONGO }, + { SPLIT_TYPE_BOSS, ACTOR_BOSS_VA }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANONDROF }, + { SPLIT_TYPE_BOSS, ACTOR_BOSS_FD2 }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_MO }, + { SPLIT_TYPE_BOSS, ACTOR_BOSS_SST }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_TW }, + { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANON }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANON2 }, + { SPLIT_TYPE_ITEM, ITEM_SINGLE_MAGIC }, { SPLIT_TYPE_ITEM, ITEM_DOUBLE_MAGIC }, + { SPLIT_TYPE_ITEM, ITEM_DOUBLE_DEFENSE }, +}; + +nlohmann::json TimesplitObject_to_json(const TimesplitObject& split) { + return nlohmann::json{ + { "splitId", split.splitId }, + { "splitName", split.splitName }, + { "splitCurrentTime", split.splitCurrentTime }, + { "splitPreviousBest", split.splitPreviousBest }, + { "splitStatus", SPLIT_INACTIVE }, + { "splitType", split.splitType }, + }; +} + +TimesplitObject json_to_TimesplitObject(const nlohmann::json& jsonSplit) { + TimesplitObject split; + split.splitId = jsonSplit["splitId"]; + split.splitName = jsonSplit["splitName"].get(); + split.splitCurrentTime = jsonSplit["splitCurrentTime"]; + split.splitPreviousBest = jsonSplit["splitPreviousBest"]; + split.splitStatus = jsonSplit["splitStatus"]; + if (jsonSplit.contains("splitType")) { + split.splitType = jsonSplit["splitType"]; + } else { + split.splitType = SPLIT_TYPE_ITEM; + } + + uint32_t legacyIndex = split.splitId - LEGACY_SPLIT_ID_BASE; + if (split.splitType == SPLIT_TYPE_ITEM && split.splitId >= LEGACY_SPLIT_ID_BASE && + legacyIndex < std::size(legacySplitIds)) { + split.splitType = legacySplitIds[legacyIndex].splitType; + split.splitId = legacySplitIds[legacyIndex].splitId; + } + + return split; +} + +uint32_t GetCurrentActiveSplit(const std::vector& list) { + for (size_t i = 0; i < splitList.size(); i++) { + if (splitList[i].splitStatus == SPLIT_ACTIVE) { + return (uint32_t)i; + } + } + return -1; +} + +TimesplitObject GetSplitObjectBySceneId(uint32_t sceneId) { + TimesplitObject splitObject; + for (auto& list : sceneObjectList) { + if (list.splitId == sceneId) { + splitObject = list; + splitObject.splitType = SPLIT_TYPE_SCENE; + break; + } + } + return splitObject; +} + +TimesplitObject GetSplitObject(uint32_t splitType, uint32_t splitId) { + TimesplitObject splitObject; + for (auto& list : splitObjectList) { + if (list.splitType == splitType && list.splitId == splitId) { + splitObject = list; + } + } + return splitObject; +} + +void HandlePopUpContext(uint32_t popupId) { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + if (shouldPopUpOpen && ImGui::BeginPopup("ItemSubMenu")) { + std::vector itemList; + + for (auto& item : itemSubMenuList) { + if (item.first == popupId) { + itemList = item.second; + break; + } + } + + if (itemList.size() == 0) { + ImGui::EndPopup(); + return; + } + + uint32_t slotIndex = 0; + for (auto& list : itemList) { + TimesplitObject split = GetSplitObject(SPLIT_TYPE_ITEM, list); + SplitsPushImageButtonStyle(); + if (ImGui::ImageButton(std::to_string(list).c_str(), gui->GetTextureByName(GetSplitImage(split)), + GetSplitImageSize(split) * 1.5f, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), + GetSplitColor(split))) { + AddSplitEntry(SPLIT_TYPE_ITEM, list); + ImGui::CloseCurrentPopup(); + shouldPopUpOpen = false; + } + UIWidgets::Tooltip(split.splitName.c_str()); + SplitsPopImageButtonStyle(); + + if (slotIndex == 4) { + slotIndex = -1; + } else { + ImGui::SameLine(); + } + slotIndex++; + } + ImGui::EndPopup(); + } +} + +void HandleDragAndDrop(size_t i) { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("SPLIT_DRAG", &i, sizeof(size_t)); + ImGui::ImageButton(std::to_string(splitList[i].splitId).c_str(), + gui->GetTextureByName(GetSplitImage(splitList[i])), GetSplitImageSize(splitList[i]), + ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), GetSplitColor(splitList[i])); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SPLIT_DRAG")) { + size_t srcIndex = *(const size_t*)payload->Data; + if (srcIndex != i && srcIndex < splitList.size()) { + auto item = splitList[srcIndex]; + splitList.erase(splitList.begin() + srcIndex); + + if (srcIndex < i) { + i--; + } + + splitList.insert(splitList.begin() + i, item); + } + } + ImGui::EndDragDropTarget(); + } +} + +void CheckSplitsCompleted(uint32_t index) { + if (index == splitList.size() - 1) { + gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TIMESPLITS_COMPLETED] = GetTotalTime(); + gSaveContext.ship.stats.gameComplete = true; + } else { + splitList[index + 1].splitStatus = SPLIT_ACTIVE; + } +} + +void AddSplitEntryBySceneId(uint32_t sceneId) { + TimesplitObject splitObject = GetSplitObjectBySceneId(sceneId); + + if (splitList.size() == 0) { + splitObject.splitStatus = SPLIT_ACTIVE; + } + splitList.push_back(splitObject); +} + +void AddSplitEntry(uint32_t splitType, uint32_t splitId) { + TimesplitObject splitObject = GetSplitObject(splitType, splitId); + + if (splitList.size() == 0) { + splitObject.splitStatus = SPLIT_ACTIVE; + } + splitList.push_back(splitObject); +} + +void RemoveSplitEntry(uint32_t index) { + if (index == GetCurrentActiveSplit(splitList)) { + CheckSplitsCompleted(index); + } + + splitList.erase(splitList.begin() + index); +} + +void SkipSplitEntry(uint32_t index) { + if (splitList[index].splitStatus == SPLIT_ACTIVE) { + CheckSplitsCompleted(index); + } + splitList[index].splitStatus = SPLIT_SKIPPED; +} + +void UpdateSplitBests() { + for (auto& splits : splitList) { + if (splits.splitCurrentTime < splits.splitPreviousBest || splits.splitPreviousBest == 0) { + splits.splitPreviousBest = splits.splitCurrentTime; + } + } +} + +void UpdateSplitStatus(uint32_t splitType, uint32_t splitId) { + uint32_t activeIndex = GetCurrentActiveSplit(splitList); + + if (activeIndex == UINT32_MAX) { + return; + } + + if (splitList[activeIndex].splitType == splitType && splitList[activeIndex].splitId == splitId) { + splitList[activeIndex].splitCurrentTime = GetTotalTime(); + splitList[activeIndex].splitStatus = SPLIT_COMPLETE; + CheckSplitsCompleted(activeIndex); + } +} + +static bool IsUpgradeSplitDone(const TimesplitObject& split) { + if (split.splitType != SPLIT_TYPE_ITEM) { + return false; + } + switch (split.splitId) { + case ITEM_SINGLE_MAGIC: + return gSaveContext.isMagicAcquired; + case ITEM_DOUBLE_MAGIC: + return gSaveContext.isDoubleMagicAcquired; + case ITEM_DOUBLE_DEFENSE: + return gSaveContext.isDoubleDefenseAcquired; + default: + return false; + } +} + +void UpdateSplitStatusByUpgrades() { + uint32_t activeIndex = GetCurrentActiveSplit(splitList); + + if (activeIndex == UINT32_MAX || !IsUpgradeSplitDone(splitList[activeIndex])) { + return; + } + + UpdateSplitStatus(splitList[activeIndex].splitType, splitList[activeIndex].splitId); +} + +void SplitLoadComparisonList() { + std::string filename = Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json"); + json compareFile; + json listArray = nlohmann::json::array(); + + std::ifstream inputFile(filename); + if (inputFile.is_open()) { + inputFile >> compareFile; + inputFile.close(); + } + + if (compareFile.contains(savedLists[comparedIndex])) { + listArray = compareFile[savedLists[comparedIndex]]; + comparisonList.clear(); + + for (auto& data : listArray) { + comparisonList.push_back(json_to_TimesplitObject(data)); + } + } +} + +void SplitSaveFileAction(uint32_t action, std::string listName) { + std::string filename = ""; + + if (action == SPLIT_CONVERT) { + filename = Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json"); + } else { + filename = Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json"); + } + + json saveFile; + json listArray = nlohmann::json::array(); + + std::ifstream inputFile(filename); + if (inputFile.is_open()) { + inputFile >> saveFile; + inputFile.close(); + } + + if (action == SPLIT_SAVE) { + for (auto& data : splitList) { + listArray.push_back(TimesplitObject_to_json(data)); + } + saveFile[listName] = listArray; + + std::ofstream outputFile(filename); + if (outputFile.is_open()) { + outputFile << saveFile.dump(4); + outputFile.close(); + } + } + + if (action == SPLIT_LOAD) { + if (saveFile.contains(listName)) { + listArray = saveFile[listName]; + splitList.clear(); + + for (auto& data : listArray) { + splitList.push_back(json_to_TimesplitObject(data)); + } + splitList[0].splitStatus = SPLIT_ACTIVE; + } + } + + if (action == SPLIT_RETRIEVE) { + savedLists.clear(); + + for (auto& data : saveFile.items()) { + if (!data.key().empty()) { + savedLists.push_back(data.key()); + } + } + if (savedLists.size() == 0) { + savedLists.push_back("Create a List First"); + } + } + + if (action == SPLIT_CONVERT) { + for (auto& data : saveFile.items()) { + splitList.clear(); + for (auto& items : data.value()) { + if (items["splitName"].get() == "Skulltula Token") { + continue; + } + + TimesplitObject splitObject; + splitObject.splitName = items["splitName"].get(); + splitObject.splitCurrentTime = items["splitTimeCurrent"]; + splitObject.splitPreviousBest = items["splitTimePreviousBest"]; + splitObject.splitStatus = SPLIT_INACTIVE; + + // 4 is the old SPLIT_TYPE_BOSS which does not exist anymore + if (items["splitType"] == 4) { + splitObject.splitId = ITEM_NONE; + splitObject.splitType = SPLIT_TYPE_ITEM; + for (auto& split : splitObjectList) { + if (split.splitName == splitObject.splitName) { + splitObject.splitId = split.splitId; + splitObject.splitType = split.splitType; + break; + } + } + } else { + // 5 is the old SPLIT_TYPE_ENTRANCE which is replaced by SPLIT_TYPE_SCENE + splitObject.splitId = items["splitID"]; + splitObject.splitType = items["splitType"] == 5 ? SPLIT_TYPE_SCENE : SPLIT_TYPE_ITEM; + } + splitList.push_back(splitObject); + } + SplitSaveFileAction(SPLIT_SAVE, data.key().c_str()); + } + splitList.clear(); + } +} + +void RegisterTimesplits() { + if (!std::filesystem::exists(Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json"))) { + json initFile; + std::ofstream file(Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json")); + file << initFile.dump(4); + file.close(); + } + + // Handles Converting any old save data into the new format. + if (std::filesystem::exists(Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json"))) { + SplitSaveFileAction(SPLIT_CONVERT, ""); + std::filesystem::rename(Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json"), + Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.backup")); + } + + SplitSaveFileAction(SPLIT_RETRIEVE, ""); + + COND_HOOK(OnItemReceive, CVAR, [](GetItemEntry itemEntry) { + if (itemEntry.modIndex == MOD_RANDOMIZER) { + return; + } + + if (itemEntry.itemId == ITEM_BOMBCHUS_20 || itemEntry.itemId == ITEM_BOMBCHUS_5) { + itemEntry.itemId = ITEM_BOMBCHU; + } + if (itemEntry.itemId == ITEM_STICKS_5 || itemEntry.itemId == ITEM_STICKS_10) { + itemEntry.itemId = ITEM_STICK; + } + if (itemEntry.itemId == ITEM_NUTS_5 || itemEntry.itemId == ITEM_NUTS_10) { + itemEntry.itemId = ITEM_NUT; + } + if (itemEntry.itemId == ITEM_SWORD_BGS) { + if (gSaveContext.bgsFlag == 0) { + itemEntry.itemId = ITEM_SWORD_KNIFE; + } + } + + UpdateSplitStatus(SPLIT_TYPE_ITEM, (uint32_t)itemEntry.itemId); + }); + + COND_HOOK(OnPlayerUpdate, CVAR, []() { UpdateSplitStatusByUpgrades(); }); + + COND_HOOK(OnPlayerBottleUpdate, CVAR, + [](int16_t contents) { UpdateSplitStatus(SPLIT_TYPE_ITEM, (uint32_t)contents); }); + COND_HOOK(OnBossDefeat, CVAR, [](void* refActor) { + Actor* actor = (Actor*)refActor; + UpdateSplitStatus(SPLIT_TYPE_BOSS, (uint32_t)actor->id); + }); + + COND_HOOK(OnSceneInit, CVAR, [](int16_t sceneNum) { UpdateSplitStatus(SPLIT_TYPE_SCENE, (uint32_t)sceneNum); }); +} +} // namespace TimeSplits + +static RegisterShipInitFunc initFunc(TimeSplits::RegisterTimesplits, { CVAR_NAME }); \ No newline at end of file diff --git a/soh/soh/Enhancements/timesplits/TimeSplitsSettings.cpp b/soh/soh/Enhancements/timesplits/TimeSplitsSettings.cpp new file mode 100644 index 0000000000..4dd96b41a6 --- /dev/null +++ b/soh/soh/Enhancements/timesplits/TimeSplitsSettings.cpp @@ -0,0 +1,737 @@ + +#include "TimeSplitsSettings.h" +#include "TimeSplits.h" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" +#include "fast/Fast3dGui.h" + +#include "soh/SohGui/ImGuiUtils.h" + +namespace TimeSplits { + +IndexRangeObject sceneRange = { 0, 77 }; +uint32_t sceneFilterIndex = 0; + +// clang-format off +std::vector sceneObjectList = { + // Dungeon Entrances + { SCENE_DEKU_TREE, "Inside the Deku Tree" }, + { SCENE_DODONGOS_CAVERN, "Dodongo's Cavern" }, + { SCENE_JABU_JABU, "Inside Jabu-Jabu's Belly" }, + { SCENE_FOREST_TEMPLE, "Forest Temple" }, + { SCENE_FIRE_TEMPLE, "Fire Temple" }, + { SCENE_WATER_TEMPLE, "Water Temple" }, + { SCENE_SPIRIT_TEMPLE, "Spirit Temple" }, + { SCENE_SHADOW_TEMPLE, "Shadow Temple" }, + { SCENE_BOTTOM_OF_THE_WELL, "Bottom of the Well" }, + { SCENE_ICE_CAVERN, "Ice Cavern" }, + { SCENE_GERUDO_TRAINING_GROUND, "Gerudo Training Ground" }, + { SCENE_INSIDE_GANONS_CASTLE, "Inside Ganon's Castle" }, + { SCENE_GANONS_TOWER, "Ganon's Tower" }, + { SCENE_GANONS_TOWER_COLLAPSE_INTERIOR, "Tower Collapse" }, + { SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, "Castle Collapse" }, + + // Kokiri Forest Region + { SCENE_KOKIRI_FOREST, "Kokiri Forest" }, + { SCENE_LOST_WOODS, "Lost Woods" }, + { SCENE_SACRED_FOREST_MEADOW, "Sacred Forest Meadow" }, + { SCENE_KOKIRI_SHOP, "Kokiri Shop" }, + { SCENE_LINKS_HOUSE, "Link's House" }, + { SCENE_KNOW_IT_ALL_BROS_HOUSE, "Know-It-All Bros' House" }, + { SCENE_TWINS_HOUSE, "Twins' House" }, + { SCENE_MIDOS_HOUSE, "Mido's House" }, + { SCENE_SARIAS_HOUSE, "Saria's House" }, + + // Hyrule Field & Lon Lon Ranch Region + { SCENE_HYRULE_FIELD, "Hyrule Field" }, + { SCENE_MARKET_DAY, "Market (Day)" }, + { SCENE_MARKET_NIGHT, "Market (Night)" }, + { SCENE_MARKET_RUINS, "Market (Adult)" }, + { SCENE_HYRULE_CASTLE, "Hyrule Castle" }, + { SCENE_OUTSIDE_GANONS_CASTLE, "Outside Ganon's Castle" }, + { SCENE_TEMPLE_OF_TIME, "Temple of Time" }, + { SCENE_LON_LON_RANCH, "Lon Lon Ranch" }, + { SCENE_LON_LON_BUILDINGS, "Ranch House" }, + { SCENE_STABLE, "Stable" }, + { SCENE_CASTLE_COURTYARD_ZELDA, "Castle Courtyard" }, + { SCENE_SHOOTING_GALLERY, "Shooting Gallery" }, + { SCENE_BAZAAR, "Bazaar" }, + { SCENE_BOMBCHU_SHOP, "Bombchu Shop" }, + { SCENE_HAPPY_MASK_SHOP, "Happy Mask Shop" }, + { SCENE_TREASURE_BOX_SHOP, "Treasure Box Shop" }, + { SCENE_POTION_SHOP_MARKET, "Market Potion Shop" }, + { SCENE_BOMBCHU_BOWLING_ALLEY, "Bombchu Bowling Alley" }, + { SCENE_MARKET_GUARD_HOUSE, "Guard House" }, + { SCENE_DOG_LADY_HOUSE, "Richard's House" }, + { SCENE_BACK_ALLEY_HOUSE, "Man in Green's House" }, + + // Kakariko & Death Mountain Region + { SCENE_KAKARIKO_VILLAGE, "Kakariko Village" }, + { SCENE_GRAVEYARD, "Graveyard" }, + { SCENE_HOUSE_OF_SKULLTULA, "House of Skulltula" }, + { SCENE_DEATH_MOUNTAIN_TRAIL, "Death Mountain Trail" }, + { SCENE_DEATH_MOUNTAIN_CRATER, "Death Mountain Crater" }, + { SCENE_GORON_CITY, "Goron City" }, + { SCENE_KAKARIKO_CENTER_GUEST_HOUSE, "Carpenter Boss's House" }, + { SCENE_POTION_SHOP_KAKARIKO, "Kakariko Potion Shop" }, + { SCENE_POTION_SHOP_GRANNY, "Granny's Potion Shop" }, + { SCENE_IMPAS_HOUSE, "Impa's House" }, + { SCENE_GRAVEKEEPERS_HUT, "Gravekeeper's Hut" }, + { SCENE_GRAVE_WITH_FAIRYS_FOUNTAIN, "Fairy Fountain Grave" }, + { SCENE_ROYAL_FAMILYS_TOMB, "Royal Family's Tomb" }, + { SCENE_WINDMILL_AND_DAMPES_GRAVE, "Dampe's Grave" }, + { SCENE_REDEAD_GRAVE, "Redead Grave" }, + { SCENE_GORON_SHOP, "Goron Shop" }, + + // Lake Hylia & Gerudo Valley Region + { SCENE_LAKE_HYLIA, "Lake Hylia" }, + { SCENE_GERUDO_VALLEY, "Gerudo Valley" }, + { SCENE_DESERT_COLOSSUS, "Desert Colossus" }, + { SCENE_GERUDOS_FORTRESS, "Gerudo's Fortress" }, + { SCENE_THIEVES_HIDEOUT, "Thieves' Hideout" }, + { SCENE_HAUNTED_WASTELAND, "Haunted Wasteland" }, + { SCENE_FISHING_POND, "Fishing Pond" }, + { SCENE_LAKESIDE_LABORATORY, "Lakeside Lab" }, + { SCENE_CARPENTERS_TENT, "Carpenters' Tent" }, + + // Zora Region + { SCENE_ZORAS_RIVER, "Zora's River" }, + { SCENE_ZORAS_DOMAIN, "Zora's Domain" }, + { SCENE_ZORAS_FOUNTAIN, "Zora's Fountain" }, + { SCENE_ZORA_SHOP, "Zora Shop" }, + + // Miscellaneous Areas + { SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, "Great Fairy" }, + { SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS, "Great Fairy" }, + { SCENE_FAIRYS_FOUNTAIN, "Fairy Fountain" }, + { SCENE_GROTTOS, "Grotto" }, +}; + +std::vector splitObjectList = { + // Equipment + { ITEM_SWORD_KOKIRI, "Kokiri Sword" }, + { ITEM_SWORD_MASTER, "Master Sword" }, + { ITEM_SWORD_BGS, "Biggoron's Sword" }, + { ITEM_SHIELD_DEKU, "Deku Shield" }, + { ITEM_SHIELD_HYLIAN, "Hylian Shield" }, + { ITEM_SHIELD_MIRROR, "Mirror Shield" }, + { ITEM_TUNIC_GORON, "Goron Tunic" }, + { ITEM_TUNIC_ZORA, "Zora Tunic" }, + { ITEM_BOOTS_IRON, "Iron Boots" }, + { ITEM_BOOTS_HOVER, "Hover Boots" }, + { ITEM_BRACELET, "Goron's Bracelet" }, + { ITEM_SCALE_SILVER, "Silver Scale" }, + { ITEM_SINGLE_MAGIC, "Magic" }, + { ITEM_WALLET_ADULT, "Adult's Wallet" }, + { ITEM_DOUBLE_DEFENSE, "Double Defense" }, + + // Inventory + { ITEM_STICK, "Deku Stick" }, + { ITEM_NUT, "Deku Nut" }, + { ITEM_BOMB, "Bomb" }, + { ITEM_BOW, "Fairy Bow" }, + { ITEM_ARROW_FIRE, "Fire Arrow" }, + { ITEM_DINS_FIRE, "Din's Fire" }, + { ITEM_SLINGSHOT, "Slingshot" }, + { ITEM_OCARINA_FAIRY, "Fairy Ocarina" }, + { ITEM_BOMBCHU, "Bombchu" }, + { ITEM_HOOKSHOT, "Hookshot" }, + { ITEM_ARROW_ICE, "Ice Arrow" }, + { ITEM_FARORES_WIND, "Farore's Wind" }, + { ITEM_BOOMERANG, "Boomerang" }, + { ITEM_LENS, "Lens of Truth" }, + { ITEM_BEAN, "Magic Bean" }, + { ITEM_HAMMER, "Megaton Hammer" }, + { ITEM_ARROW_LIGHT, "Light Arrow" }, + { ITEM_NAYRUS_LOVE, "Nayru's Love" }, + { ITEM_BOTTLE, "Empty Bottle" }, + { ITEM_POCKET_EGG, "Pocket Egg" }, + { ITEM_WEIRD_EGG, "Weird Egg" }, + + // Quest + { ITEM_SONG_LULLABY, "Zelda's Lullaby" }, + { ITEM_SONG_EPONA, "Epona's Song" }, + { ITEM_SONG_SARIA, "Saria's Song" }, + { ITEM_SONG_SUN, "Sun's Song" }, + { ITEM_SONG_TIME, "Song of Time" }, + { ITEM_SONG_STORMS, "Song of Storms" }, + { ITEM_SONG_MINUET, "Minuet of Forest" }, + { ITEM_SONG_BOLERO, "Bolero of Fire" }, + { ITEM_SONG_SERENADE, "Serenade of Water" }, + { ITEM_SONG_REQUIEM, "Requiem of Spirit" }, + { ITEM_SONG_NOCTURNE, "Nocturne of Shadow" }, + { ITEM_SONG_PRELUDE, "Prelude of Light" }, + { ITEM_MEDALLION_FOREST, "Forest Medallion" }, + { ITEM_MEDALLION_FIRE, "Fire Medallion" }, + { ITEM_MEDALLION_WATER, "Water Medallion" }, + { ITEM_MEDALLION_SPIRIT, "Spirit Medallion" }, + { ITEM_MEDALLION_SHADOW, "Shadow Medallion" }, + { ITEM_MEDALLION_LIGHT, "Light Medallion" }, + { ITEM_KOKIRI_EMERALD, "Kokiri's Emerald" }, + { ITEM_GORON_RUBY, "Goron's Ruby" }, + { ITEM_ZORA_SAPPHIRE, "Zora's Sapphire" }, + + // Bosses + SPLIT_BOSS(ACTOR_BOSS_GOMA, "Queen Gohma"), + SPLIT_BOSS(ACTOR_BOSS_DODONGO, "King Dodongo"), + SPLIT_BOSS(ACTOR_BOSS_VA, "Barinade"), + SPLIT_BOSS(ACTOR_BOSS_GANONDROF, "Phantom Ganon"), + SPLIT_BOSS(ACTOR_BOSS_MO, "Morpha"), + SPLIT_BOSS(ACTOR_BOSS_FD2, "Volvagia"), + SPLIT_BOSS(ACTOR_BOSS_SST, "Bongo Bongo"), + SPLIT_BOSS(ACTOR_BOSS_TW, "Twinrova"), + SPLIT_BOSS(ACTOR_BOSS_GANON, "Ganondorf"), + SPLIT_BOSS(ACTOR_BOSS_GANON2, "Ganon"), + + // Upgrade Items + { ITEM_OCARINA_TIME, "Ocarina of Time" }, + { ITEM_LONGSHOT, "Longshot" }, + { ITEM_BULLET_BAG_40, "Big Bullet Bag" }, + { ITEM_BULLET_BAG_50, "Biggest Bullet Bag" }, + { ITEM_QUIVER_40, "Big Quiver" }, + { ITEM_QUIVER_50, "Biggest Quiver" }, + { ITEM_BOMB_BAG_20, "Bomb Bag" }, + { ITEM_BOMB_BAG_30, "Big Bomb Bag" }, + { ITEM_BOMB_BAG_40, "Biggest Bomb Bag" }, + { ITEM_WALLET_GIANT, "Giant's Wallet" }, + { ITEM_GAUNTLETS_SILVER, "Silver Gauntlets" }, + { ITEM_GAUNTLETS_GOLD, "Gold Gauntlets" }, + { ITEM_SCALE_GOLDEN, "Gold Scale" }, + { ITEM_SWORD_KNIFE, "Giant's Knife" }, + { ITEM_DOUBLE_MAGIC, "Double Magic" }, + + // Trade Items + { ITEM_CHICKEN, "Chicken" }, + { ITEM_LETTER_ZELDA, "Zelda's Letter" }, + { ITEM_MASK_KEATON, "Keaton Mask" }, + { ITEM_MASK_SKULL, "Skull Mask" }, + { ITEM_MASK_SPOOKY, "Spooky Mask" }, + { ITEM_MASK_BUNNY, "Bunny Hood" }, + { ITEM_MASK_GORON, "Goron Mask" }, + { ITEM_MASK_ZORA, "Zora Mask" }, + { ITEM_MASK_GERUDO, "Gerudo Mask" }, + { ITEM_MASK_TRUTH, "Mask of Truth" }, + { ITEM_POCKET_CUCCO, "Pocket Cucco" }, + { ITEM_COJIRO, "Cojiro" }, + { ITEM_ODD_MUSHROOM, "Odd Mushroom" }, + { ITEM_ODD_POTION, "Odd Potion" }, + { ITEM_SAW, "Poacher's Saw" }, + { ITEM_SWORD_BROKEN, "Broke Goron Sword" }, + { ITEM_PRESCRIPTION, "Prescription" }, + { ITEM_FROG, "Eyeball Frog" }, + { ITEM_EYEDROPS, "Eye Drops" }, + { ITEM_CLAIM_CHECK, "Claim Check" }, + + + // Bottled Items + { ITEM_POTION_RED, "Red Potion" }, + { ITEM_POTION_GREEN, "Green Potion" }, + { ITEM_POTION_BLUE, "Blue Potion" }, + { ITEM_FAIRY, "Fairy" }, + { ITEM_FISH, "Fish" }, + { ITEM_MILK_BOTTLE, "Milk" }, + { ITEM_LETTER_RUTO, "Ruto's Letter" }, + { ITEM_BLUE_FIRE, "Blue Fire" }, + { ITEM_BUG, "Bugs" }, + { ITEM_BIG_POE, "Big Poe" }, + { ITEM_MILK_HALF, "Milk (Half)" }, + { ITEM_POE, "Poe" }, +}; + +std::map> itemSubMenuList = { + { ITEM_BOW, { ITEM_BOW, ITEM_QUIVER_40, ITEM_QUIVER_50 } }, + { ITEM_BOMB, { ITEM_BOMB_BAG_20, ITEM_BOMB_BAG_30, ITEM_BOMB_BAG_40 } }, + { ITEM_OCARINA_FAIRY, { ITEM_OCARINA_FAIRY, ITEM_OCARINA_TIME } }, + { ITEM_WEIRD_EGG, { ITEM_WEIRD_EGG, ITEM_CHICKEN, ITEM_LETTER_ZELDA, ITEM_MASK_KEATON, ITEM_MASK_SKULL, ITEM_MASK_SPOOKY, + ITEM_MASK_BUNNY, ITEM_MASK_GORON, ITEM_MASK_ZORA, ITEM_MASK_GERUDO, ITEM_MASK_TRUTH } }, + { ITEM_POCKET_EGG, { ITEM_POCKET_EGG, ITEM_POCKET_CUCCO, ITEM_COJIRO, ITEM_ODD_MUSHROOM, ITEM_ODD_POTION, + ITEM_SAW, ITEM_SWORD_BROKEN, ITEM_PRESCRIPTION, ITEM_FROG, ITEM_EYEDROPS, ITEM_CLAIM_CHECK } }, + { ITEM_BOTTLE, { ITEM_BOTTLE, ITEM_POTION_RED, ITEM_POTION_GREEN, ITEM_POTION_BLUE, ITEM_FAIRY, ITEM_FISH, + ITEM_MILK_BOTTLE, ITEM_LETTER_RUTO, ITEM_BLUE_FIRE, ITEM_BUG, ITEM_BIG_POE, ITEM_MILK_HALF, ITEM_POE } }, + { ITEM_SCALE_SILVER, { ITEM_SCALE_SILVER, ITEM_SCALE_GOLDEN } }, + { ITEM_BRACELET, { ITEM_BRACELET, ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_GOLD } }, + { ITEM_WALLET_ADULT, { ITEM_WALLET_ADULT, ITEM_WALLET_GIANT } }, + { ITEM_SWORD_BGS, { ITEM_SWORD_KNIFE, ITEM_SWORD_BGS } }, + { ITEM_SINGLE_MAGIC, { ITEM_SINGLE_MAGIC, ITEM_DOUBLE_MAGIC } }, +}; + +static std::map songColorMap = { + { ITEM_SONG_LULLABY, { 0.878f, 0.420f, 1.0f, 1.0f } }, + { ITEM_SONG_EPONA, { 1.0f, 0.765f, 0.235f, 1.0f } }, + { ITEM_SONG_SARIA, { 0.498f, 1.0f, 0.537f, 1.0f } }, + { ITEM_SONG_SUN, { 1.0f, 1.0f, 0.235f, 1.0f } }, + { ITEM_SONG_TIME, { 0.467f, 0.925f, 1.0f, 1.0f } }, + { ITEM_SONG_STORMS, { 0.647f, 0.647f, 0.647f, 1.0f } }, + { ITEM_SONG_MINUET, { 0.588f, 1.0f, 0.392f, 1.0f } }, + { ITEM_SONG_BOLERO, { 1.0f, 0.314f, 0.157f, 1.0f } }, + { ITEM_SONG_SERENADE, { 0.392f, 0.588f, 1.0f, 1.0f } }, + { ITEM_SONG_REQUIEM, { 1.0f, 0.627f, 0.0f, 1.0f } }, + { ITEM_SONG_NOCTURNE, { 1.0f, 0.392f, 1.0f, 1.0f } }, + { ITEM_SONG_PRELUDE, { 1.0f, 0.941f, 0.392f, 1.0f } }, +}; + +static std::map bossColorMap = { + { ACTOR_BOSS_GOMA, UIWidgets::ColorValues.at(UIWidgets::Colors::Green) }, + { ACTOR_BOSS_DODONGO, UIWidgets::ColorValues.at(UIWidgets::Colors::Red) }, + { ACTOR_BOSS_VA, UIWidgets::ColorValues.at(UIWidgets::Colors::Blue) }, + { ACTOR_BOSS_GANONDROF, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGreen) }, + { ACTOR_BOSS_FD2, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkRed) }, + { ACTOR_BOSS_MO, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkBlue) }, + { ACTOR_BOSS_SST, UIWidgets::ColorValues.at(UIWidgets::Colors::Purple) }, + { ACTOR_BOSS_TW, UIWidgets::ColorValues.at(UIWidgets::Colors::Orange) }, + { ACTOR_BOSS_GANON, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray) }, + { ACTOR_BOSS_GANON2, UIWidgets::ColorValues.at(UIWidgets::Colors::Yellow) }, +}; + +static std::vector sceneAreaNameMap = { + "All Scenes", + "Dungeons", + "Kokiri Forest", + "Hyrule Field & Lon Lon Ranch", + "Kakariko & Death Mountain", + "Lake Hylia & Gerudo Valley", + "Zora's River", + "Miscellaneous", +}; + +static std::unordered_map sceneAreaRangeMap = { + { "All Scenes", { SCENE_DEKU_TREE, SCENE_GROTTOS } }, + { "Dungeons", { SCENE_DEKU_TREE, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE } }, + { "Kokiri Forest", { SCENE_KOKIRI_FOREST, SCENE_SARIAS_HOUSE } }, + { "Hyrule Field & Lon Lon Ranch", { SCENE_HYRULE_FIELD, SCENE_BACK_ALLEY_HOUSE } }, + { "Kakariko & Death Mountain", { SCENE_KAKARIKO_VILLAGE, SCENE_GORON_SHOP } }, + { "Lake Hylia & Gerudo Valley", { SCENE_LAKE_HYLIA, SCENE_CARPENTERS_TENT } }, + { "Zora's River", { SCENE_ZORAS_RIVER, SCENE_ZORA_SHOP } }, + { "Miscellaneous", { SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, SCENE_GROTTOS } }, +}; +// clang-format on + +IndexRangeObject GetSceneIndexRange(uint32_t start, uint32_t end) { + IndexRangeObject setRange = { 0, 0 }; + + for (size_t i = 0; i < sceneObjectList.size(); i++) { + if (sceneObjectList[i].splitId == start) { + setRange.startIndex = static_cast(i); + } + if (sceneObjectList[i].splitId == end) { + setRange.endIndex = static_cast(i); + } + } + + return setRange; +} + +IndexRangeObject GetIndexRange(uint32_t splitType, uint32_t start, uint32_t end) { + IndexRangeObject setRange = { 0, 0 }; + + for (size_t i = 0; i < splitObjectList.size(); i++) { + if (splitObjectList[i].splitType != splitType) { + continue; + } + if (splitObjectList[i].splitId == start) { + setRange.startIndex = static_cast(i); + } + if (splitObjectList[i].splitId == end) { + setRange.endIndex = static_cast(i); + } + } + + return setRange; +} + +bool shouldPopUpOpen = false; +uint32_t popupItem = 0; +const char* popupTooltip = ""; +IndexRangeObject range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_SWORD_KOKIRI, ITEM_DOUBLE_DEFENSE); +std::string listName = "Equipment"; +uint32_t listColumns = 3; +std::string listInputName; +std::vector savedLists; +uint32_t selectedIndex = 0; +uint32_t comparedIndex = 0; + +static bool IsSongSplit(const TimesplitObject& split) { + return split.splitType == SPLIT_TYPE_ITEM && split.splitId >= ITEM_SONG_MINUET && split.splitId <= ITEM_SONG_STORMS; +} + +const char* GetSplitImage(const TimesplitObject& split) { + if (split.splitType == SPLIT_TYPE_SCENE) { + return "gPauseUnusedCursorTex"; + } + if (split.splitType == SPLIT_TYPE_BOSS) { + return "gWTriforcePieceTex"; + } + switch (split.splitId) { + case ITEM_SINGLE_MAGIC: + return GetTextureForItemId(ITEM_MAGIC_SMALL); + case ITEM_DOUBLE_MAGIC: + return GetTextureForItemId(ITEM_MAGIC_LARGE); + case ITEM_DOUBLE_DEFENSE: + return GetTextureForItemId(ITEM_HEART_CONTAINER); + default: + break; + } + if (IsSongSplit(split)) { + return "gSongNoteTex"; + } + const char* itemTexture = GetTextureForItemId(split.splitId); + return itemTexture != nullptr ? itemTexture : GetTextureForItemId(ITEM_SOLD_OUT); +} + +ImVec4 GetSplitColor(const TimesplitObject& split) { + if (split.splitType == SPLIT_TYPE_BOSS) { + return bossColorMap.at(split.splitId); + } + if (IsSongSplit(split)) { + return songColorMap.at(split.splitId); + } + return { 1, 1, 1, 1 }; +} + +ImVec2 GetSplitImageSize(const TimesplitObject& split) { + float defaultImageSize = 32.0f; + if (IsSongSplit(split)) { + return ImVec2(defaultImageSize / 1.5f, defaultImageSize); + } + return ImVec2(defaultImageSize, defaultImageSize); +} + +void DrawOptions() { + if (ImGui::BeginTable("Options", 3)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextColumn(); + UIWidgets::CVarCheckbox("Enable Time Splits", "gSettings.TimeSplits.Enable", + { + .color = THEME_COLOR, + }); + UIWidgets::Tooltip("Enables the Time Split system, splits will not occur with this unchecked."); + + ImGui::TableNextColumn(); + if (UIWidgets::CVarCheckbox("Show Headers", "gSettings.TimeSplits.ShowHeaders", + { + .color = THEME_COLOR, + })) { + UpdateSplitSettings(SPLIT_HEADERS); + }; + UIWidgets::Tooltip("Shows the column names in the Split List."); + + ImGui::TableNextColumn(); + if (UIWidgets::CVarCheckbox("Hide Background", "gSettings.TimeSplits.Opacity", + { + .color = THEME_COLOR, + })) { + UpdateSplitSettings(SPLIT_OPACITY); + }; + UIWidgets::Tooltip("Hides the background of the Splits List.\n" + "Note: The background will display if the window extrudes from the main game window."); + + ImGui::TableNextColumn(); + UIWidgets::CVarCheckbox("Highlight Active Split", "gSettings.TimeSplits.Highlight", + { + .color = THEME_COLOR, + }); + UIWidgets::Tooltip("Highlights the row with the current Active Split."); + + ImGui::TableNextColumn(); + UIWidgets::CVarCheckbox("Follow Active Split", "gSettings.TimeSplits.Follow", + { + .color = THEME_COLOR, + }); + UIWidgets::Tooltip("Forces the Split List window to keep the Active Split visible.\n" + "Note: This prevents user scrolling, disable to restore control."); + + ImGui::EndTable(); + } + + UIWidgets::CVarCheckbox("Compare Splits", "gSettings.TimeSplits.Compare", + { + .color = THEME_COLOR, + }); + UIWidgets::Tooltip("Enables Split Comparisons between lists, this will integrate within the Split List."); + + if (CVarGetInteger("gSettings.TimeSplits.Compare", 0)) { + ImGui::SameLine(); + UIWidgets::PushStyleCombobox(THEME_COLOR); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##compareSplits", savedLists[comparedIndex].c_str())) { + for (uint32_t i = 0; i < savedLists.size(); i++) { + if (ImGui::Selectable(savedLists[i].c_str())) { + comparedIndex = i; + SplitLoadComparisonList(); + break; + } + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + UIWidgets::PopStyleCombobox(); + } +} + +void DrawActionButtons() { + if (ImGui::BeginTable("Action Buttons", 2)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextColumn(); + UIWidgets::InputString("New List", &listInputName, + { + .labelPosition = UIWidgets::LabelPositions::None, + .color = THEME_COLOR, + .placeholder = "Enter new list name", + }); + + ImGui::TableNextColumn(); + if (UIWidgets::Button("Create List", { + .color = THEME_COLOR, + })) { + if (!listInputName.empty()) { + SplitSaveFileAction(SPLIT_SAVE, listInputName); + SplitSaveFileAction(SPLIT_RETRIEVE, ""); + } + } + + ImGui::TableNextColumn(); + UIWidgets::PushStyleCombobox(THEME_COLOR); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##savedSplits", savedLists[selectedIndex].c_str())) { + for (uint32_t i = 0; i < savedLists.size(); i++) { + if (ImGui::Selectable(savedLists[i].c_str())) { + selectedIndex = i; + break; + } + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + UIWidgets::PopStyleCombobox(); + + ImGui::TableNextColumn(); + if (UIWidgets::Button("Save Splits", { + .size = { (ImGui::GetContentRegionAvail().x * 0.5f), 0 }, + .color = THEME_COLOR, + })) { + if (savedLists[0] != "Create a List First") { + SplitSaveFileAction(SPLIT_SAVE, savedLists[selectedIndex]); + } + } + ImGui::SameLine(); + if (UIWidgets::Button("Load Splits", { + .size = { (ImGui::GetContentRegionAvail().x), 0 }, + .color = THEME_COLOR, + })) { + if (savedLists[0] != "Create a List First") { + SplitSaveFileAction(SPLIT_LOAD, savedLists[selectedIndex]); + } + } + + ImGui::TableNextColumn(); + if (UIWidgets::Button("New Attempt", { + .color = THEME_COLOR, + })) { + for (auto& splits : splitList) { + splits.splitStatus = SPLIT_INACTIVE; + } + if (splitList.size() != 0) { + splitList[0].splitStatus = SPLIT_ACTIVE; + } + } + + ImGui::TableNextColumn(); + if (UIWidgets::Button("Update Splits", { + .color = THEME_COLOR, + })) { + UpdateSplitBests(); + } + + ImGui::EndTable(); + } +} + +void DrawEntranceList() { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + UIWidgets::PushStyleCombobox(THEME_COLOR); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##SceneFilter", sceneAreaNameMap[sceneFilterIndex])) { + for (uint32_t i = 0; i < sceneAreaNameMap.size(); i++) { + if (ImGui::Selectable(sceneAreaNameMap[i])) { + sceneFilterIndex = i; + sceneRange = GetSceneIndexRange(sceneAreaRangeMap.at(sceneAreaNameMap[i]).startIndex, + sceneAreaRangeMap.at(sceneAreaNameMap[i]).endIndex); + break; + } + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + UIWidgets::PopStyleCombobox(); + + if (ImGui::BeginChild("Entrance List")) { + if (ImGui::BeginTable("Entrances", 2)) { + for (uint32_t i = sceneRange.startIndex; i <= sceneRange.endIndex; i++) { + ImGui::TableNextColumn(); + ImGui::PushID(sceneObjectList[i].splitId); + SplitsPushImageButtonStyle(); + + if (ImGui::ImageButton(std::to_string(sceneObjectList[i].splitId).c_str(), + gui->GetTextureByName("gPauseUnusedCursorTex"), ImVec2(32.0f, 32.0f))) { + AddSplitEntryBySceneId(sceneObjectList[i].splitId); + }; + ImGui::SameLine(); + TableCellCenteredText(UIWidgets::ColorValues.at(UIWidgets::Colors::White), + sceneObjectList[i].splitName.c_str()); + + SplitsPopImageButtonStyle(); + ImGui::PopID(); + } + + ImGui::EndTable(); + } + } + ImGui::EndChild(); +} + +void DrawItemList(std::string tableName, IndexRangeObject range, uint32_t tableSize) { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + if (ImGui::BeginTable(tableName.c_str(), tableSize)) { + for (uint32_t i = range.startIndex; i <= range.endIndex; i++) { + const TimesplitObject& split = splitObjectList[i]; + ImGui::TableNextColumn(); + SplitsPushImageButtonStyle(); + if (ImGui::ImageButton(std::to_string(split.splitId).c_str(), gui->GetTextureByName(GetSplitImage(split)), + GetSplitImageSize(split) * 1.5f, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), + GetSplitColor(split))) { + if (split.splitType == SPLIT_TYPE_ITEM && itemSubMenuList.contains(split.splitId)) { + shouldPopUpOpen = true; + popupItem = split.splitId; + ImGui::OpenPopup("ItemSubMenu"); + } else { + AddSplitEntry(split.splitType, split.splitId); + } + } + UIWidgets::Tooltip(splitObjectList[i].splitName.c_str()); + if (listName == "Bosses") { + ImGui::SameLine(); + TableCellCenteredText(UIWidgets::ColorValues.at(UIWidgets::Colors::White), + splitObjectList[i].splitName.c_str()); + } + + SplitsPopImageButtonStyle(); + } + HandlePopUpContext(popupItem); + ImGui::EndTable(); + } +} + +void TimesplitsSettingsWindow::DrawElement() { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + bool shouldRemoveEntry = false; + uint32_t entryIndex = 0; + + UIWidgets::PushStyleTabs(THEME_COLOR); + if (ImGui::BeginTabBar("Timesplit Settings Tabs")) { + if (ImGui::BeginTabItem("List Options")) { + DrawOptions(); + DrawActionButtons(); + ImGui::SeparatorText("Current Splits"); + DrawSplitsList(false); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Split Entries")) { + if (ImGui::BeginTable("Split Settings", 3)) { + ImGui::TableSetupColumn("Preview", + ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 88.0f); + ImGui::TableSetupColumn("Item Categories", + ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 120.0f); + ImGui::TableSetupColumn("Item Grids", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + ImGui::TableNextColumn(); + ImGui::BeginDisabled(); + UIWidgets::Button("Preview", { + .color = THEME_COLOR, + }); + ImGui::EndDisabled(); + ImGui::BeginChild("Preview List"); + for (size_t i = 0; i < splitList.size(); i++) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ((ImGui::GetContentRegionAvail().x - 50.0f) * 0.5f)); + ImGui::PushID((uint32_t)i); + SplitsPushImageButtonStyle(); + if (ImGui::ImageButton(std::to_string(i).c_str(), + gui->GetTextureByName(GetSplitImage(splitList[i])), + GetSplitImageSize(splitList[i]), ImVec2(0, 0), ImVec2(1, 1), + ImVec4(0, 0, 0, 0), GetSplitColor(splitList[i]))) { + shouldRemoveEntry = true; + entryIndex = (uint32_t)i; + }; + UIWidgets::Tooltip(splitList[i].splitName.c_str()); + + HandleDragAndDrop(i); + SplitsPopImageButtonStyle(); + ImGui::PopID(); + } + ImGui::EndChild(); + + ImGui::TableNextColumn(); + if (UIWidgets::Button("Equipment", { + .color = THEME_COLOR, + })) { + range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_SWORD_KOKIRI, ITEM_DOUBLE_DEFENSE); + listName = "Equipment"; + listColumns = 3; + } + if (UIWidgets::Button("Inventory", { + .color = THEME_COLOR, + })) { + range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_STICK, ITEM_WEIRD_EGG); + listName = "Inventory"; + listColumns = 6; + } + if (UIWidgets::Button("Quest", { + .color = THEME_COLOR, + })) { + range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_SONG_LULLABY, ITEM_ZORA_SAPPHIRE); + listName = "Quest"; + listColumns = 6; + } + if (UIWidgets::Button("Bosses", { + .color = THEME_COLOR, + })) { + range = GetIndexRange(SPLIT_TYPE_BOSS, ACTOR_BOSS_GOMA, ACTOR_BOSS_GANON2); + listName = "Bosses"; + listColumns = 2; + } + if (UIWidgets::Button("Entrances", { + .color = THEME_COLOR, + })) { + listName = "Entrances"; + } + ImGui::TableNextColumn(); + if (listName != "Entrances") { + DrawItemList(listName, range, listColumns); + } else { + DrawEntranceList(); + } + + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + UIWidgets::PopStyleTabs(); + + if (shouldRemoveEntry) { + RemoveSplitEntry(entryIndex); + shouldRemoveEntry = false; + } +} + +void TimesplitsSettingsWindow::InitElement() { +} + +} // namespace TimeSplits \ No newline at end of file diff --git a/soh/soh/Enhancements/timesplits/TimeSplitsSettings.h b/soh/soh/Enhancements/timesplits/TimeSplitsSettings.h new file mode 100644 index 0000000000..c8394809f8 --- /dev/null +++ b/soh/soh/Enhancements/timesplits/TimeSplitsSettings.h @@ -0,0 +1,21 @@ +#pragma once +#ifndef TIMESPLITS_SETTINGS_H +#define TIMESPLITS_SETTINGS_H + +#include + +#ifdef __cplusplus +namespace TimeSplits { + +class TimesplitsSettingsWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override{}; +}; +} // namespace TimeSplits +#endif + +#endif // TIMESPLITS_SETTINGS_H \ No newline at end of file diff --git a/soh/soh/ShipUtils.cpp b/soh/soh/ShipUtils.cpp index e85956045d..32c14ff17f 100644 --- a/soh/soh/ShipUtils.cpp +++ b/soh/soh/ShipUtils.cpp @@ -4,6 +4,7 @@ #include #include "soh_assets.h" #include +#include "spdlog/common.h" extern "C" { #include "z64.h" @@ -15,6 +16,15 @@ extern f32 sFontWidths[144]; extern const char* fontTbl[140]; } +extern std::string Ship_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 spdlog::fmt_lib::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds); +} + constexpr f32 fourByThree = 4.0f / 3.0f; // Gets the additional ratio of the screen compared to the original 4:3 ratio, clamping to 1 if smaller diff --git a/soh/soh/ShipUtils.h b/soh/soh/ShipUtils.h index 26ca532d4a..7e5f515c72 100644 --- a/soh/soh/ShipUtils.h +++ b/soh/soh/ShipUtils.h @@ -7,6 +7,9 @@ #include #include +#include +std::string Ship_FormatTimeDisplay(uint32_t value); + void LoadGuiTextures(); extern "C" { diff --git a/soh/soh/SohGui/ImGuiUtils.cpp b/soh/soh/SohGui/ImGuiUtils.cpp index c978540eca..a75b65c017 100644 --- a/soh/soh/SohGui/ImGuiUtils.cpp +++ b/soh/soh/SohGui/ImGuiUtils.cpp @@ -272,4 +272,11 @@ void RegisterImGuiItemIcons() { for (const auto& entry : gSeedTextures) { gui->LoadGuiTexture(entry.tex, entry.tex, "", ImVec4(1, 1, 1, 1)); } + + gui->LoadGuiTexture("gPauseUnusedCursorTex", gPauseUnusedCursorTex, "", ImVec4(1, 1, 1, 1)); + + gui->LoadGuiTexture("gWTriforcePieceTex", gWTriforcePieceTex, "", ImVec4(1, 1, 1, 1)); + + // TODO: Figure out why this texture wouldn't load in the TimeSplits Windows + gui->LoadGuiTexture("gSongNoteTex", gSongNoteTex, "", ImVec4(1, 1, 1, 1)); } \ No newline at end of file diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 3e4aa5d7f8..4b99e3ac4f 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -83,7 +83,8 @@ std::shared_ptr mHintTrackerSettingsWind std::shared_ptr mHintTrackerWindow; std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; -std::shared_ptr mTimeSplitWindow; +std::shared_ptr mTimeSplitsWindow; +std::shared_ptr mTimeSplitSettingsWindow; std::shared_ptr mPlandomizerWindow; std::shared_ptr mModalWindow; std::shared_ptr mNotificationWindow; @@ -189,8 +190,13 @@ void SetupGuiElements() { mItemTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("ItemTrackerSettings"), "Item Tracker Settings", ImVec2(733, 472)); gui->AddGuiWindow(mItemTrackerSettingsWindow); - mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplits"), "Time Splits", ImVec2(450, 660)); - gui->AddGuiWindow(mTimeSplitWindow); + + mTimeSplitsWindow = + std::make_shared(CVAR_WINDOW("TimeSplits"), "Time Splits", ImVec2(450, 660)); + gui->AddGuiWindow(mTimeSplitsWindow); + mTimeSplitSettingsWindow = std::make_shared( + CVAR_WINDOW("TimeSplitSettings"), "Time Splits Settings Window", ImVec2(450, 660)); + gui->AddGuiWindow(mTimeSplitSettingsWindow); mPlandomizerWindow = std::make_shared(CVAR_WINDOW("PlandomizerEditor"), "Plandomizer Editor", ImVec2(850, 760)); gui->AddGuiWindow(mPlandomizerWindow); @@ -233,7 +239,8 @@ void Destroy() { mGfxDebuggerWindow = nullptr; mInputViewer = nullptr; mInputViewerSettings = nullptr; - mTimeSplitWindow = nullptr; + mTimeSplitsWindow = nullptr; + mTimeSplitSettingsWindow = nullptr; mPlandomizerWindow = nullptr; mTimeDisplayWindow = nullptr; mAnchorRoomWindow = nullptr; diff --git a/soh/soh/SohGui/SohGui.hpp b/soh/soh/SohGui/SohGui.hpp index ca3032f891..b2cbf69296 100644 --- a/soh/soh/SohGui/SohGui.hpp +++ b/soh/soh/SohGui/SohGui.hpp @@ -26,6 +26,7 @@ #include "soh/Enhancements/randomizer/randomizer_hint_tracker.h" #include "soh/Enhancements/randomizer/randomizer_item_tracker.h" #include "soh/Enhancements/timesplits/TimeSplits.h" +#include "soh/Enhancements/timesplits/TimeSplitsSettings.h" #include "soh/Enhancements/randomizer/Plandomizer.h" #include "SohModals.h" diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 4bec60dfd4..1c6f46db5b 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -2176,12 +2176,9 @@ void SohMenu::AddMenuEnhancements() { // Time Splits path.sidebarName = "Time Splits"; AddSidebarEntry("Enhancements", path.sidebarName, 1); - AddWidget(path, "Popout Time Splits Window", WIDGET_WINDOW_BUTTON) - .CVar(CVAR_WINDOW("TimeSplits")) - .RaceDisable(false) - .WindowName("Time Splits") - .HideInSearch(true) - .Options(WindowButtonOptions().Tooltip("Enables the separate Time Splits Window.")); + AddWidget(path, "Popout Timesplits Settings", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.Timesplits.Settings") + .WindowName("Time Splits Settings Window"); // Timers path.sidebarName = "Timers"; diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 69fd6ded3d..402b9362bc 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -8,7 +8,6 @@ #include "soh/Enhancements/gameplaystats.h" #include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" -#include "soh/Enhancements/enhancementTypes.h" #include "soh/ShipUtils.h" #include @@ -1839,7 +1838,9 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) { RandomizerGet randomizerGet = RetrieveRandomizerGetFromItemID(itemID); if (randomizerGet != RG_MAX) { // Vanilla ItemID with an associated RandomizerGet (These are items in extendedVanillaGetItemTable) - return Return_Item_Entry(ItemTable_RetrieveEntry(MOD_RANDOMIZER, randomizerGet), returnItem); + GetItemEntry itemEntry = ItemTable_RetrieveEntry(MOD_RANDOMIZER, randomizerGet); + itemEntry.modIndex = modId; + return Return_Item_Entry(itemEntry, returnItem); } // All randomizer items should go through Randomizer_Item_Give, so this should never be reached