diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index 2a3cb7bfc..d9adeb4b8 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -160,6 +160,7 @@ set(Header_Files__soh__Enhancements "soh/Enhancements/presets.h" "soh/Enhancements/savestates.h" "soh/Enhancements/savestates_extern.inc" + "soh/Enhancements/gameplaystats.h" ) source_group("Header Files\\soh\\Enhancements" FILES ${Header_Files__soh__Enhancements}) @@ -290,6 +291,7 @@ set(Source_Files__soh__Enhancements "soh/Enhancements/gameconsole.c" "soh/Enhancements/presets.cpp" "soh/Enhancements/savestates.cpp" + "soh/Enhancements/gameplaystats.cpp" ) source_group("Source Files\\soh\\Enhancements" FILES ${Source_Files__soh__Enhancements}) diff --git a/soh/include/global.h b/soh/include/global.h index 0fbb7b2f2..795da58d9 100644 --- a/soh/include/global.h +++ b/soh/include/global.h @@ -6,6 +6,7 @@ #include "macros.h" #include "soh/OTRGlobals.h" #include "soh/Enhancements/gameconsole.h" +#include "soh/Enhancements/gameplaystats.h" #include diff --git a/soh/include/z64save.h b/soh/include/z64save.h index b29e0e6cf..fddf9889a 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -6,6 +6,7 @@ #include "z64audio.h" #include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/Enhancements/randomizer/randomizer_inf.h" +#include "soh/Enhancements/gameplaystats.h" #include "soh/Enhancements/randomizer/randomizer_entrance.h" typedef struct { @@ -30,6 +31,11 @@ typedef struct { /* */ u8 heartPieces; /* */ u8 heartContainers; /* */ u8 dungeonKeys[19]; + /* */ u32 playTimer; + /* */ u32 pauseTimer; + /* */ bool gameComplete; + /* */ u32 timestamp[TIMESTAMP_MAX]; + /* */ u32 count[COUNT_MAX]; } SohStats; typedef struct { diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp new file mode 100644 index 000000000..ccdee19ec --- /dev/null +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -0,0 +1,464 @@ +#include "gameplaystats.h" + +#include "ImGuiImpl.h" +#include "../UIWidgets.hpp" + +#include +#include +#include +#include + +extern "C" { +#include +#include "variables.h" +} + +#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) +#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) + +char timestampDisplayName[TIMESTAMP_MAX][21] = { "" }; +ImVec4 timestampDisplayColor[TIMESTAMP_MAX]; + +typedef struct { + char name[21]; + u32 time; + ImVec4 color; +}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]; + +void DisplayTimeHHMMSS(uint32_t timeInTenthsOfSeconds, const char* text, ImVec4 color) { + + uint32_t sec = timeInTenthsOfSeconds / 10; + uint32_t hh = sec / 3600; + uint32_t mm = (sec - hh * 3600) / 60; + uint32_t ss = sec - hh * 3600 - mm * 60; + uint32_t ds = timeInTenthsOfSeconds % 10; + + ImGui::PushStyleColor(ImGuiCol_Text, color); + + ImGui::Text(text); + ImGui::SameLine(); + + // Hack to keep the timers aligned and prevent them from shifting around + // Put a leading zero in front of the seconds or minutes if they're less than 10 + if (mm < 10 && ss < 10) { + ImGui::Text("%u:0%u:0%u.%u", hh, mm, ss, ds); + } + if (mm < 10 && ss >= 10) { + ImGui::Text("%u:0%u:%u.%u", hh, mm, ss, ds); + } + if (mm >= 10 && ss < 10) { + ImGui::Text("%u:%u:0%u.%u", hh, mm, ss, ds); + } + if (mm >= 10 && ss >= 10) { + ImGui::Text("%u:%u:%u.%u", hh, mm, ss, ds); + } + + ImGui::PopStyleColor(); +} + +void SortChronological(TimestampInfo* arr, size_t len) { + TimestampInfo temp; + for (int i = 0; i < len; i++) { + for (int j = 0; j + 1 < len - i; j++) { + if (arr[j].time > arr[j + 1].time) { + temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } +} + +void DisplayStat(const char* text, uint32_t value) { + + ImGui::Text(text); + ImGui::SameLine(); + // Hack to keep the digits properly aligned in the column + if (value < 10) { + ImGui::Text(" %u", value); + } else if (value < 100) { + ImGui::Text(" %u", value); + } else if (value < 1000) { + ImGui::Text(" %u", value); + } else if (value < 10000) { + ImGui::Text(" %u", value); + } else if (value < 100000) { + ImGui::Text(" %u", value); + } else { + ImGui::Text("%u", value); + } +} + +void DisplayStatIfNonZero(const char* text, uint32_t value) { + if (value > 0) { + DisplayStat(text, value); + } + return; +} + +void DrawStatsTracker(bool& open) { + if (!open) { + CVar_SetS32("gGameplayStatsEnabled", 0); + return; + } + + ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing); + if (!ImGui::Begin("Gameplay Stats", &open, ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::End(); + return; + } + + u32 totalTimer = GAMEPLAYSTAT_TOTAL_TIME; + u32 enemiesDefeated = 0; + u32 ammoUsed = 0; + u32 buttonPresses = 0; + + // Sum of all enemies defeated + for (int i = COUNT_ENEMIES_DEFEATED_ANUBIS; i <= COUNT_ENEMIES_DEFEATED_WOLFOS; i++) { + if (i == COUNT_ENEMIES_DEFEATED_FLOORMASTER) { + // Special case: You must kill 3 mini Floormasters for it count as one defeated Floormaster + enemiesDefeated += gSaveContext.sohStats.count[i] / 3; + } else { + enemiesDefeated += gSaveContext.sohStats.count[i]; + } + } + // Sum of all ammo used + for (int i = COUNT_AMMO_USED_STICK; i <= COUNT_AMMO_USED_BEAN; i++) { + ammoUsed += gSaveContext.sohStats.count[i]; + } + // Sum of all button presses + 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 + 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]; + } + SortChronological(timestampDisplay, sizeof(timestampDisplay) / sizeof(timestampDisplay[0])); + + + // Begin drawing the table and showing the stats + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); + ImGui::BeginTable("timers", 1, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); + ImGui::TableSetupColumn("Timers", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableNextColumn(); + + DisplayTimeHHMMSS(totalTimer, "Total Game Time: ", COLOR_WHITE); + UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading."); + 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); + + ImGui::PopStyleVar(1); + ImGui::EndTable(); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); + ImGui::BeginTable("gameStatsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); + + ImGui::TableSetupColumn("Timestamps", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableSetupColumn("Counts", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + 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); + } + } + + 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("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]); + + 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]); + DisplayStat("Rolls: ", gSaveContext.sohStats.count[COUNT_ROLLS]); + DisplayStat("Bonks: ", gSaveContext.sohStats.count[COUNT_BONKS]); + 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::PopStyleVar(1); + ImGui::EndTable(); + + ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving."); + + ImGui::End(); +} + +// Entries listed here will have a timestamp shown in the stat window +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_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_SINGLE_MAGIC], "Magic: "); + strcpy(timestampDisplayName[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: "); +} + +void SetupDisplayColors() { + for (int i = 0; i < TIMESTAMP_MAX; i++) { + switch (i) { + case ITEM_SONG_MINUET: + case ITEM_KOKIRI_EMERALD: + case ITEM_SONG_SARIA: + case ITEM_MEDALLION_FOREST: + timestampDisplayColor[i] = COLOR_GREEN; + break; + case ITEM_SONG_BOLERO: + case ITEM_GORON_RUBY: + case ITEM_MEDALLION_FIRE: + timestampDisplayColor[i] = COLOR_RED; + break; + case ITEM_SONG_SERENADE: + case ITEM_ZORA_SAPPHIRE: + case ITEM_MEDALLION_WATER: + timestampDisplayColor[i] = COLOR_BLUE; + break; + case ITEM_SONG_LULLABY: + case ITEM_SONG_NOCTURNE: + case ITEM_MEDALLION_SHADOW: + timestampDisplayColor[i] = COLOR_PURPLE; + break; + case ITEM_SONG_EPONA: + case ITEM_SONG_REQUIEM: + case ITEM_MEDALLION_SPIRIT: + timestampDisplayColor[i] = COLOR_ORANGE; + break; + case ITEM_SONG_SUN: + case ITEM_SONG_PRELUDE: + case ITEM_MEDALLION_LIGHT: + case ITEM_ARROW_LIGHT: + timestampDisplayColor[i] = COLOR_YELLOW; + break; + case ITEM_SONG_STORMS: + timestampDisplayColor[i] = COLOR_GREY; + break; + case ITEM_SONG_TIME: + timestampDisplayColor[i] = COLOR_LIGHT_BLUE; + break; + default: + timestampDisplayColor[i] = COLOR_WHITE; + break; + } + } +} + +void InitStatTracker() { + SohImGui::AddWindow("Enhancements", "Gameplay Stats", DrawStatsTracker); + SetupDisplayNames(); + SetupDisplayColors(); +} \ No newline at end of file diff --git a/soh/soh/Enhancements/gameplaystats.h b/soh/soh/Enhancements/gameplaystats.h new file mode 100644 index 000000000..dc0cfaa38 --- /dev/null +++ b/soh/soh/Enhancements/gameplaystats.h @@ -0,0 +1,138 @@ +#pragma once + +// Total gameplay time is tracked in tenths of seconds +// I.E. game time counts frames at 20fps/2, pause time counts frames at 30fps/3 +// Frame counts in z_play.c and z_kaleido_scope_call.c +#define GAMEPLAYSTAT_TOTAL_TIME (gSaveContext.sohStats.playTimer / 2 + gSaveContext.sohStats.pauseTimer / 3) + +void InitStatTracker(); + +typedef enum { + // 0x00 to 0x9B (0 to 155) used for getting items, + // piggybacked off enum "ItemID" in z64item.h + + /* 0xA0 */ TIMESTAMP_DEFEAT_GOHMA = 0xA0, // z_boss_goma.c + /* 0xA1 */ TIMESTAMP_DEFEAT_KING_DODONGO, // z_boss_dodongo.c + /* 0xA2 */ TIMESTAMP_DEFEAT_BARINADE, // z_boss_va.c + /* 0xA3 */ TIMESTAMP_DEFEAT_PHANTOM_GANON, // z_boss_ganondrof.c + /* 0xA4 */ TIMESTAMP_DEFEAT_VOLVAGIA, // z_boss_fd2.c + /* 0xA5 */ TIMESTAMP_DEFEAT_MORPHA, // z_boss_mo.c + /* 0xA6 */ TIMESTAMP_DEFEAT_BONGO_BONGO, // z_boss_sst.c + /* 0xA7 */ TIMESTAMP_DEFEAT_TWINROVA, // z_boss_tw.c + /* 0xA8 */ TIMESTAMP_DEFEAT_GANONDORF, // z_boss_ganon.c + /* 0xA9 */ TIMESTAMP_DEFEAT_GANON, // z_boss_ganon2.c + /* 0xAA */ TIMESTAMP_MAX + +}GameplayStatTimestamp; + +typedef enum { + // Enemies defeated + COUNT_ENEMIES_DEFEATED_ANUBIS, // EN_ANUBICE + COUNT_ENEMIES_DEFEATED_ARMOS, // EN_AM + COUNT_ENEMIES_DEFEATED_BARI, // EN_VALI + COUNT_ENEMIES_DEFEATED_BEAMOS, // EN_VM + COUNT_ENEMIES_DEFEATED_BIG_OCTO, // EN_BIGOKUTA + COUNT_ENEMIES_DEFEATED_BIRI, // EN_BILI + COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN, // EN_BB + COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE, // EN_BB + COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE, // EN_BB + COUNT_ENEMIES_DEFEATED_BUBBLE_RED, // EN_BB + COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB, // EN_DNS + COUNT_ENEMIES_DEFEATED_DARK_LINK, // EN_TORCH2 + COUNT_ENEMIES_DEFEATED_DEAD_HAND, // EN_DH + COUNT_ENEMIES_DEFEATED_DEKU_BABA, // EN_DEKUBABA + COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG, // EN_DEKUBABA + COUNT_ENEMIES_DEFEATED_DEKU_SCRUB, // EN_HINTNUTS + COUNT_ENEMIES_DEFEATED_DINOLFOS, // EN_ZF + COUNT_ENEMIES_DEFEATED_DODONGO, // EN_DODONGO + COUNT_ENEMIES_DEFEATED_DODONGO_BABY, // EN_DODOJR + COUNT_ENEMIES_DEFEATED_DOOR_TRAP, // DOOR_KILLER + COUNT_ENEMIES_DEFEATED_FLARE_DANCER, // EN_FD + COUNT_ENEMIES_DEFEATED_FLOORMASTER, // EN_FLOORMAS + COUNT_ENEMIES_DEFEATED_FLYING_POT, // EN_TUBO_TRAP + COUNT_ENEMIES_DEFEATED_FLOOR_TILE, // EN_YUKABYUN + COUNT_ENEMIES_DEFEATED_FREEZARD, // EN_FZ + COUNT_ENEMIES_DEFEATED_GERUDO_THIEF, // EN_GELDB + COUNT_ENEMIES_DEFEATED_GIBDO, // EN_RD + COUNT_ENEMIES_DEFEATED_GOHMA_LARVA, // EN_GOMA + COUNT_ENEMIES_DEFEATED_GUAY, // EN_CROW + COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE, // EN_IK + COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU, // EN_IK + COUNT_ENEMIES_DEFEATED_KEESE, // EN_FIREFLY + COUNT_ENEMIES_DEFEATED_KEESE_FIRE, // EN_FIREFLY + COUNT_ENEMIES_DEFEATED_KEESE_ICE, // EN_FIREFLY + COUNT_ENEMIES_DEFEATED_LEEVER, // EN_REEBA + COUNT_ENEMIES_DEFEATED_LEEVER_BIG, // EN_REEBA + COUNT_ENEMIES_DEFEATED_LIKE_LIKE, // EN_RR + COUNT_ENEMIES_DEFEATED_LIZALFOS, // EN_ZF + COUNT_ENEMIES_DEFEATED_MAD_SCRUB, // EN_DEKUNUTS + COUNT_ENEMIES_DEFEATED_MOBLIN, // EN_MB + COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB, // EN_MB + COUNT_ENEMIES_DEFEATED_OCTOROK, // EN_OKUTA + COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE, // EN_BA + COUNT_ENEMIES_DEFEATED_PEAHAT, // EN_PEEHAT + COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA, // EN_PEEHAT + COUNT_ENEMIES_DEFEATED_POE, // EN_POH + COUNT_ENEMIES_DEFEATED_POE_BIG, // EN_PO_FIELD + COUNT_ENEMIES_DEFEATED_POE_COMPOSER, // EN_POH + COUNT_ENEMIES_DEFEATED_POE_SISTERS, // EN_PO_SISTERS + COUNT_ENEMIES_DEFEATED_REDEAD, // EN_RD + COUNT_ENEMIES_DEFEATED_SHABOM, // EN_BUBBLE + COUNT_ENEMIES_DEFEATED_SHELLBLADE, // EN_SB + COUNT_ENEMIES_DEFEATED_SKULLTULA, // EN_ST + COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG, // EN_ST + COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD, // EN_SW + COUNT_ENEMIES_DEFEATED_SKULLWALLTULA, // EN_SW + COUNT_ENEMIES_DEFEATED_SKULL_KID, // EN_SKJ + COUNT_ENEMIES_DEFEATED_SPIKE, // EN_NY + COUNT_ENEMIES_DEFEATED_STALCHILD, // EN_SKB + COUNT_ENEMIES_DEFEATED_STALFOS, // EN_TEST + COUNT_ENEMIES_DEFEATED_STINGER, // EN_WEIYER + COUNT_ENEMIES_DEFEATED_TAILPASARAN, // EN_TP + COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE, // EN_TITE + COUNT_ENEMIES_DEFEATED_TEKTITE_RED, // EN_TITE + COUNT_ENEMIES_DEFEATED_TORCH_SLUG, // EN_BW + COUNT_ENEMIES_DEFEATED_WALLMASTER, // EN_WALLMAS + COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA, // EN_KAREBABA + COUNT_ENEMIES_DEFEATED_WOLFOS, // EN_WF + COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE, // EN_WF + // Ammo used (z_parameter.c) + COUNT_AMMO_USED_STICK, + COUNT_AMMO_USED_NUT, + COUNT_AMMO_USED_BOMB, + COUNT_AMMO_USED_ARROW, + COUNT_AMMO_USED_SEED, + COUNT_AMMO_USED_BOMBCHU, + COUNT_AMMO_USED_BEAN, + // Buttons pressed (z_play.c) + COUNT_BUTTON_PRESSES_A, + COUNT_BUTTON_PRESSES_B, + COUNT_BUTTON_PRESSES_L, + COUNT_BUTTON_PRESSES_R, + COUNT_BUTTON_PRESSES_Z, + COUNT_BUTTON_PRESSES_CUP, + COUNT_BUTTON_PRESSES_CRIGHT, + COUNT_BUTTON_PRESSES_CDOWN, + COUNT_BUTTON_PRESSES_CLEFT, + COUNT_BUTTON_PRESSES_DUP, + COUNT_BUTTON_PRESSES_DRIGHT, + COUNT_BUTTON_PRESSES_DDOWN, + COUNT_BUTTON_PRESSES_DLEFT, + COUNT_BUTTON_PRESSES_START, + // Other counts + COUNT_RUPEES_COLLECTED, // z_parameter.c + COUNT_RUPEES_SPENT, // z_parameter.c + COUNT_CHESTS_OPENED, // z_en_box.c + COUNT_DAMAGE_TAKEN, // z_parameter.c + COUNT_ICE_TRAPS, // z_player.c + COUNT_ROLLS, // z_player.c + COUNT_BONKS, // z_player.c + COUNT_PAUSES, // z_kaleido_scope_call.c + COUNT_STEPS, // z_player.c + COUNT_POTS_BROKEN, // z_obj_tsubo.c + COUNT_BUSHES_CUT, // z_en_kusa.c + COUNT_SWORD_SWINGS, // z_player.c + + COUNT_MAX + +} GameplayStatCount; \ No newline at end of file diff --git a/soh/soh/GameMenuBar.cpp b/soh/soh/GameMenuBar.cpp index 400c49831..ae71f2711 100644 --- a/soh/soh/GameMenuBar.cpp +++ b/soh/soh/GameMenuBar.cpp @@ -802,6 +802,12 @@ namespace GameMenuBar { SohImGui::RequestCvarSaveOnNextTick(); SohImGui::EnableWindow("SFX Editor", CVar_GetS32("gSfxEditor", 0)); } + if (ImGui::Button(GetWindowButtonText("Gameplay Stats", CVar_GetS32("gGameplayStatsEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + bool currentValue = CVar_GetS32("gGameplayStatsEnabled", 0); + CVar_SetS32("gGameplayStatsEnabled", !currentValue); + SohImGui::RequestCvarSaveOnNextTick(); + SohImGui::EnableWindow("Gameplay Stats", CVar_GetS32("gGameplayStatsEnabled", 0)); + } ImGui::PopStyleVar(3); ImGui::PopStyleColor(1); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index bccc00e71..bcbe81c21 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -39,6 +39,7 @@ #include "Enhancements/randomizer/randomizer.h" #include "Enhancements/randomizer/randomizer_item_tracker.h" #include "Enhancements/randomizer/3drando/random.hpp" +#include "Enhancements/gameplaystats.h" #include "Enhancements/n64_weird_frame_data.inc" #include "frame_interpolation.h" #include "variables.h" @@ -438,6 +439,7 @@ extern "C" void InitOTR() { Debug_Init(); Rando_Init(); InitItemTracker(); + InitStatTracker(); OTRExtScanner(); VanillaItemTable_Init(); diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 0fcc78122..70a14824d 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -439,6 +439,15 @@ void SaveManager::InitFileNormal() { for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) { gSaveContext.sohStats.dungeonKeys[dungeon] = 0; } + 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 count = 0; count < ARRAY_COUNT(gSaveContext.sohStats.count); count++) { + gSaveContext.sohStats.count[count] = 0; + } + gSaveContext.sohStats.gameComplete = false; for (int scene = 0; scene < ARRAY_COUNT(gSaveContext.sceneFlags); scene++) { gSaveContext.sceneFlags[scene].chest = 0; gSaveContext.sceneFlags[scene].swch = 0; @@ -989,6 +998,14 @@ void SaveManager::LoadBaseVersion2() { SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]); }); + 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("counts", ARRAY_COUNT(gSaveContext.sohStats.count), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.count[i]); + }); }); SaveManager::Instance->LoadArray("sceneFlags", ARRAY_COUNT(gSaveContext.sceneFlags), [](size_t i) { SaveManager::Instance->LoadStruct("", [&i]() { @@ -1150,6 +1167,14 @@ void SaveManager::SaveBase() { SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) { SaveManager::Instance->SaveData("", gSaveContext.sohStats.dungeonKeys[i]); }); + 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("counts", ARRAY_COUNT(gSaveContext.sohStats.count), [](size_t i) { + SaveManager::Instance->SaveData("", gSaveContext.sohStats.count[i]); + }); }); SaveManager::Instance->SaveArray("sceneFlags", ARRAY_COUNT(gSaveContext.sceneFlags), [](size_t i) { SaveManager::Instance->SaveStruct("", [&i]() { diff --git a/soh/src/code/z_kaleido_scope_call.c b/soh/src/code/z_kaleido_scope_call.c index 81e442d8a..5d1f6937d 100644 --- a/soh/src/code/z_kaleido_scope_call.c +++ b/soh/src/code/z_kaleido_scope_call.c @@ -56,6 +56,10 @@ void KaleidoScopeCall_Update(PlayState* play) { KaleidoMgrOverlay* kaleidoScopeOvl = &gKaleidoMgrOverlayTable[KALEIDO_OVL_KALEIDO_SCOPE]; PauseContext* pauseCtx = &play->pauseCtx; + if (!gSaveContext.sohStats.gameComplete) { + gSaveContext.sohStats.pauseTimer++; + } + if ((pauseCtx->state != 0) || (pauseCtx->debugState != 0)) { if (pauseCtx->state == 1) { if (ShrinkWindow_GetCurrentVal() == 0) { @@ -65,6 +69,7 @@ void KaleidoScopeCall_Update(PlayState* play) { pauseCtx->unk_1E4 = 0; pauseCtx->unk_1EC = 0; pauseCtx->state = (pauseCtx->state & 0xFFFF) + 1; + gSaveContext.sohStats.count[COUNT_PAUSES]++; } } else if (pauseCtx->state == 8) { HREG(80) = 7; diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 0c3afc651..f7af5b728 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1614,6 +1614,72 @@ void func_80084BF4(PlayState* play, u16 flag) { } } +// Gameplay stat tracking: Update time the item was acquired +// (special cases for some duplicate items) +void GameplayStats_SetTimestamp(u8 item) { + + if (gSaveContext.sohStats.timestamp[item] != 0) { + return; + } + + u32 time = GAMEPLAYSTAT_TOTAL_TIME; + + // Have items in Link's pocket shown as being obtained at 0.1 seconds + if (time == 0) { + time = 1; + } + + // 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; + } + 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; + } + return; + } + + gSaveContext.sohStats.timestamp[item] = time; +} + +// Gameplay stat tracking: Update time the item was acquired +// (special cases for rando items) +void Randomizer_GameplayStats_SetTimestamp(uint16_t item) { + + u32 time = GAMEPLAYSTAT_TOTAL_TIME; + + // Have items in Link's pocket shown as being obtained at 0.1 seconds + if (time == 0) { + time = 1; + } + + // 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; + } + 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; + } + return; + } + if (item == RG_MAGIC_SINGLE) { + gSaveContext.sohStats.timestamp[ITEM_SINGLE_MAGIC] = time; + } + if (item == RG_DOUBLE_DEFENSE) { + gSaveContext.sohStats.timestamp[ITEM_DOUBLE_DEFENSE] = time; + } +} + /** * @brief Adds the given item to Link's inventory. * @@ -1631,6 +1697,9 @@ u8 Item_Give(PlayState* play, u8 item) { s16 slot; s16 temp; + // Gameplay stats: Update the time the item was obtained + GameplayStats_SetTimestamp(item); + slot = SLOT(item); if (item >= ITEM_STICKS_5) { slot = SLOT(sExtraItemBases[item - ITEM_STICKS_5]); @@ -2291,6 +2360,9 @@ u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { uint16_t i; uint16_t slot; + // Gameplay stats: Update the time the item was obtained + Randomizer_GameplayStats_SetTimestamp(item); + slot = SLOT(item); if (item == RG_MAGIC_SINGLE) { gSaveContext.magicAcquired = true; @@ -2975,6 +3047,10 @@ s32 Health_ChangeBy(PlayState* play, s16 healthChange) { osSyncPrintf("***** 増減=%d (now=%d, max=%d) ***", healthChange, gSaveContext.health, gSaveContext.healthCapacity); + if (healthChange < 0) { + gSaveContext.sohStats.count[COUNT_DAMAGE_TAKEN] += -healthChange; + } + // If one-hit ko mode is on, any damage kills you and you cannot gain health. if (chaosEffectOneHitKO) { if (healthChange < 0) { @@ -3041,6 +3117,43 @@ void Health_RemoveHearts(s16 hearts) { void Rupees_ChangeBy(s16 rupeeChange) { gSaveContext.rupeeAccumulator += rupeeChange; + + if (rupeeChange > 0) { + gSaveContext.sohStats.count[COUNT_RUPEES_COLLECTED] += rupeeChange; + } + if (rupeeChange < 0) { + gSaveContext.sohStats.count[COUNT_RUPEES_SPENT] += -rupeeChange; + } +} + +void GameplayStats_UpdateAmmoUsed(s16 item, s16 ammoUsed) { + + switch (item) { + case ITEM_STICK: + gSaveContext.sohStats.count[COUNT_AMMO_USED_STICK] += ammoUsed; + break; + case ITEM_NUT: + gSaveContext.sohStats.count[COUNT_AMMO_USED_NUT] += ammoUsed; + break; + case ITEM_BOMB: + gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMB] += ammoUsed; + break; + case ITEM_BOW: + gSaveContext.sohStats.count[COUNT_AMMO_USED_ARROW] += ammoUsed; + break; + case ITEM_SLINGSHOT: + gSaveContext.sohStats.count[COUNT_AMMO_USED_SEED] += ammoUsed; + break; + case ITEM_BOMBCHU: + gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMBCHU] += ammoUsed; + break; + case ITEM_BEAN: + gSaveContext.sohStats.count[COUNT_AMMO_USED_BEAN] += ammoUsed; + break; + default: + break; + } + return; } void Inventory_ChangeAmmo(s16 item, s16 ammoChange) { @@ -3100,6 +3213,10 @@ void Inventory_ChangeAmmo(s16 item, s16 ammoChange) { } osSyncPrintf("合計 = (%d)\n", AMMO(item)); // "Total = (%d)" + + if (ammoChange < 0) { + GameplayStats_UpdateAmmoUsed(item, -ammoChange); + } } void Magic_Fill(PlayState* play) { diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 0203c7ca2..2cd647c79 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -699,6 +699,24 @@ void Play_Update(PlayState* play) { play->transitionMode = 1; } + // Gameplay stats: Count button presses + if (!gSaveContext.sohStats.gameComplete) { + if (CHECK_BTN_ALL(input[0].press.button, BTN_A)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_A]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_B)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_B]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_CUP)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CUP]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_CRIGHT)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CLEFT]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_CLEFT)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CDOWN]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_CDOWN)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CRIGHT]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_DUP)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DUP]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_DRIGHT)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DRIGHT]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_DDOWN)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DDOWN]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_DLEFT)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DLEFT]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_L)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_L]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_R)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_R]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_Z)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_Z]++;} + if (CHECK_BTN_ALL(input[0].press.button, BTN_START)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_START]++;} + } + if (gTrnsnUnkState != 0) { switch (gTrnsnUnkState) { case 2: @@ -1066,6 +1084,10 @@ void Play_Update(PlayState* play) { } play->gameplayFrames++; + // Gameplay stat tracking + if (!gSaveContext.sohStats.gameComplete) { + gSaveContext.sohStats.playTimer++; + } func_800AA178(1); 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 74ae077e3..3781df076 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,6 +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; 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 b17081425..a13c13d45 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,6 +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; } 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 e2e54d1a2..1f00f27a7 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 @@ -2754,6 +2754,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; } 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 c5d2b23fc..421826f95 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 @@ -1670,6 +1670,8 @@ void func_8090120C(BossGanon2* this, PlayState* play) { if ((ABS(temp_a0_2) < 0x2000) && (sqrtf(SQ(temp_f14) + SQ(temp_f12)) < 70.0f) && (player->swordState != 0) && (player->heldItemActionParam == PLAYER_AP_SWORD_MASTER)) { func_80064520(play, &play->csCtx); + gSaveContext.sohStats.gameComplete = true; + gSaveContext.sohStats.timestamp[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 71889cdc7..dc0f2fd5c 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,6 +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; 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 5bafed742..97d354ac0 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 @@ -1832,6 +1832,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; } 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 3df60d385..1d7256dd2 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 @@ -1780,6 +1780,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; 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 a3d35b1d0..83680e0e0 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 @@ -2548,6 +2548,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; } 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 c09dd9fb5..2f5843ffb 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,6 +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; 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 d351632a6..e78925d7f 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,6 +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; return; } this->actor.speedXZ = -10.0f; diff --git a/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c b/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c index 149f57dad..93b12f27a 100644 --- a/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c +++ b/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c @@ -268,6 +268,7 @@ void DoorKiller_Die(DoorKiller* this, PlayState* play) { Flags_SetSwitch(play, switchFlag); } Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DOOR_TRAP]++; } /** diff --git a/soh/src/overlays/actors/ovl_En_Am/z_en_am.c b/soh/src/overlays/actors/ovl_En_Am/z_en_am.c index 27b4b3769..d5bdab432 100644 --- a/soh/src/overlays/actors/ovl_En_Am/z_en_am.c +++ b/soh/src/overlays/actors/ovl_En_Am/z_en_am.c @@ -883,6 +883,8 @@ void EnAm_Update(Actor* thisx, PlayState* play) { dustPosScale += 60.0f; } + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARMOS]++; + Actor_Kill(&this->dyna.actor); return; } diff --git a/soh/src/overlays/actors/ovl_En_Anubice/z_en_anubice.c b/soh/src/overlays/actors/ovl_En_Anubice/z_en_anubice.c index 2042d906a..fc07ed0cb 100644 --- a/soh/src/overlays/actors/ovl_En_Anubice/z_en_anubice.c +++ b/soh/src/overlays/actors/ovl_En_Anubice/z_en_anubice.c @@ -312,6 +312,7 @@ void EnAnubice_SetupDie(EnAnubice* this, PlayState* play) { } this->actionFunc = EnAnubice_Die; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ANUBIS]++; } void EnAnubice_Die(EnAnubice* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Ba/z_en_ba.c b/soh/src/overlays/actors/ovl_En_Ba/z_en_ba.c index 449b4a4c5..dc1ec1a4f 100644 --- a/soh/src/overlays/actors/ovl_En_Ba/z_en_ba.c +++ b/soh/src/overlays/actors/ovl_En_Ba/z_en_ba.c @@ -455,6 +455,7 @@ void EnBa_Update(Actor* thisx, PlayState* play) { this->actor.colChkInfo.health--; if (this->actor.colChkInfo.health == 0) { func_809B75A0(this, play); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE]++; } else { func_809B7174(this); } diff --git a/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c b/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c index 16aebb71c..f29b1e9f5 100644 --- a/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c +++ b/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c @@ -462,6 +462,19 @@ void EnBb_SetupDeath(EnBb* this, PlayState* play) { } this->action = BB_KILL; EnBb_SetupAction(this, EnBb_Death); + + if (this->actor.params == ENBB_GREEN || this->actor.params == ENBB_GREEN_BIG) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN]++; + } + if (this->actor.params == ENBB_BLUE) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE]++; + } + if (this->actor.params == ENBB_WHITE) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE]++; + } + if (this->actor.params == ENBB_RED) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_RED]++; + } } void EnBb_Death(EnBb* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c b/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c index d5de281a6..dcea1c0b8 100644 --- a/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c +++ b/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c @@ -646,6 +646,7 @@ void func_809BE26C(EnBigokuta* this, PlayState* play) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 50, NA_SE_EN_OCTAROCK_BUBLE); Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0xB0); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIG_OCTO]++; } } } diff --git a/soh/src/overlays/actors/ovl_En_Bili/z_en_bili.c b/soh/src/overlays/actors/ovl_En_Bili/z_en_bili.c index 7174497e7..cb6770c96 100644 --- a/soh/src/overlays/actors/ovl_En_Bili/z_en_bili.c +++ b/soh/src/overlays/actors/ovl_En_Bili/z_en_bili.c @@ -231,6 +231,7 @@ void EnBili_SetupDie(EnBili* this) { this->actor.flags &= ~ACTOR_FLAG_0; this->actionFunc = EnBili_Die; this->actor.speedXZ = 0.0f; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIRI]++; } /** diff --git a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c index 734f3af7e..73a1dc2a7 100644 --- a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c +++ b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c @@ -532,6 +532,7 @@ void EnBox_Open(EnBox* this, PlayState* play) { if (Animation_OnFrame(&this->skelanime, 30.0f)) { sfxId = NA_SE_EV_TBOX_UNLOCK; + gSaveContext.sohStats.count[COUNT_CHESTS_OPENED]++; } else if (Animation_OnFrame(&this->skelanime, 90.0f)) { sfxId = NA_SE_EV_TBOX_OPEN; } diff --git a/soh/src/overlays/actors/ovl_En_Bubble/z_en_bubble.c b/soh/src/overlays/actors/ovl_En_Bubble/z_en_bubble.c index 84d602202..fc9546a26 100644 --- a/soh/src/overlays/actors/ovl_En_Bubble/z_en_bubble.c +++ b/soh/src/overlays/actors/ovl_En_Bubble/z_en_bubble.c @@ -372,6 +372,7 @@ void EnBubble_Pop(EnBubble* this, PlayState* play) { if (EnBubble_Explosion(this, play) >= 0) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 60, NA_SE_EN_AWA_BREAK); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHABOM]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Bw/z_en_bw.c b/soh/src/overlays/actors/ovl_En_Bw/z_en_bw.c index 2b303d7ec..e8e9d792e 100644 --- a/soh/src/overlays/actors/ovl_En_Bw/z_en_bw.c +++ b/soh/src/overlays/actors/ovl_En_Bw/z_en_bw.c @@ -575,6 +575,7 @@ void func_809D00F4(EnBw* this) { this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_BUBLEWALK_DEAD); EnBw_SetupAction(this, func_809D014C); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TORCH_SLUG]++; } void func_809D014C(EnBw* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c b/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c index fd7c1e811..e7160ca15 100644 --- a/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c +++ b/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c @@ -189,6 +189,7 @@ void EnCrow_SetupDamaged(EnCrow* this, PlayState* play) { void EnCrow_SetupDie(EnCrow* this) { this->actor.colorFilterTimer = 0; this->actionFunc = EnCrow_Die; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GUAY]++; } void EnCrow_SetupTurnAway(EnCrow* this) { diff --git a/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c b/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c index e87704671..1ee5e144c 100644 --- a/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c +++ b/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c @@ -406,6 +406,12 @@ void EnDekubaba_SetupPrunedSomersault(EnDekubaba* this) { this->actor.speedXZ = this->size * 3.0f; this->actor.flags |= ACTOR_FLAG_4 | ACTOR_FLAG_5; this->actionFunc = EnDekubaba_PrunedSomersault; + + if (this->actor.params == DEKUBABA_BIG) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]++; + } } void EnDekubaba_SetupShrinkDie(EnDekubaba* this) { @@ -413,6 +419,12 @@ void EnDekubaba_SetupShrinkDie(EnDekubaba* this) { 0.0f, ANIMMODE_ONCE, -3.0f); this->collider.base.acFlags &= ~AC_ON; this->actionFunc = EnDekubaba_ShrinkDie; + + if (this->actor.params == DEKUBABA_BIG) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]++; + } } void EnDekubaba_SetupStunnedVertical(EnDekubaba* this) { diff --git a/soh/src/overlays/actors/ovl_En_Dekunuts/z_en_dekunuts.c b/soh/src/overlays/actors/ovl_En_Dekunuts/z_en_dekunuts.c index e468ec7e8..684bf7cd5 100644 --- a/soh/src/overlays/actors/ovl_En_Dekunuts/z_en_dekunuts.c +++ b/soh/src/overlays/actors/ovl_En_Dekunuts/z_en_dekunuts.c @@ -233,6 +233,7 @@ void EnDekunuts_SetupDie(EnDekunuts* this) { this->actionFunc = EnDekunuts_Die; this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_NUTS_DEAD); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MAD_SCRUB]++; } void EnDekunuts_Wait(EnDekunuts* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Dh/z_en_dh.c b/soh/src/overlays/actors/ovl_En_Dh/z_en_dh.c index a183d1129..c27374ee1 100644 --- a/soh/src/overlays/actors/ovl_En_Dh/z_en_dh.c +++ b/soh/src/overlays/actors/ovl_En_Dh/z_en_dh.c @@ -440,6 +440,7 @@ void EnDh_SetupDeath(EnDh* this) { this->actor.params = ENDH_DEATH; Audio_PlayActorSound2(&this->actor, NA_SE_EN_DEADHAND_DEAD); EnDh_SetupAction(this, EnDh_Death); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEAD_HAND]++; } void EnDh_Death(EnDh* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c index 6f77ee380..05485d911 100644 --- a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c +++ b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c @@ -504,6 +504,7 @@ void EnDns_Burrow(EnDns* this, PlayState* play) { } } Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Dodojr/z_en_dodojr.c b/soh/src/overlays/actors/ovl_En_Dodojr/z_en_dodojr.c index 5e4c6907f..ccf761d2e 100644 --- a/soh/src/overlays/actors/ovl_En_Dodojr/z_en_dodojr.c +++ b/soh/src/overlays/actors/ovl_En_Dodojr/z_en_dodojr.c @@ -183,6 +183,7 @@ void func_809F6A20(EnDodojr* this) { this->unk_1FC = 3; this->actor.velocity.y = 10.0f; } + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO_BABY]++; } void func_809F6AC4(EnDodojr* this) { diff --git a/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c b/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c index 406684ca2..02e639ad1 100644 --- a/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c +++ b/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c @@ -666,6 +666,7 @@ void EnDodongo_SetupDeath(EnDodongo* this, PlayState* play) { this->actor.flags &= ~ACTOR_FLAG_0; this->actor.speedXZ = 0.0f; EnDodongo_SetupAction(this, EnDodongo_Death); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO]++; } void EnDodongo_Death(EnDodongo* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Eiyer/z_en_eiyer.c b/soh/src/overlays/actors/ovl_En_Eiyer/z_en_eiyer.c index 6822ca6b7..f3ad3a4f5 100644 --- a/soh/src/overlays/actors/ovl_En_Eiyer/z_en_eiyer.c +++ b/soh/src/overlays/actors/ovl_En_Eiyer/z_en_eiyer.c @@ -611,6 +611,7 @@ void EnEiyer_UpdateDamage(EnEiyer* this, PlayState* play) { Enemy_StartFinishingBlow(play, &this->actor); Audio_PlayActorSound2(&this->actor, NA_SE_EN_EIER_DEAD); this->actor.flags &= ~ACTOR_FLAG_0; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STINGER]++; } // If underground, one hit kill diff --git a/soh/src/overlays/actors/ovl_En_Fd/z_en_fd.c b/soh/src/overlays/actors/ovl_En_Fd/z_en_fd.c index 977fc1662..acc923a6e 100644 --- a/soh/src/overlays/actors/ovl_En_Fd/z_en_fd.c +++ b/soh/src/overlays/actors/ovl_En_Fd/z_en_fd.c @@ -646,6 +646,7 @@ void EnFd_WaitForCore(EnFd* this, PlayState* play) { } else if (this->actor.params & FLG_COREDEAD) { this->actor.params = 0; this->spinTimer = 30; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLARE_DANCER]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c b/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c index 17668ff32..ae5e989a7 100644 --- a/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c +++ b/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c @@ -223,6 +223,15 @@ void EnFirefly_SetupDie(EnFirefly* this) { this->timer = 15; this->actor.speedXZ = 0.0f; this->actionFunc = EnFirefly_Die; + if (this->actor.params == KEESE_NORMAL_FLY || this->actor.params == KEESE_NORMAL_PERCH) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE]++; + } + if (this->actor.params == KEESE_FIRE_FLY || this->actor.params == KEESE_FIRE_PERCH) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_FIRE]++; + } + if (this->actor.params == KEESE_ICE_FLY) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_ICE]++; + } } void EnFirefly_SetupRebound(EnFirefly* this) { diff --git a/soh/src/overlays/actors/ovl_En_Floormas/z_en_floormas.c b/soh/src/overlays/actors/ovl_En_Floormas/z_en_floormas.c index a0dda1ddd..362f43115 100644 --- a/soh/src/overlays/actors/ovl_En_Floormas/z_en_floormas.c +++ b/soh/src/overlays/actors/ovl_En_Floormas/z_en_floormas.c @@ -439,6 +439,7 @@ void EnFloormas_Die(EnFloormas* this, PlayState* play) { // Die Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x90); EnFloormas_SetupSmShrink(this, play); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOORMASTER]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c b/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c index 982dd1e94..36b4d373b 100644 --- a/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c +++ b/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c @@ -363,6 +363,7 @@ void EnFz_ApplyDamage(EnFz* this, PlayState* play) { vec.z = this->actor.world.pos.z; EnFz_Damaged(this, play, &vec, 30, 10.0f); EnFz_SetupDespawn(this, play); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FREEZARD]++; } } } else { diff --git a/soh/src/overlays/actors/ovl_En_GeldB/z_en_geldb.c b/soh/src/overlays/actors/ovl_En_GeldB/z_en_geldb.c index 647980079..426fd4ccd 100644 --- a/soh/src/overlays/actors/ovl_En_GeldB/z_en_geldb.c +++ b/soh/src/overlays/actors/ovl_En_GeldB/z_en_geldb.c @@ -1318,6 +1318,7 @@ void EnGeldB_SetupDefeated(EnGeldB* this) { this->actor.flags &= ~ACTOR_FLAG_0; Audio_PlayActorSound2(&this->actor, NA_SE_EN_GERUDOFT_DEAD); EnGeldB_SetupAction(this, EnGeldB_Defeated); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GERUDO_THIEF]++; } void EnGeldB_Defeated(EnGeldB* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Goma/z_en_goma.c b/soh/src/overlays/actors/ovl_En_Goma/z_en_goma.c index 274f98257..4d99b703a 100644 --- a/soh/src/overlays/actors/ovl_En_Goma/z_en_goma.c +++ b/soh/src/overlays/actors/ovl_En_Goma/z_en_goma.c @@ -396,6 +396,7 @@ void EnGoma_SetupDead(EnGoma* this) { Animation_GetLastFrame(&gObjectGolDeadTwitchingAnim), ANIMMODE_LOOP, -2.0f); this->actionFunc = EnGoma_Dead; this->actionTimer = 3; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GOHMA_LARVA]++; } void EnGoma_Dead(EnGoma* this, PlayState* play) { @@ -666,6 +667,7 @@ void EnGoma_UpdateHit(EnGoma* this, PlayState* play) { EnGoma_SpawnHatchDebris(this, play); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GOHMA_LARVA]++; } } } diff --git a/soh/src/overlays/actors/ovl_En_Hintnuts/z_en_hintnuts.c b/soh/src/overlays/actors/ovl_En_Hintnuts/z_en_hintnuts.c index 7097172f7..5870c5a74 100644 --- a/soh/src/overlays/actors/ovl_En_Hintnuts/z_en_hintnuts.c +++ b/soh/src/overlays/actors/ovl_En_Hintnuts/z_en_hintnuts.c @@ -428,6 +428,7 @@ void EnHintnuts_Leave(EnHintnuts* this, PlayState* play) { Actor_ChangeCategory(play, &play->actorCtx, this->actor.child, ACTORCAT_PROP); } Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_SCRUB]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c b/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c index 4b344e984..79d65259c 100644 --- a/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c +++ b/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c @@ -628,6 +628,7 @@ void func_80A7598C(EnIk* this) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_IRONNACK_DEAD); Audio_PlayActorSound2(&this->actor, NA_SE_EN_NUTS_CUTBODY); EnIk_SetupAction(this, func_80A75A38); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE]++; } void func_80A75A38(EnIk* this, PlayState* play) { @@ -1446,6 +1447,7 @@ void func_80A781CC(Actor* thisx, PlayState* play) { } gSaveContext.eventChkInf[3] |= 0x1000; func_80A7735C(this, play); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Karebaba/z_en_karebaba.c b/soh/src/overlays/actors/ovl_En_Karebaba/z_en_karebaba.c index 4c3277061..b08465246 100644 --- a/soh/src/overlays/actors/ovl_En_Karebaba/z_en_karebaba.c +++ b/soh/src/overlays/actors/ovl_En_Karebaba/z_en_karebaba.c @@ -180,6 +180,7 @@ void EnKarebaba_SetupDying(EnKarebaba* this) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_DEKU_JR_DEAD); this->actor.flags |= ACTOR_FLAG_4 | ACTOR_FLAG_5; this->actionFunc = EnKarebaba_Dying; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA]++; } void EnKarebaba_SetupDeadItemDrop(EnKarebaba* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Kusa/z_en_kusa.c b/soh/src/overlays/actors/ovl_En_Kusa/z_en_kusa.c index 87a37cf07..906f84a71 100644 --- a/soh/src/overlays/actors/ovl_En_Kusa/z_en_kusa.c +++ b/soh/src/overlays/actors/ovl_En_Kusa/z_en_kusa.c @@ -310,6 +310,7 @@ void EnKusa_Main(EnKusa* this, PlayState* play) { EnKusa_SpawnFragments(this, play); EnKusa_DropCollectible(this, play); SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EV_PLANT_BROKEN); + gSaveContext.sohStats.count[COUNT_BUSHES_CUT]++; if ((this->actor.params >> 4) & 1) { EnKusa_SpawnBugs(this, play); @@ -378,6 +379,7 @@ void EnKusa_Fall(EnKusa* this, PlayState* play) { if (this->actor.bgCheckFlags & 0xB) { if (!(this->actor.bgCheckFlags & 0x20)) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EV_PLANT_BROKEN); + gSaveContext.sohStats.count[COUNT_BUSHES_CUT]++; } EnKusa_SpawnFragments(this, play); EnKusa_DropCollectible(this, play); diff --git a/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c b/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c index 1449a7f6e..d700f8e97 100644 --- a/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c +++ b/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c @@ -1400,12 +1400,14 @@ void EnMb_CheckColliding(EnMb* this, PlayState* play) { if (this->actor.params == ENMB_TYPE_CLUB) { if (this->actor.colChkInfo.health == 0) { EnMb_SetupClubDead(this); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB]++; } else if (this->state != ENMB_STATE_CLUB_KNEELING) { EnMb_SetupClubDamaged(this); } } else { if (this->actor.colChkInfo.health == 0) { EnMb_SetupSpearDead(this); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN]++; } else { EnMb_SetupSpearDamaged(this); } diff --git a/soh/src/overlays/actors/ovl_En_Ny/z_en_ny.c b/soh/src/overlays/actors/ovl_En_Ny/z_en_ny.c index 869b32206..94f21f314 100644 --- a/soh/src/overlays/actors/ovl_En_Ny/z_en_ny.c +++ b/soh/src/overlays/actors/ovl_En_Ny/z_en_ny.c @@ -447,6 +447,7 @@ void EnNy_SetupDie(EnNy* this, PlayState* play) { } Audio_PlayActorSound2(&this->actor, NA_SE_EN_NYU_DEAD); this->actionFunc = EnNy_Die; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SPIKE]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c index 696f83c77..6aa61d905 100644 --- a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c +++ b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c @@ -253,6 +253,7 @@ void EnOkuta_SetupDie(EnOkuta* this) { Animation_MorphToPlayOnce(&this->skelAnime, &gOctorokDieAnim, -3.0f); this->timer = 0; this->actionFunc = EnOkuta_Die; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_OCTOROK]++; } void EnOkuta_SetupFreeze(EnOkuta* this) { diff --git a/soh/src/overlays/actors/ovl_En_Peehat/z_en_peehat.c b/soh/src/overlays/actors/ovl_En_Peehat/z_en_peehat.c index 2d66eb2ff..13857fd9a 100644 --- a/soh/src/overlays/actors/ovl_En_Peehat/z_en_peehat.c +++ b/soh/src/overlays/actors/ovl_En_Peehat/z_en_peehat.c @@ -590,6 +590,7 @@ void EnPeehat_Larva_StateSeekPlayer(EnPeehat* this, PlayState* play) { } Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x20); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]++; } } } @@ -759,6 +760,7 @@ void EnPeehat_StateAttackRecoil(EnPeehat* this, PlayState* play) { 1); } Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]++; } else { EnPeehat_Ground_SetStateSeekPlayer(this); // Is PEAHAT_TYPE_GROUNDED @@ -875,6 +877,7 @@ void EnPeehat_StateExplode(EnPeehat* this, PlayState* play) { Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x40); Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x40); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT]++; } } diff --git a/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c b/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c index 821e50ae7..d1722a53d 100644 --- a/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c +++ b/soh/src/overlays/actors/ovl_En_Po_Field/z_en_po_field.c @@ -577,6 +577,11 @@ void EnPoField_Death(EnPoField* this, PlayState* play) { 255, 0, 0, 255, 1, 9, 1); if (this->actionTimer == 1) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_EXTINCT); + if (this->actor.params == EN_PO_FIELD_BIG) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_BIG]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]++; + } } } else if (this->actionTimer == 28) { EnPoField_SetupSoulIdle(this, play); diff --git a/soh/src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.c b/soh/src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.c index dbbd1d46e..2bb126290 100644 --- a/soh/src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.c +++ b/soh/src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.c @@ -1179,6 +1179,7 @@ void func_80ADC10C(EnPoSisters* this, PlayState* play) { } else { Enemy_StartFinishingBlow(play, &this->actor); Audio_PlayActorSound2(&this->actor, NA_SE_EN_PO_SISTER_DEAD); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_SISTERS]++; } func_80AD95D8(this); } diff --git a/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c b/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c index 0474bbd8d..59c34d5d8 100644 --- a/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c +++ b/soh/src/overlays/actors/ovl_En_Poh/z_en_poh.c @@ -636,6 +636,11 @@ void func_80ADF15C(EnPoh* this, PlayState* play) { 0, 0, 255, 1, 9, 1); if (this->unk_198 == 1) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_EXTINCT); + if (this->actor.params == EN_POH_FLAT || this->actor.params == EN_POH_SHARP) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_COMPOSER]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]++; + } } } else if (this->unk_198 == 28) { EnPoh_SetupDeath(this, play); diff --git a/soh/src/overlays/actors/ovl_En_Rd/z_en_rd.c b/soh/src/overlays/actors/ovl_En_Rd/z_en_rd.c index c9415cc1a..1b9852c97 100644 --- a/soh/src/overlays/actors/ovl_En_Rd/z_en_rd.c +++ b/soh/src/overlays/actors/ovl_En_Rd/z_en_rd.c @@ -639,6 +639,11 @@ void func_80AE3C20(EnRd* this) { this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_REDEAD_DEAD); EnRd_SetupAction(this, func_80AE3C98); + if (this->actor.params >= -1) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_REDEAD]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GIBDO]++; + } } void func_80AE3C98(EnRd* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Reeba/z_en_reeba.c b/soh/src/overlays/actors/ovl_En_Reeba/z_en_reeba.c index 93529183e..e691828cf 100644 --- a/soh/src/overlays/actors/ovl_En_Reeba/z_en_reeba.c +++ b/soh/src/overlays/actors/ovl_En_Reeba/z_en_reeba.c @@ -439,6 +439,7 @@ void func_80AE5A9C(EnReeba* this, PlayState* play) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_RIVA_DEAD); Enemy_StartFinishingBlow(play, &this->actor); this->actionfunc = func_80AE5C38; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER]++; } } @@ -493,6 +494,11 @@ void func_80AE5C38(EnReeba* this, PlayState* play) { } Actor_Kill(&this->actor); + if (this->isBig) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER_BIG]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER]++; + } } } } diff --git a/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c b/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c index f3a7a3c32..9e63d7bc7 100644 --- a/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c +++ b/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c @@ -381,6 +381,7 @@ void EnRr_SetupDeath(EnRr* this) { this->actionFunc = EnRr_Death; Audio_PlayActorSound2(&this->actor, NA_SE_EN_LIKE_DEAD); this->actor.flags &= ~ACTOR_FLAG_0; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIKE_LIKE]++; } void EnRr_SetupStunned(EnRr* this) { diff --git a/soh/src/overlays/actors/ovl_En_Sb/z_en_sb.c b/soh/src/overlays/actors/ovl_En_Sb/z_en_sb.c index 4d66a124c..6f2aa6bd3 100644 --- a/soh/src/overlays/actors/ovl_En_Sb/z_en_sb.c +++ b/soh/src/overlays/actors/ovl_En_Sb/z_en_sb.c @@ -456,6 +456,7 @@ void EnSb_Update(Actor* thisx, PlayState* play) { } else { Item_DropCollectible(play, &this->actor.world.pos, 8); } + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHELLBLADE]++; Actor_Kill(&this->actor); } } else { diff --git a/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c b/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c index 0ddea6bcf..c1d9a3a7a 100644 --- a/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c +++ b/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c @@ -412,6 +412,7 @@ void func_80AFD7B4(EnSkb* this, PlayState* play) { this->unk_283 |= 4; EffectSsDeadSound_SpawnStationary(play, &this->actor.projectedPos, NA_SE_EN_STALKID_DEAD, 1, 1, 0x28); EnSkb_SetupAction(this, func_80AFD880); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALCHILD]++; } void func_80AFD880(EnSkb* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c b/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c index b5f738aff..652ad64fe 100644 --- a/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c +++ b/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c @@ -733,6 +733,7 @@ void EnSkj_SariasSongKidIdle(EnSkj* this, PlayState* play) { void EnSkj_SetupDie(EnSkj* this) { EnSkj_ChangeAnim(this, SKJ_ANIM_DIE); EnSkj_SetupAction(this, SKJ_ACTION_WAIT_FOR_DEATH_ANIM); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULL_KID]++; } void EnSkj_WaitForDeathAnim(EnSkj* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_St/z_en_st.c b/soh/src/overlays/actors/ovl_En_St/z_en_st.c index 0e7fc6d4d..70a7edf80 100644 --- a/soh/src/overlays/actors/ovl_En_St/z_en_st.c +++ b/soh/src/overlays/actors/ovl_En_St/z_en_st.c @@ -465,6 +465,11 @@ s32 EnSt_CheckHitBackside(EnSt* this, PlayState* play) { this->deathTimer = 20; this->actor.gravity = -1.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALWALL_DEAD); + if (this->actor.params == 1) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA]++; + } if (flags & 0x1F820) { // arrow, fire arrow, ice arrow, light arrow, diff --git a/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c b/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c index 901e33686..151d4e84e 100644 --- a/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c +++ b/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c @@ -346,6 +346,7 @@ s32 func_80B0C9F0(EnSw* this, PlayState* play) { this->unk_38A = 1; this->unk_420 *= 4.0f; this->actionFunc = func_80B0D878; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD]++; } else { this->actor.shape.shadowDraw = ActorShadow_DrawCircle; this->actor.shape.shadowAlpha = 0xFF; @@ -354,6 +355,7 @@ s32 func_80B0C9F0(EnSw* this, PlayState* play) { this->actor.gravity = -1.0f; this->actor.flags &= ~ACTOR_FLAG_0; this->actionFunc = func_80B0DB00; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLWALLTULA]++; } Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALWALL_DEAD); diff --git a/soh/src/overlays/actors/ovl_En_Test/z_en_test.c b/soh/src/overlays/actors/ovl_En_Test/z_en_test.c index 7c0c582e8..025626b81 100644 --- a/soh/src/overlays/actors/ovl_En_Test/z_en_test.c +++ b/soh/src/overlays/actors/ovl_En_Test/z_en_test.c @@ -1525,6 +1525,7 @@ void func_80862E6C(EnTest* this, PlayState* play) { } Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALFOS]++; } } } @@ -1633,6 +1634,7 @@ void func_808633E8(EnTest* this, PlayState* play) { } Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALFOS]++; } } @@ -1723,6 +1725,7 @@ void EnTest_Update(Actor* thisx, PlayState* play) { if ((floorProperty == 5) || (floorProperty == 0xC) || func_80041D4C(&play->colCtx, this->actor.floorPoly, this->actor.floorBgId) == 9) { Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALFOS]++; return; } } diff --git a/soh/src/overlays/actors/ovl_En_Tite/z_en_tite.c b/soh/src/overlays/actors/ovl_En_Tite/z_en_tite.c index 4357fdcc7..546275d2e 100644 --- a/soh/src/overlays/actors/ovl_En_Tite/z_en_tite.c +++ b/soh/src/overlays/actors/ovl_En_Tite/z_en_tite.c @@ -764,8 +764,10 @@ void EnTite_FallApart(EnTite* this, PlayState* play) { if (BodyBreak_SpawnParts(&this->actor, &this->bodyBreak, play, this->actor.params + 0xB)) { if (this->actor.params == TEKTITE_BLUE) { Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0xE0); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]++; } else { Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x40); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]++; } Actor_Kill(&this->actor); } diff --git a/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c b/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c index 26708d0a9..7f24cc4e6 100644 --- a/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c +++ b/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c @@ -546,6 +546,7 @@ void EnTorch2_Update(Actor* thisx, PlayState* play2) { case ENTORCH2_DEATH: if (sAlpha - 13 <= 0) { sAlpha = 0; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DARK_LINK]++; Actor_Kill(&this->actor); return; } diff --git a/soh/src/overlays/actors/ovl_En_Tp/z_en_tp.c b/soh/src/overlays/actors/ovl_En_Tp/z_en_tp.c index de8785825..bbe6435e8 100644 --- a/soh/src/overlays/actors/ovl_En_Tp/z_en_tp.c +++ b/soh/src/overlays/actors/ovl_En_Tp/z_en_tp.c @@ -289,6 +289,9 @@ void EnTp_SetupDie(EnTp* this) { } this->actionIndex = TAILPASARAN_ACTION_DIE; EnTp_SetupAction(this, EnTp_Die); + if (this->actor.params == TAILPASARAN_HEAD) { // Only count the head, otherwise each body segment will increment + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TAILPASARAN]++; + } } /** diff --git a/soh/src/overlays/actors/ovl_En_Tubo_Trap/z_en_tubo_trap.c b/soh/src/overlays/actors/ovl_En_Tubo_Trap/z_en_tubo_trap.c index b8441ee94..f8e93d61f 100644 --- a/soh/src/overlays/actors/ovl_En_Tubo_Trap/z_en_tubo_trap.c +++ b/soh/src/overlays/actors/ovl_En_Tubo_Trap/z_en_tubo_trap.c @@ -176,6 +176,7 @@ void EnTuboTrap_HandleImpact(EnTuboTrap* this, PlayState* play) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 40, NA_SE_EV_BOMB_DROP_WATER); EnTuboTrap_DropCollectible(this, play); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]++; return; } @@ -186,6 +187,7 @@ void EnTuboTrap_HandleImpact(EnTuboTrap* this, PlayState* play) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 40, NA_SE_EV_POT_BROKEN); EnTuboTrap_DropCollectible(this, play); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]++; return; } @@ -196,6 +198,7 @@ void EnTuboTrap_HandleImpact(EnTuboTrap* this, PlayState* play) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 40, NA_SE_EV_POT_BROKEN); EnTuboTrap_DropCollectible(this, play); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]++; return; } @@ -207,6 +210,7 @@ void EnTuboTrap_HandleImpact(EnTuboTrap* this, PlayState* play) { SoundSource_PlaySfxAtFixedWorldPos(play, &player2->actor.world.pos, 40, NA_SE_PL_BODY_HIT); EnTuboTrap_DropCollectible(this, play); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]++; return; } } @@ -216,6 +220,7 @@ void EnTuboTrap_HandleImpact(EnTuboTrap* this, PlayState* play) { SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 40, NA_SE_EV_POT_BROKEN); EnTuboTrap_DropCollectible(this, play); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]++; return; } } diff --git a/soh/src/overlays/actors/ovl_En_Vali/z_en_vali.c b/soh/src/overlays/actors/ovl_En_Vali/z_en_vali.c index 203189b6a..79b6d8a6b 100644 --- a/soh/src/overlays/actors/ovl_En_Vali/z_en_vali.c +++ b/soh/src/overlays/actors/ovl_En_Vali/z_en_vali.c @@ -252,6 +252,7 @@ void EnVali_SetupDivideAndDie(EnVali* this, PlayState* play) { this->actor.flags &= ~ACTOR_FLAG_0; this->actor.draw = NULL; this->actionFunc = EnVali_DivideAndDie; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BARI]++; } void EnVali_SetupStunned(EnVali* this) { diff --git a/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c b/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c index 0f5b8b4c1..875baab12 100644 --- a/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c +++ b/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c @@ -366,6 +366,7 @@ void EnVm_SetupDie(EnVm* this) { this->actor.speedXZ = Rand_ZeroOne() + 1.0f; this->actor.world.rot.y = Rand_CenteredFloat(65535.0f); EnVm_SetupAction(this, EnVm_Die); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BEAMOS]++; } void EnVm_Die(EnVm* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Wallmas/z_en_wallmas.c b/soh/src/overlays/actors/ovl_En_Wallmas/z_en_wallmas.c index 06851c2d0..bb5cbf674 100644 --- a/soh/src/overlays/actors/ovl_En_Wallmas/z_en_wallmas.c +++ b/soh/src/overlays/actors/ovl_En_Wallmas/z_en_wallmas.c @@ -252,6 +252,7 @@ void EnWallmas_SetupDie(EnWallmas* this, PlayState* play) { Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0xC0); this->actionFunc = EnWallmas_Die; + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WALLMASTER]++; } void EnWallmas_SetupTakePlayer(EnWallmas* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Weiyer/z_en_weiyer.c b/soh/src/overlays/actors/ovl_En_Weiyer/z_en_weiyer.c index d69634cab..865fe71af 100644 --- a/soh/src/overlays/actors/ovl_En_Weiyer/z_en_weiyer.c +++ b/soh/src/overlays/actors/ovl_En_Weiyer/z_en_weiyer.c @@ -574,6 +574,7 @@ void func_80B3368C(EnWeiyer* this, PlayState* play) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_EIER_DEAD); this->actor.flags &= ~ACTOR_FLAG_0; func_80B32724(this); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STINGER]++; } else { func_80B325A0(this); } diff --git a/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c b/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c index 831c19c63..fa3701dd6 100644 --- a/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c +++ b/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c @@ -1193,6 +1193,11 @@ void EnWf_SetupDie(EnWf* this) { this->actionTimer = this->skelAnime.animLength; Audio_PlayActorSound2(&this->actor, NA_SE_EN_WOLFOS_DEAD); EnWf_SetupAction(this, EnWf_Die); + if (this->actor.params == WOLFOS_WHITE) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS]++; + } } void EnWf_Die(EnWf* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Yukabyun/z_en_yukabyun.c b/soh/src/overlays/actors/ovl_En_Yukabyun/z_en_yukabyun.c index 007c20134..50f334d42 100644 --- a/soh/src/overlays/actors/ovl_En_Yukabyun/z_en_yukabyun.c +++ b/soh/src/overlays/actors/ovl_En_Yukabyun/z_en_yukabyun.c @@ -111,6 +111,7 @@ void EnYukabyun_Break(EnYukabyun* this, PlayState* play) { EffectSsHahen_SpawnBurst(play, &this->actor.world.pos, 8.0f, 0, 1300, 300, 15, OBJECT_YUKABYUN, 10, gFloorTileEnemyFragmentDL); Actor_Kill(&this->actor); + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOOR_TILE]++; } void EnYukabyun_Update(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c b/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c index a54a93cac..e73360268 100644 --- a/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c +++ b/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c @@ -1921,6 +1921,12 @@ void EnZf_SetupDie(EnZf* this) { D_80B4A1B0 = 0; Audio_PlayActorSound2(&this->actor, NA_SE_EN_RIZA_DEAD); EnZf_SetupAction(this, EnZf_Die); + + if (this->actor.params == ENZF_TYPE_DINOLFOS) { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DINOLFOS]++; + } else { + gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIZALFOS]++; + } } void EnZf_Die(EnZf* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c b/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c index 157e017a2..3b717cb3c 100644 --- a/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c +++ b/soh/src/overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.c @@ -188,6 +188,7 @@ void ObjTsubo_AirBreak(ObjTsubo* this, PlayState* play) { sObjectIds[(this->actor.params >> 8) & 1], D_80BA1B8C[(this->actor.params >> 8) & 1]); } func_80033480(play, &this->actor.world.pos, 30.0f, 4, 20, 50, 1); + gSaveContext.sohStats.count[COUNT_POTS_BROKEN]++; } void ObjTsubo_WaterBreak(ObjTsubo* this, PlayState* play) { @@ -216,6 +217,7 @@ void ObjTsubo_WaterBreak(ObjTsubo* this, PlayState* play) { (Rand_ZeroOne() * 95.0f) + 15.0f, 0, 32, 70, KAKERA_COLOR_NONE, sObjectIds[(this->actor.params >> 8) & 1], D_80BA1B8C[(this->actor.params >> 8) & 1]); } + gSaveContext.sohStats.count[COUNT_POTS_BROKEN]++; } void ObjTsubo_SetupWaitForObject(ObjTsubo* this) { diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index c4c546590..43057769a 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -1460,6 +1460,12 @@ void func_808327F8(Player* this, f32 arg1) { } func_800F4010(&this->actor.projectedPos, sfxId, arg1); + // Gameplay stats: Count footsteps + // Only count while game isn't complete and don't count Link's idle animations or crawling in crawlspaces + if (!gSaveContext.sohStats.gameComplete && !(this->stateFlags2 & PLAYER_STATE2_28) && + !(this->stateFlags2 & PLAYER_STATE2_18)) { + gSaveContext.sohStats.count[COUNT_STEPS]++; + } } void func_80832854(Player* this) { @@ -1950,6 +1956,10 @@ void func_80833A20(Player* this, s32 newSwordState) { if ((this->swordAnimation < 0x10) || (this->swordAnimation >= 0x14)) { func_80832698(this, voiceSfx); } + + if (this->heldItemActionParam >= PLAYER_AP_SWORD_MASTER && this->heldItemActionParam <= PLAYER_AP_SWORD_BGS) { + gSaveContext.sohStats.count[COUNT_SWORD_SWINGS]++; + } } this->swordState = newSwordState; @@ -5285,6 +5295,7 @@ void func_8083BC04(Player* this, PlayState* play) { func_80835C58(play, this, func_80844708, 0); LinkAnimation_PlayOnceSetSpeed(play, &this->skelAnime, D_80853914[PLAYER_ANIMGROUP_16][this->modelAnimType], 1.25f * D_808535E8); + gSaveContext.sohStats.count[COUNT_ROLLS]++; } s32 func_8083BC7C(Player* this, PlayState* play) { @@ -6258,6 +6269,8 @@ s32 func_8083E5A8(Player* this, PlayState* play) { func_80837C0C(play, this, 3, 0.0f, 0.0f, 0, 20); this->getItemId = GI_NONE; this->getItemEntry = (GetItemEntry) GET_ITEM_NONE; + // Gameplay stats: Increment Ice Trap count + gSaveContext.sohStats.count[COUNT_ICE_TRAPS]++; return 1; } @@ -8648,6 +8661,7 @@ void func_80844708(Player* this, PlayState* play) { func_8002F7DC(&this->actor, NA_SE_PL_BODY_HIT); func_80832698(this, NA_SE_VO_LI_CLIMB_END); this->unk_850 = 1; + gSaveContext.sohStats.count[COUNT_BONKS]++; return; } } @@ -12887,6 +12901,8 @@ void func_8084E6D4(Player* this, PlayState* play) { func_80837C0C(play, this, 3, 0.0f, 0.0f, 0, 20); this->getItemId = GI_NONE; this->getItemEntry = (GetItemEntry)GET_ITEM_NONE; + // Gameplay stats: Increment Ice Trap count + gSaveContext.sohStats.count[COUNT_ICE_TRAPS]++; } return; }