Built-in Time Splits [v2.1] (#4455)

* Adds 2 new Hooks, OnTimeStamp and OnPlayerHealthChange.

* Re-add Timesplit code

* Committing custom Assets

* Commit Asset definitions

* Adding Windows

* Fix Modal Window and others

* Time Splits v2 new Hooks etc.

* Fixing Time Displays

* Adding Big Poe split

* Update Options Tab

* List Preview and Removing Split Entries

* Allow Split Table to resize

* Fix Window Color and Adding Non Items to Split List

* Correct Song Image Skew
This commit is contained in:
Caladius 2024-10-22 13:17:30 -04:00 committed by GitHub
parent 8c304d54b2
commit 75a0979bc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1104 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -97,6 +97,12 @@ static const ALIGN_ASSET(2) char gArrowDownTex[] = dgArrowDown;
#define dgTriforcePiece "__OTR__textures/parameter_static/gTriforcePiece" #define dgTriforcePiece "__OTR__textures/parameter_static/gTriforcePiece"
static const ALIGN_ASSET(2) char gTriforcePieceTex[] = dgTriforcePiece; 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" #define dgBossSoul "__OTR__textures/parameter_static/gBossSoul"
static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul; static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul;

View File

@ -25,7 +25,10 @@ DEFINE_HOOK(OnActorUpdate, (void* actor));
DEFINE_HOOK(OnActorKill, (void* actor)); DEFINE_HOOK(OnActorKill, (void* actor));
DEFINE_HOOK(OnEnemyDefeat, (void* actor)); DEFINE_HOOK(OnEnemyDefeat, (void* actor));
DEFINE_HOOK(OnBossDefeat, (void* actor)); DEFINE_HOOK(OnBossDefeat, (void* actor));
DEFINE_HOOK(OnTimestamp, (u8 item));
DEFINE_HOOK(OnPlayerBonk, ()); DEFINE_HOOK(OnPlayerBonk, ());
DEFINE_HOOK(OnPlayerHealthChange, (int16_t amount));
DEFINE_HOOK(OnPlayerBottleUpdate, (int16_t contents));
DEFINE_HOOK(OnPlayDestroy, ()); DEFINE_HOOK(OnPlayDestroy, ());
DEFINE_HOOK(OnPlayDrawEnd, ()); DEFINE_HOOK(OnPlayDrawEnd, ());
DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, va_list originalArgs)); DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, va_list originalArgs));

View File

@ -107,10 +107,22 @@ void GameInteractor_ExecuteOnBossDefeat(void* actor) {
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnBossDefeat>(actor); GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnBossDefeat>(actor);
} }
void GameInteractor_ExecuteOnTimestamp (u8 item) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnTimestamp>(item);
}
void GameInteractor_ExecuteOnPlayerBonk() { void GameInteractor_ExecuteOnPlayerBonk() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerBonk>(); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerBonk>();
} }
void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerHealthChange>(amount);
}
void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerBottleUpdate>(contents);
}
void GameInteractor_ExecuteOnPlayDestroy() { void GameInteractor_ExecuteOnPlayDestroy() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayDestroy>(); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayDestroy>();
} }

View File

@ -24,7 +24,10 @@ void GameInteractor_ExecuteOnActorUpdate(void* actor);
void GameInteractor_ExecuteOnActorKill(void* actor); void GameInteractor_ExecuteOnActorKill(void* actor);
void GameInteractor_ExecuteOnEnemyDefeat(void* actor); void GameInteractor_ExecuteOnEnemyDefeat(void* actor);
void GameInteractor_ExecuteOnBossDefeat(void* actor); void GameInteractor_ExecuteOnBossDefeat(void* actor);
void GameInteractor_ExecuteOnTimestamp (u8 item);
void GameInteractor_ExecuteOnPlayerBonk(); void GameInteractor_ExecuteOnPlayerBonk();
void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount);
void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents);
void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnOcarinaSongAction();
void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price); void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price);
void GameInteractor_ExecuteOnPlayDestroy(); void GameInteractor_ExecuteOnPlayDestroy();

View File

