From ff1d8a9e9d27de17b0718ef31f2ad6ac0fab3874 Mon Sep 17 00:00:00 2001 From: Ralphie Morell Date: Mon, 3 Apr 2023 00:06:55 -0400 Subject: [PATCH] Enhancement: Room/Scene Timers (#2478) * Groundwork on scene/room timers; naming changes * added to save manager; reworked storing timestamps * actually saved stuff to savemanager; accounted for null playstate * finally fixed the fucking timers * Added scene mapping * Added CVar for room/scene level; fixed some displays * reworked logic * increase name spec for scene timestamps * Actually save item timestamps when loading v3 save * Cleanup * fix merge artifact * apply suggestions --- soh/include/z64save.h | 16 +- soh/soh/Enhancements/gameplaystats.cpp | 756 +++++++++++------- soh/soh/Enhancements/gameplaystats.h | 3 + soh/soh/SaveManager.cpp | 45 +- soh/src/code/z_parameter.c | 28 +- soh/src/code/z_play.c | 29 + soh/src/code/z_room.c | 12 +- .../actors/ovl_Boss_Dodongo/z_boss_dodongo.c | 2 +- .../overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c | 2 +- .../actors/ovl_Boss_Ganon/z_boss_ganon.c | 2 +- .../actors/ovl_Boss_Ganon2/z_boss_ganon2.c | 2 +- .../ovl_Boss_Ganondrof/z_boss_ganondrof.c | 2 +- .../actors/ovl_Boss_Goma/z_boss_goma.c | 2 +- .../overlays/actors/ovl_Boss_Mo/z_boss_mo.c | 2 +- .../overlays/actors/ovl_Boss_Sst/z_boss_sst.c | 2 +- .../overlays/actors/ovl_Boss_Tw/z_boss_tw.c | 2 +- .../overlays/actors/ovl_Boss_Va/z_boss_va.c | 2 +- 17 files changed, 591 insertions(+), 318 deletions(-) diff --git a/soh/include/z64save.h b/soh/include/z64save.h index cb0d03428..0a3352e1c 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -53,6 +53,14 @@ typedef struct { /* 0x5C */ s16 gsTokens; } Inventory; // size = 0x5E +typedef struct { + u16 scene; + u8 room; + u32 sceneTime; + u32 roomTime; + u8 isRoom; +} SceneTimestamp; + typedef struct { /* */ char buildVersion[50]; /* */ s16 buildVersionMajor; @@ -63,8 +71,14 @@ typedef struct { /* */ u8 dungeonKeys[19]; /* */ u32 playTimer; /* */ u32 pauseTimer; + /* */ u32 sceneTimer; + /* */ u32 roomTimer; + /* */ s16 sceneNum; + /* */ s8 roomNum; /* */ bool gameComplete; - /* */ u32 timestamp[TIMESTAMP_MAX]; + /* */ u32 itemTimestamp[TIMESTAMP_MAX]; + /* */ SceneTimestamp sceneTimestamps[8191]; + /* */ u32 tsIdx; /* */ u32 count[COUNT_MAX]; /* */ u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT]; /* */ u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT]; diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index e35a06ddc..50583b554 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -3,7 +3,7 @@ #include "ImGuiImpl.h" #include "../UIWidgets.hpp" -#include +#include #include #include #include @@ -11,8 +11,123 @@ extern "C" { #include #include "variables.h" +extern PlayState* gPlayState; } +const std::vector sceneMappings = { + {"Inside the Deku Tree"}, + {"Dodongo's Cavern"}, + {"Inside Jabu-Jabu's Belly"}, + {"Forest Temple"}, + {"Fire Temple"}, + {"Water Temple"}, + {"Spirit Temple"}, + {"Shadow Temple"}, + {"Bottom of the Well"}, + {"Ice Cavern"}, + {"Ganon's Tower"}, + {"Gerudo Training Ground"}, + {"Theives' Hideout"}, + {"Inside Ganon's Castle"}, + {"Tower Collapse"}, + {"Castle Collapse"}, + {"Treasure Box Shop"}, + {"Gohma's Lair"}, + {"King Dodongo's Lair"}, + {"Barinade's Lair"}, + {"Phantom Ganon's Lair"}, + {"Volvagia's Lair"}, + {"Morpha's Lair"}, + {"Twinrova's Lair"}, + {"Bongo Bongo's Lair"}, + {"Ganondorf's Lair"}, + {"Ganon's Lair"}, + {"Market Entrance (Day)"}, + {"Market Entrance (Night)"}, + {"Market Entrance (Adult)"}, + {"Back Alley (Day)"}, + {"Back Alley (Night)"}, + {"Market (Day)"}, + {"Market (Night)"}, + {"Market (Adult)"}, + {"Outside ToT (Day)"}, + {"Outside ToT (Night)"}, + {"Outside ToT (Adult)"}, + {"Know-It-All Bros' House"}, + {"Twins' House"}, + {"Mido's House"}, + {"Saria's House"}, + {"Carpenter Boss's House"}, + {"Man in Green's House"}, + {"Bazaar"}, + {"Kokiri Shop"}, + {"Goron Shop"}, + {"Zora Shop"}, + {"Kakariko Potion Shop"}, + {"Market Potion Shop"}, + {"Bombchu Shop"}, + {"Happy Mask Shop"}, + {"Link's House"}, + {"Richard's House"}, + {"Stable"}, + {"Impa's House"}, + {"Lakeside Lab"}, + {"Carpenters' Tent"}, + {"Gravekeeper's Hut"}, + {"Great Fairy"}, + {"Fairy Fountain"}, + {"Great Fairy"}, + {"Grotto"}, + {"Redead Grave"}, + {"Fairy Fountain Grave"}, + {"Royal Family's Tomb"}, + {"Shooting Gallery"}, + {"Temple of Time"}, + {"Chamber of Sages"}, + {"Castle Maze (Day)"}, + {"Castle Maze (Night)"}, + {"Cutscene Map"}, + {"Dampe's Grave"}, + {"Fishing Pond"}, + {"Castle Courtyard"}, + {"Bombchu Bowling Alley"}, + {"Ranch House"}, + {"Guard House"}, + {"Granny's Potion Shop"}, + {"Ganon Fight"}, + {"House of Skulltula"}, + {"Hyrule Field"}, + {"Kakariko Village"}, + {"Graveyard"}, + {"Zora's River"}, + {"Kokiri Forest"}, + {"Sacred Forest Meadow"}, + {"Lake Hylia"}, + {"Zora's Domain"}, + {"Zora's Fountain"}, + {"Gerudo Valley"}, + {"Lost Woods"}, + {"Desert Colossus"}, + {"Gerudo's Fortress"}, + {"Haunted Wasteland"}, + {"Hyrule Castle"}, + {"Death Mountain Trail"}, + {"Death Mountain Crater"}, + {"Goron City"}, + {"Lon Lon Ranch"}, + {"Outside Ganon's Castle"}, + //Debug Rooms + {"Test Map"}, + {"Test Room"}, + {"Depth Test"}, + {"Stalfos Mini-Boss"}, + {"Stalfos Boss"}, + {"Dark Link"}, + {"Castle Maze (Broken)"}, + {"SRD Room"}, + {"Chest Room"} +}; + #define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f) #define COLOR_RED ImVec4(1.00f, 0.00f, 0.00f, 1.00f) #define COLOR_GREEN ImVec4(0.10f, 1.00f, 0.10f, 1.00f) @@ -23,21 +138,24 @@ extern "C" { #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) -char timestampDisplayName[TIMESTAMP_MAX][21] = { "" }; -ImVec4 timestampDisplayColor[TIMESTAMP_MAX]; +char itemTimestampDisplayName[TIMESTAMP_MAX][21] = { "" }; +ImVec4 itemTimestampDisplayColor[TIMESTAMP_MAX]; typedef struct { - char name[21]; + char name[40]; u32 time; ImVec4 color; + bool isRoom; }TimestampInfo; // Timestamps are an array of structs, each with a name, time, and color // Names and colors are set up at the bottom of this file -// Times are stored in gSaveContext.sohStats.timestamp -TimestampInfo timestampDisplay[TIMESTAMP_MAX]; +// Times are stored in gSaveContext.sohStats.itemTimestamp +TimestampInfo itemTimestampDisplay[TIMESTAMP_MAX]; +TimestampInfo sceneTimestampDisplay[8191]; +//std::vector sceneTimestampDisplay; -void DisplayTimeHHMMSS(uint32_t timeInTenthsOfSeconds, const char* text, ImVec4 color) { +void DisplayTimeHHMMSS(uint32_t timeInTenthsOfSeconds, std::string text, ImVec4 color) { uint32_t sec = timeInTenthsOfSeconds / 10; uint32_t hh = sec / 3600; @@ -47,7 +165,8 @@ void DisplayTimeHHMMSS(uint32_t timeInTenthsOfSeconds, const char* text, ImVec4 ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::Text(text); + std::string padded = fmt::format("{:<40}", text); + ImGui::Text(padded.c_str()); ImGui::SameLine(); ImGui::Text("%2u:%02u:%02u.%u", hh, mm, ss, ds); ImGui::PopStyleColor(); @@ -80,6 +199,64 @@ void DisplayStatIfNonZero(const char* text, uint32_t value) { return; } +std::string ResolveSceneID(int sceneID, int roomID){ + std::string scene = ""; + if (sceneID == SCENE_KAKUSIANA) { + switch (roomID) { + case 0: + scene = "Generic Grotto"; + break; + case 1: + scene = "Lake Hylia Scrub Grotto"; + break; + case 2: + scene = "Redead Grotto"; + break; + case 3: + scene = "Cow Grotto"; + break; + case 4: + scene = "Scrub Trio"; + break; + case 5: + scene = "Flooded Grotto"; + break; + case 6: + scene = "Scrub Duo (Upgrade)"; + break; + case 7: + scene = "Wolfos Grotto"; + break; + case 8: + scene = "Hyrule Castle Storms Grotto"; + break; + case 9: + scene = "Scrub Duo"; + break; + case 10: + scene = "Tektite Grotto"; + break; + case 11: + scene = "Forest Stage"; + break; + case 12: + scene = "Webbed Grotto"; + break; + case 13: + scene = "Big Skulltula Grotto"; + break; + default: + scene = "???"; + }; + } else if (sceneID == SCENE_HAKASITARELAY) { + //Only the last room of Dampe's Grave (rm 6) is considered the windmill + scene = roomID == 6 ? "Windmill" : "Dampe's Grave"; + } else { + scene = sceneMappings[sceneID]; + } + return scene; +} + void DrawStatsTracker(bool& open) { if (!open) { CVarSetInteger("gGameplayStatsEnabled", 0); @@ -91,10 +268,6 @@ void DrawStatsTracker(bool& open) { ImGui::End(); return; } - - bool showTimestamps = (CVarGetInteger("gGameplayStatsMode", 0) <= 1); - bool showCounts = ( (CVarGetInteger("gGameplayStatsMode", 0) == 0) || (CVarGetInteger("gGameplayStatsMode", 0) == 2) ); - u32 totalTimer = GAMEPLAYSTAT_TOTAL_TIME; u32 enemiesDefeated = 0; u32 ammoUsed = 0; @@ -117,13 +290,29 @@ void DrawStatsTracker(bool& open) { for (int i = COUNT_BUTTON_PRESSES_A; i <= COUNT_BUTTON_PRESSES_START; i++) { buttonPresses += gSaveContext.sohStats.count[i]; } - // Set up the array of timestamps and then sort it chronologically + // Set up the array of item timestamps and then sort it chronologically for (int i = 0; i < TIMESTAMP_MAX; i++) { - strcpy(timestampDisplay[i].name, timestampDisplayName[i]); - timestampDisplay[i].time = gSaveContext.sohStats.timestamp[i]; - timestampDisplay[i].color = timestampDisplayColor[i]; + strcpy(itemTimestampDisplay[i].name, itemTimestampDisplayName[i]); + itemTimestampDisplay[i].time = gSaveContext.sohStats.itemTimestamp[i]; + itemTimestampDisplay[i].color = itemTimestampDisplayColor[i]; } - SortChronological(timestampDisplay, sizeof(timestampDisplay) / sizeof(timestampDisplay[0])); + + for (int i = 0; i < gSaveContext.sohStats.tsIdx; i++) { + std::string sceneName = ResolveSceneID(gSaveContext.sohStats.sceneTimestamps[i].scene, gSaveContext.sohStats.sceneTimestamps[i].room); + std::string name; + if (CVarGetInteger("gGameplayStatRoomBreakdown", 0) && gSaveContext.sohStats.sceneTimestamps[i].scene != SCENE_KAKUSIANA) { + name = fmt::format("{:s} Room {:d}", sceneName, gSaveContext.sohStats.sceneTimestamps[i].room); + } else { + name = sceneName; + } + strcpy(sceneTimestampDisplay[i].name, name.c_str()); + sceneTimestampDisplay[i].time = CVarGetInteger("gGameplayStatRoomBreakdown", 0) ? + gSaveContext.sohStats.sceneTimestamps[i].roomTime : gSaveContext.sohStats.sceneTimestamps[i].sceneTime; + sceneTimestampDisplay[i].color = COLOR_GREY; + sceneTimestampDisplay[i].isRoom = gSaveContext.sohStats.sceneTimestamps[i].isRoom; + } + + SortChronological(itemTimestampDisplay, sizeof(itemTimestampDisplay) / sizeof(itemTimestampDisplay[0])); // Begin drawing the table and showing the stats @@ -138,193 +327,193 @@ void DrawStatsTracker(bool& open) { DisplayTimeHHMMSS(gSaveContext.sohStats.playTimer / 2, "Gameplay Time: ", COLOR_WHITE); UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading."); DisplayTimeHHMMSS(gSaveContext.sohStats.pauseTimer / 3, "Pause Menu Time: ", COLOR_WHITE); + DisplayTimeHHMMSS(gSaveContext.sohStats.sceneTimer / 2, "Time in scene: ", COLOR_LIGHT_BLUE); + UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading."); + DisplayTimeHHMMSS(gSaveContext.sohStats.roomTimer / 2, "Time in room: ", COLOR_LIGHT_BLUE); + UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading."); + ImGui::Text("Current room: %d", gSaveContext.sohStats.roomNum); ImGui::PopStyleVar(1); ImGui::EndTable(); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); - ImGui::BeginTable("gameStatsTable", (showTimestamps && showCounts) ? 2 : 1, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); - - if (showTimestamps) { - ImGui::TableSetupColumn("Timestamps", ImGuiTableColumnFlags_WidthStretch, 200.0f); - } - if (showCounts) { - ImGui::TableSetupColumn("Counts", ImGuiTableColumnFlags_WidthStretch, 200.0f); - } - ImGui::TableHeadersRow(); - ImGui::TableNextRow(); - - if (showTimestamps) { - ImGui::TableNextColumn(); - - // Display chronological timestamps of items obtained and bosses defeated - for (int i = 0; i < TIMESTAMP_MAX; i++) { - // To be shown, the entry must have a non-zero time and a string for its display name - if (timestampDisplay[i].time > 0 && strnlen(timestampDisplay[i].name, 21) > 1) { - DisplayTimeHHMMSS(timestampDisplay[i].time, timestampDisplay[i].name, timestampDisplay[i].color); + if (ImGui::BeginTabBar("Stats", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { + if (ImGui::BeginTabItem("Timestamps")) { + // Display chronological timestamps of items obtained and bosses defeated + for (int i = 0; i < TIMESTAMP_MAX; i++) { + // To be shown, the entry must have a non-zero time and a string for its display name + if (itemTimestampDisplay[i].time > 0 && strnlen(itemTimestampDisplay[i].name, 21) > 1) { + DisplayTimeHHMMSS(itemTimestampDisplay[i].time, itemTimestampDisplay[i].name, itemTimestampDisplay[i].color); + } } + ImGui::EndTabItem(); } - } - - if (showCounts) { - ImGui::TableNextColumn(); - - DisplayStat("Enemies Defeated: ", enemiesDefeated); - // Show breakdown of enemies defeated in a tree. Only show counts for enemies if they've been defeated at least once. - if (enemiesDefeated > 0) { - if (ImGui::TreeNode("Enemy Details...")) { - - DisplayStatIfNonZero("Anubis: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ANUBIS]); - DisplayStatIfNonZero("Armos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARMOS]); - DisplayStatIfNonZero("Arwing: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARWING]); - DisplayStatIfNonZero("Bari: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BARI]); - DisplayStatIfNonZero("Biri: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIRI]); - DisplayStatIfNonZero("Beamos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BEAMOS]); - DisplayStatIfNonZero("Big Octo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIG_OCTO]); - DisplayStatIfNonZero("Bubble (Blue): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE]); - DisplayStatIfNonZero("Bubble (Green): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN]); - DisplayStatIfNonZero("Bubble (Red): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_RED]); - DisplayStatIfNonZero("Bubble (White): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE]); - DisplayStatIfNonZero("Business Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB]); - DisplayStatIfNonZero("Dark Link: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DARK_LINK]); - DisplayStatIfNonZero("Dead Hand: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEAD_HAND]); - DisplayStatIfNonZero("Deku Baba: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]); - DisplayStatIfNonZero("Deku Baba (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]); - DisplayStatIfNonZero("Deku Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_SCRUB]); - DisplayStatIfNonZero("Dinolfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DINOLFOS]); - DisplayStatIfNonZero("Dodongo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO]); - DisplayStatIfNonZero("Dodongo (Baby): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO_BABY]); - DisplayStatIfNonZero("Door Mimic: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DOOR_TRAP]); - DisplayStatIfNonZero("Flare Dancer: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLARE_DANCER]); - DisplayStatIfNonZero("Floormaster: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOORMASTER]/3); - DisplayStatIfNonZero("Flying Floor Tile: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOOR_TILE]); - DisplayStatIfNonZero("Flying Pot: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]); - DisplayStatIfNonZero("Freezard: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FREEZARD]); - DisplayStatIfNonZero("Gerudo Thief: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GERUDO_THIEF]); - DisplayStatIfNonZero("Gibdo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GIBDO]); - DisplayStatIfNonZero("Gohma Larva: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GOHMA_LARVA]); - DisplayStatIfNonZero("Guay: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GUAY]); - DisplayStatIfNonZero("Iron Knuckle: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE]); - DisplayStatIfNonZero("Iron Knuckle (Nab): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU]); - DisplayStatIfNonZero("Keese: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE]); - DisplayStatIfNonZero("Keese (Fire): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_FIRE]); - DisplayStatIfNonZero("Keese (Ice): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_ICE]); - DisplayStatIfNonZero("Leever: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER]); - DisplayStatIfNonZero("Leever (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER_BIG]); - DisplayStatIfNonZero("Like-Like: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIKE_LIKE]); - DisplayStatIfNonZero("Lizalfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIZALFOS]); - DisplayStatIfNonZero("Mad Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MAD_SCRUB]); - DisplayStatIfNonZero("Moblin: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN]); - DisplayStatIfNonZero("Moblin (Club): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB]); - DisplayStatIfNonZero("Octorok: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_OCTOROK]); - DisplayStatIfNonZero("Parasitic Tentacle: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE]); - DisplayStatIfNonZero("Peahat: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT]); - DisplayStatIfNonZero("Peahat Larva: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]); - DisplayStatIfNonZero("Poe: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]); - DisplayStatIfNonZero("Poe (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_BIG]); - DisplayStatIfNonZero("Poe (Composer): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_COMPOSER]); - DisplayStatIfNonZero("Poe Sisters: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_SISTERS]); - DisplayStatIfNonZero("Redead: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_REDEAD]); - DisplayStatIfNonZero("Shabom: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHABOM]); - DisplayStatIfNonZero("Shellblade: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHELLBLADE]); - DisplayStatIfNonZero("Skull Kid: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULL_KID]); - DisplayStatIfNonZero("Skulltula: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA]); - DisplayStatIfNonZero("Skulltula (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG]); - DisplayStatIfNonZero("Skulltula (Gold): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD]); - DisplayStatIfNonZero("Skullwalltula: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLWALLTULA]); - DisplayStatIfNonZero("Spike: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SPIKE]); - DisplayStatIfNonZero("Stalchild: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALCHILD]); - DisplayStatIfNonZero("Stalfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALFOS]); - DisplayStatIfNonZero("Stinger: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STINGER]); - DisplayStatIfNonZero("Tailpasaran: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TAILPASARAN]); - DisplayStatIfNonZero("Tektite (Blue): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]); - DisplayStatIfNonZero("Tektite (Red): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]); - DisplayStatIfNonZero("Torch Slug: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TORCH_SLUG]); - DisplayStatIfNonZero("Wallmaster: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WALLMASTER]); - DisplayStatIfNonZero("Withered Deku Baba: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA]); - DisplayStatIfNonZero("Wolfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS]); - DisplayStatIfNonZero("Wolfos (White): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE]); - - ImGui::NewLine(); - ImGui::TreePop(); - } - } - - DisplayStat("Rupees Collected: ", gSaveContext.sohStats.count[COUNT_RUPEES_COLLECTED]); - UIWidgets::Tooltip("Includes rupees collected with a full wallet."); - DisplayStat("Rupees Spent: ", gSaveContext.sohStats.count[COUNT_RUPEES_SPENT]); - DisplayStat("Chests Opened: ", gSaveContext.sohStats.count[COUNT_CHESTS_OPENED]); - - DisplayStat("Ammo Used: ", ammoUsed); - // Show breakdown of ammo used in a collapsible tree. Only show ammo types if they've been used at least once. - if (ammoUsed > 0) { - if (ImGui::TreeNode("Ammo Details...")) { - - DisplayStatIfNonZero("Deku Sticks: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_STICK]); - DisplayStatIfNonZero("Deku Nuts: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_NUT]); - DisplayStatIfNonZero("Deku Seeds: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_SEED]); - DisplayStatIfNonZero("Bombs: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMB]); - DisplayStatIfNonZero("Bombchus: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMBCHU]); - DisplayStatIfNonZero("Arrows: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_ARROW]); - DisplayStatIfNonZero("Beans: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BEAN]); - + if (ImGui::BeginTabItem("Counts")) { + DisplayStat("Enemies Defeated: ", enemiesDefeated); + // Show breakdown of enemies defeated in a tree. Only show counts for enemies if they've been defeated at least once. + if (enemiesDefeated > 0) { + if (ImGui::TreeNode("Enemy Details...")) { + DisplayStatIfNonZero("Anubis: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ANUBIS]); + DisplayStatIfNonZero("Armos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARMOS]); + DisplayStatIfNonZero("Arwing: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARWING]); + DisplayStatIfNonZero("Bari: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BARI]); + DisplayStatIfNonZero("Biri: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIRI]); + DisplayStatIfNonZero("Beamos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BEAMOS]); + DisplayStatIfNonZero("Big Octo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIG_OCTO]); + DisplayStatIfNonZero("Bubble (Blue): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE]); + DisplayStatIfNonZero("Bubble (Green): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN]); + DisplayStatIfNonZero("Bubble (Red): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_RED]); + DisplayStatIfNonZero("Bubble (White): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE]); + DisplayStatIfNonZero("Business Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB]); + DisplayStatIfNonZero("Dark Link: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DARK_LINK]); + DisplayStatIfNonZero("Dead Hand: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEAD_HAND]); + DisplayStatIfNonZero("Deku Baba: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]); + DisplayStatIfNonZero("Deku Baba (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]); + DisplayStatIfNonZero("Deku Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_SCRUB]); + DisplayStatIfNonZero("Dinolfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DINOLFOS]); + DisplayStatIfNonZero("Dodongo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO]); + DisplayStatIfNonZero("Dodongo (Baby): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO_BABY]); + DisplayStatIfNonZero("Door Mimic: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DOOR_TRAP]); + DisplayStatIfNonZero("Flare Dancer: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLARE_DANCER]); + DisplayStatIfNonZero("Floormaster: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOORMASTER]/3); + DisplayStatIfNonZero("Flying Floor Tile: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOOR_TILE]); + DisplayStatIfNonZero("Flying Pot: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]); + DisplayStatIfNonZero("Freezard: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FREEZARD]); + DisplayStatIfNonZero("Gerudo Thief: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GERUDO_THIEF]); + DisplayStatIfNonZero("Gibdo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GIBDO]); + DisplayStatIfNonZero("Gohma Larva: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GOHMA_LARVA]); + DisplayStatIfNonZero("Guay: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GUAY]); + DisplayStatIfNonZero("Iron Knuckle: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE]); + DisplayStatIfNonZero("Iron Knuckle (Nab): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU]); + DisplayStatIfNonZero("Keese: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE]); + DisplayStatIfNonZero("Keese (Fire): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_FIRE]); + DisplayStatIfNonZero("Keese (Ice): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_ICE]); + DisplayStatIfNonZero("Leever: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER]); + DisplayStatIfNonZero("Leever (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER_BIG]); + DisplayStatIfNonZero("Like-Like: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIKE_LIKE]); + DisplayStatIfNonZero("Lizalfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIZALFOS]); + DisplayStatIfNonZero("Mad Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MAD_SCRUB]); + DisplayStatIfNonZero("Moblin: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN]); + DisplayStatIfNonZero("Moblin (Club): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB]); + DisplayStatIfNonZero("Octorok: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_OCTOROK]); + DisplayStatIfNonZero("Parasitic Tentacle: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE]); + DisplayStatIfNonZero("Peahat: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT]); + DisplayStatIfNonZero("Peahat Larva: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]); + DisplayStatIfNonZero("Poe: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]); + DisplayStatIfNonZero("Poe (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_BIG]); + DisplayStatIfNonZero("Poe (Composer): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_COMPOSER]); + DisplayStatIfNonZero("Poe Sisters: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_SISTERS]); + DisplayStatIfNonZero("Redead: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_REDEAD]); + DisplayStatIfNonZero("Shabom: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHABOM]); + DisplayStatIfNonZero("Shellblade: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHELLBLADE]); + DisplayStatIfNonZero("Skull Kid: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULL_KID]); + DisplayStatIfNonZero("Skulltula: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA]); + DisplayStatIfNonZero("Skulltula (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG]); + DisplayStatIfNonZero("Skulltula (Gold): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD]); + DisplayStatIfNonZero("Skullwalltula: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLWALLTULA]); + DisplayStatIfNonZero("Spike: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SPIKE]); + DisplayStatIfNonZero("Stalchild: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALCHILD]); + DisplayStatIfNonZero("Stalfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALFOS]); + DisplayStatIfNonZero("Stinger: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STINGER]); + DisplayStatIfNonZero("Tailpasaran: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TAILPASARAN]); + DisplayStatIfNonZero("Tektite (Blue): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]); + DisplayStatIfNonZero("Tektite (Red): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]); + DisplayStatIfNonZero("Torch Slug: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TORCH_SLUG]); + DisplayStatIfNonZero("Wallmaster: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WALLMASTER]); + DisplayStatIfNonZero("Withered Deku Baba: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA]); + DisplayStatIfNonZero("Wolfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS]); + DisplayStatIfNonZero("Wolfos (White): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE]); ImGui::NewLine(); ImGui::TreePop(); + } } - } + + DisplayStat("Rupees Collected: ", gSaveContext.sohStats.count[COUNT_RUPEES_COLLECTED]); + UIWidgets::Tooltip("Includes rupees collected with a full wallet."); + DisplayStat("Rupees Spent: ", gSaveContext.sohStats.count[COUNT_RUPEES_SPENT]); + DisplayStat("Chests Opened: ", gSaveContext.sohStats.count[COUNT_CHESTS_OPENED]); - DisplayStat("Damage Taken: ", gSaveContext.sohStats.count[COUNT_DAMAGE_TAKEN]); - DisplayStat("Sword Swings: ", gSaveContext.sohStats.count[COUNT_SWORD_SWINGS]); - DisplayStat("Steps Taken: ", gSaveContext.sohStats.count[COUNT_STEPS]); - // If using MM Bunny Hood enhancement, show how long it's been equipped (not counting pause time) - if (CVarGetInteger("gMMBunnyHood", 0) || gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] > 0) { - DisplayTimeHHMMSS(gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] / 2, "Bunny Hood Time: ", COLOR_WHITE); - } - DisplayStat("Rolls: ", gSaveContext.sohStats.count[COUNT_ROLLS]); - DisplayStat("Bonks: ", gSaveContext.sohStats.count[COUNT_BONKS]); - DisplayStat("Sidehops: ", gSaveContext.sohStats.count[COUNT_SIDEHOPS]); - DisplayStat("Backflips: ", gSaveContext.sohStats.count[COUNT_BACKFLIPS]); - DisplayStat("Ice Traps: ", gSaveContext.sohStats.count[COUNT_ICE_TRAPS]); - DisplayStat("Pauses: ", gSaveContext.sohStats.count[COUNT_PAUSES]); - DisplayStat("Pots Smashed: ", gSaveContext.sohStats.count[COUNT_POTS_BROKEN]); - DisplayStat("Bushes Cut: ", gSaveContext.sohStats.count[COUNT_BUSHES_CUT]); - - DisplayStat("Buttons Pressed: ", buttonPresses); - // Show breakdown of ammo used in a collapsible tree. Only show ammo types if they've been used at least once. - if (buttonPresses > 0) { - if (ImGui::TreeNode("Buttons...")) { - - DisplayStatIfNonZero("A: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_A]); - DisplayStatIfNonZero("B: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_B]); - DisplayStatIfNonZero("L: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_L]); - DisplayStatIfNonZero("R: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_R]); - DisplayStatIfNonZero("Z: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_Z]); - DisplayStatIfNonZero("C-Up: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CUP]); - DisplayStatIfNonZero("C-Right: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CRIGHT]); - DisplayStatIfNonZero("C-Down: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CDOWN]); - DisplayStatIfNonZero("C-Left: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CLEFT]); - DisplayStatIfNonZero("D-Up: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DUP]); - DisplayStatIfNonZero("D-Right: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DRIGHT]); - DisplayStatIfNonZero("D-Down: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DDOWN]); - DisplayStatIfNonZero("D-Left: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DLEFT]); - DisplayStatIfNonZero("Start: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_START]); - - ImGui::NewLine(); - ImGui::TreePop(); + DisplayStat("Ammo Used: ", ammoUsed); + // Show breakdown of ammo used in a collapsible tree. Only show ammo types if they've been used at least once. + if (ammoUsed > 0) { + if (ImGui::TreeNode("Ammo Details...")) { + DisplayStatIfNonZero("Deku Sticks: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_STICK]); + DisplayStatIfNonZero("Deku Nuts: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_NUT]); + DisplayStatIfNonZero("Deku Seeds: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_SEED]); + DisplayStatIfNonZero("Bombs: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMB]); + DisplayStatIfNonZero("Bombchus: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMBCHU]); + DisplayStatIfNonZero("Arrows: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_ARROW]); + DisplayStatIfNonZero("Beans: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BEAN]); + ImGui::NewLine(); + ImGui::TreePop(); + } } + DisplayStat("Damage Taken: ", gSaveContext.sohStats.count[COUNT_DAMAGE_TAKEN]); + DisplayStat("Sword Swings: ", gSaveContext.sohStats.count[COUNT_SWORD_SWINGS]); + DisplayStat("Steps Taken: ", gSaveContext.sohStats.count[COUNT_STEPS]); + // If using MM Bunny Hood enhancement, show how long it's been equipped (not counting pause time) + if (CVarGetInteger("gMMBunnyHood", 0) || gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] > 0) { + DisplayTimeHHMMSS(gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] / 2, "Bunny Hood Time: ", COLOR_WHITE); + } + DisplayStat("Rolls: ", gSaveContext.sohStats.count[COUNT_ROLLS]); + DisplayStat("Bonks: ", gSaveContext.sohStats.count[COUNT_BONKS]); + DisplayStat("Sidehops: ", gSaveContext.sohStats.count[COUNT_SIDEHOPS]); + DisplayStat("Backflips: ", gSaveContext.sohStats.count[COUNT_BACKFLIPS]); + DisplayStat("Ice Traps: ", gSaveContext.sohStats.count[COUNT_ICE_TRAPS]); + DisplayStat("Pauses: ", gSaveContext.sohStats.count[COUNT_PAUSES]); + DisplayStat("Pots Smashed: ", gSaveContext.sohStats.count[COUNT_POTS_BROKEN]); + DisplayStat("Bushes Cut: ", gSaveContext.sohStats.count[COUNT_BUSHES_CUT]); + DisplayStat("Buttons Pressed: ", buttonPresses); + // Show breakdown of ammo used in a collapsible tree. Only show ammo types if they've been used at least once. + if (buttonPresses > 0) { + if (ImGui::TreeNode("Buttons...")) { + DisplayStatIfNonZero("A: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_A]); + DisplayStatIfNonZero("B: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_B]); + DisplayStatIfNonZero("L: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_L]); + DisplayStatIfNonZero("R: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_R]); + DisplayStatIfNonZero("Z: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_Z]); + DisplayStatIfNonZero("C-Up: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CUP]); + DisplayStatIfNonZero("C-Right: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CRIGHT]); + DisplayStatIfNonZero("C-Down: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CDOWN]); + DisplayStatIfNonZero("C-Left: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CLEFT]); + DisplayStatIfNonZero("D-Up: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DUP]); + DisplayStatIfNonZero("D-Right: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DRIGHT]); + DisplayStatIfNonZero("D-Down: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DDOWN]); + DisplayStatIfNonZero("D-Left: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DLEFT]); + DisplayStatIfNonZero("Start: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_START]); + ImGui::NewLine(); + ImGui::TreePop(); + } + } + ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Breakdown")) { + UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", "gGameplayStatRoomBreakdown"); + ImGui::SameLine(); + UIWidgets::InsertHelpHoverText("Allows a more in-depth perspective of time spent in a certain map."); + if (gPlayState == NULL) { + ImGui::Text("Waiting for file load..."); + } else { + for (int i = 0; i < gSaveContext.sohStats.tsIdx; i++) { + TimestampInfo tsInfo = sceneTimestampDisplay[i]; + bool canShow = !tsInfo.isRoom || CVarGetInteger("gGameplayStatRoomBreakdown", 0); + if (tsInfo.time > 0 && strnlen(tsInfo.name, 40) > 1 && canShow) { + DisplayTimeHHMMSS(tsInfo.time, tsInfo.name, tsInfo.color); + } + } + std::string toPass; + if (CVarGetInteger("gGameplayStatRoomBreakdown", 0) && gSaveContext.sohStats.sceneNum != SCENE_KAKUSIANA) { + toPass = fmt::format("{:s} Room {:d}", ResolveSceneID(gSaveContext.sohStats.sceneNum, gSaveContext.sohStats.roomNum), gSaveContext.sohStats.roomNum); + } else { + toPass = ResolveSceneID(gSaveContext.sohStats.sceneNum, gSaveContext.sohStats.roomNum); + } + DisplayTimeHHMMSS(CURRENT_MODE_TIMER / 2, toPass.c_str(), COLOR_WHITE); + } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } - ImGui::PopStyleVar(1); - ImGui::EndTable(); - - const char* gameplayStatsModeOptions[3] = { "Both", "Timestamps", "Counts"}; - - ImGui::Text("Display Mode"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); - UIWidgets::EnhancementCombobox("gGameplayStatsMode", gameplayStatsModeOptions, 0); - ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving."); ImGui::End(); @@ -335,94 +524,93 @@ void SetupDisplayNames() { // To add a timestamp for an item or event, add it to this list and ensure // it has a corresponding entry in the enum (see gameplaystats.h) - strcpy(timestampDisplayName[ITEM_BOW], "Fairy Bow: "); - strcpy(timestampDisplayName[ITEM_ARROW_FIRE], "Fire Arrows: "); - strcpy(timestampDisplayName[ITEM_DINS_FIRE], "Din's Fire: "); - strcpy(timestampDisplayName[ITEM_SLINGSHOT], "Slingshot: "); - strcpy(timestampDisplayName[ITEM_OCARINA_FAIRY], "Fairy Ocarina: "); - strcpy(timestampDisplayName[ITEM_OCARINA_TIME], "Ocarina of Time: "); - strcpy(timestampDisplayName[ITEM_BOMBCHU], "Bombchus: "); - strcpy(timestampDisplayName[ITEM_HOOKSHOT], "Hookshot: "); - strcpy(timestampDisplayName[ITEM_LONGSHOT], "Longshot: "); - strcpy(timestampDisplayName[ITEM_ARROW_ICE], "Ice Arrows: "); - strcpy(timestampDisplayName[ITEM_FARORES_WIND], "Farore's Wind: "); - strcpy(timestampDisplayName[ITEM_BOOMERANG], "Boomerang: "); - strcpy(timestampDisplayName[ITEM_LENS], "Lens of Truth: "); - strcpy(timestampDisplayName[ITEM_HAMMER], "Megaton Hammer: "); - strcpy(timestampDisplayName[ITEM_ARROW_LIGHT], "Light Arrows: "); - strcpy(timestampDisplayName[ITEM_BOTTLE], "Bottle: "); - strcpy(timestampDisplayName[ITEM_LETTER_ZELDA], "Zelda's Letter: "); - strcpy(timestampDisplayName[ITEM_SWORD_KOKIRI], "Kokiri Sword: "); - strcpy(timestampDisplayName[ITEM_SWORD_MASTER], "Master Sword: "); - strcpy(timestampDisplayName[ITEM_SWORD_BGS], "Biggoron's Sword: "); - strcpy(timestampDisplayName[ITEM_SHIELD_DEKU], "Deku Shield: "); - strcpy(timestampDisplayName[ITEM_SHIELD_HYLIAN], "Hylian Shield: "); - strcpy(timestampDisplayName[ITEM_SHIELD_MIRROR], "Mirror Shield: "); - strcpy(timestampDisplayName[ITEM_TUNIC_GORON], "Goron Tunic: "); - strcpy(timestampDisplayName[ITEM_TUNIC_ZORA], "Zora Tunic: "); - strcpy(timestampDisplayName[ITEM_BOOTS_IRON], "Iron Boots: "); - strcpy(timestampDisplayName[ITEM_BOOTS_HOVER], "Hover Boots: "); - strcpy(timestampDisplayName[ITEM_BOMB_BAG_20], "Bomb Bag: "); - strcpy(timestampDisplayName[ITEM_BRACELET], "Goron's Bracelet: "); - strcpy(timestampDisplayName[ITEM_GAUNTLETS_SILVER], "Silver Gauntlets: "); - strcpy(timestampDisplayName[ITEM_GAUNTLETS_GOLD], "Gold Gauntlets: "); - strcpy(timestampDisplayName[ITEM_SCALE_SILVER], "Silver Scale: "); - strcpy(timestampDisplayName[ITEM_SCALE_GOLDEN], "Gold Scale: "); - strcpy(timestampDisplayName[ITEM_WALLET_ADULT], "Adult's Wallet: "); - strcpy(timestampDisplayName[ITEM_WALLET_GIANT], "Giant's Wallet: "); - strcpy(timestampDisplayName[ITEM_WEIRD_EGG], "Weird Egg: "); - strcpy(timestampDisplayName[ITEM_GERUDO_CARD], "Gerudo's Card: "); - strcpy(timestampDisplayName[ITEM_COJIRO], "Cojiro: "); - strcpy(timestampDisplayName[ITEM_POCKET_EGG], "Pocket Egg: "); - strcpy(timestampDisplayName[ITEM_MASK_SKULL], "Skull Mask: "); - strcpy(timestampDisplayName[ITEM_MASK_SPOOKY], "Spooky Mask: "); - strcpy(timestampDisplayName[ITEM_MASK_KEATON], "Keaton Mask: "); - strcpy(timestampDisplayName[ITEM_MASK_BUNNY], "Bunny Hood: "); - strcpy(timestampDisplayName[ITEM_ODD_MUSHROOM], "Odd Mushroom: "); - strcpy(timestampDisplayName[ITEM_ODD_POTION], "Odd Potion: "); - strcpy(timestampDisplayName[ITEM_SAW], "Poacher's Saw: "); - strcpy(timestampDisplayName[ITEM_SWORD_BROKEN], "Broken Goron Sword: "); - strcpy(timestampDisplayName[ITEM_PRESCRIPTION], "Prescription: "); - strcpy(timestampDisplayName[ITEM_FROG], "Eyeball Frog: "); - strcpy(timestampDisplayName[ITEM_EYEDROPS], "Eye Drops: "); - strcpy(timestampDisplayName[ITEM_CLAIM_CHECK], "Claim Check: "); - strcpy(timestampDisplayName[ITEM_SONG_MINUET], "Minuet of Forest: "); - strcpy(timestampDisplayName[ITEM_SONG_BOLERO], "Bolero of Fire: "); - strcpy(timestampDisplayName[ITEM_SONG_SERENADE], "Serenade of Water: "); - strcpy(timestampDisplayName[ITEM_SONG_REQUIEM], "Requiem of Spirit: "); - strcpy(timestampDisplayName[ITEM_SONG_NOCTURNE], "Nocturne of Shadow: "); - strcpy(timestampDisplayName[ITEM_SONG_PRELUDE], "Prelude of Light: "); - strcpy(timestampDisplayName[ITEM_SONG_LULLABY], "Zelda's Lullaby: "); - strcpy(timestampDisplayName[ITEM_SONG_EPONA], "Epona's Song: "); - strcpy(timestampDisplayName[ITEM_SONG_SARIA], "Saria's Song: "); - strcpy(timestampDisplayName[ITEM_SONG_SUN], "Sun's Song: "); - strcpy(timestampDisplayName[ITEM_SONG_TIME], "Song of Time: "); - strcpy(timestampDisplayName[ITEM_SONG_STORMS], "Song of Storms: "); - strcpy(timestampDisplayName[ITEM_MEDALLION_FOREST], "Forest Medallion: "); - strcpy(timestampDisplayName[ITEM_MEDALLION_FIRE], "Fire Medallion: "); - strcpy(timestampDisplayName[ITEM_MEDALLION_WATER], "Water Medallion: "); - strcpy(timestampDisplayName[ITEM_MEDALLION_SPIRIT], "Spirit Medallion: "); - strcpy(timestampDisplayName[ITEM_MEDALLION_SHADOW], "Shadow Medallion: "); - strcpy(timestampDisplayName[ITEM_MEDALLION_LIGHT], "Light Medallion: "); - strcpy(timestampDisplayName[ITEM_KOKIRI_EMERALD], "Kokiri's Emerald: "); - strcpy(timestampDisplayName[ITEM_GORON_RUBY], "Goron's Ruby: "); - strcpy(timestampDisplayName[ITEM_ZORA_SAPPHIRE], "Zora's Sapphire: "); - strcpy(timestampDisplayName[ITEM_KEY_BOSS], "Ganon's Boss Key: "); - strcpy(timestampDisplayName[ITEM_SINGLE_MAGIC], "Magic: "); - strcpy(timestampDisplayName[ITEM_DOUBLE_DEFENSE], "Double Defense: "); + strcpy(itemTimestampDisplayName[ITEM_BOW], "Fairy Bow: "); + strcpy(itemTimestampDisplayName[ITEM_ARROW_FIRE], "Fire Arrows: "); + strcpy(itemTimestampDisplayName[ITEM_DINS_FIRE], "Din's Fire: "); + strcpy(itemTimestampDisplayName[ITEM_SLINGSHOT], "Slingshot: "); + strcpy(itemTimestampDisplayName[ITEM_OCARINA_FAIRY], "Fairy Ocarina: "); + strcpy(itemTimestampDisplayName[ITEM_OCARINA_TIME], "Ocarina of Time: "); + strcpy(itemTimestampDisplayName[ITEM_BOMBCHU], "Bombchus: "); + strcpy(itemTimestampDisplayName[ITEM_HOOKSHOT], "Hookshot: "); + strcpy(itemTimestampDisplayName[ITEM_LONGSHOT], "Longshot: "); + strcpy(itemTimestampDisplayName[ITEM_ARROW_ICE], "Ice Arrows: "); + strcpy(itemTimestampDisplayName[ITEM_FARORES_WIND], "Farore's Wind: "); + strcpy(itemTimestampDisplayName[ITEM_BOOMERANG], "Boomerang: "); + strcpy(itemTimestampDisplayName[ITEM_LENS], "Lens of Truth: "); + strcpy(itemTimestampDisplayName[ITEM_HAMMER], "Megaton Hammer: "); + strcpy(itemTimestampDisplayName[ITEM_ARROW_LIGHT], "Light Arrows: "); + strcpy(itemTimestampDisplayName[ITEM_BOTTLE], "Bottle: "); + strcpy(itemTimestampDisplayName[ITEM_LETTER_ZELDA], "Zelda's Letter: "); + strcpy(itemTimestampDisplayName[ITEM_SWORD_KOKIRI], "Kokiri Sword: "); + strcpy(itemTimestampDisplayName[ITEM_SWORD_MASTER], "Master Sword: "); + strcpy(itemTimestampDisplayName[ITEM_SWORD_BGS], "Biggoron's Sword: "); + strcpy(itemTimestampDisplayName[ITEM_SHIELD_DEKU], "Deku Shield: "); + strcpy(itemTimestampDisplayName[ITEM_SHIELD_HYLIAN], "Hylian Shield: "); + strcpy(itemTimestampDisplayName[ITEM_SHIELD_MIRROR], "Mirror Shield: "); + strcpy(itemTimestampDisplayName[ITEM_TUNIC_GORON], "Goron Tunic: "); + strcpy(itemTimestampDisplayName[ITEM_TUNIC_ZORA], "Zora Tunic: "); + strcpy(itemTimestampDisplayName[ITEM_BOOTS_IRON], "Iron Boots: "); + strcpy(itemTimestampDisplayName[ITEM_BOOTS_HOVER], "Hover Boots: "); + strcpy(itemTimestampDisplayName[ITEM_BOMB_BAG_20], "Bomb Bag: "); + strcpy(itemTimestampDisplayName[ITEM_BRACELET], "Goron's Bracelet: "); + strcpy(itemTimestampDisplayName[ITEM_GAUNTLETS_SILVER], "Silver Gauntlets: "); + strcpy(itemTimestampDisplayName[ITEM_GAUNTLETS_GOLD], "Gold Gauntlets: "); + strcpy(itemTimestampDisplayName[ITEM_SCALE_SILVER], "Silver Scale: "); + strcpy(itemTimestampDisplayName[ITEM_SCALE_GOLDEN], "Gold Scale: "); + strcpy(itemTimestampDisplayName[ITEM_WALLET_ADULT], "Adult's Wallet: "); + strcpy(itemTimestampDisplayName[ITEM_WALLET_GIANT], "Giant's Wallet: "); + strcpy(itemTimestampDisplayName[ITEM_WEIRD_EGG], "Weird Egg: "); + strcpy(itemTimestampDisplayName[ITEM_GERUDO_CARD], "Gerudo's Card: "); + strcpy(itemTimestampDisplayName[ITEM_COJIRO], "Cojiro: "); + strcpy(itemTimestampDisplayName[ITEM_POCKET_EGG], "Pocket Egg: "); + strcpy(itemTimestampDisplayName[ITEM_MASK_SKULL], "Skull Mask: "); + strcpy(itemTimestampDisplayName[ITEM_MASK_SPOOKY], "Spooky Mask: "); + strcpy(itemTimestampDisplayName[ITEM_MASK_KEATON], "Keaton Mask: "); + strcpy(itemTimestampDisplayName[ITEM_MASK_BUNNY], "Bunny Hood: "); + strcpy(itemTimestampDisplayName[ITEM_ODD_MUSHROOM], "Odd Mushroom: "); + strcpy(itemTimestampDisplayName[ITEM_ODD_POTION], "Odd Potion: "); + strcpy(itemTimestampDisplayName[ITEM_SAW], "Poacher's Saw: "); + strcpy(itemTimestampDisplayName[ITEM_SWORD_BROKEN], "Broken Goron Sword: "); + strcpy(itemTimestampDisplayName[ITEM_PRESCRIPTION], "Prescription: "); + strcpy(itemTimestampDisplayName[ITEM_FROG], "Eyeball Frog: "); + strcpy(itemTimestampDisplayName[ITEM_EYEDROPS], "Eye Drops: "); + strcpy(itemTimestampDisplayName[ITEM_CLAIM_CHECK], "Claim Check: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_MINUET], "Minuet of Forest: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_BOLERO], "Bolero of Fire: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_SERENADE], "Serenade of Water: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_REQUIEM], "Requiem of Spirit: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_NOCTURNE], "Nocturne of Shadow: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_PRELUDE], "Prelude of Light: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_LULLABY], "Zelda's Lullaby: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_EPONA], "Epona's Song: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_SARIA], "Saria's Song: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_SUN], "Sun's Song: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_TIME], "Song of Time: "); + strcpy(itemTimestampDisplayName[ITEM_SONG_STORMS], "Song of Storms: "); + strcpy(itemTimestampDisplayName[ITEM_MEDALLION_FOREST], "Forest Medallion: "); + strcpy(itemTimestampDisplayName[ITEM_MEDALLION_FIRE], "Fire Medallion: "); + strcpy(itemTimestampDisplayName[ITEM_MEDALLION_WATER], "Water Medallion: "); + strcpy(itemTimestampDisplayName[ITEM_MEDALLION_SPIRIT], "Spirit Medallion: "); + strcpy(itemTimestampDisplayName[ITEM_MEDALLION_SHADOW], "Shadow Medallion: "); + strcpy(itemTimestampDisplayName[ITEM_MEDALLION_LIGHT], "Light Medallion: "); + strcpy(itemTimestampDisplayName[ITEM_KOKIRI_EMERALD], "Kokiri's Emerald: "); + strcpy(itemTimestampDisplayName[ITEM_GORON_RUBY], "Goron's Ruby: "); + strcpy(itemTimestampDisplayName[ITEM_ZORA_SAPPHIRE], "Zora's Sapphire: "); + strcpy(itemTimestampDisplayName[ITEM_KEY_BOSS], "Ganon's Boss Key: "); + strcpy(itemTimestampDisplayName[ITEM_SINGLE_MAGIC], "Magic: "); + strcpy(itemTimestampDisplayName[ITEM_DOUBLE_DEFENSE], "Double Defense: "); // Other events - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_GOHMA], "Gohma Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_KING_DODONGO], "KD Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_BARINADE], "Barinade Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_PHANTOM_GANON], "PG Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_VOLVAGIA], "Volvagia Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_MORPHA], "Morpha Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_BONGO_BONGO], "Bongo Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_TWINROVA], "Twinrova Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_GANONDORF], "Ganondorf Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_DEFEAT_GANON], "Ganon Defeated: "); - strcpy(timestampDisplayName[TIMESTAMP_FOUND_GREG], "Greg Found: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_GOHMA], "Gohma Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_KING_DODONGO], "KD Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_BARINADE], "Barinade Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_PHANTOM_GANON], "PG Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_VOLVAGIA], "Volvagia Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_MORPHA], "Morpha Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_BONGO_BONGO], "Bongo Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_TWINROVA], "Twinrova Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_GANONDORF], "Ganondorf Defeated: "); + strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_GANON], "Ganon Defeated: "); } void SetupDisplayColors() { @@ -433,42 +621,42 @@ void SetupDisplayColors() { case ITEM_SONG_SARIA: case ITEM_MEDALLION_FOREST: case TIMESTAMP_FOUND_GREG: - timestampDisplayColor[i] = COLOR_GREEN; + itemTimestampDisplayColor[i] = COLOR_GREEN; break; case ITEM_SONG_BOLERO: case ITEM_GORON_RUBY: case ITEM_MEDALLION_FIRE: - timestampDisplayColor[i] = COLOR_RED; + itemTimestampDisplayColor[i] = COLOR_RED; break; case ITEM_SONG_SERENADE: case ITEM_ZORA_SAPPHIRE: case ITEM_MEDALLION_WATER: - timestampDisplayColor[i] = COLOR_BLUE; + itemTimestampDisplayColor[i] = COLOR_BLUE; break; case ITEM_SONG_LULLABY: case ITEM_SONG_NOCTURNE: case ITEM_MEDALLION_SHADOW: - timestampDisplayColor[i] = COLOR_PURPLE; + itemTimestampDisplayColor[i] = COLOR_PURPLE; break; case ITEM_SONG_EPONA: case ITEM_SONG_REQUIEM: case ITEM_MEDALLION_SPIRIT: - timestampDisplayColor[i] = COLOR_ORANGE; + itemTimestampDisplayColor[i] = COLOR_ORANGE; break; case ITEM_SONG_SUN: case ITEM_SONG_PRELUDE: case ITEM_MEDALLION_LIGHT: case ITEM_ARROW_LIGHT: - timestampDisplayColor[i] = COLOR_YELLOW; + itemTimestampDisplayColor[i] = COLOR_YELLOW; break; case ITEM_SONG_STORMS: - timestampDisplayColor[i] = COLOR_GREY; + itemTimestampDisplayColor[i] = COLOR_GREY; break; case ITEM_SONG_TIME: - timestampDisplayColor[i] = COLOR_LIGHT_BLUE; + itemTimestampDisplayColor[i] = COLOR_LIGHT_BLUE; break; default: - timestampDisplayColor[i] = COLOR_WHITE; + itemTimestampDisplayColor[i] = COLOR_WHITE; break; } } diff --git a/soh/soh/Enhancements/gameplaystats.h b/soh/soh/Enhancements/gameplaystats.h index c5b685e32..d44056970 100644 --- a/soh/soh/Enhancements/gameplaystats.h +++ b/soh/soh/Enhancements/gameplaystats.h @@ -4,6 +4,9 @@ // I.E. game time counts frames at 20fps/2, pause time counts frames at 30fps/3 // Frame counts in z_play.c and z_kaleido_scope_call.c #define GAMEPLAYSTAT_TOTAL_TIME (gSaveContext.sohStats.playTimer / 2 + gSaveContext.sohStats.pauseTimer / 3) +#define CURRENT_MODE_TIMER (CVarGetInteger("gGameplayStatRoomBreakdown", 0) ?\ + gSaveContext.sohStats.roomTimer :\ + gSaveContext.sohStats.sceneTimer) void InitStatTracker(); diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index fb1c3da23..a051da7f7 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -489,9 +489,17 @@ void SaveManager::InitFileNormal() { } gSaveContext.sohStats.playTimer = 0; gSaveContext.sohStats.pauseTimer = 0; - for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.timestamp); timestamp++) { - gSaveContext.sohStats.timestamp[timestamp] = 0; + for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp); timestamp++) { + gSaveContext.sohStats.itemTimestamp[timestamp] = 0; } + for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.sceneTimestamps); timestamp++) { + gSaveContext.sohStats.sceneTimestamps[timestamp].sceneTime = 0; + gSaveContext.sohStats.sceneTimestamps[timestamp].roomTime = 0; + gSaveContext.sohStats.sceneTimestamps[timestamp].scene = 254; + gSaveContext.sohStats.sceneTimestamps[timestamp].room = 254; + gSaveContext.sohStats.sceneTimestamps[timestamp].isRoom = 0; + } + gSaveContext.sohStats.tsIdx = 0; for (int count = 0; count < ARRAY_COUNT(gSaveContext.sohStats.count); count++) { gSaveContext.sohStats.count[count] = 0; } @@ -1069,8 +1077,8 @@ void SaveManager::LoadBaseVersion2() { }); SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer); SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer); - SaveManager::Instance->LoadArray("timestamps", ARRAY_COUNT(gSaveContext.sohStats.timestamp), [](size_t i) { - SaveManager::Instance->LoadData("", gSaveContext.sohStats.timestamp[i]); + SaveManager::Instance->LoadArray("timestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.itemTimestamp[i]); }); SaveManager::Instance->LoadArray("counts", ARRAY_COUNT(gSaveContext.sohStats.count), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.sohStats.count[i]); @@ -1283,9 +1291,20 @@ void SaveManager::LoadBaseVersion3() { }); SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer); SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer); - SaveManager::Instance->LoadArray("timestamps", ARRAY_COUNT(gSaveContext.sohStats.timestamp), [](size_t i) { - SaveManager::Instance->LoadData("", gSaveContext.sohStats.timestamp[i]); + SaveManager::Instance->LoadArray("itemTimestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.itemTimestamp[i]); }); + SaveManager::Instance->LoadArray("sceneTimestamps", ARRAY_COUNT(gSaveContext.sohStats.sceneTimestamps), [](size_t i) { + SaveManager::Instance->LoadStruct("", [&i]() { + SaveManager::Instance->LoadData("scene", gSaveContext.sohStats.sceneTimestamps[i].scene); + SaveManager::Instance->LoadData("room", gSaveContext.sohStats.sceneTimestamps[i].room); + SaveManager::Instance->LoadData("sceneTime", gSaveContext.sohStats.sceneTimestamps[i].sceneTime); + SaveManager::Instance->LoadData("roomTime", gSaveContext.sohStats.sceneTimestamps[i].roomTime); + SaveManager::Instance->LoadData("isRoom", gSaveContext.sohStats.sceneTimestamps[i].isRoom); + + }); + }); + SaveManager::Instance->LoadData("tsIdx", gSaveContext.sohStats.tsIdx); SaveManager::Instance->LoadArray("counts", ARRAY_COUNT(gSaveContext.sohStats.count), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.sohStats.count[i]); }); @@ -1481,9 +1500,19 @@ void SaveManager::SaveBase() { }); SaveManager::Instance->SaveData("playTimer", gSaveContext.sohStats.playTimer); SaveManager::Instance->SaveData("pauseTimer", gSaveContext.sohStats.pauseTimer); - SaveManager::Instance->SaveArray("timestamps", ARRAY_COUNT(gSaveContext.sohStats.timestamp), [](size_t i) { - SaveManager::Instance->SaveData("", gSaveContext.sohStats.timestamp[i]); + SaveManager::Instance->SaveArray("itemTimestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) { + SaveManager::Instance->SaveData("", gSaveContext.sohStats.itemTimestamp[i]); }); + SaveManager::Instance->SaveArray("sceneTimestamps", ARRAY_COUNT(gSaveContext.sohStats.sceneTimestamps), [](size_t i) { + SaveManager::Instance->SaveStruct("", [&i]() { + SaveManager::Instance->SaveData("scene", gSaveContext.sohStats.sceneTimestamps[i].scene); + SaveManager::Instance->SaveData("room", gSaveContext.sohStats.sceneTimestamps[i].room); + SaveManager::Instance->SaveData("sceneTime", gSaveContext.sohStats.sceneTimestamps[i].sceneTime); + SaveManager::Instance->SaveData("roomTime", gSaveContext.sohStats.sceneTimestamps[i].roomTime); + SaveManager::Instance->SaveData("isRoom", gSaveContext.sohStats.sceneTimestamps[i].isRoom); + }); + }); + SaveManager::Instance->SaveData("tsIdx", gSaveContext.sohStats.tsIdx); SaveManager::Instance->SaveArray("counts", ARRAY_COUNT(gSaveContext.sohStats.count), [](size_t i) { SaveManager::Instance->SaveData("", gSaveContext.sohStats.count[i]); }); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 488c9a320..f1a0eb36a 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1625,7 +1625,7 @@ void func_80084BF4(PlayState* play, u16 flag) { void GameplayStats_SetTimestamp(PlayState* play, u8 item) { // If we already have a timestamp for this item, do nothing - if (gSaveContext.sohStats.timestamp[item] != 0){ + if (gSaveContext.sohStats.itemTimestamp[item] != 0){ return; } // Use ITEM_KEY_BOSS only for Ganon's boss key - not any other boss keys @@ -1644,20 +1644,20 @@ void GameplayStats_SetTimestamp(PlayState* play, u8 item) { // Count any bottled item as a bottle if (item >= ITEM_BOTTLE && item <= ITEM_POE) { - if (gSaveContext.sohStats.timestamp[ITEM_BOTTLE] == 0) { - gSaveContext.sohStats.timestamp[ITEM_BOTTLE] = time; + if (gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] == 0) { + gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] = time; } return; } // Count any bombchu pack as bombchus if (item == ITEM_BOMBCHU || (item >= ITEM_BOMBCHUS_5 && item <= ITEM_BOMBCHUS_20)) { - if (gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] == 0) { - gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] = time; + if (gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] == 0) { + gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] = time; } return; } - gSaveContext.sohStats.timestamp[item] = time; + gSaveContext.sohStats.itemTimestamp[item] = time; } // Gameplay stat tracking: Update time the item was acquired @@ -1673,28 +1673,28 @@ void Randomizer_GameplayStats_SetTimestamp(uint16_t item) { // Use ITEM_KEY_BOSS to timestamp Ganon's boss key if (item == RG_GANONS_CASTLE_BOSS_KEY) { - gSaveContext.sohStats.timestamp[ITEM_KEY_BOSS] = time; + gSaveContext.sohStats.itemTimestamp[ITEM_KEY_BOSS] = time; } // Count any bottled item as a bottle if (item >= RG_EMPTY_BOTTLE && item <= RG_BOTTLE_WITH_BIG_POE) { - if (gSaveContext.sohStats.timestamp[ITEM_BOTTLE] == 0) { - gSaveContext.sohStats.timestamp[ITEM_BOTTLE] = time; + if (gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] == 0) { + gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] = time; } return; } // Count any bombchu pack as bombchus if (item >= RG_BOMBCHU_5 && item <= RG_BOMBCHU_DROP) { - if (gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] = 0) { - gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] = time; + if (gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] = 0) { + gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] = time; } return; } if (item == RG_MAGIC_SINGLE) { - gSaveContext.sohStats.timestamp[ITEM_SINGLE_MAGIC] = time; + gSaveContext.sohStats.itemTimestamp[ITEM_SINGLE_MAGIC] = time; } if (item == RG_DOUBLE_DEFENSE) { - gSaveContext.sohStats.timestamp[ITEM_DOUBLE_DEFENSE] = time; + gSaveContext.sohStats.itemTimestamp[ITEM_DOUBLE_DEFENSE] = time; } } @@ -2525,7 +2525,7 @@ u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { if (item == RG_GREG_RUPEE) { Rupees_ChangeBy(1); Flags_SetRandomizerInf(RAND_INF_GREG_FOUND); - gSaveContext.sohStats.timestamp[TIMESTAMP_FOUND_GREG] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_FOUND_GREG] = GAMEPLAYSTAT_TOTAL_TIME; return Return_Item_Entry(giEntry, RG_NONE); } diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 5d280ca0b..488207a97 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -647,6 +647,33 @@ void Play_Init(GameState* thisx) { gSaveContext.natureAmbienceId = play->sequenceCtx.natureAmbienceId; func_8002DF18(play, GET_PLAYER(play)); AnimationContext_Update(play, &play->animationCtx); + + if (gSaveContext.sohStats.sceneNum != gPlayState->sceneNum) { + u16 idx = gSaveContext.sohStats.tsIdx; + gSaveContext.sohStats.sceneTimestamps[idx].sceneTime = gSaveContext.sohStats.sceneTimer / 2; + gSaveContext.sohStats.sceneTimestamps[idx].roomTime = gSaveContext.sohStats.roomTimer / 2; + gSaveContext.sohStats.sceneTimestamps[idx].scene = gSaveContext.sohStats.sceneNum; + gSaveContext.sohStats.sceneTimestamps[idx].room = gSaveContext.sohStats.roomNum; + gSaveContext.sohStats.sceneTimestamps[idx].isRoom = + gPlayState->sceneNum == gSaveContext.sohStats.sceneTimestamps[idx].scene && + gPlayState->roomCtx.curRoom.num != gSaveContext.sohStats.sceneTimestamps[idx].room; + gSaveContext.sohStats.tsIdx++; + gSaveContext.sohStats.sceneTimer = 0; + gSaveContext.sohStats.roomTimer = 0; + } else if (gSaveContext.sohStats.roomNum != gPlayState->roomCtx.curRoom.num) { + u16 idx = gSaveContext.sohStats.tsIdx; + gSaveContext.sohStats.sceneTimestamps[idx].roomTime = gSaveContext.sohStats.roomTimer / 2; + gSaveContext.sohStats.sceneTimestamps[idx].scene = gSaveContext.sohStats.sceneNum; + gSaveContext.sohStats.sceneTimestamps[idx].room = gSaveContext.sohStats.roomNum; + gSaveContext.sohStats.sceneTimestamps[idx].isRoom = + gPlayState->sceneNum == gSaveContext.sohStats.sceneTimestamps[idx].scene && + gPlayState->roomCtx.curRoom.num != gSaveContext.sohStats.sceneTimestamps[idx].room; + gSaveContext.sohStats.tsIdx++; + gSaveContext.sohStats.roomTimer = 0; + } + + gSaveContext.sohStats.sceneNum = gPlayState->sceneNum; + gSaveContext.sohStats.roomNum = gPlayState->roomCtx.curRoom.num; gSaveContext.respawnFlag = 0; #if 0 if (dREG(95) != 0) { @@ -1085,6 +1112,8 @@ void Play_Update(PlayState* play) { // Gameplay stat tracking if (!gSaveContext.sohStats.gameComplete) { gSaveContext.sohStats.playTimer++; + gSaveContext.sohStats.sceneTimer++; + gSaveContext.sohStats.roomTimer++; if (CVarGetInteger("gMMBunnyHood", 0) && Player_GetMask(play) == PLAYER_MASK_BUNNY) { gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD]++; diff --git a/soh/src/code/z_room.c b/soh/src/code/z_room.c index 23fb6a4d7..59dea1ab3 100644 --- a/soh/src/code/z_room.c +++ b/soh/src/code/z_room.c @@ -641,11 +641,21 @@ void Room_Draw(PlayState* play, Room* room, u32 flags) { void func_80097534(PlayState* play, RoomContext* roomCtx) { roomCtx->prevRoom.num = -1; roomCtx->prevRoom.segment = NULL; - func_80031B14(play, &play->actorCtx); + func_80031B14(play, &play->actorCtx); //kills all actors without room num set to -1 Actor_SpawnTransitionActors(play, &play->actorCtx); Map_InitRoomData(play, roomCtx->curRoom.num); if (!((play->sceneNum >= SCENE_SPOT00) && (play->sceneNum <= SCENE_SPOT20))) { Map_SavePlayerInitialInfo(play); } Audio_SetEnvReverb(play->roomCtx.curRoom.echo); + u8 idx = gSaveContext.sohStats.tsIdx; + gSaveContext.sohStats.sceneTimestamps[idx].scene = gSaveContext.sohStats.sceneNum; + gSaveContext.sohStats.sceneTimestamps[idx].room = gSaveContext.sohStats.roomNum; + gSaveContext.sohStats.sceneTimestamps[idx].roomTime = gSaveContext.sohStats.roomTimer / 2; + gSaveContext.sohStats.sceneTimestamps[idx].isRoom = + gPlayState->sceneNum == gSaveContext.sohStats.sceneTimestamps[idx].scene && + gPlayState->roomCtx.curRoom.num != gSaveContext.sohStats.sceneTimestamps[idx].room; + gSaveContext.sohStats.tsIdx++; + gSaveContext.sohStats.roomNum = roomCtx->curRoom.num; + gSaveContext.sohStats.roomTimer = 0; } diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c index 255b0034f..b616a292f 100644 --- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c +++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c @@ -1330,7 +1330,7 @@ void BossDodongo_DeathCutscene(BossDodongo* this, PlayState* play) { this->cameraAt.x = camera->at.x; this->cameraAt.y = camera->at.y; this->cameraAt.z = camera->at.z; - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_KING_DODONGO] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_KING_DODONGO] = GAMEPLAYSTAT_TOTAL_TIME; break; case 5: tempSin = Math_SinS(this->actor.shape.rot.y - 0x1388) * 150.0f; diff --git a/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c b/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c index 15b3ba17b..a8577c2da 100644 --- a/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c +++ b/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c @@ -893,7 +893,7 @@ void BossFd2_CollisionCheck(BossFd2* this, PlayState* play) { Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF); Audio_PlayActorSound2(&this->actor, NA_SE_EN_VALVAISA_DEAD); Enemy_StartFinishingBlow(play, &this->actor); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_VOLVAGIA] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_VOLVAGIA] = GAMEPLAYSTAT_TOTAL_TIME; } else if (damage) { BossFd2_SetupDamaged(this, play); this->work[FD2_DAMAGE_FLASH_TIMER] = 10; diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c index f919628bd..413014834 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c @@ -2802,7 +2802,7 @@ void BossGanon_UpdateDamage(BossGanon* this, PlayState* play) { func_80078914(&sZeroVec, NA_SE_EN_LAST_DAMAGE); Audio_QueueSeqCmd(0x100100FF); this->screenFlashTimer = 4; - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_GANONDORF] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANONDORF] = GAMEPLAYSTAT_TOTAL_TIME; } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_DAMAGE2); Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_CUTBODY); diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c index bc0e4dd18..f4c239d6d 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c @@ -1680,7 +1680,7 @@ void func_8090120C(BossGanon2* this, PlayState* play) { (player->swordState != 0) && (player->heldItemAction == PLAYER_IA_SWORD_MASTER)) { func_80064520(play, &play->csCtx); gSaveContext.sohStats.gameComplete = true; - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; this->unk_39E = Play_CreateSubCamera(play); Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT); Play_ChangeCameraStatus(play, this->unk_39E, CAM_STAT_ACTIVE); diff --git a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c index 3d28dd16d..ee63f6a64 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c @@ -1280,7 +1280,7 @@ void BossGanondrof_CollisionCheck(BossGanondrof* this, PlayState* play) { if ((s8)this->actor.colChkInfo.health <= 0) { BossGanondrof_SetupDeath(this, play); Enemy_StartFinishingBlow(play, &this->actor); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME; return; } } diff --git a/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c b/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c index bcd29cf41..732e0b514 100644 --- a/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c +++ b/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c @@ -1841,7 +1841,7 @@ void BossGoma_UpdateHit(BossGoma* this, PlayState* play) { } else { BossGoma_SetupDefeated(this, play); Enemy_StartFinishingBlow(play, &this->actor); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_GOHMA] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GOHMA] = GAMEPLAYSTAT_TOTAL_TIME; } this->invincibilityFrames = 10; diff --git a/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c b/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c index cf9239862..3df8252fb 100644 --- a/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c +++ b/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c @@ -1786,7 +1786,7 @@ void BossMo_CoreCollisionCheck(BossMo* this, PlayState* play) { if (((sMorphaTent1->csCamera == 0) && (sMorphaTent2 == NULL)) || ((sMorphaTent1->csCamera == 0) && (sMorphaTent2 != NULL) && (sMorphaTent2->csCamera == 0))) { Enemy_StartFinishingBlow(play, &this->actor); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_MORPHA] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_MORPHA] = GAMEPLAYSTAT_TOTAL_TIME; Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF); this->csState = MO_DEATH_START; sMorphaTent1->drawActor = false; diff --git a/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c b/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c index b131d9af4..36781cb81 100644 --- a/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c +++ b/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c @@ -2557,7 +2557,7 @@ void BossSst_HeadCollisionCheck(BossSst* this, PlayState* play) { if (Actor_ApplyDamage(&this->actor) == 0) { Enemy_StartFinishingBlow(play, &this->actor); BossSst_HeadSetupDeath(this, play); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_BONGO_BONGO] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BONGO_BONGO] = GAMEPLAYSTAT_TOTAL_TIME; } else { BossSst_HeadSetupDamage(this); } diff --git a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c index d9b9ad6fb..8797a86f2 100644 --- a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c +++ b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c @@ -5285,7 +5285,7 @@ void BossTw_TwinrovaDamage(BossTw* this, PlayState* play, u8 damage) { BossTw_TwinrovaSetupDeathCS(this, play); Enemy_StartFinishingBlow(play, &this->actor); Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DEAD); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_TWINROVA] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_TWINROVA] = GAMEPLAYSTAT_TOTAL_TIME; return; } diff --git a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c index 1e9f8d48d..6e79660e7 100644 --- a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c +++ b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c @@ -1394,7 +1394,7 @@ void BossVa_BodyPhase4(BossVa* this, PlayState* play) { if (sFightPhase >= PHASE_DEATH) { BossVa_SetupBodyDeath(this, play); Enemy_StartFinishingBlow(play, &this->actor); - gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_BARINADE] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BARINADE] = GAMEPLAYSTAT_TOTAL_TIME; return; } this->actor.speedXZ = -10.0f;