From 41610bd6e51e731a1979bdc990b2a75fe8f9e167 Mon Sep 17 00:00:00 2001 From: Caladius Date: Fri, 18 Oct 2024 15:31:27 -0400 Subject: [PATCH 01/14] Adds 2 new Hooks, OnTimeStamp and OnPlayerHealthChange. --- .../game-interactor/GameInteractor_HookTable.h | 2 ++ .../Enhancements/game-interactor/GameInteractor_Hooks.cpp | 8 ++++++++ .../Enhancements/game-interactor/GameInteractor_Hooks.h | 2 ++ soh/src/code/z_parameter.c | 3 +++ 4 files changed, 15 insertions(+) diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 46f306a3c83..62c74d8a1bf 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -25,7 +25,9 @@ DEFINE_HOOK(OnActorUpdate, (void* actor)); DEFINE_HOOK(OnActorKill, (void* actor)); DEFINE_HOOK(OnEnemyDefeat, (void* actor)); DEFINE_HOOK(OnBossDefeat, (void* actor)); +DEFINE_HOOK(OnTimestamp, (u8 item)); DEFINE_HOOK(OnPlayerBonk, ()); +DEFINE_HOOK(OnPlayerHealthChange, (int16_t amount)); DEFINE_HOOK(OnPlayDestroy, ()); DEFINE_HOOK(OnPlayDrawEnd, ()); DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, va_list originalArgs)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 24efb40c151..7a6362f1e82 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -107,10 +107,18 @@ void GameInteractor_ExecuteOnBossDefeat(void* actor) { GameInteractor::Instance->ExecuteHooksForFilter(actor); } +void GameInteractor_ExecuteOnTimestamp (u8 item) { + GameInteractor::Instance->ExecuteHooks(item); +} + void GameInteractor_ExecuteOnPlayerBonk() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount) { + GameInteractor::Instance->ExecuteHooks(amount); +} + void GameInteractor_ExecuteOnPlayDestroy() { GameInteractor::Instance->ExecuteHooks(); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index dccc9930ae2..30b5d10d6f3 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -24,7 +24,9 @@ void GameInteractor_ExecuteOnActorUpdate(void* actor); void GameInteractor_ExecuteOnActorKill(void* actor); void GameInteractor_ExecuteOnEnemyDefeat(void* actor); void GameInteractor_ExecuteOnBossDefeat(void* actor); +void GameInteractor_ExecuteOnTimestamp (u8 item); void GameInteractor_ExecuteOnPlayerBonk(); +void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount); void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price); void GameInteractor_ExecuteOnPlayDestroy(); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index ba1f89bbc09..3135eb2106b 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1844,6 +1844,7 @@ void GameplayStats_SetTimestamp(PlayState* play, u8 item) { } gSaveContext.sohStats.itemTimestamp[item] = time; + GameInteractor_ExecuteOnTimestamp(item); } // Gameplay stat tracking: Update time the item was acquired @@ -3321,6 +3322,8 @@ s32 Health_ChangeBy(PlayState* play, s16 healthChange) { // "Life=%d *** %d ******" osSyncPrintf(" ライフ=%d *** %d ******\n", gSaveContext.health, healthLevel); + GameInteractor_ExecuteOnPlayerHealthChange(healthChange); + if (gSaveContext.health <= 0) { gSaveContext.health = 0; return 0; From 07ba62cace516be2b78575fbb891189d61c72894 Mon Sep 17 00:00:00 2001 From: Caladius Date: Fri, 18 Oct 2024 15:41:51 -0400 Subject: [PATCH 02/14] Re-add Timesplit code --- .../Enhancements/timesplits/TimeSplits.cpp | 863 ++++++++++++++++++ soh/soh/Enhancements/timesplits/TimeSplits.h | 65 ++ 2 files changed, 928 insertions(+) create mode 100644 soh/soh/Enhancements/timesplits/TimeSplits.cpp create mode 100644 soh/soh/Enhancements/timesplits/TimeSplits.h diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp new file mode 100644 index 00000000000..d8d47093b1b --- /dev/null +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -0,0 +1,863 @@ +#include "TimeSplits.h" +#include "soh/UIWidgets.hpp" +#include "soh/Enhancements/gameplaystats.h" +#include "soh/SaveManager.h" +#include "soh/util.h" +#include +#include "include/z64item.h" + +#include +#include + +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/debugger/debugSaveEditor.h" +#include "soh_assets.h" +#include "assets/textures/parameter_static/parameter_static.h" + +extern "C" { + extern SaveContext gSaveContext; + extern PlayState* gPlayState; +} + +// 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) + +using json = nlohmann::json; + +static uint32_t splitBestTimeDisplay; +static uint32_t popupID = 0; +static uint32_t tableSize = 0; +static float timeSplitsWindowSize = 1.0f; + +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); + +std::vector keys; + +char listNameBuf[25]; +const char* lastLoadedlistName; +int dragSourceIndex = -1; +int dragTargetIndex = -1; + +std::vector splitList; +std::vector emptyList; + +std::vector splitObjectList = { + { SPLIT_ITEM, ITEM_STICK, "Deku Stick", "ITEM_STICK", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_NUT, "Deku Nut", "ITEM_NUT", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BOMB, "Bomb", "ITEM_BOMB", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BOW, "Fairy Bow", "ITEM_BOW", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_ARROW_FIRE, "Fire Arrow", "ITEM_ARROW_FIRE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_DINS_FIRE, "Din's Fire", "ITEM_DINS_FIRE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_SLINGSHOT, "Fairy Slingshot", "ITEM_SLINGSHOT", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_OCARINA_FAIRY, "Fairy Ocarina", "ITEM_OCARINA_FAIRY", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_OCARINA_TIME, "Ocarina of Time", "ITEM_OCARINA_TIME", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BOMBCHU, "Bombchu", "ITEM_BOMBCHU", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_HOOKSHOT, "Hookshot", "ITEM_HOOKSHOT", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_LONGSHOT, "Longshot", "ITEM_LONGSHOT", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_ARROW_ICE, "Ice Arrow", "ITEM_ARROW_ICE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_FARORES_WIND, "Farore's Wind", "ITEM_FARORES_WIND", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BOOMERANG, "Boomerang", "ITEM_BOOMERANG", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_LENS, "Lens of Truth", "ITEM_LENS", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BEAN, "Magic Bean", "ITEM_BEAN", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_HAMMER, "Megaton Hammer", "ITEM_HAMMER", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_ARROW_LIGHT, "Light Arrow", "ITEM_ARROW_LIGHT", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_NAYRUS_LOVE, "Nayru's Love", "ITEM_NAYRUS_LOVE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BOTTLE, "Empty Bottle", "ITEM_BOTTLE", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_POTION_RED, "Red Potion", "ITEM_POTION_RED", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_POTION_GREEN, "Green Potion", "ITEM_POTION_GREEN", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_POTION_BLUE, "Blue Potion", "ITEM_POTION_BLUE", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_FAIRY, "Bottled Fairy", "ITEM_FAIRY", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_FISH, "Fish", "ITEM_FISH", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MILK_BOTTLE, "Milk", "ITEM_MILK_BOTTLE", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_LETTER_RUTO, "Ruto's Letter", "ITEM_LETTER_RUTO", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BLUE_FIRE, "Blue Fire", "ITEM_BLUE_FIRE", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BUG, "Bug", "ITEM_BUG", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BIG_POE, "Big Poe", "ITEM_BIG_POE", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_POE, "Poe", "ITEM_POE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_WEIRD_EGG, "Weird Egg", "ITEM_WEIRD_EGG", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_CHICKEN, "Chicken", "ITEM_CHICKEN", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_LETTER_ZELDA, "Zelda's Letter", "ITEM_LETTER_ZELDA", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_KEATON, "Keaton Mask", "ITEM_MASK_KEATON", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_SKULL, "Skull Mask", "ITEM_MASK_SKULL", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_SPOOKY, "Spooky Mask", "ITEM_MASK_SPOOKY", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_BUNNY, "Bunny Hood", "ITEM_MASK_BUNNY", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_GORON, "Goron Mask", "ITEM_MASK_GORON", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_ZORA, "Zora Mask", "ITEM_MASK_ZORA", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_GERUDO, "Gerudo Mask", "ITEM_MASK_GERUDO", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_MASK_TRUTH, "Mask of Truth", "ITEM_MASK_TRUTH", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_POCKET_EGG, "Pocket Egg", "ITEM_POCKET_EGG", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_POCKET_CUCCO, "Pocket Cucco", "ITEM_POCKET_CUCCO", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_COJIRO, "Cojiro", "ITEM_COJIRO", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_ODD_MUSHROOM, "Odd Mushroom", "ITEM_ODD_MUSHROOM", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_ODD_POTION, "Odd Potion", "ITEM_ODD_POTION", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_SAW, "Poacher's Saw", "ITEM_SAW", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_SWORD_BROKEN, "Goron's Sword (Broken)", "ITEM_SWORD_BROKEN", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_PRESCRIPTION, "Prescription", "ITEM_PRESCRIPTION", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_FROG, "Eyeball Frog", "ITEM_FROG", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_EYEDROPS, "Eye Drops", "ITEM_EYEDROPS", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_CLAIM_CHECK, "Claim Check", "ITEM_CLAIM_CHECK", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_SWORD_KOKIRI, "Kokiri Sword", "ITEM_SWORD_KOKIRI", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_SWORD_MASTER, "Master Sword", "ITEM_SWORD_MASTER", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_SWORD_BGS, "Giant's Knife & Biggoron's Sword", "ITEM_SWORD_BGS", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_SHIELD_DEKU, "Deku Shield", "ITEM_SHIELD_DEKU", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_SHIELD_HYLIAN, "Hylian Shield", "ITEM_SHIELD_HYLIAN", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_SHIELD_MIRROR, "Mirror Shield", "ITEM_SHIELD_MIRROR", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_TUNIC_GORON, "Goron Tunic", "ITEM_TUNIC_GORON", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_TUNIC_ZORA, "Zora Tunic", "ITEM_TUNIC_ZORA", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_BOOTS_IRON, "Iron Boots", "ITEM_BOOTS_IRON", COLOR_WHITE }, + { SPLIT_EQUIPMENT, ITEM_BOOTS_HOVER, "Hover Boots", "ITEM_BOOTS_HOVER", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BULLET_BAG_30, "Bullet Bag (30)", "ITEM_BULLET_BAG_30", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BULLET_BAG_40, "Bullet Bag (40)", "ITEM_BULLET_BAG_40", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BULLET_BAG_50, "Bullet Bag (50)", "ITEM_BULLET_BAG_50", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_QUIVER_30, "Quiver (30)", "ITEM_QUIVER_30", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_QUIVER_40, "Big Quiver (40)", "ITEM_QUIVER_40", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_QUIVER_50, "Biggest Quiver (50)", "ITEM_QUIVER_50", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BOMB_BAG_20, "Bomb Bag (20)", "ITEM_BOMB_BAG_20", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BOMB_BAG_30, "Big Bomb Bag (30)", "ITEM_BOMB_BAG_30", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_BOMB_BAG_40, "Biggest Bomb Bag (40)", "ITEM_BOMB_BAG_40", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_BRACELET, "Goron's Bracelet", "ITEM_BRACELET", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_GAUNTLETS_SILVER, "Silver Gauntlets", "ITEM_GAUNTLETS_SILVER", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_GAUNTLETS_GOLD, "Golden Gauntlets", "ITEM_GAUNTLETS_GOLD", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_SCALE_SILVER, "Silver Scale", "ITEM_SCALE_SILVER", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_SCALE_GOLDEN, "Golden Scale", "ITEM_SCALE_GOLDEN", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_SWORD_KNIFE, "Giant's Knife (Broken)", "ITEM_SWORD_KNIFE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_WALLET_ADULT, "Adult's Wallet", "ITEM_WALLET_ADULT", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_WALLET_GIANT, "Giant's Wallet", "ITEM_WALLET_GIANT", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_FISHING_POLE, "Fishing Pole", "ITEM_FISHING_POLE", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_MINUET, "Minuet of Forest", "QUEST_SONG_MINUET", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_BOLERO, "Bolero of Fire", "QUEST_SONG_BOLERO", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_SERENADE, "Serenade of Water", "QUEST_SONG_SERENADE", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_REQUIEM, "Requiem of Spirit", "QUEST_SONG_REQUIEM", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_NOCTURNE, "Nocturne of Shadow", "QUEST_SONG_NOCTURNE", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_PRELUDE, "Prelude of Light", "QUEST_SONG_PRELUDE", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_LULLABY, "Zelda's Lullaby", "QUEST_SONG_LULLABY", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_EPONA, "Epona's Song", "QUEST_SONG_EPONA", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_SARIA, "Saria's Song", "QUEST_SONG_SARIA", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_SUN, "Sun's Song", "QUEST_SONG_SUN", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_TIME, "Song of Time", "QUEST_SONG_TIME", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SONG_STORMS, "Song of Storms", "QUEST_SONG_STORMS", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_MEDALLION_FOREST, "Forest Medallion", "QUEST_MEDALLION_FOREST", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_MEDALLION_FIRE, "Fire Medallion", "QUEST_MEDALLION_FIRE", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_MEDALLION_WATER, "Water Medallion", "QUEST_MEDALLION_WATER", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_MEDALLION_SPIRIT, "Spirit Medallion", "QUEST_MEDALLION_SPIRIT", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_MEDALLION_SHADOW, "Shadow Medallion", "QUEST_MEDALLION_SHADOW", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_MEDALLION_LIGHT, "Light Medallion", "QUEST_MEDALLION_LIGHT", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_KOKIRI_EMERALD, "Kokiri's Emerald", "QUEST_KOKIRI_EMERALD", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_GORON_RUBY, "Goron's Ruby", "QUEST_GORON_RUBY", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_ZORA_SAPPHIRE, "Zora's Sapphire", "QUEST_ZORA_SAPPHIRE", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_STONE_OF_AGONY, "Stone of Agony", "QUEST_STONE_OF_AGONY", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_GERUDO_CARD, "Gerudo's Card", "QUEST_GERUDO_CARD", COLOR_WHITE }, + { SPLIT_QUEST, ITEM_SKULL_TOKEN, "Skulltula Token", "QUEST_SKULL_TOKEN", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_SINGLE_MAGIC, "Magic Meter", "ITEM_MAGIC_SMALL", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_DOUBLE_MAGIC, "Double Magic", "ITEM_MAGIC_LARGE", COLOR_WHITE }, + { SPLIT_ITEM, ITEM_DOUBLE_DEFENSE, "Double Defense", "ITEM_HEART_CONTAINER", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_STICK_UPGRADE_20, "Deku Stick Upgrade (20)", "ITEM_STICK", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_STICK_UPGRADE_30, "Deku Stick Upgrade (30)", "ITEM_STICK", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_NUT_UPGRADE_30, "Deku Nut Upgrade (30)", "ITEM_NUT", COLOR_WHITE }, + { SPLIT_UPGRADE, ITEM_NUT_UPGRADE_40, "Deku Nut Upgrade (40)", "ITEM_NUT", COLOR_WHITE }, + { SPLIT_BOSS, ACTOR_BOSS_GOMA, "Queen Gohma", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_LIGHT_GREEN }, + { SPLIT_BOSS, ACTOR_BOSS_DODONGO, "King Dodongo", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_LIGHT_RED }, + { SPLIT_BOSS, ACTOR_BOSS_VA, "Barinade", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_LIGHT_BLUE }, + { SPLIT_BOSS, ACTOR_BOSS_GANONDROF, "Phantom Ganon", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_GREEN }, + { SPLIT_BOSS, ACTOR_BOSS_FD2, "Volvagia", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_RED }, + { SPLIT_BOSS, ACTOR_BOSS_MO, "Morpha", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_BLUE }, + { SPLIT_BOSS, ACTOR_BOSS_SST, "Bongo Bongo", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_PURPLE }, + { SPLIT_BOSS, ACTOR_BOSS_TW, "Twinrova", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_ORANGE }, + { SPLIT_BOSS, ACTOR_BOSS_GANON, "Ganondorf", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_GREY }, + { SPLIT_BOSS, ACTOR_BOSS_GANON2, "Ganon", "SPECIAL_TRIFORCE_PIECE_WHITE", COLOR_YELLOW }, + { SPLIT_ENTRANCE, SCENE_DEKU_TREE, "Enter Deku Tree", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_DODONGOS_CAVERN, "Enter Dodongos Cavern", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_JABU_JABU, "Enter Jabu Jabu's Belly", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_FOREST_TEMPLE, "Enter Forest Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_FIRE_TEMPLE, "Enter Fire Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_WATER_TEMPLE, "Enter Water Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_SPIRIT_TEMPLE, "Enter Spirit Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_SHADOW_TEMPLE, "Enter Shadow Temple", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_BOTTOM_OF_THE_WELL, "Enter Bottom of the Well", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_ICE_CAVERN, "Enter Ice Cavern", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_GANONS_TOWER, "Enter Ganons Tower", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_GERUDO_TRAINING_GROUND, "Enter Gerudo Training Grounds", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_THIEVES_HIDEOUT, "Enter Thieves Hideout", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_INSIDE_GANONS_CASTLE, "Enter Ganons Castle", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_GANONS_TOWER_COLLAPSE_INTERIOR, "Enter Tower Collapse Interior", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_ENTRANCE, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, "Enter Ganons Castle Collapse", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_MISC, SCENE_ZORAS_RIVER, "Lost Woods Escape", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_MISC, SCENE_LOST_WOODS, "Forest Escape", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, + { SPLIT_MISC, SCENE_KAKARIKO_VILLAGE, "Watchtower Death", "SPECIAL_SPLIT_ENTRANCE", COLOR_WHITE }, +}; + +std::map> popupList = { + // { ITEM_STICK, { ITEM_STICK, ITEM_STICK_UPGRADE_20, ITEM_STICK_UPGRADE_30, ITEM_STICK } }, + // { 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_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 } }, +}; + +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 fmt::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds); +} + +void TimeSplitsUpdateStatus(uint32_t index) { + for (int i = index + 1; i < splitList.size(); i++) { + if (splitList[i].splitTimeStatus != SPLIT_COLLECTED) { + splitList[i].splitTimeStatus = SPLIT_INACTIVE; + } + } +} + +uint32_t findFirstInactiveSplit(const std::vector& splitList) { + uint32_t index = 0; + for (const auto& split : splitList) { + if (split.splitTimeStatus == SPLIT_ACTIVE) { + return UINT32_MAX; + } else { + if (split.splitTimeStatus == SPLIT_INACTIVE && + split.splitTimeStatus != SPLIT_COLLECTED) { + return index; + } + index++; + } + } + return UINT32_MAX; +} + +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 (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.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.gameComplete = true; +} + +//void TimeSplitSkipSplit(uint32_t splitID) { +// splitStatus[splitID] = 3; +// if (splitID + 1 == splitItem.size()) { +// TimeSplitCompleteSplits(); +// } else { +// splitStatus[splitID + 1] = 2; +// } +// +//} + +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.splitTimeStatus}, + {"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 TimeSplitsFileManagement(uint32_t action, const char* listEntry, std::vector listData) { + std::string filename = "timesplitdata.json"; + json saveFile; + json listArray = nlohmann::json::array(); + + std::ifstream inputFile(filename); + if (inputFile.is_open()) { + inputFile >> saveFile; + inputFile.close(); + } + + if (action == 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 == ACTION_LOAD) { + if (saveFile.contains(listEntry)) { + listArray = saveFile[listEntry]; + splitList.clear(); + + for (auto& data : listArray) { + splitList.push_back(json_to_SplitObject(data)); + } + } + } + + if (action == ACTION_UPDATE) { + for (auto& update : listData) { + if (update.splitTimeBest < update.splitTimePreviousBest) { + update.splitTimePreviousBest = update.splitTimeBest; + } + } + } + + if (action == ACTION_COLLECT) { + keys.clear(); + for (auto& data : saveFile.items()) { + keys.push_back(data.key()); + } + if (keys.size() == 0) { + keys.push_back("No Saved Lists"); + } + } +} + +void TimeSplitsPopUpContext() { + int rowIndex = 0; + if (ImGui::BeginPopup("TimeSplitsPopUp") && popupID) { + 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; + } + + SplitObject& popupObject = *findID; + if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(popupObject.splitImage), + ImVec2(26.0f, 26.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), popupObject.splitTint)) { + splitList.push_back(popupObject); + if (splitList.size() == 1) { + splitList[0].splitTimeStatus = SPLIT_ACTIVE; + } else { + splitList[splitList.size() - 1].splitTimeStatus = SPLIT_INACTIVE; + } + ImGui::CloseCurrentPopup(); + } + if (rowIndex != 5) { + ImGui::SameLine(); + } + rowIndex++; + } + ImGui::EndPopup(); + } +} + +void TimeSplitsItemSplitEvent(u8 item) { + uint32_t index = 0; + 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 == gSaveContext.inventory.gsTokens + 1) { + return split.splitID == item; + } + }); + if (it == splitList.end()) { + return; + } + } + + for (auto& split : splitList) { + if (split.splitType <= SPLIT_QUEST) { + if (item == split.splitID) { + if (split.splitTimeStatus == SPLIT_ACTIVE) { + split.splitTimeCurrent = GAMEPLAYSTAT_TOTAL_TIME; + split.splitTimeStatus = SPLIT_COLLECTED; + if (split.splitTimeBest > GAMEPLAYSTAT_TOTAL_TIME || split.splitTimeBest == 0) { + split.splitTimeBest = GAMEPLAYSTAT_TOTAL_TIME; + } + if (split.splitTimePreviousBest == 0) { + split.splitTimePreviousBest = GAMEPLAYSTAT_TOTAL_TIME; + } + if (index == splitList.size() - 1) { + // Completed List + TimeSplitCompleteSplits(); + } else { + splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE; + } + } + } + } + index++; + } +} + +void TimeSplitsBossSplitEvent(Actor* actor) { + uint32_t index = 0; + for (auto& split : splitList) { + if (split.splitType == SPLIT_BOSS) { + if (actor->id == split.splitID) { + if (split.splitTimeStatus == SPLIT_ACTIVE) { + split.splitTimeCurrent = GAMEPLAYSTAT_TOTAL_TIME; + split.splitTimeStatus = SPLIT_COLLECTED; + if (split.splitTimeBest > GAMEPLAYSTAT_TOTAL_TIME || split.splitTimeBest == 0) { + split.splitTimeBest = GAMEPLAYSTAT_TOTAL_TIME; + } + if (split.splitTimePreviousBest == 0) { + split.splitTimePreviousBest = GAMEPLAYSTAT_TOTAL_TIME; + } + if (index == splitList.size() - 1) { + // Completed List + TimeSplitCompleteSplits(); + } else { + splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE; + } + } + } + } + index++; + } +} + +void TimeSplitsSceneSplitEvent(int16_t scene) { + uint32_t setType = SPLIT_ENTRANCE; + + if ((scene == SCENE_ZORAS_RIVER && gSaveContext.entranceIndex == ENTR_ZORAS_RIVER_4) || + (scene == SCENE_LOST_WOODS && (gSaveContext.entranceIndex == ENTR_LOST_WOODS_9 || + gSaveContext.entranceIndex == ENTR_LOST_WOODS_0)) || scene == SCENE_KAKARIKO_VILLAGE) { + setType = SPLIT_MISC; + } + + uint32_t index = 0; + for (auto& split : splitList) { + if (split.splitType == setType) { + if (scene == split.splitID) { + if (split.splitTimeStatus == SPLIT_ACTIVE) { + split.splitTimeCurrent = GAMEPLAYSTAT_TOTAL_TIME; + split.splitTimeStatus = SPLIT_COLLECTED; + if (split.splitTimeBest > GAMEPLAYSTAT_TOTAL_TIME || split.splitTimeBest == 0) { + split.splitTimeBest = GAMEPLAYSTAT_TOTAL_TIME; + } + if (split.splitTimePreviousBest == 0) { + split.splitTimePreviousBest = GAMEPLAYSTAT_TOTAL_TIME; + } + if (index == splitList.size() - 1) { + // Completed List + TimeSplitCompleteSplits(); + } else { + splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE; + } + } + } + } + index++; + } +} + +void TimeSplitsSplitBestTimeDisplay(uint32_t status, uint32_t currentBestTime, uint32_t previousBestTime) { + if (status == SPLIT_ACTIVE) { + if (GAMEPLAYSTAT_TOTAL_TIME > previousBestTime) { + splitTimeColor = COLOR_RED; + splitBestTimeDisplay = (GAMEPLAYSTAT_TOTAL_TIME - previousBestTime); + } + if (GAMEPLAYSTAT_TOTAL_TIME == previousBestTime) { + splitTimeColor = COLOR_WHITE; + splitBestTimeDisplay = GAMEPLAYSTAT_TOTAL_TIME; + } + if (GAMEPLAYSTAT_TOTAL_TIME < previousBestTime) { + splitTimeColor = COLOR_GREEN; + splitBestTimeDisplay = (previousBestTime - GAMEPLAYSTAT_TOTAL_TIME); + } + } + if (status == SPLIT_INACTIVE) { + splitTimeColor = COLOR_WHITE; + splitBestTimeDisplay = currentBestTime; + } + if (status == SPLIT_COLLECTED) { + if (currentBestTime > previousBestTime) { + splitTimeColor = COLOR_RED; + } + if (currentBestTime == previousBestTime) { + splitTimeColor = COLOR_WHITE; + } + if (currentBestTime < previousBestTime) { + splitTimeColor = COLOR_GREEN; + } + splitBestTimeDisplay = currentBestTime; + } +} + +void TimeSplitsDrawSplitsList() { + uint32_t dragIndex = 0; + ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); + ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable); + ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); + ImGui::TableSetupColumn("Item Name"); + ImGui::TableSetupColumn("Current Time", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("+/-", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("Prev. Best", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableHeadersRow(); + + 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)); + + for (auto& split : splitList) { + ImGui::TableNextColumn(); + ImGui::PushID(split.splitID); + ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), + ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint); + HandleDragAndDrop(splitList, dragIndex, split.splitName); + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped(split.splitName.c_str()); + ImGui::TableNextColumn(); + // Current Time + ImGui::Text((split.splitTimeStatus == SPLIT_ACTIVE) + ? formatTimestampTimeSplit(GAMEPLAYSTAT_TOTAL_TIME).c_str() : (split.splitTimeStatus == SPLIT_COLLECTED) + ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() : "--:--:-"); + ImGui::TableNextColumn(); + // +/- Difference + TimeSplitsSplitBestTimeDisplay(split.splitTimeStatus, split.splitTimeBest, split.splitTimePreviousBest); + ImGui::TextColored(splitTimeColor, formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); + ImGui::TableNextColumn(); + // Previous Best + ImGui::Text((split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); + ImGui::PopID(); + ImGui::PopStyleVar(1); + dragIndex++; + } + + if (dragTargetIndex != -1) { + SplitObject tempSourceSplitObject = splitList[dragSourceIndex]; + + splitList.erase(splitList.begin() + dragSourceIndex); + splitList.insert(splitList.begin() + dragTargetIndex, tempSourceSplitObject); + dragTargetIndex = -1; + dragSourceIndex = -1; + + uint32_t firstInactiveID = findFirstInactiveSplit(splitList); + if (firstInactiveID != UINT32_MAX) { + splitList[firstInactiveID].splitTimeStatus = SPLIT_ACTIVE; + TimeSplitsUpdateStatus(firstInactiveID); + } + } + + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(1); + ImGui::EndTable(); + ImGui::EndChild(); +} + +void TimeSplitsGetTableSize(uint32_t type) { + switch (type) { + case SPLIT_ITEM: + case SPLIT_QUEST: + tableSize = 6; + break; + case SPLIT_EQUIPMENT: + tableSize = 3; + break; + default: + tableSize = 2; + break; + } +} + +void TimeSplitsDrawItemList(uint32_t type) { + TimeSplitsGetTableSize(type); + + ImGui::BeginChild("Item Child"); + ImGui::BeginTable("Item List", tableSize); + for (int i = 0; i < tableSize; i++) { + if (i == 0) { + ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 39.0f); + } else { + if (type > SPLIT_QUEST) { + ImGui::TableSetupColumn("Item Name"); + } else { + ImGui::TableSetupColumn(std::to_string(i).c_str(), ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 39.0f); + } + } + } + + 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)); + + for (auto& split : splitObjectList) { + if (split.splitType == type) { + ImGui::TableNextColumn(); + ImGui::PushID(split.splitID); + if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), + ImVec2(38.0f, 38.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint)) { + + if (popupList.contains(split.splitID)) { + popupID = split.splitID; + ImGui::OpenPopup("TimeSplitsPopUp"); + } else { + splitList.push_back(split); + + + if (splitList.size() == 1) { + splitList[0].splitTimeStatus = SPLIT_ACTIVE; + } else { + splitList[splitList.size() - 1].splitTimeStatus = SPLIT_INACTIVE; + } + } + } + + TimeSplitsPopUpContext(); + ImGui::PopID(); + + if (type > SPLIT_QUEST) { + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 7.0f)); + ImGui::AlignTextToFramePadding(); + ImGui::Text(split.splitName.c_str()); + ImGui::PopStyleVar(1); + } + + } + } + ImGui::PopStyleColor(3); + ImGui::EndTable(); + ImGui::EndChild(); +} + +void TimeSplitsUpdateWindowSize() { + timeSplitsWindowSize = CVarGetFloat(CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 0); + if (timeSplitsWindowSize < 1.0f) { + timeSplitsWindowSize = 1.0f; + } +} + +void TimeSplitsDrawOptionsMenu() { + ImGui::SeparatorText("Window Options"); + if (ImGui::ColorEdit4("Background Color", (float*)&windowColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) { + Color_RGBA8 color; + color.r = windowColor.x; + color.g = windowColor.y; + color.b = windowColor.z; + color.a = windowColor.w; + } + ImGui::SameLine(); + if (ImGui::Button("Reset")) { + windowColor = { 0.0f, 0.0f, 0.0f, 1.0f }; + } + + if (UIWidgets::PaddedEnhancementSliderFloat("Window Size: %.1fx", "##windowSize", + CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 1.0f, 3.0f, "", 1.0f, false, false, true, false)) { + TimeSplitsUpdateWindowSize(); + } + //UIWidgets::PaddedEnhancementCheckbox("Set Last Split as Goal", CVAR_SETTING("TimesplitGoal")); + + ImGui::SeparatorText("Split List Management"); + ImGui::Text("New List Name: "); + ImGui::PushItemWidth(100.0f); + ImGui::InputText("##listName", listNameBuf, 25); + ImGui::PopItemWidth(); + ImGui::SameLine(); + if (ImGui::Button("Create List")) { + TimeSplitsFileManagement(ACTION_SAVE, listNameBuf, splitList); + } + UIWidgets::PaddedSeparator(); + + TimeSplitsFileManagement(ACTION_COLLECT, "", emptyList); + static uint32_t selectedItem = 0; + static std::string listItem = keys[0]; + ImGui::Text("Select List to Load: "); + ImGui::PushItemWidth(150.0f); + if (ImGui::BeginCombo("##listEntries", keys[selectedItem].c_str())) { + for (int i = 0; i < keys.size(); i++) { + bool isSelected = (selectedItem == i); + if (ImGui::Selectable(keys[i].c_str(), isSelected)) { + selectedItem = i; + listItem = keys[i].c_str(); + } + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(); + if (ImGui::Button("Load List")) { + TimeSplitsFileManagement(ACTION_LOAD, keys[selectedItem].c_str(), emptyList); + lastLoadedlistName = keys[selectedItem].c_str(); + } + UIWidgets::PaddedSeparator(); + + if (ImGui::Button("Update Splits")) { + TimeSplitsFileManagement(ACTION_UPDATE, lastLoadedlistName, splitList); + } + ImGui::SameLine(); + if (ImGui::Button("Save List")) { + TimeSplitsFileManagement(ACTION_SAVE, lastLoadedlistName, splitList); + } +} + +void TimeSplitsDrawManageList() { + ImGui::BeginTabBar("List Options"); + if (ImGui::BeginTabItem("Equipment")) { + TimeSplitsDrawItemList(SPLIT_EQUIPMENT); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Inventory")) { + TimeSplitsDrawItemList(SPLIT_ITEM); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Quest Items")) { + TimeSplitsDrawItemList(SPLIT_QUEST); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Entrances")) { + TimeSplitsDrawItemList(SPLIT_ENTRANCE); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Bosses")) { + TimeSplitsDrawItemList(SPLIT_BOSS); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Miscellaneous")) { + TimeSplitsDrawItemList(SPLIT_MISC); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); +} + +void InitializeSplitDataFile() { + if (!std::filesystem::exists("timesplitdata.json")) { + json j; + std::ofstream file("timesplitdata.json"); + file << j.dump(4); + file.close(); + } +} + +static bool initialized = false; + +void TimeSplitWindow::DrawElement() { + ImGui::SetWindowFontScale(timeSplitsWindowSize); + if (!initialized) { + InitializeSplitDataFile(); + initialized = true; + } + + ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor); + if (!ImGui::Begin("Time Splitter Window", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::End(); + ImGui::PopStyleColor(1); + return; + } + 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(); + ImGui::End(); + ImGui::PopStyleColor(1); +} + +void TimeSplitWindow::InitElement() { + TimeSplitsUpdateWindowSize(); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_TRIFORCE_PIECE_WHITE", gWTriforcePieceTex, ImVec4(1, 1, 1, 1)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_SPLIT_ENTRANCE", gSplitEntranceTex, ImVec4(1, 1, 1, 1)); + + GameInteractor::Instance->RegisterGameHook([](u8 item) { + TimeSplitsItemSplitEvent(item); + }); + + GameInteractor::Instance->RegisterGameHook([](void* refActor) { + TimeSplitsBossSplitEvent((Actor*)refActor); + }); + + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { + if (gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { + TimeSplitsSceneSplitEvent(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) { + TimeSplitsSceneSplitEvent(gPlayState->sceneNum); + } + } + }); +} \ No newline at end of file diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.h b/soh/soh/Enhancements/timesplits/TimeSplits.h new file mode 100644 index 00000000000..fbde8adb1fd --- /dev/null +++ b/soh/soh/Enhancements/timesplits/TimeSplits.h @@ -0,0 +1,65 @@ +#pragma once +#ifndef TIMESPLITS_H +#define TIMESPLITS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TIMESPLITS_H + +#include + +#ifdef __cplusplus +class TimeSplitWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override{}; +}; + +typedef enum { + ACTION_SAVE, + ACTION_LOAD, + ACTION_UPDATE, + ACTION_COLLECT +}; + +typedef enum { + SPLIT_ACTIVE, + SPLIT_INACTIVE, + SPLIT_COLLECTED +}; + +typedef enum { + SPLIT_ITEM, + SPLIT_UPGRADE, + SPLIT_EQUIPMENT, + SPLIT_QUEST, + SPLIT_BOSS, + SPLIT_ENTRANCE, + SPLIT_MISC +}; + +typedef struct { + uint32_t splitType; + 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; + +#endif From 3e3ab622338427733c39ca6cd1a6b521c5abb140 Mon Sep 17 00:00:00 2001 From: Caladius Date: Fri, 18 Oct 2024 15:42:48 -0400 Subject: [PATCH 03/14] Committing custom Assets --- .../parameter_static/gSplitEntrance.rgba32.png | Bin 0 -> 315 bytes .../parameter_static/gWTriforcePiece.rgba32.png | Bin 0 -> 1486 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 soh/assets/custom/textures/parameter_static/gSplitEntrance.rgba32.png create mode 100644 soh/assets/custom/textures/parameter_static/gWTriforcePiece.rgba32.png diff --git a/soh/assets/custom/textures/parameter_static/gSplitEntrance.rgba32.png b/soh/assets/custom/textures/parameter_static/gSplitEntrance.rgba32.png new file mode 100644 index 0000000000000000000000000000000000000000..f53685c22e59ac9e25b6ea1adc89149496946659 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWNq$1fP z$d`ekN|k}3p_zf<=YJsml7XSrfPvvv0t1893n@#G`>H6 zx;pTf#H%%Irs1dUF07PdEKXqhqJ7GexjI2u?FqyBbF06rUs&1IxRyICQ1pO~&y8A! zl2vU77&HFuc+G0Smo)jd@rEw~=ZupdN*y}!_>FIDL!-OHgIi2bcS){&SfxMxQm*s1 zUq!rct>!%_(q?~A`tO0VPy+*t0s|w50|S#l149Px#1ZP1_K>z@;j|==^1poj532;bRa{vG?BLDy{BLR4&KXw2B1!GA>K~z{r?N>`| z8&wp&_RQGh8GCHkP1?q85-2HYQUPkasX&4v(W>1e1Qiks5G+8fx`GOc6+fw{hy_Im zRxCheS0PmbAt4o|ZThE;(zCPSao9BobY~k)P)G-aF@hXKd?q zI`AlAw~mht>k^R3yQX!XH07X$sqodn?7~MYni6}bft*Je+CJ)TaCO+u2J0l*C3~rAb$-Pjm zR=qPb@%PW3|NB&{Ww@-VJ+W#5YPmWdxs^I%v)RFBvw>G}Lm&_Uj^j{71r`_8T{os8 zpIOs;V$}jJT%7vG>-7koP8)Q+4nD68qA0?aEd$VMHKA6kK_r@bH<`-5Y%N(4s}e95 zNxa2z{IhiKa0nm>9284c2o40m>2yM0|0WcmL95wJoNag^S%gNl{Xf&a!)nIF=4`fLKNs`esD5BMpC(d2_rB^;Yw1BDbtz&LkRzwlW zaX6gYodlG?EXh>*u?I%>KO`Nc9577A?!x7&^3X+Rs3_0r|(6Qxpl%-Zq}^c0Xv=RVZ6 z+7n(yV#HK>_`?>4i|sb%1wn8iF%S8Cfdn%z4GsozQU|@$rs|OXnz(Y3jA5HD!-@hh zi^mhm+!4hiG0Pb_J+PW53F1)zd4Q^b=HzlEc1>%BhX+|Mkf?H58$Eme>Irk@ZLBC@ zHj?@l8F@((O{)>a-RUYokQZ1ea8Q_Z>;si*ja~bFZU_dcKNw6zJf1l`I~RY$TFUT1 z0qIQs?P9UKcLQ$1z?hL5N4sqfIw!Am1@IUK8ktNUEpM@Bfq()Y#e)J`OvvQq^{pwK35c(H5*2X!@#U2OK4DrlL(3lB_0!i9&k9!L|w=h**sdScsx)n zE@DV4=$*3k`yUf0sZS8seFe~z9h4R2G zOCo3II0(7yA|w(SNG2oT^=?2f0$ z-d-Z1QZ%!&dw=qk$j^`ey+U{C+ zo79f3wLt@u%e1m9;x;J}kYlI;MWYcs)?pwx#F~-U>(lqY^ul|G4(>aIC99e%aJA`5 zz<0Sm9=;j7EIJ+Ba*N0dYFWpAzX0>;B&ekVjEz4H+ee0QRW~FZ6lPH0fK)02vFI!a z4kz1};=VN$I``tfy|4RxN|@bTh9v>9_`=8Ka^)ylSI0LTepui;Acu`51NFL&Z$BAi z*%cYw8azET6#6X?@Yga6`5)0ZH|2+h-v;6EO)Z&>+Wh`LN2OA%i=xc$+5Oa~yLavU z2#qm`?!q14gw<+o5B7AwVKj5~T0P*B8;)Nss~i13Uj#4FP@93HZ9eUd obqiQ8?&$;nHvSjzUl#!I4;WvzDY1oMy8r+H07*qoM6N<$g7g-trT_o{ literal 0 HcmV?d00001 From 7f39bbb7debee8a5d26f2b2b17a77975acb9d4c8 Mon Sep 17 00:00:00 2001 From: Caladius Date: Fri, 18 Oct 2024 15:45:52 -0400 Subject: [PATCH 04/14] Commit Asset definitions --- soh/assets/soh_assets.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h index 3f9039e35f0..c0acacf218f 100644 --- a/soh/assets/soh_assets.h +++ b/soh/assets/soh_assets.h @@ -97,6 +97,12 @@ static const ALIGN_ASSET(2) char gArrowDownTex[] = dgArrowDown; #define dgTriforcePiece "__OTR__textures/parameter_static/gTriforcePiece" static const ALIGN_ASSET(2) char gTriforcePieceTex[] = dgTriforcePiece; +#define dgWTriforcePiece "__OTR__textures/parameter_static/gWTriforcePiece" +static const ALIGN_ASSET(2) char gWTriforcePieceTex[] = dgWTriforcePiece; + +#define dgSplitEntrance "__OTR__textures/parameter_static/gSplitEntrance" +static const ALIGN_ASSET(2) char gSplitEntranceTex[] = dgSplitEntrance; + #define dgBossSoul "__OTR__textures/parameter_static/gBossSoul" static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul; From d0a6d7b5abd39ee329f73d549ccb392fa30b5165 Mon Sep 17 00:00:00 2001 From: Caladius Date: Fri, 18 Oct 2024 16:08:16 -0400 Subject: [PATCH 05/14] Adding Windows --- soh/soh/SohGui.cpp | 3 +++ soh/soh/SohMenuBar.cpp | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 76e91acb537..6bb68f167e6 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -134,6 +134,7 @@ namespace SohGui { std::shared_ptr mEntranceTrackerWindow; std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; + std::shared_ptr mTimeSplitWindow; std::shared_ptr mRandomizerSettingsWindow; std::shared_ptr mAdvancedResolutionSettingsWindow; std::shared_ptr mModalWindow; @@ -210,6 +211,8 @@ namespace SohGui { gui->AddGuiWindow(mItemTrackerSettingsWindow); mRandomizerSettingsWindow = std::make_shared(CVAR_WINDOW("RandomizerSettings"), "Randomizer Settings", ImVec2(920, 600)); gui->AddGuiWindow(mRandomizerSettingsWindow); + mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplitEnabled"), "Time Splits"); + gui->AddGuiWindow(mTimeSplitWindow); mAdvancedResolutionSettingsWindow = std::make_shared(CVAR_WINDOW("AdvancedResolutionEditor"), "Advanced Resolution Settings", ImVec2(497, 599)); gui->AddGuiWindow(mAdvancedResolutionSettingsWindow); mModalWindow = std::make_shared(CVAR_WINDOW("ModalWindow"), "Modal Window"); diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index d465d257386..2d8fbe9db21 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -39,6 +39,7 @@ #include "Enhancements/randomizer/randomizer_settings_window.h" #include "Enhancements/resolution-editor/ResolutionEditor.h" #include "Enhancements/enemyrandomizer.h" +#include "Enhancements/timesplits/TimeSplits.h" // FA icons are kind of wonky, if they worked how I expected them to the "+ 2.0f" wouldn't be needed, but // they don't work how I expect them to so I added that because it looked good when I eyeballed it @@ -571,6 +572,7 @@ void DrawSettingsMenu() { extern std::shared_ptr mAudioEditorWindow; extern std::shared_ptr mCosmeticsEditorWindow; extern std::shared_ptr mGameplayStatsWindow; +extern std::shared_ptr mTimeSplitWindow; void DrawEnhancementsMenu() { if (ImGui::BeginMenu("Enhancements")) @@ -1650,6 +1652,13 @@ void DrawEnhancementsMenu() { mGameplayStatsWindow->ToggleVisibility(); } } + + if (mTimeSplitWindow) { + if (ImGui::Button(GetWindowButtonText("Time Splits", CVarGetInteger(CVAR_WINDOW("TimeSplitEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + mTimeSplitWindow->ToggleVisibility(); + } + } +} ImGui::PopStyleVar(3); ImGui::PopStyleColor(1); From 477cb793ba8d5f50cbb28af0100a713c8a26ca1e Mon Sep 17 00:00:00 2001 From: Caladius Date: Fri, 18 Oct 2024 21:40:27 -0400 Subject: [PATCH 06/14] Fix Modal Window and others --- soh/soh/Enhancements/timesplits/TimeSplits.cpp | 10 ++++------ soh/soh/SohGui.cpp | 3 ++- soh/soh/SohGui.hpp | 1 + soh/soh/SohMenuBar.cpp | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index d8d47093b1b..498ae87753e 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -391,6 +391,9 @@ void TimeSplitsPopUpContext() { } SplitObject& popupObject = *findID; + if (popupObject.splitID == ITEM_SKULL_TOKEN) { + + } if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(popupObject.splitImage), ImVec2(26.0f, 26.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), popupObject.splitTint)) { splitList.push_back(popupObject); @@ -809,11 +812,6 @@ void TimeSplitWindow::DrawElement() { } ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor); - if (!ImGui::Begin("Time Splitter Window", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::End(); - ImGui::PopStyleColor(1); - return; - } ImGui::BeginTabBar("Split Tabs"); if (ImGui::BeginTabItem("Splits")) { TimeSplitsDrawSplitsList(); @@ -828,8 +826,8 @@ void TimeSplitWindow::DrawElement() { ImGui::EndTabItem(); } ImGui::EndTabBar(); - ImGui::End(); ImGui::PopStyleColor(1); + } void TimeSplitWindow::InitElement() { diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 6bb68f167e6..442d5509fe3 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -211,7 +211,7 @@ namespace SohGui { gui->AddGuiWindow(mItemTrackerSettingsWindow); mRandomizerSettingsWindow = std::make_shared(CVAR_WINDOW("RandomizerSettings"), "Randomizer Settings", ImVec2(920, 600)); gui->AddGuiWindow(mRandomizerSettingsWindow); - mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplitEnabled"), "Time Splits"); + mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplitEnabled"), "Time Splits", ImVec2(450, 660)); gui->AddGuiWindow(mTimeSplitWindow); mAdvancedResolutionSettingsWindow = std::make_shared(CVAR_WINDOW("AdvancedResolutionEditor"), "Advanced Resolution Settings", ImVec2(497, 599)); gui->AddGuiWindow(mAdvancedResolutionSettingsWindow); @@ -250,6 +250,7 @@ namespace SohGui { mSohMenuBar = nullptr; mInputViewer = nullptr; mInputViewerSettings = nullptr; + mTimeSplitWindow = nullptr; } void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohGui.hpp b/soh/soh/SohGui.hpp index 9bdedc50464..1c8eecb7996 100644 --- a/soh/soh/SohGui.hpp +++ b/soh/soh/SohGui.hpp @@ -24,6 +24,7 @@ #include "Enhancements/randomizer/randomizer_entrance_tracker.h" #include "Enhancements/randomizer/randomizer_item_tracker.h" #include "Enhancements/randomizer/randomizer_settings_window.h" +#include "Enhancements/timesplits/TimeSplits.h" #include "SohModals.h" #ifdef __cplusplus diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 2d8fbe9db21..5cc7f0c211e 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -1658,7 +1658,6 @@ void DrawEnhancementsMenu() { mTimeSplitWindow->ToggleVisibility(); } } -} ImGui::PopStyleVar(3); ImGui::PopStyleColor(1); From 89224d31e3e3607135cf06aa1b72a713b4b00158 Mon Sep 17 00:00:00 2001 From: Caladius Date: Sat, 19 Oct 2024 21:13:23 -0400 Subject: [PATCH 07/14] Time Splits v2 new Hooks etc. --- .../GameInteractor_HookTable.h | 1 + .../game-interactor/GameInteractor_Hooks.cpp | 4 + .../game-interactor/GameInteractor_Hooks.h | 1 + .../Enhancements/timesplits/TimeSplits.cpp | 380 ++++++++++-------- soh/soh/Enhancements/timesplits/TimeSplits.h | 3 +- .../actors/ovl_player_actor/z_player.c | 1 + 6 files changed, 215 insertions(+), 175 deletions(-) diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 62c74d8a1bf..a2d5c56ec52 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -28,6 +28,7 @@ DEFINE_HOOK(OnBossDefeat, (void* actor)); DEFINE_HOOK(OnTimestamp, (u8 item)); DEFINE_HOOK(OnPlayerBonk, ()); DEFINE_HOOK(OnPlayerHealthChange, (int16_t amount)); +DEFINE_HOOK(OnPlayerBottleUpdate, (int16_t contents)); DEFINE_HOOK(OnPlayDestroy, ()); DEFINE_HOOK(OnPlayDrawEnd, ()); DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, va_list originalArgs)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 7a6362f1e82..39fc298a889 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -119,6 +119,10 @@ void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount) { GameInteractor::Instance->ExecuteHooks(amount); } +void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents) { + GameInteractor::Instance->ExecuteHooks(contents); +} + void GameInteractor_ExecuteOnPlayDestroy() { GameInteractor::Instance->ExecuteHooks(); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 30b5d10d6f3..3438d269d84 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -27,6 +27,7 @@ void GameInteractor_ExecuteOnBossDefeat(void* actor); void GameInteractor_ExecuteOnTimestamp (u8 item); void GameInteractor_ExecuteOnPlayerBonk(); void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount); +void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents); void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price); void GameInteractor_ExecuteOnPlayDestroy(); diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index 498ae87753e..97b3358aaf4 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -36,18 +36,19 @@ extern "C" { using json = nlohmann::json; static uint32_t splitBestTimeDisplay; -static uint32_t popupID = 0; +static int32_t popupID = -1; static uint32_t tableSize = 0; +static int skullTokenCount = 0; static float timeSplitsWindowSize = 1.0f; 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); std::vector keys; char listNameBuf[25]; -const char* lastLoadedlistName; int dragSourceIndex = -1; int dragTargetIndex = -1; @@ -200,14 +201,16 @@ std::vector splitObjectList = { }; std::map> popupList = { - // { ITEM_STICK, { ITEM_STICK, ITEM_STICK_UPGRADE_20, ITEM_STICK_UPGRADE_30, ITEM_STICK } }, - // { ITEM_NUT, { ITEM_NUT, ITEM_NUT_UPGRADE_30, ITEM_NUT_UPGRADE_40 } }, + { 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_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 } }, @@ -216,8 +219,22 @@ std::map> popupList = { 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, { } } }; +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; + } + } + return result; +} + std::string formatTimestampTimeSplit(uint32_t value) { uint32_t sec = value / 10; uint32_t hh = sec / 3600; @@ -227,62 +244,6 @@ std::string formatTimestampTimeSplit(uint32_t value) { return fmt::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds); } -void TimeSplitsUpdateStatus(uint32_t index) { - for (int i = index + 1; i < splitList.size(); i++) { - if (splitList[i].splitTimeStatus != SPLIT_COLLECTED) { - splitList[i].splitTimeStatus = SPLIT_INACTIVE; - } - } -} - -uint32_t findFirstInactiveSplit(const std::vector& splitList) { - uint32_t index = 0; - for (const auto& split : splitList) { - if (split.splitTimeStatus == SPLIT_ACTIVE) { - return UINT32_MAX; - } else { - if (split.splitTimeStatus == SPLIT_INACTIVE && - split.splitTimeStatus != SPLIT_COLLECTED) { - return index; - } - index++; - } - } - return UINT32_MAX; -} - -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 (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.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; - gSaveContext.sohStats.gameComplete = true; -} - -//void TimeSplitSkipSplit(uint32_t splitID) { -// splitStatus[splitID] = 3; -// if (splitID + 1 == splitItem.size()) { -// TimeSplitCompleteSplits(); -// } else { -// splitStatus[splitID + 1] = 2; -// } -// -//} - nlohmann::json ImVec4_to_json(const ImVec4& vec) { return nlohmann::json{ {"x", vec.x}, @@ -307,7 +268,7 @@ nlohmann::json SplitObject_to_json(const SplitObject& split) { {"splitTimeBest", split.splitTimeBest}, {"splitTimePreviousBest", split.splitTimePreviousBest}, {"splitTimeStatus", split.splitTimeStatus}, - {"splitSkullTokencount", split.splitSkullTokenCount} + {"splitSkullTokenCount", split.splitSkullTokenCount} }; } @@ -326,6 +287,53 @@ SplitObject json_to_SplitObject(const nlohmann::json& jsonSplit) { return split; } +void TimeSplitsUpdateSplitStatus() { + uint32_t index = 0; + for (auto& data : splitList) { + if (data.splitTimeStatus == SPLIT_INACTIVE || data.splitTimeStatus == SPLIT_ACTIVE) { + data.splitTimeStatus = SPLIT_ACTIVE; + break; + } + index++; + } + for (int i = index; i < splitList.size(); i++) { + if (splitList[i].splitTimeStatus != SPLIT_ACTIVE && splitList[i].splitTimeStatus != SPLIT_COLLECTED) { + splitList[i].splitTimeStatus = SPLIT_INACTIVE; + } + } +} + +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 (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.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.gameComplete = true; +} + +void TimeSplitsSkipSplit(uint32_t index) { + splitList[index].splitTimeStatus = SPLIT_SKIPPED; + if (index + 1 == splitList.size()) { + TimeSplitCompleteSplits(); + } else { + TimeSplitsUpdateSplitStatus(); + } +} + void TimeSplitsFileManagement(uint32_t action, const char* listEntry, std::vector listData) { std::string filename = "timesplitdata.json"; json saveFile; @@ -382,120 +390,110 @@ void TimeSplitsFileManagement(uint32_t action, const char* listEntry, std::vecto } void TimeSplitsPopUpContext() { - int rowIndex = 0; - if (ImGui::BeginPopup("TimeSplitsPopUp") && popupID) { - 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; - } - - SplitObject& popupObject = *findID; - if (popupObject.splitID == ITEM_SKULL_TOKEN) { - - } - if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(popupObject.splitImage), - ImVec2(26.0f, 26.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), popupObject.splitTint)) { - splitList.push_back(popupObject); - if (splitList.size() == 1) { - splitList[0].splitTimeStatus = SPLIT_ACTIVE; - } else { - splitList[splitList.size() - 1].splitTimeStatus = SPLIT_INACTIVE; - } + if ((popupID != -1) && ImGui::BeginPopup("TimeSplitsPopUp")) { + if (popupID == ITEM_SKULL_TOKEN) { + ImGui::BeginTable("Token Table", 2); + ImGui::TableNextColumn(); + ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("QUEST_SKULL_TOKEN"), + ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0)); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(150.0f); + ImGui::SliderInt("##count", &skullTokenCount, 0, 100, "%d Tokens"); + 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; } - if (rowIndex != 5) { - ImGui::SameLine(); - } - rowIndex++; - } - ImGui::EndPopup(); - } -} + ImGui::EndTable(); + } else { + int rowIndex = 0; + 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; + } -void TimeSplitsItemSplitEvent(u8 item) { - uint32_t index = 0; - 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 == gSaveContext.inventory.gsTokens + 1) { - return split.splitID == item; - } - }); - if (it == splitList.end()) { - return; - } - } - - for (auto& split : splitList) { - if (split.splitType <= SPLIT_QUEST) { - if (item == split.splitID) { - if (split.splitTimeStatus == SPLIT_ACTIVE) { - split.splitTimeCurrent = GAMEPLAYSTAT_TOTAL_TIME; - split.splitTimeStatus = SPLIT_COLLECTED; - if (split.splitTimeBest > GAMEPLAYSTAT_TOTAL_TIME || split.splitTimeBest == 0) { - split.splitTimeBest = GAMEPLAYSTAT_TOTAL_TIME; - } - if (split.splitTimePreviousBest == 0) { - split.splitTimePreviousBest = GAMEPLAYSTAT_TOTAL_TIME; - } - if (index == splitList.size() - 1) { - // Completed List - TimeSplitCompleteSplits(); + SplitObject& popupObject = *findID; + ImGui::BeginGroup(); + ImGui::PushID(popupObject.splitID); + if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(popupObject.splitImage), + ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2, ImVec4(0, 0, 0, 0), popupObject.splitTint)) { + splitList.push_back(popupObject); + if (splitList.size() == 1) { + splitList[0].splitTimeStatus = SPLIT_ACTIVE; } else { - splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE; + splitList[splitList.size() - 1].splitTimeStatus = SPLIT_INACTIVE; } + ImGui::CloseCurrentPopup(); + popupID = -1; } + ImGui::PopID(); + + if (popupObject.splitType == SPLIT_UPGRADE) { + if (popupID <= ITEM_SLINGSHOT && popupID != -1) { + ImVec2 imageMin = ImGui::GetItemRectMin(); + ImVec2 imageMax = ImGui::GetItemRectMax(); + ImVec2 imageSize = ImVec2(imageMax.x - imageMin.x, imageMax.y - imageMin.y); + 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(upgOutput.c_str()); + } + } + ImGui::EndGroup(); + if (rowIndex != 5) { + ImGui::SameLine(); + } + rowIndex++; } } - index++; + ImGui::EndPopup(); } } -void TimeSplitsBossSplitEvent(Actor* actor) { +void TimeSplitsItemSplitEvent(uint32_t type, u8 item) { uint32_t index = 0; - for (auto& split : splitList) { - if (split.splitType == SPLIT_BOSS) { - if (actor->id == split.splitID) { - if (split.splitTimeStatus == SPLIT_ACTIVE) { - split.splitTimeCurrent = GAMEPLAYSTAT_TOTAL_TIME; - split.splitTimeStatus = SPLIT_COLLECTED; - if (split.splitTimeBest > GAMEPLAYSTAT_TOTAL_TIME || split.splitTimeBest == 0) { - split.splitTimeBest = GAMEPLAYSTAT_TOTAL_TIME; - } - if (split.splitTimePreviousBest == 0) { - split.splitTimePreviousBest = GAMEPLAYSTAT_TOTAL_TIME; - } - if (index == splitList.size() - 1) { - // Completed List - TimeSplitCompleteSplits(); - } else { - splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE; - } + if (type <= SPLIT_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 == gSaveContext.inventory.gsTokens) { + return split.splitID == item; + } else { + return split.splitID == ITEM_NONE; } + }); + if (it == splitList.end()) { + return; } } - index++; } -} - -void TimeSplitsSceneSplitEvent(int16_t scene) { - uint32_t setType = SPLIT_ENTRANCE; - - if ((scene == SCENE_ZORAS_RIVER && gSaveContext.entranceIndex == ENTR_ZORAS_RIVER_4) || - (scene == SCENE_LOST_WOODS && (gSaveContext.entranceIndex == ENTR_LOST_WOODS_9 || - gSaveContext.entranceIndex == ENTR_LOST_WOODS_0)) || scene == SCENE_KAKARIKO_VILLAGE) { - setType = SPLIT_MISC; + if (type == SPLIT_ENTRANCE) { + if ((item == SCENE_ZORAS_RIVER && gSaveContext.entranceIndex == ENTR_ZORAS_RIVER_4) || + (item == SCENE_LOST_WOODS && + (gSaveContext.entranceIndex == ENTR_LOST_WOODS_9 || gSaveContext.entranceIndex == ENTR_LOST_WOODS_0))) { + type = SPLIT_MISC; + } } - - uint32_t index = 0; + for (auto& split : splitList) { - if (split.splitType == setType) { - if (scene == split.splitID) { + if (split.splitType == type) { + if (item == split.splitID) { if (split.splitTimeStatus == SPLIT_ACTIVE) { split.splitTimeCurrent = GAMEPLAYSTAT_TOTAL_TIME; split.splitTimeStatus = SPLIT_COLLECTED; @@ -506,7 +504,6 @@ void TimeSplitsSceneSplitEvent(int16_t scene) { split.splitTimePreviousBest = GAMEPLAYSTAT_TOTAL_TIME; } if (index == splitList.size() - 1) { - // Completed List TimeSplitCompleteSplits(); } else { splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE; @@ -519,6 +516,7 @@ void TimeSplitsSceneSplitEvent(int16_t scene) { } void TimeSplitsSplitBestTimeDisplay(uint32_t status, uint32_t currentBestTime, uint32_t previousBestTime) { + activeSplitHighlight = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); if (status == SPLIT_ACTIVE) { if (GAMEPLAYSTAT_TOTAL_TIME > previousBestTime) { splitTimeColor = COLOR_RED; @@ -532,6 +530,7 @@ void TimeSplitsSplitBestTimeDisplay(uint32_t status, uint32_t currentBestTime, u splitTimeColor = COLOR_GREEN; splitBestTimeDisplay = (previousBestTime - GAMEPLAYSTAT_TOTAL_TIME); } + activeSplitHighlight = COLOR_LIGHT_BLUE; } if (status == SPLIT_INACTIVE) { splitTimeColor = COLOR_WHITE; @@ -569,9 +568,13 @@ void TimeSplitsDrawSplitsList() { for (auto& split : splitList) { ImGui::TableNextColumn(); + TimeSplitsSplitBestTimeDisplay(split.splitTimeStatus, split.splitTimeBest, split.splitTimePreviousBest); + ImGui::PushStyleColor(ImGuiTableBgTarget_RowBg0, activeSplitHighlight); ImGui::PushID(split.splitID); - ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint); + if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), + ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint)) { + TimeSplitsSkipSplit(dragIndex); + } HandleDragAndDrop(splitList, dragIndex, split.splitName); ImGui::TableNextColumn(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); @@ -584,29 +587,31 @@ void TimeSplitsDrawSplitsList() { ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() : "--:--:-"); ImGui::TableNextColumn(); // +/- Difference - TimeSplitsSplitBestTimeDisplay(split.splitTimeStatus, split.splitTimeBest, split.splitTimePreviousBest); ImGui::TextColored(splitTimeColor, formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); ImGui::TableNextColumn(); // Previous Best ImGui::Text((split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); ImGui::PopID(); ImGui::PopStyleVar(1); + ImGui::PopStyleColor(1); dragIndex++; } if (dragTargetIndex != -1) { SplitObject tempSourceSplitObject = splitList[dragSourceIndex]; + if (tempSourceSplitObject.splitTimeStatus == SPLIT_ACTIVE) { + tempSourceSplitObject.splitTimeStatus = SPLIT_INACTIVE; + } + if (splitList[dragTargetIndex].splitTimeStatus == SPLIT_ACTIVE) { + splitList[dragTargetIndex].splitTimeStatus = SPLIT_INACTIVE; + } splitList.erase(splitList.begin() + dragSourceIndex); splitList.insert(splitList.begin() + dragTargetIndex, tempSourceSplitObject); dragTargetIndex = -1; dragSourceIndex = -1; - uint32_t firstInactiveID = findFirstInactiveSplit(splitList); - if (firstInactiveID != UINT32_MAX) { - splitList[firstInactiveID].splitTimeStatus = SPLIT_ACTIVE; - TimeSplitsUpdateStatus(firstInactiveID); - } + TimeSplitsUpdateSplitStatus(); } ImGui::PopStyleColor(3); @@ -751,16 +756,15 @@ void TimeSplitsDrawOptionsMenu() { ImGui::SameLine(); if (ImGui::Button("Load List")) { TimeSplitsFileManagement(ACTION_LOAD, keys[selectedItem].c_str(), emptyList); - lastLoadedlistName = keys[selectedItem].c_str(); } UIWidgets::PaddedSeparator(); if (ImGui::Button("Update Splits")) { - TimeSplitsFileManagement(ACTION_UPDATE, lastLoadedlistName, splitList); + TimeSplitsFileManagement(ACTION_UPDATE, keys[selectedItem].c_str(), splitList); } ImGui::SameLine(); if (ImGui::Button("Save List")) { - TimeSplitsFileManagement(ACTION_SAVE, lastLoadedlistName, splitList); + TimeSplitsFileManagement(ACTION_SAVE, keys[selectedItem].c_str(), splitList); } } @@ -837,16 +841,44 @@ void TimeSplitWindow::InitElement() { Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_SPLIT_ENTRANCE", gSplitEntranceTex, ImVec4(1, 1, 1, 1)); GameInteractor::Instance->RegisterGameHook([](u8 item) { - TimeSplitsItemSplitEvent(item); + if (item != ITEM_SKULL_TOKEN) { + uint32_t tempType = SPLIT_ITEM; + for (auto& data : splitList) { + if (data.splitID == item) { + tempType = data.splitType; + break; + } + } + TimeSplitsItemSplitEvent(tempType, item); + } + }); + + GameInteractor::Instance->RegisterGameHook([](GetItemEntry itemEntry) { + GetItemEntry testItem = itemEntry; + if (itemEntry.itemId == ITEM_SKULL_TOKEN || itemEntry.itemId == ITEM_BOTTLE || itemEntry.itemId == ITEM_POE) { + uint32_t tempType = SPLIT_ITEM; + for (auto& data : splitList) { + if (data.splitID == itemEntry.itemId) { + tempType = data.splitType; + break; + } + } + TimeSplitsItemSplitEvent(tempType, itemEntry.itemId); + } + }); + + GameInteractor::Instance->RegisterGameHook([](int16_t contents) { + TimeSplitsItemSplitEvent(SPLIT_UPGRADE, contents); }); GameInteractor::Instance->RegisterGameHook([](void* refActor) { - TimeSplitsBossSplitEvent((Actor*)refActor); + Actor* bossActor = (Actor*)refActor; + TimeSplitsItemSplitEvent(SPLIT_BOSS, bossActor->id); }); GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { if (gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { - TimeSplitsSceneSplitEvent(sceneNum); + TimeSplitsItemSplitEvent(SPLIT_ENTRANCE, sceneNum); } }); @@ -854,7 +886,7 @@ void TimeSplitWindow::InitElement() { if (gPlayState->sceneNum == SCENE_KAKARIKO_VILLAGE) { Player* player = GET_PLAYER(gPlayState); if (player->fallDistance > 500 && gSaveContext.health <= 0) { - TimeSplitsSceneSplitEvent(gPlayState->sceneNum); + TimeSplitsItemSplitEvent(SPLIT_MISC, gPlayState->sceneNum); } } }); diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.h b/soh/soh/Enhancements/timesplits/TimeSplits.h index fbde8adb1fd..3ea7ba17612 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.h +++ b/soh/soh/Enhancements/timesplits/TimeSplits.h @@ -36,7 +36,8 @@ typedef enum { typedef enum { SPLIT_ACTIVE, SPLIT_INACTIVE, - SPLIT_COLLECTED + SPLIT_COLLECTED, + SPLIT_SKIPPED }; typedef enum { diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 9bd60c9d6aa..f972699e0b3 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -14686,6 +14686,7 @@ void Player_Action_SwingBottle(Player* this, PlayState* play) { Message_StartTextbox(play, sBottleCatchInfo[this->av1.bottleCatchType - 1].textId, &this->actor); } Audio_PlayFanfare(NA_BGM_ITEM_GET | 0x900); + GameInteractor_ExecuteOnPlayerBottleUpdate((sBottleCatchInfo[this->av1.bottleCatchType - 1].itemId)); this->av2.startedTextbox = true; } else if (Message_GetState(&play->msgCtx) == TEXT_STATE_CLOSING) { this->av1.bottleCatchType = BOTTLE_CATCH_NONE; From 658bfcfc14c6eb1b628c3abea8b010a4603fdce4 Mon Sep 17 00:00:00 2001 From: Caladius Date: Sat, 19 Oct 2024 22:13:15 -0400 Subject: [PATCH 08/14] Fixing Time Displays --- .../Enhancements/timesplits/TimeSplits.cpp | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index 97b3358aaf4..e313dca07e3 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -267,7 +267,7 @@ nlohmann::json SplitObject_to_json(const SplitObject& split) { {"splitTimeCurrent", split.splitTimeCurrent}, {"splitTimeBest", split.splitTimeBest}, {"splitTimePreviousBest", split.splitTimePreviousBest}, - {"splitTimeStatus", split.splitTimeStatus}, + {"splitTimeStatus", SPLIT_INACTIVE}, {"splitSkullTokenCount", split.splitSkullTokenCount} }; } @@ -367,6 +367,7 @@ void TimeSplitsFileManagement(uint32_t action, const char* listEntry, std::vecto for (auto& data : listArray) { splitList.push_back(json_to_SplitObject(data)); } + splitList[0].splitTimeStatus = SPLIT_ACTIVE; } } @@ -515,38 +516,40 @@ void TimeSplitsItemSplitEvent(uint32_t type, u8 item) { } } -void TimeSplitsSplitBestTimeDisplay(uint32_t status, uint32_t currentBestTime, uint32_t previousBestTime) { +void TimeSplitsSplitBestTimeDisplay(SplitObject split) { activeSplitHighlight = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); - if (status == SPLIT_ACTIVE) { - if (GAMEPLAYSTAT_TOTAL_TIME > previousBestTime) { + if (split.splitTimeStatus == SPLIT_ACTIVE) { + if (GAMEPLAYSTAT_TOTAL_TIME > split.splitTimePreviousBest) { splitTimeColor = COLOR_RED; - splitBestTimeDisplay = (GAMEPLAYSTAT_TOTAL_TIME - previousBestTime); + splitBestTimeDisplay = (GAMEPLAYSTAT_TOTAL_TIME - split.splitTimePreviousBest); } - if (GAMEPLAYSTAT_TOTAL_TIME == previousBestTime) { + if (GAMEPLAYSTAT_TOTAL_TIME == split.splitTimePreviousBest) { splitTimeColor = COLOR_WHITE; splitBestTimeDisplay = GAMEPLAYSTAT_TOTAL_TIME; } - if (GAMEPLAYSTAT_TOTAL_TIME < previousBestTime) { + if (GAMEPLAYSTAT_TOTAL_TIME < split.splitTimePreviousBest) { splitTimeColor = COLOR_GREEN; - splitBestTimeDisplay = (previousBestTime - GAMEPLAYSTAT_TOTAL_TIME); + splitBestTimeDisplay = (split.splitTimePreviousBest - GAMEPLAYSTAT_TOTAL_TIME); } activeSplitHighlight = COLOR_LIGHT_BLUE; } - if (status == SPLIT_INACTIVE) { + if (split.splitTimeStatus == SPLIT_INACTIVE) { splitTimeColor = COLOR_WHITE; - splitBestTimeDisplay = currentBestTime; + splitBestTimeDisplay = split.splitTimeBest; } - if (status == SPLIT_COLLECTED) { - if (currentBestTime > previousBestTime) { + if (split.splitTimeStatus == SPLIT_COLLECTED) { + if (split.splitTimeCurrent > split.splitTimePreviousBest) { splitTimeColor = COLOR_RED; + splitBestTimeDisplay = (split.splitTimeCurrent - split.splitTimePreviousBest); } - if (currentBestTime == previousBestTime) { + if (split.splitTimeCurrent == split.splitTimePreviousBest) { splitTimeColor = COLOR_WHITE; + splitBestTimeDisplay = split.splitTimeCurrent; } - if (currentBestTime < previousBestTime) { + if (split.splitTimeCurrent < split.splitTimePreviousBest) { splitTimeColor = COLOR_GREEN; + splitBestTimeDisplay = (split.splitTimePreviousBest - split.splitTimeCurrent); } - splitBestTimeDisplay = currentBestTime; } } @@ -568,9 +571,12 @@ void TimeSplitsDrawSplitsList() { for (auto& split : splitList) { ImGui::TableNextColumn(); - TimeSplitsSplitBestTimeDisplay(split.splitTimeStatus, split.splitTimeBest, split.splitTimePreviousBest); - ImGui::PushStyleColor(ImGuiTableBgTarget_RowBg0, activeSplitHighlight); + TimeSplitsSplitBestTimeDisplay(split); + ImGui::PushID(split.splitID); + if (split.splitTimeStatus == SPLIT_ACTIVE) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); + } if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint)) { TimeSplitsSkipSplit(dragIndex); @@ -593,7 +599,7 @@ void TimeSplitsDrawSplitsList() { ImGui::Text((split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); ImGui::PopID(); ImGui::PopStyleVar(1); - ImGui::PopStyleColor(1); + dragIndex++; } @@ -721,11 +727,18 @@ void TimeSplitsDrawOptionsMenu() { CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 1.0f, 3.0f, "", 1.0f, false, false, true, false)) { TimeSplitsUpdateWindowSize(); } - //UIWidgets::PaddedEnhancementCheckbox("Set Last Split as Goal", CVAR_SETTING("TimesplitGoal")); ImGui::SeparatorText("Split List Management"); + if (ImGui::Button("New Attempt")) { + for (auto& data : splitList) { + data.splitTimeStatus = SPLIT_INACTIVE; + } + splitList[0].splitTimeStatus = SPLIT_ACTIVE; + } + UIWidgets::PaddedSeparator(); + ImGui::Text("New List Name: "); - ImGui::PushItemWidth(100.0f); + ImGui::PushItemWidth(150.0f); ImGui::InputText("##listName", listNameBuf, 25); ImGui::PopItemWidth(); ImGui::SameLine(); From 6f50efb8d2bebce4576f14ec98edd7b726a6fa08 Mon Sep 17 00:00:00 2001 From: Caladius Date: Sun, 20 Oct 2024 15:35:39 -0400 Subject: [PATCH 09/14] Adding Big Poe split --- soh/soh/Enhancements/timesplits/TimeSplits.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index e313dca07e3..e5d530ab7a8 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -868,7 +868,8 @@ void TimeSplitWindow::InitElement() { GameInteractor::Instance->RegisterGameHook([](GetItemEntry itemEntry) { GetItemEntry testItem = itemEntry; - if (itemEntry.itemId == ITEM_SKULL_TOKEN || itemEntry.itemId == ITEM_BOTTLE || itemEntry.itemId == ITEM_POE) { + if (itemEntry.itemId == ITEM_SKULL_TOKEN || itemEntry.itemId == ITEM_BOTTLE || itemEntry.itemId == ITEM_POE + || itemEntry.itemId == ITEM_BIG_POE) { uint32_t tempType = SPLIT_ITEM; for (auto& data : splitList) { if (data.splitID == itemEntry.itemId) { From ace4d91da48f956b3fc95e1e8469540915c7440e Mon Sep 17 00:00:00 2001 From: Caladius Date: Sun, 20 Oct 2024 16:03:48 -0400 Subject: [PATCH 10/14] Update Options Tab --- .../Enhancements/timesplits/TimeSplits.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index e5d530ab7a8..0c84d375ba1 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -729,13 +729,6 @@ void TimeSplitsDrawOptionsMenu() { } ImGui::SeparatorText("Split List Management"); - if (ImGui::Button("New Attempt")) { - for (auto& data : splitList) { - data.splitTimeStatus = SPLIT_INACTIVE; - } - splitList[0].splitTimeStatus = SPLIT_ACTIVE; - } - UIWidgets::PaddedSeparator(); ImGui::Text("New List Name: "); ImGui::PushItemWidth(150.0f); @@ -746,7 +739,7 @@ void TimeSplitsDrawOptionsMenu() { TimeSplitsFileManagement(ACTION_SAVE, listNameBuf, splitList); } UIWidgets::PaddedSeparator(); - + TimeSplitsFileManagement(ACTION_COLLECT, "", emptyList); static uint32_t selectedItem = 0; static std::string listItem = keys[0]; @@ -770,14 +763,21 @@ void TimeSplitsDrawOptionsMenu() { if (ImGui::Button("Load List")) { TimeSplitsFileManagement(ACTION_LOAD, keys[selectedItem].c_str(), emptyList); } + ImGui::SameLine(); + if (ImGui::Button("Save List")) { + TimeSplitsFileManagement(ACTION_SAVE, keys[selectedItem].c_str(), splitList); + } UIWidgets::PaddedSeparator(); - if (ImGui::Button("Update Splits")) { - TimeSplitsFileManagement(ACTION_UPDATE, keys[selectedItem].c_str(), splitList); + if (ImGui::Button("New Attempt")) { + for (auto& data : splitList) { + data.splitTimeStatus = SPLIT_INACTIVE; + } + splitList[0].splitTimeStatus = SPLIT_ACTIVE; } ImGui::SameLine(); - if (ImGui::Button("Save List")) { - TimeSplitsFileManagement(ACTION_SAVE, keys[selectedItem].c_str(), splitList); + if (ImGui::Button("Update Splits")) { + TimeSplitsFileManagement(ACTION_UPDATE, keys[selectedItem].c_str(), splitList); } } From 8ba2b442c2c03eb33d2c26a5bcb356211b8214d2 Mon Sep 17 00:00:00 2001 From: Caladius Date: Sun, 20 Oct 2024 19:15:46 -0400 Subject: [PATCH 11/14] List Preview and Removing Split Entries --- .../Enhancements/timesplits/TimeSplits.cpp | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index 0c84d375ba1..cf0d66602e2 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -37,6 +37,7 @@ using json = nlohmann::json; 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; @@ -463,6 +464,25 @@ void TimeSplitsPopUpContext() { } } +void TimeSplitsPostDragAndDrop() { + if (dragTargetIndex != -1) { + SplitObject tempSourceSplitObject = splitList[dragSourceIndex]; + if (tempSourceSplitObject.splitTimeStatus == SPLIT_ACTIVE) { + tempSourceSplitObject.splitTimeStatus = SPLIT_INACTIVE; + } + if (splitList[dragTargetIndex].splitTimeStatus == SPLIT_ACTIVE) { + splitList[dragTargetIndex].splitTimeStatus = SPLIT_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_QUEST) { @@ -599,26 +619,11 @@ void TimeSplitsDrawSplitsList() { ImGui::Text((split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); ImGui::PopID(); ImGui::PopStyleVar(1); - + dragIndex++; } - if (dragTargetIndex != -1) { - SplitObject tempSourceSplitObject = splitList[dragSourceIndex]; - if (tempSourceSplitObject.splitTimeStatus == SPLIT_ACTIVE) { - tempSourceSplitObject.splitTimeStatus = SPLIT_INACTIVE; - } - if (splitList[dragTargetIndex].splitTimeStatus == SPLIT_ACTIVE) { - splitList[dragTargetIndex].splitTimeStatus = SPLIT_INACTIVE; - } - - splitList.erase(splitList.begin() + dragSourceIndex); - splitList.insert(splitList.begin() + dragTargetIndex, tempSourceSplitObject); - dragTargetIndex = -1; - dragSourceIndex = -1; - - TimeSplitsUpdateSplitStatus(); - } + TimeSplitsPostDragAndDrop(); ImGui::PopStyleColor(3); ImGui::PopStyleVar(1); @@ -781,7 +786,51 @@ void TimeSplitsDrawOptionsMenu() { } } +void TimeSplitsRemoveSplitEntry(uint32_t index) { + if (removeIndex != -1) { + splitList.erase(splitList.begin() + index); + removeIndex = -1; + } +} + void TimeSplitsDrawManageList() { + uint32_t index = 0; + ImGui::BeginTable("List Management", 2, ImGuiTableFlags_BordersInnerV); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, 60.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")) { + 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 + } + + if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage), + ImVec2(38.0f, 38.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), data.splitTint)) { + 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_EQUIPMENT); @@ -791,7 +840,7 @@ void TimeSplitsDrawManageList() { TimeSplitsDrawItemList(SPLIT_ITEM); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Quest Items")) { + if (ImGui::BeginTabItem("Quest")) { TimeSplitsDrawItemList(SPLIT_QUEST); ImGui::EndTabItem(); } @@ -807,7 +856,11 @@ void TimeSplitsDrawManageList() { TimeSplitsDrawItemList(SPLIT_MISC); ImGui::EndTabItem(); } + + TimeSplitsPostDragAndDrop(); + ImGui::EndTabBar(); + ImGui::EndTable(); } void InitializeSplitDataFile() { From b869371aa4f5787ae27df40ac7129a906e411578 Mon Sep 17 00:00:00 2001 From: Caladius Date: Sun, 20 Oct 2024 19:59:41 -0400 Subject: [PATCH 12/14] Allow Split Table to resize --- .../Enhancements/timesplits/TimeSplits.cpp | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index cf0d66602e2..8f5c81752b8 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -580,9 +580,9 @@ void TimeSplitsDrawSplitsList() { ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable); ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); ImGui::TableSetupColumn("Item Name"); - ImGui::TableSetupColumn("Current Time", ImGuiTableColumnFlags_WidthFixed, 90.0f); - ImGui::TableSetupColumn("+/-", ImGuiTableColumnFlags_WidthFixed, 80.0f); - ImGui::TableSetupColumn("Prev. Best", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Current Time"); + ImGui::TableSetupColumn("+/-"); + ImGui::TableSetupColumn("Prev. Best"); ImGui::TableHeadersRow(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); @@ -880,24 +880,23 @@ void TimeSplitWindow::DrawElement() { InitializeSplitDataFile(); initialized = true; } - ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor); - 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(); + 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(); } - ImGui::EndTabBar(); - ImGui::PopStyleColor(1); - + ImGui::PopStyleColor(); } void TimeSplitWindow::InitElement() { From 6f8f07ca3da1bbefb3e8808a71fd1e91eff11578 Mon Sep 17 00:00:00 2001 From: Caladius Date: Sun, 20 Oct 2024 20:50:30 -0400 Subject: [PATCH 13/14] Fix Window Color and Adding Non Items to Split List --- .../Enhancements/timesplits/TimeSplits.cpp | 31 ++++++++++++++++--- soh/soh/Enhancements/timesplits/TimeSplits.h | 4 ++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index 8f5c81752b8..bfa39775467 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -389,6 +389,18 @@ void TimeSplitsFileManagement(uint32_t action, const char* listEntry, std::vecto keys.push_back("No Saved Lists"); } } + + if (action == 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() { @@ -674,7 +686,7 @@ void TimeSplitsDrawItemList(uint32_t type) { if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), ImVec2(38.0f, 38.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint)) { - if (popupList.contains(split.splitID)) { + if (popupList.contains(split.splitID) && (split.splitType < SPLIT_BOSS)) { popupID = split.splitID; ImGui::OpenPopup("TimeSplitsPopUp"); } else { @@ -729,7 +741,7 @@ void TimeSplitsDrawOptionsMenu() { } if (UIWidgets::PaddedEnhancementSliderFloat("Window Size: %.1fx", "##windowSize", - CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 1.0f, 3.0f, "", 1.0f, false, false, true, false)) { + CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 1.0f, 3.0f, "", 1.0f, false, true, true, false)) { TimeSplitsUpdateWindowSize(); } @@ -772,6 +784,10 @@ void TimeSplitsDrawOptionsMenu() { if (ImGui::Button("Save List")) { TimeSplitsFileManagement(ACTION_SAVE, keys[selectedItem].c_str(), splitList); } + ImGui::SameLine(); + if (ImGui::Button("Delete List")) { + TimeSplitsFileManagement(ACTION_DELETE, keys[selectedItem].c_str(), emptyList); + } UIWidgets::PaddedSeparator(); if (ImGui::Button("New Attempt")) { @@ -872,6 +888,12 @@ void InitializeSplitDataFile() { } } +void TimeSplitWindow::Draw() { + ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor); + GuiWindow::Draw(); + ImGui::PopStyleColor(); +} + static bool initialized = false; void TimeSplitWindow::DrawElement() { @@ -880,7 +902,8 @@ void TimeSplitWindow::DrawElement() { InitializeSplitDataFile(); initialized = true; } - ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor); + + if (ImGui::BeginTabBar("Split Tabs")) { if (ImGui::BeginTabItem("Splits")) { TimeSplitsDrawSplitsList(); @@ -896,7 +919,7 @@ void TimeSplitWindow::DrawElement() { } ImGui::EndTabBar(); } - ImGui::PopStyleColor(); + } void TimeSplitWindow::InitElement() { diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.h b/soh/soh/Enhancements/timesplits/TimeSplits.h index 3ea7ba17612..e0eba37db63 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.h +++ b/soh/soh/Enhancements/timesplits/TimeSplits.h @@ -22,6 +22,7 @@ class TimeSplitWindow : public Ship::GuiWindow { using GuiWindow::GuiWindow; void InitElement() override; + void Draw() override; void DrawElement() override; void UpdateElement() override{}; }; @@ -30,7 +31,8 @@ typedef enum { ACTION_SAVE, ACTION_LOAD, ACTION_UPDATE, - ACTION_COLLECT + ACTION_COLLECT, + ACTION_DELETE }; typedef enum { From 1d7ee9535eaf326ef2b1d1b2f1d218116a940a52 Mon Sep 17 00:00:00 2001 From: Caladius Date: Mon, 21 Oct 2024 18:56:44 -0400 Subject: [PATCH 14/14] Correct Song Image Skew --- .../Enhancements/timesplits/TimeSplits.cpp | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index bfa39775467..6f47ba3477c 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -47,6 +47,9 @@ 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); +static ImVec2 imageSize = ImVec2(38.0f, 38.0f); +static float imagePadding = 2.0f; + std::vector keys; char listNameBuf[25]; @@ -288,6 +291,16 @@ SplitObject json_to_SplitObject(const nlohmann::json& jsonSplit) { 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; + } +} + void TimeSplitsUpdateSplitStatus() { uint32_t index = 0; for (auto& data : splitList) { @@ -609,8 +622,9 @@ void TimeSplitsDrawSplitsList() { if (split.splitTimeStatus == SPLIT_ACTIVE) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); } + TimeSplitsGetImageSize(split.splitID); if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint)) { + imageSize, ImVec2(0, 0), ImVec2(1, 1), imagePadding, ImVec4(0, 0, 0, 0), split.splitTint)) { TimeSplitsSkipSplit(dragIndex); } HandleDragAndDrop(splitList, dragIndex, split.splitName); @@ -683,8 +697,9 @@ void TimeSplitsDrawItemList(uint32_t type) { if (split.splitType == type) { ImGui::TableNextColumn(); ImGui::PushID(split.splitID); + TimeSplitsGetImageSize(split.splitID); if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), - ImVec2(38.0f, 38.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), split.splitTint)) { + imageSize, ImVec2(0, 0), ImVec2(1, 1), imagePadding, ImVec4(0, 0, 0, 0), split.splitTint)) { if (popupList.contains(split.splitID) && (split.splitType < SPLIT_BOSS)) { popupID = split.splitID; @@ -831,9 +846,9 @@ void TimeSplitsDrawManageList() { if (offsetX > 0.0f) { ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); // Apply the offset to center } - + TimeSplitsGetImageSize(data.splitID); if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage), - ImVec2(38.0f, 38.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0), data.splitTint)) { + imageSize, ImVec2(0, 0), ImVec2(1, 1), imagePadding, ImVec4(0, 0, 0, 0), data.splitTint)) { removeIndex = index; } HandleDragAndDrop(splitList, index, splitList[index].splitName); @@ -902,7 +917,6 @@ void TimeSplitWindow::DrawElement() { InitializeSplitDataFile(); initialized = true; } - if (ImGui::BeginTabBar("Split Tabs")) { if (ImGui::BeginTabItem("Splits")) { @@ -919,7 +933,6 @@ void TimeSplitWindow::DrawElement() { } ImGui::EndTabBar(); } - } void TimeSplitWindow::InitElement() {