From c68c8f128439fd02f955cc2a0482a6a172373a8e Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Sun, 23 Nov 2025 16:45:19 +0000 Subject: [PATCH 01/16] Adds a toggle for the entrance labels on signs near loading zones. (#5974) --- soh/soh/OTRGlobals.cpp | 3 ++- soh/soh/SohGui/SohMenuRandomizer.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 8f57b1c06..9023d8990 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -2270,7 +2270,8 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { s16 actorParams = 0; if (IS_RANDO) { auto ctx = Rando::Context::GetInstance(); - if (ctx->GetOption(RSK_SHUFFLE_ENTRANCES)) { + if (ctx->GetOption(RSK_SHUFFLE_ENTRANCES) && + CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("EntrancesOnSigns"), 0)) { s16 entrance = -1; switch (textId) { case TEXT_WATERFALL: diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp index ad688740a..60849ee02 100644 --- a/soh/soh/SohGui/SohMenuRandomizer.cpp +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -97,6 +97,9 @@ void SohMenu::AddMenuRandomizer() { }) .Options(FloatSliderOptions().Min(5.0f).Max(15.0f).Format("%.2f").DefaultValue(10.0f).Tooltip( "The size of the item when it is picked up.")); + AddWidget(path, "Signs Hint Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("EntrancesOnSigns")) + .Options(CheckboxOptions().Tooltip("If enabled, signs near loading zones will tell you where they lead to.")); // Plandomizer path.sidebarName = "Plandomizer"; From 2ee4e70b9a414ded4b9d4609e6b3cd16462f6939 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Mon, 24 Nov 2025 07:00:37 -0700 Subject: [PATCH 02/16] Fix menu header width and scrollbar calculations. (#5975) --- soh/soh/SohGui/Menu.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index 592c0c5e6..e3db0ae01 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -650,18 +650,15 @@ void Menu::DrawElement() { ImVec2 pos = window->DC.CursorPos; float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (menuEntries.size() + 1)); std::vector headerSizes; - float headerWidth = style.ItemSpacing.x + 20; + float headerWidth = 0.0f; bool headerSearch = !CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0); if (headerSearch) { - headerWidth += 200.0f + style.ItemSpacing.x + style.FramePadding.x; + headerWidth += 200.0f; } for (auto& label : menuOrder) { ImVec2 size = ImGui::CalcTextSize(label.c_str()); headerSizes.push_back(size); - headerWidth += size.x + style.FramePadding.x * 2; - if (label == headerIndex) { - headerWidth += style.ItemSpacing.x; - } + headerWidth += size.x + style.FramePadding.x * 2 + style.ItemSpacing.x; } // Full screen menu with widths below 1280, heights below 800. From 00737364678d22446dda5ea67b390905b709966b Mon Sep 17 00:00:00 2001 From: Malkierian Date: Tue, 25 Nov 2025 10:07:45 -0700 Subject: [PATCH 03/16] Fix entrance value assigned to DMT sign outside Dodongo's for hinting. (#5973) --- soh/soh/OTRGlobals.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 9023d8990..e7fe5f31b 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -2312,7 +2312,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { entrance = ENTR_GROTTOS_13; break; case TEXT_DMT_DC_SIGN: - entrance = ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN; + entrance = ENTR_DODONGOS_CAVERN_ENTRANCE; break; case TEXT_DMT_GC_SIGN: entrance = ENTR_GORON_CITY_UPPER_EXIT; From 9d8addca0483567bbee693e8b64ddeb1a24eaac1 Mon Sep 17 00:00:00 2001 From: Shishu the Dragon <183069616+ShishuTheDragon@users.noreply.github.com> Date: Thu, 11 Dec 2025 05:50:07 +1300 Subject: [PATCH 04/16] Update macOS build instructions (#6012) --- docs/BUILDING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 72a1863b0..ba7a980bf 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -239,7 +239,7 @@ cmake --build build-cmake --target ExtractAssetHeaders ``` ## macOS -Requires Xcode (or xcode-tools) && `sdl2, libpng, glew, ninja, cmake, tinyxml2, nlohmann-json, libzip` (can be installed via [homebrew](https://brew.sh/), macports, etc) +Requires Xcode (or xcode-tools) && `sdl2, libpng, glew, ninja, cmake, tinyxml2, nlohmann-json, libzip, opusfile, libvorbis` (can be installed via [homebrew](https://brew.sh/), macports, etc) **Important: For maximum performance make sure you have ninja build tools installed!** @@ -254,7 +254,7 @@ cd ShipWright git submodule update --init # Install development dependencies (assuming homebrew) -brew install sdl2 libpng glew ninja cmake tinyxml2 nlohmann-json libzip +brew install sdl2 libpng glew ninja cmake tinyxml2 nlohmann-json libzip opusfile libvorbis # Generate Ninja project # Add `-DCMAKE_BUILD_TYPE:STRING=Release` if you're packaging From 05d865337c1c78efa724bafd30d031c6383eae06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Mon, 15 Dec 2025 19:47:07 +0000 Subject: [PATCH 05/16] MQ forest: fix raised island GS logic (#6020) --- .../randomizer/location_access/dungeons/forest_temple.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp index a4a0563cf..38b72ebd5 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp @@ -463,7 +463,7 @@ void RegionTable_Init_ForestTemple() { }, { //Locations LOCATION(RC_FOREST_TEMPLE_MQ_WELL_CHEST, logic->CanHitEyeTargets() || (logic->CanOpenUnderwaterChest() && logic->WaterTimer() >= 8)), - LOCATION(RC_FOREST_TEMPLE_MQ_GS_RAISED_ISLAND_COURTYARD, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)), + LOCATION(RC_FOREST_TEMPLE_MQ_GS_RAISED_ISLAND_COURTYARD, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_BOOMERANG)), //implies logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA) LOCATION(RC_FOREST_TEMPLE_MQ_GS_WELL, logic->CanHitEyeTargets() || (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT))), LOCATION(RC_FOREST_TEMPLE_MQ_WELL_WEST_HEART, (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) || logic->CanHitEyeTargets()), From 352a4e92604744c72143d747bb84171f547131f5 Mon Sep 17 00:00:00 2001 From: xxAtrain223 Date: Mon, 15 Dec 2025 13:49:11 -0600 Subject: [PATCH 06/16] Added small key doors special case for Thieves Hideout. (#6023) --- soh/soh/Enhancements/randomizer/logic.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index fd578d7de..382be7bdf 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -2118,6 +2118,21 @@ void Logic::SetQuestItem(uint32_t item, bool state) { } } +const std::vector& GetThievesHideoutSmallKeyDoors() { + // Retrieved from scenes/shared/gerudoway_scene/gerudoway_room_%d + // SOH::SceneCommandID::SetActorList, actor.id == ACTOR_DOOR_GERUDO, actor.params & 0x3F + static const std::vector normalSmallKeyDoors{ 1, 2, 3, 4 }; + static const std::vector fastSmallKeyDoors{ 1 }; + static const std::vector freeSmallKeyDoors{}; + + if (RAND_GET_OPTION(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_NORMAL) { + return normalSmallKeyDoors; + } else if (RAND_GET_OPTION(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST) { + return fastSmallKeyDoors; + } + return freeSmallKeyDoors; +} + // Get the swch bit positions for the dungeon const std::vector& GetDungeonSmallKeyDoors(SceneID sceneId) { static const std::vector emptyVector; @@ -2183,7 +2198,8 @@ const std::vector& GetDungeonSmallKeyDoors(SceneID sceneId) { } int8_t Logic::GetUsedSmallKeyCount(SceneID sceneId) { - const auto& smallKeyDoors = GetDungeonSmallKeyDoors(sceneId); + const auto& smallKeyDoors = + (sceneId == SCENE_THIEVES_HIDEOUT) ? GetThievesHideoutSmallKeyDoors() : GetDungeonSmallKeyDoors(sceneId); // Get the swch value for the scene uint32_t swch; From 999f980d7c35da929518d9a308a610ae14f805e5 Mon Sep 17 00:00:00 2001 From: xxAtrain223 Date: Sat, 27 Dec 2025 05:10:57 -0600 Subject: [PATCH 07/16] Fix 2 TimePass Issues (#6038) * Set Kak time pass to false. * Set lon lon time pass to false. --- soh/soh/Enhancements/randomizer/location_access.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access.cpp b/soh/soh/Enhancements/randomizer/location_access.cpp index 0f517aa3f..b4185088c 100644 --- a/soh/soh/Enhancements/randomizer/location_access.cpp +++ b/soh/soh/Enhancements/randomizer/location_access.cpp @@ -372,6 +372,7 @@ bool GetTimePassFromScene(SceneID scene) { case SCENE_POTION_SHOP_GRANNY: case SCENE_GANON_BOSS: case SCENE_HOUSE_OF_SKULLTULA: + case SCENE_KAKARIKO_VILLAGE: case SCENE_KOKIRI_FOREST: case SCENE_SACRED_FOREST_MEADOW: case SCENE_LOST_WOODS: @@ -383,6 +384,7 @@ bool GetTimePassFromScene(SceneID scene) { case SCENE_GERUDOS_FORTRESS: case SCENE_HAUNTED_WASTELAND: case SCENE_DEATH_MOUNTAIN_CRATER: + case SCENE_LON_LON_RANCH: case SCENE_ID_MAX: return false; @@ -393,14 +395,12 @@ bool GetTimePassFromScene(SceneID scene) { return false; case SCENE_HYRULE_FIELD: - case SCENE_KAKARIKO_VILLAGE: case SCENE_ZORAS_RIVER: case SCENE_LAKE_HYLIA: case SCENE_GERUDO_VALLEY: case SCENE_DESERT_COLOSSUS: case SCENE_HYRULE_CASTLE: case SCENE_DEATH_MOUNTAIN_TRAIL: - case SCENE_LON_LON_RANCH: return true; case SCENE_TEST01: From ace2f7869bb7e4c33f79f51cdc7fbbc0387dd4b0 Mon Sep 17 00:00:00 2001 From: xxAtrain223 Date: Sun, 28 Dec 2025 10:20:16 -0600 Subject: [PATCH 08/16] Check for Pocket Cucco in HasItem. (#6049) --- soh/soh/Enhancements/randomizer/logic.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index 382be7bdf..da68e8e0a 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -223,7 +223,8 @@ bool Logic::HasItem(RandomizerGet itemName) { case RG_GOLDEN_SCALE: return CurrentUpgrade(UPG_SCALE) >= 2; case RG_POCKET_EGG: - return CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG); + return CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG) || + CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_CUCCO); case RG_COJIRO: case RG_ODD_MUSHROOM: case RG_ODD_POTION: From 50c023b86b7ddcf3d9281263235d6d44a6fe4af3 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Tue, 30 Dec 2025 20:43:20 -0500 Subject: [PATCH 09/16] Modularize randomized enemy size hook & fix enemy health bars (#5887) --- .../ExtraModes/RandomizedEnemySizes.cpp | 88 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 9 ++ soh/soh/Enhancements/mods.cpp | 55 ------------ soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c | 6 +- 4 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp diff --git a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp new file mode 100644 index 000000000..acb4b94d3 --- /dev/null +++ b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp @@ -0,0 +1,88 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorMaximumHealth.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "functions.h" +#include "src/overlays/actors/ovl_En_Fz/z_en_fz.h" +} + +static constexpr int32_t CVAR_RANDO_ENEMY_SIZE_DEFAULT = 0; +#define CVAR_RANDO_ENEMY_SIZE_NAME CVAR_ENHANCEMENT("RandomizedEnemySizes") +#define CVAR_RANDO_ENEMY_SIZE_VALUE CVarGetInteger(CVAR_RANDO_ENEMY_SIZE_NAME, CVAR_RANDO_ENEMY_SIZE_DEFAULT) + +static constexpr int32_t CVAR_ENEMY_SCALE_HEALTH_DEFAULT = 0; +#define CVAR_ENEMY_SCALE_HEALTH_NAME CVAR_ENHANCEMENT("EnemySizeScalesHealth") +#define CVAR_ENEMY_SCALE_HEALTH_VALUE CVarGetInteger(CVAR_ENEMY_SCALE_HEALTH_NAME, CVAR_ENEMY_SCALE_HEALTH_DEFAULT) + +static void RandomizedEnemySizes(void* refActor) { + // Randomized Enemy Sizes + Actor* actor = static_cast(refActor); + + // Exclude wobbly platforms in Jabu because they need to act like platforms. + // Exclude demo effect for Zora sapphire being re-categorized as a "boss". + // Exclude Dead Hand hands and Bongo Bongo main body because they make the fights (near) impossible. + bool excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || actor->id == ACTOR_DEMO_EFFECT || + (actor->id == ACTOR_BOSS_SST && actor->params == -1); + + // Only apply to enemies and bosses. + if ((actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) { + return; + } + + float randomNumber; + float randomScale; + + // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger. + bool smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || + actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH; + + bool bigActor = !smallOnlyEnemy && (rand() % 2); + + // Big actor + if (bigActor) { + randomNumber = rand() % 200; + // Between 100% and 300% size. + randomScale = 1.0f + (randomNumber / 100); + } else { + // Small actor + randomNumber = rand() % 90; + // Between 10% and 100% size. + randomScale = 0.1f + (randomNumber / 100); + } + + Actor_SetScale(actor, actor->scale.z * randomScale); + + if (CVAR_ENEMY_SCALE_HEALTH_VALUE && (actor->category == ACTORCAT_ENEMY)) { + // Scale the health based on a smaller factor than randomScale + float healthScalingFactor = 0.8f; // Adjust this factor as needed + float scaledHealth = actor->colChkInfo.health * (randomScale * healthScalingFactor); + + // Ensure the scaled health doesn't go below zero + actor->colChkInfo.health = fmax(scaledHealth, 1.0f); + + // Ensure maximum health gets set + SetActorMaximumHealth(actor, actor->colChkInfo.health); + } +} + +static void RegisterRandomizedEnemySizes() { + COND_HOOK(OnActorInit, CVAR_RANDO_ENEMY_SIZE_VALUE, RandomizedEnemySizes); +} + +static void RegisterFreezardHealthScale() { + COND_VB_SHOULD(VB_FREEZARD_SCALE_HEALTH_WITH_SIZE, CVAR_RANDO_ENEMY_SIZE_VALUE && CVAR_ENEMY_SCALE_HEALTH_VALUE, { + // With enemy health scaling, the Freezard's health could cause an index out of bounds for the displayLists, so + // we need to recompute the index based on the scaled health (using the maximum health value) and clamp the + // final result for safety. + Actor* actor = va_arg(args, Actor*); + s32* index = va_arg(args, s32*); + + u8 scaledHealth = (u8)(((f32)actor->colChkInfo.health / GetActorMaximumHealth(actor)) * 6); + *index = CLAMP((6 - scaledHealth) >> 1, 0, 2); + }); +} + +static RegisterShipInitFunc initFunc_EnemySizes(RegisterRandomizedEnemySizes, { CVAR_RANDO_ENEMY_SIZE_NAME }); +static RegisterShipInitFunc initFunc_Freezard(RegisterFreezardHealthScale, + { CVAR_RANDO_ENEMY_SIZE_NAME, CVAR_ENEMY_SCALE_HEALTH_NAME }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 08d663c7e..bee6ba72d 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -535,6 +535,15 @@ typedef enum { // - None VB_FIX_SAW_SOFTLOCK, + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*EnFz` + // - `*s32` + VB_FREEZARD_SCALE_HEALTH_WITH_SIZE, + // #### `result` // ```c // true diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 2577b7dd2..a5a3e4980 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -677,60 +677,6 @@ void RegisterHurtContainerModeHandler() { [](int32_t fileNum) { UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); }); } -void RegisterRandomizedEnemySizes() { - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - // Randomized Enemy Sizes - Player* player = GET_PLAYER(gPlayState); - Actor* actor = static_cast(refActor); - - // Exclude wobbly platforms in Jabu because they need to act like platforms. - // Exclude demo effect for Zora sapphire being re-categorized as a "boss". - // Exclude Dead Hand hands and Bongo Bongo main body because they make the fights (near) impossible. - uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || - actor->id == ACTOR_DEMO_EFFECT || (actor->id == ACTOR_BOSS_SST && actor->params == -1); - - // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger. - uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || - actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH; - - // Only apply to enemies and bosses. - if (!CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0) || - (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) { - return; - } - - float randomNumber; - float randomScale; - - uint8_t bigActor = rand() % 2; - - // Big actor - if (bigActor && !smallOnlyEnemy) { - randomNumber = rand() % 200; - // Between 100% and 300% size. - randomScale = 1.0f + (randomNumber / 100); - } else { - // Small actor - randomNumber = rand() % 90; - // Between 10% and 100% size. - randomScale = 0.1f + (randomNumber / 100); - } - - Actor_SetScale(actor, actor->scale.z * randomScale); - - if (CVarGetInteger(CVAR_ENHANCEMENT("EnemySizeScalesHealth"), 0) && (actor->category == ACTORCAT_ENEMY)) { - // Scale the health based on a smaller factor than randomScale - float healthScalingFactor = 0.8f; // Adjust this factor as needed - float scaledHealth = actor->colChkInfo.health * (randomScale * healthScalingFactor); - - // Ensure the scaled health doesn't go below zero - actor->colChkInfo.health = fmax(scaledHealth, 1.0f); - } else { - return; - } - }); -} - void RegisterFloorSwitchesHook() { GameInteractor::Instance->RegisterGameHook([](void* refActor) { Actor* actor = static_cast(refActor); @@ -784,7 +730,6 @@ void InitMods() { RegisterResetNaviTimer(); RegisterEnemyDefeatCounts(); RegisterBossDefeatTimestamps(); - RegisterRandomizedEnemySizes(); RegisterFloorSwitchesHook(); RegisterPatchHandHandler(); RegisterHurtContainerModeHandler(); 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 13afa66f6..fe4f1a914 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 @@ -725,11 +725,7 @@ void EnFz_Draw(Actor* thisx, PlayState* play) { // SOH [Enhancement] - With enemy health scaling, the Freezards health could cause an index out of bounds for the // displayLists, so we need to recompute the index based on the scaled health (using the maximum health value) and // clamp the final result for safety. - if (CVarGetInteger(CVAR_ENHANCEMENT("EnemySizeScalesHealth"), 0)) { - u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / GetActorMaximumHealth(this)) * 6); - index = (6 - scaledHealth) >> 1; - index = CLAMP(index, 0, 2); - } + GameInteractor_Should(VB_FREEZARD_SCALE_HEALTH_WITH_SIZE, false, this, &index); OPEN_DISPS(play->state.gfxCtx); From 5d6314626778cc27bee47a6e93d0f147fe6610e8 Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 31 Dec 2025 02:44:01 +0100 Subject: [PATCH 10/16] Add setting updater for rando logic setting (#6030) --- soh/soh/OTRGlobals.cpp | 1 + soh/soh/config/ConfigUpdaters.cpp | 9 +++++++++ soh/soh/config/ConfigUpdaters.h | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index e7fe5f31b..60ea90432 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1271,6 +1271,7 @@ extern "C" void InitOTR(int argc, char* argv[]) { conf->RegisterVersionUpdater(std::make_shared()); conf->RegisterVersionUpdater(std::make_shared()); conf->RegisterVersionUpdater(std::make_shared()); + conf->RegisterVersionUpdater(std::make_shared()); conf->RunVersionUpdates(); SohGui::SetupGuiElements(); diff --git a/soh/soh/config/ConfigUpdaters.cpp b/soh/soh/config/ConfigUpdaters.cpp index a7c50a845..d8a7e3f35 100644 --- a/soh/soh/config/ConfigUpdaters.cpp +++ b/soh/soh/config/ConfigUpdaters.cpp @@ -11,6 +11,8 @@ ConfigVersion3Updater::ConfigVersion3Updater() : ConfigVersionUpdater(3) { } ConfigVersion4Updater::ConfigVersion4Updater() : ConfigVersionUpdater(4) { } +ConfigVersion5Updater::ConfigVersion5Updater() : ConfigVersionUpdater(5) { +} void ConfigVersion1Updater::Update(Ship::Config* conf) { if (conf->GetInt("Window.Width", 640) == 640) { @@ -122,4 +124,11 @@ void ConfigVersion4Updater::Update(Ship::Config* conf) { CVarClear(migration.from.c_str()); } } + +void ConfigVersion5Updater::Update(Ship::Config* conf) { + // After removal of Vanilla, make sure it doesn't crash because of an out of range on the combobox + if (CVarGetInteger("gRandoSettings.LogicRules", 0) == 2) { + CVarSetInteger("gRandoSettings.LogicRules", 0); + } +} } // namespace SOH diff --git a/soh/soh/config/ConfigUpdaters.h b/soh/soh/config/ConfigUpdaters.h index 948188c19..4c6af35b3 100644 --- a/soh/soh/config/ConfigUpdaters.h +++ b/soh/soh/config/ConfigUpdaters.h @@ -24,4 +24,10 @@ class ConfigVersion4Updater final : public Ship::ConfigVersionUpdater { ConfigVersion4Updater(); void Update(Ship::Config* conf); }; + +class ConfigVersion5Updater final : public Ship::ConfigVersionUpdater { + public: + ConfigVersion5Updater(); + void Update(Ship::Config* conf); +}; } // namespace SOH From d62e8108fdf017b21e04e3be4a3e3f1d96d14996 Mon Sep 17 00:00:00 2001 From: Shishu the Dragon <183069616+ShishuTheDragon@users.noreply.github.com> Date: Fri, 2 Jan 2026 08:54:34 +1300 Subject: [PATCH 11/16] Mod Menu: Fix empty list crash (#6015) --- soh/soh/Enhancements/mod_menu.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/soh/soh/Enhancements/mod_menu.cpp b/soh/soh/Enhancements/mod_menu.cpp index e60eee7e9..7630cbb40 100644 --- a/soh/soh/Enhancements/mod_menu.cpp +++ b/soh/soh/Enhancements/mod_menu.cpp @@ -96,6 +96,8 @@ void ModsHandleDragAndDrop(std::vector& objectList, int targetIndex std::vector GetEnabledModsFromCVar() { std::string enabledModsCVarValue = CVAR_ENABLED_MODS_VALUE; + if (enabledModsCVarValue.empty()) + return {}; return StringHelper::Split(enabledModsCVarValue, SEPARATOR); } From 8584ced40be91bbf8e650b8d12c164307f9cf2f5 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Thu, 8 Jan 2026 12:20:16 -0600 Subject: [PATCH 12/16] Proper fix for 2 handed idle animation (#6109) --- .../Fixes/FixTwoHandedIdleAnim.cpp | 59 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 21 +++++++ .../actors/ovl_player_actor/z_player.c | 15 ++--- 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 soh/soh/Enhancements/Fixes/FixTwoHandedIdleAnim.cpp diff --git a/soh/soh/Enhancements/Fixes/FixTwoHandedIdleAnim.cpp b/soh/soh/Enhancements/Fixes/FixTwoHandedIdleAnim.cpp new file mode 100644 index 000000000..08bec0564 --- /dev/null +++ b/soh/soh/Enhancements/Fixes/FixTwoHandedIdleAnim.cpp @@ -0,0 +1,59 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "functions.h" +} + +#define FIDGET_SWORD_SWING 9 +#define FIDGET_ADJUST_SHIELD 12 + +static constexpr int32_t CVAR_FIX_TWO_HANDED_IDLE_DEFAULT = 0; +#define CVAR_FIX_TWO_HANDED_IDLE_NAME CVAR_ENHANCEMENT("TwoHandedIdle") +#define CVAR_FIX_TWO_HANDED_IDLE_VALUE CVarGetInteger(CVAR_FIX_TWO_HANDED_IDLE_NAME, CVAR_FIX_TWO_HANDED_IDLE_DEFAULT) + +// clang-format off +static RegisterShipInitFunc initFunc([]() { + COND_VB_SHOULD(VB_SET_IDLE_ANIM, CVAR_FIX_TWO_HANDED_IDLE_VALUE, { + Player* player = va_arg(args, Player*); + s32 commonType = va_arg(args, s32); + + // Fixes a bug here where the condition for reaching two-handed idle animation was impossible. Original condition: + /* + ( + ( + (commonType + FIDGET_SWORD_SWING != FIDGET_SWORD_SWING) && + (commonType + FIDGET_SWORD_SWING != FIDGET_ADJUST_SHIELD) + ) || + ( + (player->rightHandType == PLAYER_MODELTYPE_RH_SHIELD) && + ( + (commonType + FIDGET_SWORD_SWING == FIDGET_ADJUST_SHIELD) || + // This should not have been grouped here, because two handed melee weapons do not have shield. + (Player_GetMeleeWeaponHeld2(player) != 0) + ) + ) + ) + */ + + *should = ( + // Animation is not FIDGET_SWORD_SWING and FIDGET_ADJUST_SHIELD (So it's either FIDGET_ADJUST_TUNIC or FIDGET_TAP_FEET) + ( + (commonType + FIDGET_SWORD_SWING != FIDGET_SWORD_SWING) && + (commonType + FIDGET_SWORD_SWING != FIDGET_ADJUST_SHIELD) + ) || + // Animation is FIDGET_ADJUST_SHIELD and player is holding a shield in right hand + ( + (player->rightHandType == PLAYER_MODELTYPE_RH_SHIELD) && + (commonType + FIDGET_SWORD_SWING == FIDGET_ADJUST_SHIELD) + ) || + // Animation is FIDGET_SWORD_SWING and player is holding a melee weapon + ( + (commonType + FIDGET_SWORD_SWING == FIDGET_SWORD_SWING) && + (Player_GetMeleeWeaponHeld(player) != 0) + ) + ); + }); +}, { CVAR_FIX_TWO_HANDED_IDLE_NAME }); +// clang-format on diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index bee6ba72d..eef6bbb39 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2327,6 +2327,27 @@ typedef enum { // - `*Player` VB_SET_STATIC_FLOOR_TYPE, + // #### `result` + // ```c + // ( + // ( + // (commonType + FIDGET_SWORD_SWING != FIDGET_SWORD_SWING) && + // (commonType + FIDGET_SWORD_SWING != FIDGET_ADJUST_SHIELD) + // ) || + // ( + // (player->rightHandType == PLAYER_MODELTYPE_RH_SHIELD) && + // ( + // (commonType + FIDGET_SWORD_SWING == FIDGET_ADJUST_SHIELD) || + // (Player_GetMeleeWeaponHeld2(player) != 0) + // ) + // ) + // ) + // ``` + // #### `args` + // - `Player*` + // - `s32` commonType + VB_SET_IDLE_ANIM, + } GIVanillaBehavior; #endif 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 7da8447c0..e6d73a529 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -8293,19 +8293,20 @@ void Player_ChooseNextIdleAnim(PlayState* play, Player* this) { // // Note that `FIDGET_SWORD_SWING` is the first common fidget type, which is why // all operations are done relative to this type. - if (((commonType + FIDGET_SWORD_SWING != FIDGET_SWORD_SWING) && - (commonType + FIDGET_SWORD_SWING != FIDGET_ADJUST_SHIELD)) || - ((this->rightHandType == PLAYER_MODELTYPE_RH_SHIELD) && - ((commonType + FIDGET_SWORD_SWING == FIDGET_ADJUST_SHIELD) || - (Player_GetMeleeWeaponHeld2(this) != 0)))) { + if (GameInteractor_Should(VB_SET_IDLE_ANIM, + (((commonType + FIDGET_SWORD_SWING != FIDGET_SWORD_SWING) && + (commonType + FIDGET_SWORD_SWING != FIDGET_ADJUST_SHIELD)) || + ((this->rightHandType == PLAYER_MODELTYPE_RH_SHIELD) && + ((commonType + FIDGET_SWORD_SWING == FIDGET_ADJUST_SHIELD) || + (Player_GetMeleeWeaponHeld2(this) != 0)))), + this, commonType)) { //! @bug It is possible for `FIDGET_ADJUST_SHIELD` to be used even if //! a shield is not currently equipped. This is because of how being shieldless //! is implemented. There is no sword-only model type, only //! `PLAYER_MODELGROUP_SWORD_AND_SHIELD` exists. Therefore, the right hand type will be //! `PLAYER_MODELTYPE_RH_SHIELD` if sword is in hand, even if no shield is equipped. if ((commonType + FIDGET_SWORD_SWING == FIDGET_SWORD_SWING) && - Player_HoldsTwoHandedWeapon(this) && - CVarGetInteger(CVAR_ENHANCEMENT("TwoHandedIdle"), 0) == 1) { + Player_HoldsTwoHandedWeapon(this)) { //! @bug This code is unreachable. //! The check above groups the `Player_GetMeleeWeaponHeld2` check and //! `PLAYER_MODELTYPE_RH_SHIELD` conditions together, meaning sword and shield must be From 3e0225272fdca600132da124dcaae28eb60bd747 Mon Sep 17 00:00:00 2001 From: OtherBlue <93625085+OtherBlue@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:03:03 -0300 Subject: [PATCH 13/16] Make "Move in First Person" require "Right Stick Aiming" (#6104) --- .../controls/SohInputEditorWindow.cpp | 11 +++++------ .../overlays/actors/ovl_player_actor/z_player.c | 15 ++++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp index 208833d61..4bf41ec49 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp @@ -1377,12 +1377,11 @@ void SohInputEditorWindow::DrawCameraControlPanel() { CheckboxOptions() .Color(THEME_COLOR) .Tooltip("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming")); - if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) { - CVarCheckbox("Allow moving while in first-person mode", CVAR_SETTING("MoveInFirstPerson"), - CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip("Changes the left stick to move the player while in first-person mode")); - } + CVarCheckbox("Allow moving while in first-person mode", CVAR_SETTING("MoveInFirstPerson"), + CheckboxOptions({ { .disabled = !CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0), + .disabledTooltip = "Forced off because Right Stick Aiming is disabled." } }) + .Color(THEME_COLOR) + .Tooltip("Changes the left stick to move the player while in first-person mode")); CVarCheckbox("Invert Aiming X Axis", CVAR_SETTING("Controls.InvertAimingXAxis"), CheckboxOptions() .Color(THEME_COLOR) 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 e6d73a529..218f232ad 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -12746,7 +12746,8 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) { if (!func_8002DD78(this) && !func_808334B4(this) && (arg2 == 0)) { // First person without weapon // Y Axis - if (!CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0)) { + if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) && + CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) { temp2 += sControlInput->rel.stick_y * 240.0f * invertYAxisMulti * yAxisMulti; } if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) { @@ -12764,7 +12765,8 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) { // X Axis temp2 = 0; - if (!CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0)) { + if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) && + CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) { temp2 += sControlInput->rel.stick_x * -16.0f * invertXAxisMulti * xAxisMulti; } if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) { @@ -12779,7 +12781,8 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) { // Y Axis temp1 = (this->stateFlags1 & PLAYER_STATE1_ON_HORSE) ? 3500 : 14000; - if (!CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0)) { + if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) && + CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) { temp3 += ((sControlInput->rel.stick_y >= 0) ? 1 : -1) * (s32)((1.0f - Math_CosS(sControlInput->rel.stick_y * 200)) * 1500.0f) * invertYAxisMulti * yAxisMulti; @@ -12799,7 +12802,8 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) { temp1 = 19114; temp2 = this->actor.focus.rot.y - this->actor.shape.rot.y; temp3 = 0; - if (!CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0)) { + if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) && + CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) { temp3 = ((sControlInput->rel.stick_x >= 0) ? 1 : -1) * (s32)((1.0f - Math_CosS(sControlInput->rel.stick_x * 200)) * -1500.0f) * invertXAxisMulti * xAxisMulti; @@ -12816,7 +12820,8 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) { this->actor.focus.rot.y = CLAMP(temp2, -temp1, temp1) + this->actor.shape.rot.y; } - if (CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0)) { + if (CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) && + CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) { f32 movementSpeed = LINK_IS_ADULT ? 9.0f : 8.25f; if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && this->currentMask == PLAYER_MASK_BUNNY) { From 0821c2e3150ece5ff9d85260cf451785b6c12999 Mon Sep 17 00:00:00 2001 From: aMannus Date: Thu, 15 Jan 2026 16:51:04 +0100 Subject: [PATCH 14/16] Fix boss souls on item tracker (#6142) --- .../randomizer/randomizer_item_tracker.cpp | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index cd32cc4b1..8c06a8adb 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -755,6 +755,14 @@ void DrawQuest(ItemTrackerItem item) { Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); }; +bool HasBossSoul(RandomizerInf bossSoul) { + uint8_t soulSetting = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BOSS_SOULS); + bool isSoulRandomized = IS_RANDO && (soulSetting == RO_BOSS_SOULS_ON_PLUS_GANON || + (soulSetting == RO_BOSS_SOULS_ON && bossSoul != RAND_INF_GANON_SOUL)); + + return isSoulRandomized ? Flags_GetRandomizerInf(bossSoul) : true; +} + void DrawItem(ItemTrackerItem item) { uint32_t actualItemId = GameInteractor::IsSaveLoaded() ? INV_CONTENT(item.id) : ITEM_NONE; @@ -811,50 +819,47 @@ void DrawItem(ItemTrackerItem item) { break; case RG_GOHMA_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_GOHMA_SOUL); + hasItem = HasBossSoul(RAND_INF_GOHMA_SOUL); itemName = "Gohma's Soul"; break; case RG_KING_DODONGO_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_KING_DODONGO_SOUL); + hasItem = HasBossSoul(RAND_INF_KING_DODONGO_SOUL); itemName = "King Dodongo's Soul"; break; case RG_BARINADE_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_BARINADE_SOUL); + hasItem = HasBossSoul(RAND_INF_BARINADE_SOUL); itemName = "Barinade's Soul"; break; case RG_PHANTOM_GANON_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_PHANTOM_GANON_SOUL); + hasItem = HasBossSoul(RAND_INF_PHANTOM_GANON_SOUL); itemName = "Phantom Ganon's Soul"; break; case RG_VOLVAGIA_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_VOLVAGIA_SOUL); + hasItem = HasBossSoul(RAND_INF_VOLVAGIA_SOUL); itemName = "Volvagia's Soul"; break; case RG_MORPHA_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_MORPHA_SOUL); + hasItem = HasBossSoul(RAND_INF_MORPHA_SOUL); itemName = "Morpha's Soul"; break; case RG_BONGO_BONGO_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_BONGO_BONGO_SOUL); + hasItem = HasBossSoul(RAND_INF_BONGO_BONGO_SOUL); itemName = "Bongo Bongo's Soul"; break; case RG_TWINROVA_SOUL: actualItemId = item.id; - hasItem = Flags_GetRandomizerInf(RAND_INF_TWINROVA_SOUL); + hasItem = HasBossSoul(RAND_INF_TWINROVA_SOUL); itemName = "Twinrova's Soul"; break; case RG_GANON_SOUL: actualItemId = item.id; - hasItem = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BOSS_SOULS) == - RO_BOSS_SOULS_ON_PLUS_GANON - ? Flags_GetRandomizerInf(RAND_INF_GANON_SOUL) - : true; + hasItem = HasBossSoul(RAND_INF_GANON_SOUL); itemName = "Ganon's Soul"; break; From 7850fa82dfea0c31b8e1da01c956d4121da5b989 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Sat, 17 Jan 2026 00:35:45 -0500 Subject: [PATCH 15/16] Modularize enemy defeat count hook & fix counting bugs (#5885) --- .../GameplayStats/EnemyDefeatCounts.cpp | 223 ++++++++++++++++++ soh/soh/Enhancements/gameplaystats.cpp | 182 +++++++------- soh/soh/Enhancements/mods.cpp | 208 ---------------- 3 files changed, 314 insertions(+), 299 deletions(-) create mode 100644 soh/soh/Enhancements/GameplayStats/EnemyDefeatCounts.cpp diff --git a/soh/soh/Enhancements/GameplayStats/EnemyDefeatCounts.cpp b/soh/soh/Enhancements/GameplayStats/EnemyDefeatCounts.cpp new file mode 100644 index 000000000..0174cc74d --- /dev/null +++ b/soh/soh/Enhancements/GameplayStats/EnemyDefeatCounts.cpp @@ -0,0 +1,223 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "src/overlays/actors/ovl_En_Bb/z_en_bb.h" +#include "src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.h" +#include "src/overlays/actors/ovl_En_Mb/z_en_mb.h" +#include "src/overlays/actors/ovl_En_Tite/z_en_tite.h" +#include "src/overlays/actors/ovl_En_Zf/z_en_zf.h" +#include "src/overlays/actors/ovl_En_Wf/z_en_wf.h" +#include "src/overlays/actors/ovl_En_Reeba/z_en_reeba.h" +#include "src/overlays/actors/ovl_En_Peehat/z_en_peehat.h" +#include "src/overlays/actors/ovl_En_Po_Field/z_en_po_field.h" +#include "src/overlays/actors/ovl_En_Poh/z_en_poh.h" +#include "src/overlays/actors/ovl_En_Tp/z_en_tp.h" +#include "src/overlays/actors/ovl_En_Firefly/z_en_firefly.h" + +extern SaveContext gSaveContext; +} + +static void IncrementEnemyDefeatCount(GameplayStatCount countType) { + gSaveContext.ship.stats.count[countType]++; +} + +#define ENEMY_DEFEAT_COUNT(actorID, func) \ + COND_ID_HOOK(OnEnemyDefeat, actorID, true, [](void* refActor) { func(static_cast(refActor)); }); + +#define ENEMY_DEFEAT_COUNT_UNIQUE(actorID, countType) \ + COND_ID_HOOK(OnEnemyDefeat, actorID, true, [](void*) { IncrementEnemyDefeatCount(countType); }); + +static void EnemyDefeatCounts_EnBb(Actor* actor) { + GameplayStatCount countType; + switch (actor->params) { + case ENBB_GREEN: + case ENBB_GREEN_BIG: + countType = COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN; + break; + case ENBB_BLUE: + countType = COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE; + break; + case ENBB_WHITE: + countType = COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE; + break; + case ENBB_RED: + countType = COUNT_ENEMIES_DEFEATED_BUBBLE_RED; + break; + default: + return; + } + + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnDekubaba(Actor* actor) { + GameplayStatCount countType = + (actor->params == DEKUBABA_BIG) ? COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG : COUNT_ENEMIES_DEFEATED_DEKU_BABA; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnZf(Actor* actor) { + GameplayStatCount countType = + (actor->params == ENZF_TYPE_DINOLFOS) ? COUNT_ENEMIES_DEFEATED_DINOLFOS : COUNT_ENEMIES_DEFEATED_LIZALFOS; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnRd(Actor* actor) { + GameplayStatCount countType = (actor->params >= -1) ? COUNT_ENEMIES_DEFEATED_REDEAD : COUNT_ENEMIES_DEFEATED_GIBDO; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnIk(Actor* actor) { + GameplayStatCount countType = + (actor->params == 0) ? COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU : COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnFirefly(Actor* actor) { + GameplayStatCount countType; + switch (actor->params) { + case KEESE_NORMAL_FLY: + case KEESE_NORMAL_PERCH: + countType = COUNT_ENEMIES_DEFEATED_KEESE; + break; + case KEESE_FIRE_FLY: + case KEESE_FIRE_PERCH: + countType = COUNT_ENEMIES_DEFEATED_KEESE_FIRE; + break; + case KEESE_ICE_FLY: + countType = COUNT_ENEMIES_DEFEATED_KEESE_ICE; + break; + default: + return; + } + + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnTp(Actor* actor) { + // Only count the head, otherwise each body segment will increment + if (actor->params == TAILPASARAN_HEAD) { + IncrementEnemyDefeatCount(COUNT_ENEMIES_DEFEATED_TAILPASARAN); + } +} + +static void EnemyDefeatCounts_EnReeba(Actor* actor) { + EnReeba* reeba = (EnReeba*)actor; + GameplayStatCount countType = reeba->isBig ? COUNT_ENEMIES_DEFEATED_LEEVER_BIG : COUNT_ENEMIES_DEFEATED_LEEVER; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnMb(Actor* actor) { + GameplayStatCount countType = + (actor->params == 0) ? COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB : COUNT_ENEMIES_DEFEATED_MOBLIN; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnPeehat(Actor* actor) { + GameplayStatCount countType = + (actor->params == PEAHAT_TYPE_LARVA) ? COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA : COUNT_ENEMIES_DEFEATED_PEAHAT; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnPoh(Actor* actor) { + GameplayStatCount countType = (actor->params == EN_POH_FLAT || actor->params == EN_POH_SHARP) + ? COUNT_ENEMIES_DEFEATED_POE_COMPOSER + : COUNT_ENEMIES_DEFEATED_POE; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnPoField(Actor* actor) { + GameplayStatCount countType = + (actor->params == EN_PO_FIELD_BIG) ? COUNT_ENEMIES_DEFEATED_POE_BIG : COUNT_ENEMIES_DEFEATED_POE; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnSt(Actor* actor) { + GameplayStatCount countType = + (actor->params == 1) ? COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG : COUNT_ENEMIES_DEFEATED_SKULLTULA; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnSw(Actor* actor) { + GameplayStatCount countType; + if (((actor->params & 0xE000) >> 0xD) != 0) { + countType = COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD; + } else { + countType = COUNT_ENEMIES_DEFEATED_SKULLWALLTULA; + } + + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnTite(Actor* actor) { + GameplayStatCount countType = + (actor->params == TEKTITE_BLUE) ? COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE : COUNT_ENEMIES_DEFEATED_TEKTITE_RED; + IncrementEnemyDefeatCount(countType); +} + +static void EnemyDefeatCounts_EnWf(Actor* actor) { + GameplayStatCount countType = + (actor->params == WOLFOS_WHITE) ? COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE : COUNT_ENEMIES_DEFEATED_WOLFOS; + IncrementEnemyDefeatCount(countType); +} + +static void RegisterEnemyDefeatCounts() { + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_ANUBICE, COUNT_ENEMIES_DEFEATED_ANUBIS); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_AM, COUNT_ENEMIES_DEFEATED_ARMOS); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_CLEAR_TAG, COUNT_ENEMIES_DEFEATED_ARWING); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_VALI, COUNT_ENEMIES_DEFEATED_BARI); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_VM, COUNT_ENEMIES_DEFEATED_BEAMOS); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_BIGOKUTA, COUNT_ENEMIES_DEFEATED_BIG_OCTO); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_BILI, COUNT_ENEMIES_DEFEATED_BIRI); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_DNS, COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_TORCH2, COUNT_ENEMIES_DEFEATED_DARK_LINK); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_DH, COUNT_ENEMIES_DEFEATED_DEAD_HAND); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_HINTNUTS, COUNT_ENEMIES_DEFEATED_DEKU_SCRUB); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_DODONGO, COUNT_ENEMIES_DEFEATED_DODONGO); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_DODOJR, COUNT_ENEMIES_DEFEATED_DODONGO_BABY); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_DOOR_KILLER, COUNT_ENEMIES_DEFEATED_DOOR_TRAP); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_FD, COUNT_ENEMIES_DEFEATED_FLARE_DANCER); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_FLOORMAS, COUNT_ENEMIES_DEFEATED_FLOORMASTER); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_TUBO_TRAP, COUNT_ENEMIES_DEFEATED_FLYING_POT); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_YUKABYUN, COUNT_ENEMIES_DEFEATED_FLOOR_TILE); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_FZ, COUNT_ENEMIES_DEFEATED_FREEZARD); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_GELDB, COUNT_ENEMIES_DEFEATED_GERUDO_THIEF); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_GOMA, COUNT_ENEMIES_DEFEATED_GOHMA_LARVA); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_CROW, COUNT_ENEMIES_DEFEATED_GUAY); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_RR, COUNT_ENEMIES_DEFEATED_LIKE_LIKE); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_DEKUNUTS, COUNT_ENEMIES_DEFEATED_MAD_SCRUB); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_OKUTA, COUNT_ENEMIES_DEFEATED_OCTOROK); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_BA, COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_PO_SISTERS, COUNT_ENEMIES_DEFEATED_POE_SISTERS); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_BUBBLE, COUNT_ENEMIES_DEFEATED_SHABOM); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_SB, COUNT_ENEMIES_DEFEATED_SHELLBLADE); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_SKJ, COUNT_ENEMIES_DEFEATED_SKULL_KID); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_NY, COUNT_ENEMIES_DEFEATED_SPIKE); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_SKB, COUNT_ENEMIES_DEFEATED_STALCHILD); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_TEST, COUNT_ENEMIES_DEFEATED_STALFOS); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_EIYER, COUNT_ENEMIES_DEFEATED_STINGER); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_WEIYER, COUNT_ENEMIES_DEFEATED_STINGER); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_BW, COUNT_ENEMIES_DEFEATED_TORCH_SLUG); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_WALLMAS, COUNT_ENEMIES_DEFEATED_WALLMASTER); + ENEMY_DEFEAT_COUNT_UNIQUE(ACTOR_EN_KAREBABA, COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA); + + ENEMY_DEFEAT_COUNT(ACTOR_EN_BB, EnemyDefeatCounts_EnBb); + ENEMY_DEFEAT_COUNT(ACTOR_EN_DEKUBABA, EnemyDefeatCounts_EnDekubaba); + ENEMY_DEFEAT_COUNT(ACTOR_EN_ZF, EnemyDefeatCounts_EnZf); + ENEMY_DEFEAT_COUNT(ACTOR_EN_RD, EnemyDefeatCounts_EnRd); + ENEMY_DEFEAT_COUNT(ACTOR_EN_IK, EnemyDefeatCounts_EnIk); + ENEMY_DEFEAT_COUNT(ACTOR_EN_FIREFLY, EnemyDefeatCounts_EnFirefly); + ENEMY_DEFEAT_COUNT(ACTOR_EN_TP, EnemyDefeatCounts_EnTp); + ENEMY_DEFEAT_COUNT(ACTOR_EN_REEBA, EnemyDefeatCounts_EnReeba); + ENEMY_DEFEAT_COUNT(ACTOR_EN_MB, EnemyDefeatCounts_EnMb); + ENEMY_DEFEAT_COUNT(ACTOR_EN_PEEHAT, EnemyDefeatCounts_EnPeehat); + ENEMY_DEFEAT_COUNT(ACTOR_EN_POH, EnemyDefeatCounts_EnPoh); + ENEMY_DEFEAT_COUNT(ACTOR_EN_PO_FIELD, EnemyDefeatCounts_EnPoField); + ENEMY_DEFEAT_COUNT(ACTOR_EN_ST, EnemyDefeatCounts_EnSt); + ENEMY_DEFEAT_COUNT(ACTOR_EN_SW, EnemyDefeatCounts_EnSw); + ENEMY_DEFEAT_COUNT(ACTOR_EN_TITE, EnemyDefeatCounts_EnTite); + ENEMY_DEFEAT_COUNT(ACTOR_EN_WF, EnemyDefeatCounts_EnWf); +} + +static RegisterShipInitFunc initFunc(RegisterEnemyDefeatCounts); diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index ca360543f..c7df74f35 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -137,97 +137,97 @@ const char* const sceneMappings[] = { }; const char* const countMappings[] = { - "Anubis:", - "Armos:", - "Arwing:", - "Bari:", - "Biri:", - "Beamos:", - "Big Octo:", - "Bubble (Blue):", - "Bubble (Green):", - "Bubble (Red):", - "Bubble (White):", - "Business Scrub:", - "Dark Link:", - "Dead Hand:", - "Deku Baba:", - "Deku Baba (Big):", - "Deku Scrub:", - "Dinolfos:", - "Dodongo:", - "Dodongo (Baby):", - "Door Mimic:", - "Flare Dancer:", - "Floormaster:", - "Flying Floor Tile:", - "Flying Pot:", - "Freezard:", - "Gerudo Thief:", - "Gibdo:", - "Gohma Larva:", - "Guay:", - "Iron Knuckle:", - "Iron Knuckle (Nab):", - "Keese:", - "Keese (Fire):", - "Keese (Ice):", - "Leever:", - "Leever (Big):", - "Like-Like:", - "Lizalfos:", - "Mad Scrub:", - "Moblin:", - "Moblin (Club):", - "Octorok:", - "Parasitic Tentacle:", - "Peahat:", - "Peahat Larva:", - "Poe:", - "Poe (Big):", - "Poe (Composer):", - "Poe Sisters:", - "Redead:", - "Shabom:", - "Shell Blade:", - "Skull Kid:", - "Skulltula:", - "Skulltula (Big):", - "Skulltula (Gold):", - "Skullwalltula:", - "Spike:", - "Stalchild:", - "Stalfos:", - "Stinger:", - "Tailpasaran:", - "Tektite (Blue):", - "Tektite (Red):", - "Torch Slug:", - "Wallmaster:", - "Withered Deku Baba:", - "Wolfos:", - "Wolfos (White):", - "Deku Sticks:", - "Deku Nuts:", - "Bombs:", - "Arrows:", - "Deku Seeds:", - "Bombchus:", - "Beans:", - "A:", - "B:", - "L:", - "R:", - "Z:", - "C-Up:", - "C-Right:", - "C-Down:", - "C-Left:", - "D-Up:", - "D-Right:", - "D-Down:", - "D-Left:", - "Start:", + "Anubis:", // COUNT_ENEMIES_DEFEATED_ANUBIS + "Armos:", // COUNT_ENEMIES_DEFEATED_ARMOS + "Arwing:", // COUNT_ENEMIES_DEFEATED_ARWING + "Bari:", // COUNT_ENEMIES_DEFEATED_BARI + "Beamos:", // COUNT_ENEMIES_DEFEATED_BEAMOS + "Big Octo:", // COUNT_ENEMIES_DEFEATED_BIG_OCTO + "Biri:", // COUNT_ENEMIES_DEFEATED_BIRI + "Bubble (Green):", // COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN + "Bubble (Blue):", // COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE + "Bubble (White):", // COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE + "Bubble (Red):", // COUNT_ENEMIES_DEFEATED_BUBBLE_RED + "Business Scrub:", // COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB + "Dark Link:", // COUNT_ENEMIES_DEFEATED_DARK_LINK + "Dead Hand:", // COUNT_ENEMIES_DEFEATED_DEAD_HAND + "Deku Baba:", // COUNT_ENEMIES_DEFEATED_DEKU_BABA + "Deku Baba (Big):", // COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG + "Deku Scrub:", // COUNT_ENEMIES_DEFEATED_DEKU_SCRUB + "Dinolfos:", // COUNT_ENEMIES_DEFEATED_DINOLFOS + "Dodongo:", // COUNT_ENEMIES_DEFEATED_DODONGO + "Dodongo (Baby):", // COUNT_ENEMIES_DEFEATED_DODONGO_BABY + "Door Mimic:", // COUNT_ENEMIES_DEFEATED_DOOR_TRAP + "Flare Dancer:", // COUNT_ENEMIES_DEFEATED_FLARE_DANCER + "Floormaster:", // COUNT_ENEMIES_DEFEATED_FLOORMASTER + "Flying Pot:", // COUNT_ENEMIES_DEFEATED_FLYING_POT + "Flying Floor Tile:", // COUNT_ENEMIES_DEFEATED_FLOOR_TILE + "Freezard:", // COUNT_ENEMIES_DEFEATED_FREEZARD + "Gerudo Thief:", // COUNT_ENEMIES_DEFEATED_GERUDO_THIEF + "Gibdo:", // COUNT_ENEMIES_DEFEATED_GIBDO + "Gohma Larva:", // COUNT_ENEMIES_DEFEATED_GOHMA_LARVA + "Guay:", // COUNT_ENEMIES_DEFEATED_GUAY + "Iron Knuckle:", // COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE + "Iron Knuckle (Nab):", // COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU + "Keese:", // COUNT_ENEMIES_DEFEATED_KEESE + "Keese (Fire):", // COUNT_ENEMIES_DEFEATED_KEESE_FIRE + "Keese (Ice):", // COUNT_ENEMIES_DEFEATED_KEESE_ICE + "Leever:", // COUNT_ENEMIES_DEFEATED_LEEVER + "Leever (Big):", // COUNT_ENEMIES_DEFEATED_LEEVER_BIG + "Like-Like:", // COUNT_ENEMIES_DEFEATED_LIKE_LIKE + "Lizalfos:", // COUNT_ENEMIES_DEFEATED_LIZALFOS + "Mad Scrub:", // COUNT_ENEMIES_DEFEATED_MAD_SCRUB + "Moblin:", // COUNT_ENEMIES_DEFEATED_MOBLIN + "Moblin (Club):", // COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB + "Octorok:", // COUNT_ENEMIES_DEFEATED_OCTOROK + "Parasitic Tentacle:", // COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE + "Peahat:", // COUNT_ENEMIES_DEFEATED_PEAHAT + "Peahat Larva:", // COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA + "Poe:", // COUNT_ENEMIES_DEFEATED_POE + "Poe (Big):", // COUNT_ENEMIES_DEFEATED_POE_BIG + "Poe (Composer):", // COUNT_ENEMIES_DEFEATED_POE_COMPOSER + "Poe Sisters:", // COUNT_ENEMIES_DEFEATED_POE_SISTERS + "Redead:", // COUNT_ENEMIES_DEFEATED_REDEAD + "Shabom:", // COUNT_ENEMIES_DEFEATED_SHABOM + "Shell Blade:", // COUNT_ENEMIES_DEFEATED_SHELLBLADE + "Skulltula:", // COUNT_ENEMIES_DEFEATED_SKULLTULA + "Skulltula (Big):", // COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG + "Skulltula (Gold):", // COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD + "Skullwalltula:", // COUNT_ENEMIES_DEFEATED_SKULLWALLTULA + "Skull Kid:", // COUNT_ENEMIES_DEFEATED_SKULL_KID + "Spike:", // COUNT_ENEMIES_DEFEATED_SPIKE + "Stalchild:", // COUNT_ENEMIES_DEFEATED_STALCHILD + "Stalfos:", // COUNT_ENEMIES_DEFEATED_STALFOS + "Stinger:", // COUNT_ENEMIES_DEFEATED_STINGER + "Tailpasaran:", // COUNT_ENEMIES_DEFEATED_TAILPASARAN + "Tektite (Blue):", // COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE + "Tektite (Red):", // COUNT_ENEMIES_DEFEATED_TEKTITE_RED + "Torch Slug:", // COUNT_ENEMIES_DEFEATED_TORCH_SLUG + "Wallmaster:", // COUNT_ENEMIES_DEFEATED_WALLMASTER + "Withered Deku Baba:", // COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA + "Wolfos:", // COUNT_ENEMIES_DEFEATED_WOLFOS + "Wolfos (White):", // COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE + "Deku Sticks:", // COUNT_AMMO_USED_STICK + "Deku Nuts:", // COUNT_AMMO_USED_NUT + "Bombs:", // COUNT_AMMO_USED_BOMB + "Arrows:", // COUNT_AMMO_USED_ARROW + "Deku Seeds:", // COUNT_AMMO_USED_SEED + "Bombchus:", // COUNT_AMMO_USED_BOMBCHU + "Beans:", // COUNT_AMMO_USED_BEAN + "A:", // COUNT_BUTTON_PRESSES_A + "B:", // COUNT_BUTTON_PRESSES_B + "L:", // COUNT_BUTTON_PRESSES_L + "R:", // COUNT_BUTTON_PRESSES_R + "Z:", // COUNT_BUTTON_PRESSES_Z + "C-Up:", // COUNT_BUTTON_PRESSES_CUP + "C-Right:", // COUNT_BUTTON_PRESSES_CRIGHT + "C-Down:", // COUNT_BUTTON_PRESSES_CDOWN + "C-Left:", // COUNT_BUTTON_PRESSES_CLEFT + "D-Up:", // COUNT_BUTTON_PRESSES_DUP + "D-Right:", // COUNT_BUTTON_PRESSES_DRIGHT + "D-Down:", // COUNT_BUTTON_PRESSES_DDOWN + "D-Left:", // COUNT_BUTTON_PRESSES_DLEFT + "Start:", // COUNT_BUTTON_PRESSES_START }; #define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f) diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index a5a3e4980..56918a6ad 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -14,24 +14,7 @@ #include "soh/Enhancements/timesaver_hook_handlers.h" #include "soh/Enhancements/randomizer/hook_handlers.h" -#include "src/overlays/actors/ovl_En_Bb/z_en_bb.h" -#include "src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.h" -#include "src/overlays/actors/ovl_En_Mb/z_en_mb.h" -#include "src/overlays/actors/ovl_En_Tite/z_en_tite.h" -#include "src/overlays/actors/ovl_En_Zf/z_en_zf.h" -#include "src/overlays/actors/ovl_En_Wf/z_en_wf.h" -#include "src/overlays/actors/ovl_En_Reeba/z_en_reeba.h" -#include "src/overlays/actors/ovl_En_Peehat/z_en_peehat.h" -#include "src/overlays/actors/ovl_En_Po_Field/z_en_po_field.h" -#include "src/overlays/actors/ovl_En_Poh/z_en_poh.h" -#include "src/overlays/actors/ovl_En_Tp/z_en_tp.h" -#include "src/overlays/actors/ovl_En_Firefly/z_en_firefly.h" -#include "src/overlays/actors/ovl_En_Xc/z_en_xc.h" -#include "src/overlays/actors/ovl_Fishing/z_fishing.h" #include "src/overlays/actors/ovl_Obj_Switch/z_obj_switch.h" -#include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h" -#include "src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.h" -#include "src/overlays/actors/ovl_En_Elf/z_en_elf.h" #include "objects/object_link_boy/object_link_boy.h" #include "objects/object_link_child/object_link_child.h" #include "soh_assets.h" @@ -426,196 +409,6 @@ void RegisterResetNaviTimer() { }); } -// this map is used for enemies that can be uniquely identified by their id -// and that are always counted -// enemies that can't be uniquely identified by their id -// or only sometimes count (like ACTOR_EN_TP) -// have to be manually handled in RegisterEnemyDefeatCounts -static std::unordered_map uniqueEnemyIdToStatCount = { - { ACTOR_EN_ANUBICE, COUNT_ENEMIES_DEFEATED_ANUBIS }, - { ACTOR_EN_AM, COUNT_ENEMIES_DEFEATED_ARMOS }, - { ACTOR_EN_CLEAR_TAG, COUNT_ENEMIES_DEFEATED_ARWING }, - { ACTOR_EN_VALI, COUNT_ENEMIES_DEFEATED_BARI }, - { ACTOR_EN_VM, COUNT_ENEMIES_DEFEATED_BEAMOS }, - { ACTOR_EN_BIGOKUTA, COUNT_ENEMIES_DEFEATED_BIG_OCTO }, - { ACTOR_EN_BILI, COUNT_ENEMIES_DEFEATED_BIRI }, - { ACTOR_EN_DNS, COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB }, - { ACTOR_EN_TORCH, COUNT_ENEMIES_DEFEATED_DARK_LINK }, - { ACTOR_EN_DH, COUNT_ENEMIES_DEFEATED_DEAD_HAND }, - { ACTOR_EN_HINTNUTS, COUNT_ENEMIES_DEFEATED_DEKU_SCRUB }, - { ACTOR_EN_DODONGO, COUNT_ENEMIES_DEFEATED_DODONGO }, - { ACTOR_EN_DODOJR, COUNT_ENEMIES_DEFEATED_DODONGO_BABY }, - { ACTOR_DOOR_KILLER, COUNT_ENEMIES_DEFEATED_DOOR_TRAP }, - { ACTOR_EN_FD, COUNT_ENEMIES_DEFEATED_FLARE_DANCER }, - { ACTOR_EN_FLOORMAS, COUNT_ENEMIES_DEFEATED_FLOORMASTER }, - { ACTOR_EN_TUBO_TRAP, COUNT_ENEMIES_DEFEATED_FLYING_POT }, - { ACTOR_EN_YUKABYUN, COUNT_ENEMIES_DEFEATED_FLOOR_TILE }, - { ACTOR_EN_FZ, COUNT_ENEMIES_DEFEATED_FREEZARD }, - { ACTOR_EN_GELDB, COUNT_ENEMIES_DEFEATED_GERUDO_THIEF }, - { ACTOR_EN_GOMA, COUNT_ENEMIES_DEFEATED_GOHMA_LARVA }, - { ACTOR_EN_CROW, COUNT_ENEMIES_DEFEATED_GUAY }, - { ACTOR_EN_RR, COUNT_ENEMIES_DEFEATED_LIKE_LIKE }, - { ACTOR_EN_DEKUNUTS, COUNT_ENEMIES_DEFEATED_MAD_SCRUB }, - { ACTOR_EN_OKUTA, COUNT_ENEMIES_DEFEATED_OCTOROK }, - { ACTOR_EN_BA, COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE }, - { ACTOR_EN_PO_SISTERS, COUNT_ENEMIES_DEFEATED_POE_SISTERS }, - { ACTOR_EN_BUBBLE, COUNT_ENEMIES_DEFEATED_SHABOM }, - { ACTOR_EN_SB, COUNT_ENEMIES_DEFEATED_SHELLBLADE }, - { ACTOR_EN_SKJ, COUNT_ENEMIES_DEFEATED_SKULL_KID }, - { ACTOR_EN_NY, COUNT_ENEMIES_DEFEATED_SPIKE }, - { ACTOR_EN_SKB, COUNT_ENEMIES_DEFEATED_STALCHILD }, - { ACTOR_EN_TEST, COUNT_ENEMIES_DEFEATED_STALFOS }, - { ACTOR_EN_WEIYER, COUNT_ENEMIES_DEFEATED_STINGER }, - { ACTOR_EN_BW, COUNT_ENEMIES_DEFEATED_TORCH_SLUG }, - { ACTOR_EN_WALLMAS, COUNT_ENEMIES_DEFEATED_WALLMASTER }, - { ACTOR_EN_KAREBABA, COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA }, -}; - -void RegisterEnemyDefeatCounts() { - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - Actor* actor = static_cast(refActor); - if (uniqueEnemyIdToStatCount.contains(actor->id)) { - gSaveContext.ship.stats.count[uniqueEnemyIdToStatCount[actor->id]]++; - } else { - switch (actor->id) { - case ACTOR_EN_BB: - if (actor->params == ENBB_GREEN || actor->params == ENBB_GREEN_BIG) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN]++; - } else if (actor->params == ENBB_BLUE) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE]++; - } else if (actor->params == ENBB_WHITE) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE]++; - } else if (actor->params == ENBB_RED) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_RED]++; - } - break; - - case ACTOR_EN_DEKUBABA: - if (actor->params == DEKUBABA_BIG) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]++; - } - break; - - case ACTOR_EN_ZF: - if (actor->params == ENZF_TYPE_DINOLFOS) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_DINOLFOS]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_LIZALFOS]++; - } - break; - - case ACTOR_EN_RD: - if (actor->params >= -1) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_REDEAD]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_GIBDO]++; - } - break; - - case ACTOR_EN_IK: - if (actor->params == 0) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE]++; - } - break; - - case ACTOR_EN_FIREFLY: - if (actor->params == KEESE_NORMAL_FLY || actor->params == KEESE_NORMAL_PERCH) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_KEESE]++; - } else if (actor->params == KEESE_FIRE_FLY || actor->params == KEESE_FIRE_PERCH) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_KEESE_FIRE]++; - } else if (actor->params == KEESE_ICE_FLY) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_KEESE_ICE]++; - } - break; - - case ACTOR_EN_REEBA: { - EnReeba* reeba = (EnReeba*)actor; - if (reeba->isBig) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_LEEVER_BIG]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_LEEVER]++; - } - } break; - - case ACTOR_EN_MB: - if (actor->params == 0) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_MOBLIN]++; - } - break; - - case ACTOR_EN_PEEHAT: - if (actor->params == PEAHAT_TYPE_LARVA) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_PEAHAT]++; - } - break; - - case ACTOR_EN_POH: - if (actor->params == EN_POH_FLAT || actor->params == EN_POH_SHARP) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_POE_COMPOSER]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_POE]++; - } - break; - - case ACTOR_EN_PO_FIELD: - if (actor->params == EN_PO_FIELD_BIG) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_POE_BIG]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_POE]++; - } - break; - - case ACTOR_EN_ST: - if (actor->params == 1) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA]++; - } - break; - - case ACTOR_EN_SW: - if (((actor->params & 0xE000) >> 0xD) != 0) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_SKULLWALLTULA]++; - } - break; - - case ACTOR_EN_TP: - // Only count the head, otherwise each body segment will increment - if (actor->params == TAILPASARAN_HEAD) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_TAILPASARAN]++; - } - break; - - case ACTOR_EN_TITE: - if (actor->params == TEKTITE_BLUE) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]++; - } - break; - - case ACTOR_EN_WF: - if (actor->params == WOLFOS_WHITE) { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE]++; - } else { - gSaveContext.ship.stats.count[COUNT_ENEMIES_DEFEATED_WOLFOS]++; - } - break; - } - } - }); -} - void RegisterBossDefeatTimestamps() { GameInteractor::Instance->RegisterGameHook([](void* refActor) { Actor* actor = static_cast(refActor); @@ -728,7 +521,6 @@ void InitMods() { RegisterMenuPathFix(); RegisterMirrorModeHandler(); RegisterResetNaviTimer(); - RegisterEnemyDefeatCounts(); RegisterBossDefeatTimestamps(); RegisterFloorSwitchesHook(); RegisterPatchHandHandler(); From 0dc6989438d29513b960866539f63455701d296f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Mon, 19 Jan 2026 00:10:09 +0000 Subject: [PATCH 16/16] 9.1.2 (#6160) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6c6788b1..99b433067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_C_STANDARD 23 CACHE STRING "The C standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") -project(Ship VERSION 9.1.1 LANGUAGES C CXX) +project(Ship VERSION 9.1.2 LANGUAGES C CXX) include(CMake/soh-cvars.cmake) include(CMake/lus-cvars.cmake) set(SPDLOG_LEVEL_TRACE 0)