summaryrefslogtreecommitdiff
path: root/mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp
diff options
context:
space:
mode:
authorCaladius <Caladius@users.noreply.github.com>2025-10-15 12:38:14 -0400
committerGitHub <noreply@github.com>2025-10-15 12:38:14 -0400
commit894670b9008381ca4ea366736e6243bcdb3dad94 (patch)
treee02f28f38963acaa78fa22d8e1438965545aecfe /mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp
parent3b88dd05ed4e1c9c1a30af4b767db1e1e91d0b3d (diff)
[Enhancement] Time Splits (#1266)
* Adds support for Timesplits * clanged * Fix Popout Crash * wip file saving; fixes Bow and Red Potion splits * Add Active Row Highlighting * Fix D-Pad Bottle Collection * Cleanup and Add Boss Splits. * Add names to Boss Icons * clang v2 * Linker fix * Move time formatting to ShipUtils (this also cleaned up DisplayOverlay). * clang v3 * Adds Saving/Loading splits from file. * Update inventory range to exclude heart piece. * Update column name to not be abbreviated * Adds Magic Meters and Double Defense * clang cuz ya boi alwayz fogetz * Fix Fairy Splits with CS Skip Enabled * Move songColorMap to ShipUtils along with the function to get the color. Fixed Song Icon sizing. * Adds default Window size and allows resizing. * Allow scrolling of splits list * Adds Entrance Splits and cleans up the UI. Adds new splitType to the object so I added a small solution to handle older files that may not have it for testing. * UI Update and Entrance Filters. * Adds split list to the options window to fill out the space. * Fix ID mismatching. * Add Split Comparing Functionality * clang * Compare Split text display corrections * Adds auto scrolling to follow Active Splt if list is bigger than the window. Also locks the Header Row when visible to always be visible at the top instead of scrolling with the list. * Adds Tooltips for options * Fix time coloring for splits with no previous data. * Fix crash and ID mismatch * Feedback Updates * Extern is needed since my definition is in the cpp file it seems.
Diffstat (limited to 'mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp')
-rw-r--r--mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp741
1 files changed, 741 insertions, 0 deletions
diff --git a/mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp b/mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp
new file mode 100644
index 000000000..0e2978b2f
--- /dev/null
+++ b/mm/2s2h/Enhancements/Trackers/TimeSplits/TimesplitsSettings.cpp
@@ -0,0 +1,741 @@
+
+#include "TimesplitsSettings.h"
+#include "Timesplits.h"
+#include "public/bridge/consolevariablebridge.h"
+#include "Context.h"
+#include "Window.h"
+#include "2s2h/BenGui/UIWidgets.hpp"
+
+extern "C" {
+#include "variables.h"
+}
+
+#include "interface/parameter_static/parameter_static.h"
+#include "assets/archives/icon_item_24_static/icon_item_24_static_yar.h"
+#include "assets/archives/icon_item_static/icon_item_static_yar.h"
+#include "assets/interface/icon_item_dungeon_static/icon_item_dungeon_static.h"
+#include "assets/interface/icon_item_field_static/icon_item_field_static.h"
+#include "assets/objects/gameplay_keep/gameplay_keep.h"
+#include "GameInteractor/GameInteractor.h"
+
+IndexRangeObject sceneRange = { 0, 98 };
+uint32_t sceneFilterIndex = 0;
+
+std::vector<TimesplitObject> sceneObjectList = {
+ // Misc. Areas
+ { SCENE_LOST_WOODS, "Lost Woods (Intro)" },
+ { SCENE_OPENINGDAN, "Before Clock Town" },
+ { SCENE_KAKUSIANA, "Lone Peak Shrine & Grottos" },
+ { SCENE_YOUSEI_IZUMI, "Fairy's Fountain" },
+ { SCENE_KYOJINNOMA, "Giants' Chamber" },
+ // Clock Town Areas
+ { SCENE_TOWN, "East Clock Town" },
+ { SCENE_ICHIBA, "West Clock Town" },
+ { SCENE_BACKTOWN, "North Clock Town" },
+ { SCENE_CLOCKTOWER, "South Clock Town" },
+ { SCENE_INSIDETOWER, "Clock Tower Interior" },
+ { SCENE_OKUJOU, "Clock Tower Rooftop" },
+ { SCENE_ALLEY, "Laundry Pool" },
+ { SCENE_YADOYA, "Stock Pot Inn" },
+ { SCENE_DOUJOU, "Swordsman's School" },
+ { SCENE_AYASHIISHOP, "Curiosity Shop" },
+ { SCENE_8ITEMSHOP, "Trading Post" },
+ { SCENE_BOMYA, "Bomb Shop" },
+ { SCENE_BOWLING, "Honey & Darling's Shop" },
+ { SCENE_SONCHONOIE, "The Mayor's Residence" },
+ { SCENE_DEKUTES, "Deku Scrub Playground" },
+ { SCENE_TAKARAKUJI, "Lottery Shop" },
+ { SCENE_TAKARAYA, "Treasure Chest Shop" },
+ { SCENE_MILK_BAR, "Milk Bar" },
+ { SCENE_SYATEKI_MIZU, "Town Shooting Gallery" },
+ { SCENE_POSTHOUSE, "Post Office" },
+ // Termina Field Region
+ { SCENE_00KEIKOKU, "Termina Field" },
+ { SCENE_F01, "Romani Ranch" },
+ { SCENE_TENMON_DAI, "Astral Observatory" },
+ { SCENE_ROMANYMAE, "Milk Road" },
+ { SCENE_KOEPONARACE, "Gorman Track" },
+ { SCENE_OMOYA, "Mama's House & Barn" },
+ { SCENE_F01_B, "Doggy Racetrack" },
+ { SCENE_F01C, "Cucco Shack" },
+ // Woodfall Region
+ { SCENE_24KEMONOMITI, "Road to Southern Swamp" },
+ { SCENE_20SICHITAI, "Southern Swamp (poison)" },
+ { SCENE_20SICHITAI2, "Southern Swamp (Clear)" },
+ { SCENE_22DEKUCITY, "Deku Palace" },
+ { SCENE_21MITURINMAE, "Woodfall" },
+ { SCENE_MITURIN, "Woodfall Temple" },
+ { SCENE_MITURIN_BS, "Odolwa's Lair" },
+ { SCENE_26SARUNOMORI, "Woods of Mystery" },
+ { SCENE_WITCH_SHOP, "Magic Hags' Potion Shop" },
+ { SCENE_MAP_SHOP, "Tourist Information" },
+ { SCENE_DANPEI, "Deku Shrine" },
+ { SCENE_DEKU_KING, "Deku King's Chamber" },
+ { SCENE_SYATEKI_MORI, "Swamp Shooting Gallery" },
+ { SCENE_KINSTA1, "Swamp Spider House" },
+ // Snowhead Region
+ { SCENE_13HUBUKINOMITI, "Path to Mountain Village" },
+ { SCENE_10YUKIYAMANOMURA, "Mountain Village (winter)" },
+ { SCENE_10YUKIYAMANOMURA2, "Mountain Village (spring)" },
+ { SCENE_17SETUGEN, "Path to Goron Village (winter)" },
+ { SCENE_17SETUGEN2, "Path to Goron Village (spring)" },
+ { SCENE_11GORONNOSATO, "Goron Village (winter)" },
+ { SCENE_11GORONNOSATO2, "Goron Village (spring)" },
+ { SCENE_GORONRACE, "Goron Racetrack" },
+ { SCENE_14YUKIDAMANOMITI, "Path to Snowhead" },
+ { SCENE_12HAKUGINMAE, "Snowhead" },
+ { SCENE_HAKUGIN, "Snowhead Temple" },
+ { SCENE_HAKUGIN_BS, "Goht's Lair" },
+ { SCENE_KAJIYA, "Mountain Smithy" },
+ { SCENE_GORON_HAKA, "Goron Graveyard" },
+ { SCENE_16GORON_HOUSE, "Goron Shrine" },
+ { SCENE_GORONSHOP, "Goron Shop" },
+ // Great Bay Region
+ { SCENE_30GYOSON, "Great Bay Coast" },
+ { SCENE_31MISAKI, "Zora Cape" },
+ { SCENE_SINKAI, "Pinnacle Rock" },
+ { SCENE_KAIZOKU, "Pirates' Fortress" },
+ { SCENE_TORIDE, "Pirates' Fortress Moat" },
+ { SCENE_PIRATE, "Pirates' Fortress Interior" },
+ { SCENE_SEA, "Great Bay Temple" },
+ { SCENE_SEA_BS, "Gyorg's Lair" },
+ { SCENE_35TAKI, "Waterfall Rapids" },
+ { SCENE_FISHERMAN, "Fisherman's Hut" },
+ { SCENE_LABO, "Marine Research Lab" },
+ { SCENE_KINDAN2, "Oceanside Spider House" },
+ { SCENE_33ZORACITY, "Zora Hall" },
+ // Ikana Region
+ { SCENE_IKANAMAE, "Road to Ikana" },
+ { SCENE_IKANA, "Ikana Canyon" },
+ { SCENE_BOTI, "Ikana Graveyard" },
+ { SCENE_HAKASHITA, "Beneath the Graveyard" },
+ { SCENE_F40, "Stone Tower" },
+ { SCENE_F41, "Inverted Stone Tower" },
+ { SCENE_INISIE_N, "Stone Tower Temple" },
+ { SCENE_INISIE_BS, "Twinmold's Lair" },
+ { SCENE_INISIE_R, "Inverted Stone Tower Temple" },
+ { SCENE_CASTLE, "Ancient Castle of Ikana" },
+ { SCENE_IKNINSIDE, "Igos du Ikana's Lair" },
+ { SCENE_DANPEI2TEST, "Beneath Graveyard and Dampe's House" },
+ { SCENE_REDEAD, "Beneath the Well" },
+ { SCENE_SECOM, "Sakon's Hideout" },
+ { SCENE_TOUGITES, "Ghost Hut" },
+ { SCENE_MUSICHOUSE, "Music Box House" },
+ { SCENE_RANDOM, "Secret Shrine" },
+ // The Moon
+ { SCENE_SOUGEN, "The Moon" },
+ { SCENE_LAST_DEKU, "Moon Deku Trial" },
+ { SCENE_LAST_GORON, "Moon Goron Trial" },
+ { SCENE_LAST_ZORA, "Moon Zora Trial" },
+ { SCENE_LAST_LINK, "Moon Link Trial" },
+ { SCENE_LAST_BS, "Majora's Lair" },
+};
+
+std::vector<TimesplitObject> splitObjectList = {
+ // clang-format off
+ { ITEM_HEART_PIECE, "Piece of Heart" },
+
+ // Inventory
+ { ITEM_OCARINA_OF_TIME, "Ocarina of Time" },
+ { ITEM_BOW, "Bow" },
+ { ITEM_ARROW_FIRE, "Fire Arrow" },
+ { ITEM_ARROW_ICE, "Ice Arrow" },
+ { ITEM_ARROW_LIGHT, "Light Arrow" },
+ { ITEM_MOONS_TEAR, "Moon's Tear" },
+ { ITEM_BOMB, "Bomb" },
+ { ITEM_BOMBCHU, "Bombchu" },
+ { ITEM_DEKU_STICK, "Deku Stick" },
+ { ITEM_DEKU_NUT, "Deku Nut" },
+ { ITEM_MAGIC_BEANS, "Magic Bean" },
+ { ITEM_ROOM_KEY, "Room Key" },
+ { ITEM_POWDER_KEG, "Powder Keg" },
+ { ITEM_PICTOGRAPH_BOX, "Pictograph" },
+ { ITEM_LENS_OF_TRUTH, "Lens of Truth" },
+ { ITEM_HOOKSHOT, "Hookshot" },
+ { ITEM_SWORD_GREAT_FAIRY, "Great Fairy Sword" },
+ { ITEM_LETTER_TO_KAFEI, "Letter to Kafei" },
+ { ITEM_BOTTLE, "Empty Bottle" },
+
+ // Masks
+ { ITEM_MASK_POSTMAN, "Postman's Hat" },
+ { ITEM_MASK_ALL_NIGHT, "All-Night Mask" },
+ { ITEM_MASK_BLAST, "Blast Mask" },
+ { ITEM_MASK_STONE, "Stone Mask" },
+ { ITEM_MASK_GREAT_FAIRY, "Great Fairy's Mask" },
+ { ITEM_MASK_DEKU, "Deku Mask" },
+ { ITEM_MASK_KEATON, "Keaton Mask" },
+ { ITEM_MASK_BREMEN, "Bremen Mask" },
+ { ITEM_MASK_BUNNY, "Bunny Hood" },
+ { ITEM_MASK_DON_GERO, "Don Gero's Mask" },
+ { ITEM_MASK_SCENTS, "Mask of Scents" },
+ { ITEM_MASK_GORON, "Goron Mask" },
+ { ITEM_MASK_ROMANI, "Romani's Mask" },
+ { ITEM_MASK_CIRCUS_LEADER, "Circus Leader's Mask" },
+ { ITEM_MASK_KAFEIS_MASK, "Kafei's Mask" },
+ { ITEM_MASK_COUPLE, "Couple's Mask" },
+ { ITEM_MASK_TRUTH, "Mask of Truth" },
+ { ITEM_MASK_ZORA, "Zora Mask" },
+ { ITEM_MASK_KAMARO, "Kamaro's Mask" },
+ { ITEM_MASK_GIBDO, "Gibdo Mask" },
+ { ITEM_MASK_GARO, "Garo's Mask" },
+ { ITEM_MASK_CAPTAIN, "Captain's Hat" },
+ { ITEM_MASK_GIANT, "Giant's Mask" },
+ { ITEM_MASK_FIERCE_DEITY, "Fierce Deity's Mask" },
+
+ // Songs
+ { ITEM_SONG_TIME, "Song of Time" },
+ { ITEM_SONG_HEALING, "Song of Healing" },
+ { ITEM_SONG_EPONA, "Epona's Song" },
+ { ITEM_SONG_SOARING, "Song of Soaring" },
+ { ITEM_SONG_STORMS, "Song of Storms" },
+ { ITEM_SONG_SONATA, "Sonata of Awakening" },
+ { ITEM_SONG_LULLABY, "Goron Lullaby" },
+ { ITEM_SONG_NOVA, "New Wave Bossa Nova" },
+ { ITEM_SONG_ELEGY, "Elegy of Emptiness" },
+ { ITEM_SONG_OATH, "Oath to Order" },
+
+ // Quest
+ { ITEM_REMAINS_ODOLWA, "Odolwa's Remains" },
+ { ITEM_REMAINS_GOHT, "Goht's Remains" },
+ { ITEM_REMAINS_GYORG, "Gyorg's Remains" },
+ { ITEM_REMAINS_TWINMOLD, "Twinmold's Remains" },
+ { ITEM_SWORD_KOKIRI, "Kokiri Sword" },
+ { ITEM_SHIELD_HERO, "Hero's Shield" },
+ { ITEM_WALLET_ADULT, "Adult Wallet" },
+ { ITEM_BOMBERS_NOTEBOOK, "Bombers' Notebook" },
+
+ // Quest II
+ { SPLIT_SINGLE_MAGIC, "Single Magic" },
+ { SPLIT_DOUBLE_DEFENSE, "Double Defense" },
+
+ // Dungeon Bosses
+ { SPLIT_KILLED_ODOLWA, "Odolwa" },
+ { SPLIT_KILLED_GOHT, "Goht" },
+ { SPLIT_KILLED_GYORG, "Gyorg" },
+ { SPLIT_KILLED_TWINMOLD, "Twinmold" },
+ { SPLIT_KILLED_MAJORA, "Majora" },
+
+
+ // Upgrade Items
+ { ITEM_QUIVER_30, "Quiver" },
+ { ITEM_QUIVER_40, "Large Quiver" },
+ { ITEM_QUIVER_50, "Largest Quiver" },
+ { ITEM_BOMB_BAG_20, "Bomb Bag" },
+ { ITEM_BOMB_BAG_30, "Big Bomb Bag" },
+ { ITEM_BOMB_BAG_40, "Biggest Bomb Bag" },
+ { ITEM_SONG_LULLABY_INTRO, "Goron Lullaby Intro" },
+ { ITEM_SWORD_RAZOR, "Razor Sword" },
+ { ITEM_SWORD_GILDED, "Gilded Sword" },
+ { ITEM_SHIELD_MIRROR, "Mirror Shield" },
+ { ITEM_WALLET_GIANT, "Giant Wallet" },
+ { SPLIT_DOUBLE_MAGIC, "Double Magic" },
+
+ // Trade Items
+ { ITEM_DEED_LAND, "Land Title Deed" },
+ { ITEM_DEED_SWAMP, "Swamp Title Deed" },
+ { ITEM_DEED_MOUNTAIN, "Mountain Title Deed" },
+ { ITEM_DEED_OCEAN, "Ocean Title Deed" },
+ { ITEM_LETTER_MAMA, "Letter to Mama" },
+ { ITEM_PENDANT_OF_MEMORIES, "Pendant of Memories" },
+
+ // Bottled Items
+ { ITEM_POTION_RED, "Red Potion" },
+ { ITEM_POTION_GREEN, "Green Potion" },
+ { ITEM_POTION_BLUE, "Blue Potion" },
+ { ITEM_FAIRY, "Bottled Fairy" },
+ { ITEM_DEKU_PRINCESS, "Bottled Deku Princess" },
+ { ITEM_MILK_BOTTLE, "Bottled Full Milk" },
+ { ITEM_MILK_HALF, "Bottled Half Milk" },
+ { ITEM_FISH, "Bottled Fish" },
+ { ITEM_BUG, "Bottled Bug" },
+ { ITEM_BLUE_FIRE, "Bottled Blue Fire" },
+ { ITEM_POE, "Bottled Poe" },
+ { ITEM_BIG_POE, "Bottled Big Poe" },
+ { ITEM_SPRING_WATER, "Spring Water" },
+ { ITEM_HOT_SPRING_WATER, "Hot Spring Water" },
+ { ITEM_ZORA_EGG, "Bottled Zora Egg" },
+ { ITEM_GOLD_DUST, "Bottled Gold Dust" },
+ { ITEM_MUSHROOM, "Bottled Mushroom" },
+ { ITEM_SEAHORSE, "Bottled Seahorse" },
+ { ITEM_CHATEAU, "Chateau Romani" },
+
+ // clang-format on
+};
+
+std::map<uint32_t, std::vector<uint32_t>> itemSubMenuList = {
+ // clang-format off
+ { 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_MOONS_TEAR, { ITEM_MOONS_TEAR, ITEM_DEED_LAND, ITEM_DEED_SWAMP, ITEM_DEED_MOUNTAIN, ITEM_DEED_OCEAN } },
+ { ITEM_ROOM_KEY, { ITEM_ROOM_KEY, ITEM_LETTER_MAMA } },
+ { ITEM_LETTER_TO_KAFEI, { ITEM_LETTER_TO_KAFEI, ITEM_PENDANT_OF_MEMORIES } },
+ { ITEM_BOTTLE, { ITEM_POTION_RED, ITEM_POTION_GREEN, ITEM_POTION_BLUE, ITEM_FAIRY, ITEM_DEKU_PRINCESS,
+ ITEM_MILK_BOTTLE, ITEM_MILK_HALF, ITEM_FISH, ITEM_BUG, ITEM_BLUE_FIRE, ITEM_POE,
+ ITEM_BIG_POE, ITEM_SPRING_WATER, ITEM_HOT_SPRING_WATER, ITEM_ZORA_EGG, ITEM_GOLD_DUST,
+ ITEM_MUSHROOM, ITEM_SEAHORSE, ITEM_CHATEAU } },
+ { ITEM_SONG_LULLABY, { ITEM_SONG_LULLABY_INTRO, ITEM_SONG_LULLABY } },
+ { ITEM_SWORD_KOKIRI, { ITEM_SWORD_KOKIRI, ITEM_SWORD_RAZOR, ITEM_SWORD_GILDED } },
+ { ITEM_SHIELD_HERO, { ITEM_SHIELD_HERO, ITEM_SHIELD_MIRROR } },
+ { ITEM_WALLET_ADULT, { ITEM_WALLET_ADULT, ITEM_WALLET_GIANT } },
+ { SPLIT_SINGLE_MAGIC, { SPLIT_SINGLE_MAGIC, SPLIT_DOUBLE_MAGIC } },
+};
+
+static std::vector<const char*> sceneAreaNameMap = {
+ "All Scenes",
+ "Miscellaneous",
+ "Clock Town",
+ "Termina Field & Romani's Ranch",
+ "Woodfall Region",
+ "Snowhead Region",
+ "Great Bay Region",
+ "Ikana Region",
+ "The Moon",
+};
+
+static std::unordered_map<const char*, IndexRangeObject> sceneAreaRangeMap = {
+ { "All Scenes", { SCENE_LOST_WOODS, SCENE_LAST_BS } },
+ { "Miscellaneous", { SCENE_LOST_WOODS, SCENE_KYOJINNOMA } },
+ { "Clock Town", { SCENE_TOWN, SCENE_POSTHOUSE } },
+ { "Termina Field & Romani's Ranch", { SCENE_00KEIKOKU, SCENE_F01C } },
+ { "Woodfall Region", { SCENE_24KEMONOMITI, SCENE_KINSTA1 } },
+ { "Snowhead Region", { SCENE_13HUBUKINOMITI, SCENE_GORONSHOP } },
+ { "Great Bay Region", { SCENE_30GYOSON, SCENE_33ZORACITY } },
+ { "Ikana Region", { SCENE_IKANAMAE, SCENE_RANDOM } },
+ { "The Moon", { SCENE_SOUGEN, SCENE_LAST_BS } },
+};
+// 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<int>(i);
+ }
+ if (sceneObjectList[i].splitId == end) {
+ setRange.endIndex = static_cast<int>(i);
+ }
+ }
+
+ return setRange;
+}
+
+IndexRangeObject GetIndexRange(uint32_t start, uint32_t end) {
+ IndexRangeObject setRange = { 0, 0 };
+
+ for (size_t i = 0; i < splitObjectList.size(); i++) {
+ if (splitObjectList[i].splitId == start) {
+ setRange.startIndex = static_cast<int>(i);
+ }
+ if (splitObjectList[i].splitId == end) {
+ setRange.endIndex = static_cast<int>(i);
+ }
+ }
+
+ return setRange;
+}
+
+bool shouldPopUpOpen = false;
+uint32_t popupItem = 0;
+const char* popupTooltip = "";
+IndexRangeObject range = GetIndexRange((uint32_t)ITEM_OCARINA_OF_TIME, (uint32_t)ITEM_BOTTLE);
+const char* listName = "Inventory";
+uint32_t listColumns = 6;
+TexturePtr itemImage;
+std::string listInputName;
+std::vector<std::string> savedLists;
+uint32_t selectedIndex = 0;
+uint32_t comparedIndex = 0;
+
+const char* GetItemImageById(uint32_t itemId) {
+ switch (itemId) {
+ case SPLIT_KILLED_ODOLWA:
+ case SPLIT_KILLED_GOHT:
+ case SPLIT_KILLED_GYORG:
+ case SPLIT_KILLED_TWINMOLD:
+ case SPLIT_KILLED_MAJORA:
+ return gDungeonMapSkullTex;
+ case SPLIT_SINGLE_MAGIC:
+ return (const char*)gItemIcons[ITEM_MAGIC_JAR_SMALL];
+ case SPLIT_DOUBLE_MAGIC:
+ return (const char*)gItemIcons[ITEM_MAGIC_JAR_BIG];
+ case SPLIT_DOUBLE_DEFENSE:
+ return (const char*)gItemIcons[ITEM_HEART_CONTAINER];
+ default:
+ if (itemId <= ITEM_NONE) {
+ return (const char*)gItemIcons[itemId];
+ }
+ break;
+ }
+}
+
+ImVec2 GetItemImageSizeById(uint32_t itemId) {
+ float defaultImageSize = 32.0f;
+ if ((itemId >= ITEM_SONG_SONATA && itemId <= ITEM_SONG_SUN) || itemId == ITEM_SONG_LULLABY_INTRO) {
+ return ImVec2(defaultImageSize / 1.5f, defaultImageSize);
+ } else {
+ 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ });
+ 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ UpdateSplitSettings(SPLIT_HEADERS);
+ };
+ UIWidgets::Tooltip("Shows the column names in the Split List.");
+
+ ImGui::TableNextColumn();
+ if (UIWidgets::CVarCheckbox("Hide Background", "gSettings.TimeSplits.Opacity",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ });
+ UIWidgets::Tooltip("Highlights the row with the current Active Split.");
+
+ ImGui::TableNextColumn();
+ UIWidgets::CVarCheckbox("Follow Active Split", "gSettings.TimeSplits.Follow",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ });
+ 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ });
+ UIWidgets::Tooltip("Enables Split Comparisons between lists, this will integrate within the Split List.");
+
+ if (CVarGetInteger("gSettings.TimeSplits.Compare", 0)) {
+ ImGui::SameLine();
+ UIWidgets::PushStyleCombobox(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)));
+ ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
+ if (ImGui::BeginCombo("##compareSplits", savedLists[comparedIndex].c_str())) {
+ for (int 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::LabelPosition::None,
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ .placeholder = "Enter new list name",
+ });
+
+ ImGui::TableNextColumn();
+ if (UIWidgets::Button("Create List", {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ SplitSaveFileAction(SPLIT_SAVE, listInputName);
+ SplitSaveFileAction(SPLIT_RETRIEVE, "");
+ }
+
+ ImGui::TableNextColumn();
+ UIWidgets::PushStyleCombobox(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)));
+ ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
+ if (ImGui::BeginCombo("##savedSplits", savedLists[selectedIndex].c_str())) {
+ for (int 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ if (savedLists[0] != "Create a List First") {
+ SplitSaveFileAction(SPLIT_LOAD, savedLists[selectedIndex]);
+ }
+ }
+
+ ImGui::TableNextColumn();
+ if (UIWidgets::Button("New Attempt", {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ if (splitList.size() == 0) {
+ return;
+ }
+
+ for (auto& splits : splitList) {
+ splits.splitStatus = SPLIT_INACTIVE;
+ }
+ splitList[0].splitStatus = SPLIT_ACTIVE;
+ }
+
+ ImGui::TableNextColumn();
+ if (UIWidgets::Button("Update Splits",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ UpdateSplitBests();
+ }
+
+ ImGui::EndTable();
+ }
+}
+
+void DrawEntranceList() {
+ UIWidgets::PushStyleCombobox(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)));
+ ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
+ if (ImGui::BeginCombo("##SceneFilter", sceneAreaNameMap[sceneFilterIndex])) {
+ for (int 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 (int 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(),
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->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(const char* tableName, IndexRangeObject range, uint32_t tableSize) {
+ if (ImGui::BeginTable(tableName, tableSize)) {
+ for (int i = range.startIndex; i <= range.endIndex; i++) {
+ ImGui::TableNextColumn();
+ SplitsPushImageButtonStyle();
+ if (ImGui::ImageButton(std::to_string(splitObjectList[i].splitId).c_str(),
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
+ GetItemImageById(splitObjectList[i].splitId)),
+ GetItemImageSizeById(splitObjectList[i].splitId) * 1.5f, ImVec2(0, 0), ImVec2(1, 1),
+ ImVec4(0, 0, 0, 0), Ship_GetItemColorTint(splitObjectList[i].splitId))) {
+ if (itemSubMenuList.contains(splitObjectList[i].splitId)) {
+ shouldPopUpOpen = true;
+ popupItem = splitObjectList[i].splitId;
+ ImGui::OpenPopup("ItemSubMenu");
+ } else {
+ AddSplitEntryById(splitObjectList[i].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() {
+ bool shouldRemoveEntry = false;
+ uint32_t entryId = 0, entryIndex = 0;
+
+ UIWidgets::PushStyleTabs(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)));
+ 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 = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ });
+ 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(i);
+ SplitsPushImageButtonStyle();
+ if (ImGui::ImageButton(
+ std::to_string(i).c_str(),
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
+ splitList[i].splitType == SPLIT_TYPE_NORMAL ? GetItemImageById(splitList[i].splitId)
+ : gPauseUnusedCursorTex),
+ splitList[i].splitType == SPLIT_TYPE_NORMAL ? GetItemImageSizeById(splitList[i].splitId)
+ : ImVec2(32.0f, 32.0f),
+ ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0),
+ splitList[i].splitType == SPLIT_TYPE_NORMAL ? Ship_GetItemColorTint(splitList[i].splitId)
+ : ImVec4(1, 1, 1, 1))) {
+ shouldRemoveEntry = true;
+ entryId = splitList[i].splitId;
+ entryIndex = i;
+ };
+ UIWidgets::Tooltip(splitList[i].splitName.c_str());
+
+ HandleDragAndDrop(i);
+ SplitsPopImageButtonStyle();
+ ImGui::PopID();
+ }
+ ImGui::EndChild();
+
+ ImGui::TableNextColumn();
+ if (UIWidgets::Button("Inventory",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ range = GetIndexRange((uint32_t)ITEM_OCARINA_OF_TIME, (uint32_t)ITEM_BOTTLE);
+ listName = "Inventory";
+ listColumns = 6;
+ }
+ if (UIWidgets::Button("Masks",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ range = GetIndexRange((uint32_t)ITEM_MASK_POSTMAN, (uint32_t)ITEM_MASK_FIERCE_DEITY);
+ listName = "Masks";
+ listColumns = 6;
+ }
+ if (UIWidgets::Button("Songs",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ range = GetIndexRange((uint32_t)ITEM_SONG_TIME, (uint32_t)ITEM_SONG_OATH);
+ listName = "Songs";
+ listColumns = 5;
+ }
+ if (UIWidgets::Button("Quest",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ range = GetIndexRange((uint32_t)ITEM_REMAINS_ODOLWA, (uint32_t)ITEM_BOMBERS_NOTEBOOK);
+ listName = "Quest";
+ listColumns = 4;
+ }
+ if (UIWidgets::Button("Bosses",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ range = GetIndexRange((uint32_t)SPLIT_KILLED_ODOLWA, (uint32_t)SPLIT_KILLED_MAJORA);
+ listName = "Bosses";
+ listColumns = 1;
+ }
+ if (UIWidgets::Button("Entrances",
+ {
+ .color = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)),
+ })) {
+ listName = "Entrances";
+ }
+ ImGui::TableNextColumn();
+ if (listName != "Entrances") {
+ DrawItemList(listName, range, listColumns);
+ if (listName == "Quest") {
+ DrawItemList("Quest II",
+ GetIndexRange((uint32_t)SPLIT_SINGLE_MAGIC, (uint32_t)SPLIT_DOUBLE_DEFENSE), 3);
+ }
+ } else {
+ DrawEntranceList();
+ }
+
+ ImGui::EndTable();
+ }
+ ImGui::EndTabItem();
+ }
+ ImGui::EndTabBar();
+ }
+ UIWidgets::PopStyleTabs();
+
+ if (shouldRemoveEntry) {
+ RemoveSplitEntry(entryId, entryIndex);
+ shouldRemoveEntry = false;
+ }
+}
+
+void TimesplitsSettingsWindow::InitElement() {
+}