@ -0,0 +1,995 @@
#include "TimeSplits.h"
#include "soh/UIWidgets.hpp"
#include "soh/Enhancements/gameplaystats.h"
#include "soh/SaveManager.h"
#include "soh/util.h"
#include <vector>
#include "include/z64item.h"
#include <fstream>
#include <filesystem>
#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 int32_t popupID = -1;
static int32_t removeIndex = -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);
static ImVec2 imageSize = ImVec2(38.0f, 38.0f);
static float imagePadding = 2.0f;
std::vector<std::string> keys;
char listNameBuf[25];
int dragSourceIndex = -1;
int dragTargetIndex = -1;
std::vector<SplitObject> splitList;
std::vector<SplitObject> emptyList;
std::vector<SplitObject> 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<uint32_t, std::vector<uint32_t>> popupList = {
{ ITEM_STICK, { ITEM_STICK, ITEM_STICK_UPGRADE_20, ITEM_STICK_UPGRADE_30 } },
{ ITEM_NUT, { ITEM_NUT, ITEM_NUT_UPGRADE_30, ITEM_NUT_UPGRADE_40 } },
{ ITEM_BOMB, { ITEM_BOMB_BAG_20, ITEM_BOMB_BAG_30, ITEM_BOMB_BAG_40 } },
{ ITEM_BOW, { ITEM_QUIVER_30, ITEM_QUIVER_40, ITEM_QUIVER_50 } },
{ ITEM_SLINGSHOT, { ITEM_BULLET_BAG_30, ITEM_BULLET_BAG_40, ITEM_BULLET_BAG_50 } },
{ ITEM_OCARINA_FAIRY, { ITEM_OCARINA_FAIRY, ITEM_OCARINA_TIME } },
{ ITEM_HOOKSHOT, { ITEM_HOOKSHOT, ITEM_LONGSHOT } },
{ ITEM_BOTTLE, { ITEM_BOTTLE, ITEM_POTION_RED, ITEM_POTION_GREEN, ITEM_POTION_BLUE,
ITEM_FAIRY, ITEM_FISH, ITEM_MILK_BOTTLE, ITEM_LETTER_RUTO,
ITEM_BLUE_FIRE, ITEM_BUG, ITEM_BIG_POE, ITEM_POE } },
{ ITEM_WEIRD_EGG, { ITEM_WEIRD_EGG, ITEM_CHICKEN, ITEM_LETTER_ZELDA, ITEM_MASK_KEATON,
ITEM_MASK_SKULL, ITEM_MASK_SPOOKY, ITEM_MASK_BUNNY, ITEM_MASK_GORON,
ITEM_MASK_ZORA, ITEM_MASK_GERUDO, ITEM_MASK_TRUTH } },
{ ITEM_POCKET_EGG, { ITEM_POCKET_EGG, ITEM_POCKET_CUCCO, ITEM_COJIRO, ITEM_ODD_MUSHROOM,
ITEM_ODD_POTION, ITEM_SAW, ITEM_SWORD_BROKEN, ITEM_PRESCRIPTION,
ITEM_FROG, ITEM_EYEDROPS, ITEM_CLAIM_CHECK } },
{ ITEM_BRACELET, { ITEM_BRACELET, ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_GOLD } },
{ ITEM_SCALE_SILVER, { ITEM_SCALE_SILVER, ITEM_SCALE_GOLDEN } },
{ ITEM_WALLET_ADULT, { ITEM_WALLET_ADULT, ITEM_WALLET_GIANT } },
{ ITEM_SINGLE_MAGIC, { ITEM_SINGLE_MAGIC, ITEM_DOUBLE_MAGIC } },
{ ITEM_SKULL_TOKEN, { } }
};
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<unsigned char>(ch))) {
result += ch;
}
}
return result;
}
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);
}
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_INACTIVE},
{"splitSkullTokenCount", split.splitSkullTokenCount}
};
}
SplitObject json_to_SplitObject(const nlohmann::json& jsonSplit) {
SplitObject split;
split.splitType = jsonSplit["splitType"];
split.splitID = jsonSplit["splitID"];
split.splitName = jsonSplit["splitName"].get<std::string>();
split.splitImage = jsonSplit["splitImage"].get<std::string>();
split.splitTint = json_to_ImVec4(jsonSplit["splitTint"]);
split.splitTimeCurrent = jsonSplit["splitTimeCurrent"];
split.splitTimeBest = jsonSplit["splitTimeBest"];
split.splitTimePreviousBest = jsonSplit["splitTimePreviousBest"];
split.splitTimeStatus = jsonSplit["splitTimeStatus"];
split.splitSkullTokenCount = jsonSplit["splitSkullTokenCount"];
return split;
}
void TimeSplitsGetImageSize(uint32_t item) {
if (item >= ITEM_SONG_MINUET && item <= ITEM_SONG_STORMS) {
imageSize = ImVec2(30.0f, 38.0f);
imagePadding = 6.0f;
} else {
imageSize = ImVec2(38.0f, 38.0f);
imagePadding = 2.0f;
}
}
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<SplitObject>& 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<SplitObject> 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));
}
splitList[0].splitTimeStatus = SPLIT_ACTIVE;
}
}
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");
}
}
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() {
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;
}
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;
}
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[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++;
}
}
ImGui::EndPopup();
}
}
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) {
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;
}
}
}
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;
}
}
for (auto& split : splitList) {
if (split.splitType == type) {
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) {
TimeSplitCompleteSplits();
} else {
splitList[index + 1].splitTimeStatus = SPLIT_ACTIVE;
}
}
}
}
index++;
}
}
void TimeSplitsSplitBestTimeDisplay(SplitObject split) {
activeSplitHighlight = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
if (split.splitTimeStatus == SPLIT_ACTIVE) {
if (GAMEPLAYSTAT_TOTAL_TIME > split.splitTimePreviousBest) {
splitTimeColor = COLOR_RED;
splitBestTimeDisplay = (GAMEPLAYSTAT_TOTAL_TIME - split.splitTimePreviousBest);
}
if (GAMEPLAYSTAT_TOTAL_TIME == split.splitTimePreviousBest) {
splitTimeColor = COLOR_WHITE;
splitBestTimeDisplay = GAMEPLAYSTAT_TOTAL_TIME;
}
if (GAMEPLAYSTAT_TOTAL_TIME < split.splitTimePreviousBest) {
splitTimeColor = COLOR_GREEN;
splitBestTimeDisplay = (split.splitTimePreviousBest - GAMEPLAYSTAT_TOTAL_TIME);
}
activeSplitHighlight = COLOR_LIGHT_BLUE;
}
if (split.splitTimeStatus == SPLIT_INACTIVE) {
splitTimeColor = COLOR_WHITE;
splitBestTimeDisplay = split.splitTimeBest;
}
if (split.splitTimeStatus == SPLIT_COLLECTED) {
if (split.splitTimeCurrent > split.splitTimePreviousBest) {
splitTimeColor = COLOR_RED;
splitBestTimeDisplay = (split.splitTimeCurrent - split.splitTimePreviousBest);
}
if (split.splitTimeCurrent == split.splitTimePreviousBest) {
splitTimeColor = COLOR_WHITE;
splitBestTimeDisplay = split.splitTimeCurrent;
}
if (split.splitTimeCurrent < split.splitTimePreviousBest) {
splitTimeColor = COLOR_GREEN;
splitBestTimeDisplay = (split.splitTimePreviousBest - split.splitTimeCurrent);
}
}
}
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");
ImGui::TableSetupColumn("+/-");
ImGui::TableSetupColumn("Prev. Best");
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();
TimeSplitsSplitBestTimeDisplay(split);
ImGui::PushID(split.splitID);
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),
imageSize, ImVec2(0, 0), ImVec2(1, 1), imagePadding, 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));
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
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++;
}
TimeSplitsPostDragAndDrop();
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);
TimeSplitsGetImageSize(split.splitID);
if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage),
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;
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, true, true, false)) {
TimeSplitsUpdateWindowSize();
}
ImGui::SeparatorText("Split List Management");
ImGui::Text("New List Name: ");
ImGui::PushItemWidth(150.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);
}
ImGui::SameLine();
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")) {
for (auto& data : splitList) {
data.splitTimeStatus = SPLIT_INACTIVE;
}
splitList[0].splitTimeStatus = SPLIT_ACTIVE;
}
ImGui::SameLine();
if (ImGui::Button("Update Splits")) {
TimeSplitsFileManagement(ACTION_UPDATE, keys[selectedItem].c_str(), splitList);
}
}
void TimeSplitsRemoveSplitEntry(uint32_t index) {
if (removeIndex != -1) {
splitList.erase(splitList.begin() + index);
removeIndex = -1;
}
}
void TimeSplitsDrawManageList() {
uint32_t index = 0;
ImGui::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
}
TimeSplitsGetImageSize(data.splitID);
if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage),
imageSize, ImVec2(0, 0), ImVec2(1, 1), imagePadding, 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);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Inventory")) {
TimeSplitsDrawItemList(SPLIT_ITEM);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Quest")) {
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();
}
TimeSplitsPostDragAndDrop();
ImGui::EndTabBar();
ImGui::EndTable();
}
void InitializeSplitDataFile() {
if (!std::filesystem::exists("timesplitdata.json")) {
json j;
std::ofstream file("timesplitdata.json");
file << j.dump(4);
file.close();
}
}
void TimeSplitWindow::Draw() {
ImGui::PushStyleColor(ImGuiCol_WindowBg, windowColor);
GuiWindow::Draw();
ImGui::PopStyleColor();
}
static bool initialized = false;
void TimeSplitWindow::DrawElement() {
ImGui::SetWindowFontScale(timeSplitsWindowSize);
if (!initialized) {
InitializeSplitDataFile();
initialized = true;
}
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();
}
}
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<GameInteractor::OnTimestamp>([](u8 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<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) {
GetItemEntry testItem = itemEntry;
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) {
tempType = data.splitType;
break;
}
}
TimeSplitsItemSplitEvent(tempType, itemEntry.itemId);
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerBottleUpdate>([](int16_t contents) {
TimeSplitsItemSplitEvent(SPLIT_UPGRADE, contents);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnBossDefeat>([](void* refActor) {
Actor* bossActor = (Actor*)refActor;
TimeSplitsItemSplitEvent(SPLIT_BOSS, bossActor->id);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) {
if (gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) {
TimeSplitsItemSplitEvent(SPLIT_ENTRANCE, sceneNum);
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerHealthChange>([](int16_t amount) {
if (gPlayState->sceneNum == SCENE_KAKARIKO_VILLAGE) {
Player* player = GET_PLAYER(gPlayState);
if (player->fallDistance > 500 && gSaveContext.health <= 0) {
TimeSplitsItemSplitEvent(SPLIT_MISC, gPlayState->sceneNum);
}
}
});
}

View File

@ -0,0 +1,68 @@
#pragma once
#ifndef TIMESPLITS_H
#define TIMESPLITS_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif // TIMESPLITS_H
#include <libultraship/libultraship.h>
#ifdef __cplusplus
class TimeSplitWindow : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void InitElement() override;
void Draw() override;
void DrawElement() override;
void UpdateElement() override{};
};
typedef enum {
ACTION_SAVE,
ACTION_LOAD,
ACTION_UPDATE,
ACTION_COLLECT,
ACTION_DELETE
};
typedef enum {
SPLIT_ACTIVE,
SPLIT_INACTIVE,
SPLIT_COLLECTED,
SPLIT_SKIPPED
};
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

View File

@ -130,6 +130,7 @@ namespace SohGui {
std::shared_ptr<EntranceTrackerWindow> mEntranceTrackerWindow; std::shared_ptr<EntranceTrackerWindow> mEntranceTrackerWindow;
std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow; std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow;
std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow; std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
std::shared_ptr<TimeSplitWindow> mTimeSplitWindow;
std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow; std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow; std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
std::shared_ptr<SohModalWindow> mModalWindow; std::shared_ptr<SohModalWindow> mModalWindow;
@ -207,6 +208,8 @@ namespace SohGui {
gui->AddGuiWindow(mItemTrackerSettingsWindow); gui->AddGuiWindow(mItemTrackerSettingsWindow);
mRandomizerSettingsWindow = std::make_shared<RandomizerSettingsWindow>(CVAR_WINDOW("RandomizerSettings"), "Randomizer Settings", ImVec2(920, 600)); mRandomizerSettingsWindow = std::make_shared<RandomizerSettingsWindow>(CVAR_WINDOW("RandomizerSettings"), "Randomizer Settings", ImVec2(920, 600));
gui->AddGuiWindow(mRandomizerSettingsWindow); gui->AddGuiWindow(mRandomizerSettingsWindow);
mTimeSplitWindow = std::make_shared<TimeSplitWindow>(CVAR_WINDOW("TimeSplitEnabled"), "Time Splits", ImVec2(450, 660));
gui->AddGuiWindow(mTimeSplitWindow);
mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>(CVAR_WINDOW("AdvancedResolutionEditor"), "Advanced Resolution Settings", ImVec2(497, 599)); mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>(CVAR_WINDOW("AdvancedResolutionEditor"), "Advanced Resolution Settings", ImVec2(497, 599));
gui->AddGuiWindow(mAdvancedResolutionSettingsWindow); gui->AddGuiWindow(mAdvancedResolutionSettingsWindow);
mModalWindow = std::make_shared<SohModalWindow>(CVAR_WINDOW("ModalWindow"), "Modal Window"); mModalWindow = std::make_shared<SohModalWindow>(CVAR_WINDOW("ModalWindow"), "Modal Window");
@ -248,6 +251,7 @@ namespace SohGui {
mSohMenuBar = nullptr; mSohMenuBar = nullptr;
mInputViewer = nullptr; mInputViewer = nullptr;
mInputViewerSettings = nullptr; mInputViewerSettings = nullptr;
mTimeSplitWindow = nullptr;
} }
void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function<void()> button1callback, std::function<void()> button2callback) { void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function<void()> button1callback, std::function<void()> button2callback) {

View File

@ -24,6 +24,7 @@
#include "Enhancements/randomizer/randomizer_entrance_tracker.h" #include "Enhancements/randomizer/randomizer_entrance_tracker.h"
#include "Enhancements/randomizer/randomizer_item_tracker.h" #include "Enhancements/randomizer/randomizer_item_tracker.h"
#include "Enhancements/randomizer/randomizer_settings_window.h" #include "Enhancements/randomizer/randomizer_settings_window.h"
#include "Enhancements/timesplits/TimeSplits.h"
#include "SohModals.h" #include "SohModals.h"
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -40,6 +40,7 @@
#include "Enhancements/randomizer/randomizer_settings_window.h" #include "Enhancements/randomizer/randomizer_settings_window.h"
#include "Enhancements/resolution-editor/ResolutionEditor.h" #include "Enhancements/resolution-editor/ResolutionEditor.h"
#include "Enhancements/enemyrandomizer.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 // 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 // they don't work how I expect them to so I added that because it looked good when I eyeballed it
@ -601,6 +602,7 @@ void DrawSettingsMenu() {
extern std::shared_ptr<AudioEditor> mAudioEditorWindow; extern std::shared_ptr<AudioEditor> mAudioEditorWindow;
extern std::shared_ptr<CosmeticsEditorWindow> mCosmeticsEditorWindow; extern std::shared_ptr<CosmeticsEditorWindow> mCosmeticsEditorWindow;
extern std::shared_ptr<GameplayStatsWindow> mGameplayStatsWindow; extern std::shared_ptr<GameplayStatsWindow> mGameplayStatsWindow;
extern std::shared_ptr<TimeSplitWindow> mTimeSplitWindow;
void DrawEnhancementsMenu() { void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Enhancements")) if (ImGui::BeginMenu("Enhancements"))
@ -1680,6 +1682,12 @@ void DrawEnhancementsMenu() {
mGameplayStatsWindow->ToggleVisibility(); 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::PopStyleVar(3);
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);

View File

@ -1844,6 +1844,7 @@ void GameplayStats_SetTimestamp(PlayState* play, u8 item) {
} }
gSaveContext.sohStats.itemTimestamp[item] = time; gSaveContext.sohStats.itemTimestamp[item] = time;
GameInteractor_ExecuteOnTimestamp(item);
} }
// Gameplay stat tracking: Update time the item was acquired // Gameplay stat tracking: Update time the item was acquired
@ -3321,6 +3322,8 @@ s32 Health_ChangeBy(PlayState* play, s16 healthChange) {
// "Life=%d %d " // "Life=%d %d "
osSyncPrintf(" ライフ=%d %d \n", gSaveContext.health, healthLevel); osSyncPrintf(" ライフ=%d %d \n", gSaveContext.health, healthLevel);
GameInteractor_ExecuteOnPlayerHealthChange(healthChange);
if (gSaveContext.health <= 0) { if (gSaveContext.health <= 0) {
gSaveContext.health = 0; gSaveContext.health = 0;
return 0; return 0;

View File

@ -14686,6 +14686,7 @@ void Player_Action_SwingBottle(Player* this, PlayState* play) {
Message_StartTextbox(play, sBottleCatchInfo[this->av1.bottleCatchType - 1].textId, &this->actor); Message_StartTextbox(play, sBottleCatchInfo[this->av1.bottleCatchType - 1].textId, &this->actor);
} }
Audio_PlayFanfare(NA_BGM_ITEM_GET | 0x900); Audio_PlayFanfare(NA_BGM_ITEM_GET | 0x900);
GameInteractor_ExecuteOnPlayerBottleUpdate((sBottleCatchInfo[this->av1.bottleCatchType - 1].itemId));
this->av2.startedTextbox = true; this->av2.startedTextbox = true;
} else if (Message_GetState(&play->msgCtx) == TEXT_STATE_CLOSING) { } else if (Message_GetState(&play->msgCtx) == TEXT_STATE_CLOSING) {
this->av1.bottleCatchType = BOTTLE_CATCH_NONE; this->av1.bottleCatchType = BOTTLE_CATCH_NONE;