From caece5052f33a397e717f401ff1fa66e5e64249f Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 03:47:18 -0700 Subject: [PATCH 1/5] feat(enemy-randomizer): Seeded mode for randomized enemy sizes --- .../ExtraModes/EnemyRandomizer.cpp | 18 ++++- .../ExtraModes/RandomizedEnemySizes.cpp | 65 ++++++++++++++----- soh/soh/Enhancements/enhancementTypes.h | 6 ++ 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp index 04514b35205..e84c3fb6e0d 100644 --- a/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp +++ b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp @@ -947,9 +947,21 @@ void RegisterEnemyRandomizerWidgets() { "- Random: Enemies are randomized every time you load a room.\n" "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file.\n")); - SohGui::mSohMenu->AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes")) - .Options(UIWidgets::CheckboxOptions().Tooltip("Enemies and Bosses spawn with random sizes.")); + static const std::map enemySizeModes = { + { ENEMY_SIZE_OFF, "Disabled" }, + { ENEMY_SIZE_RANDOM, "Random" }, + { ENEMY_SIZE_RANDOM_SEEDED, "Random (Seeded)" } + }; + + SohGui::mSohMenu->AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes")) + .Options( + UIWidgets::ComboboxOptions() + .DefaultIndex(ENEMY_SIZE_OFF) + .ComboMap(enemySizeModes) + .Tooltip("Enemies and bosses spawn with random sizes.\n\n" + "- Random: Sizes are randomized every time you load a room.\n" + "- Random (Seeded): Sizes are consistent based on the current randomizer seed/file.\n")); SohGui::mSohMenu->AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth")) diff --git a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp index acb4b94d3b9..9c7fc6e846d 100644 --- a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp +++ b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp @@ -1,6 +1,9 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ObjectExtension/ActorMaximumHealth.h" #include "soh/ShipInit.hpp" +#include "soh/ShipUtils.h" +#include "soh/Enhancements/enhancementTypes.h" +#include "soh/Enhancements/randomizer/SeedContext.h" extern "C" { #include "functions.h" @@ -15,6 +18,50 @@ 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 float ComputeRandomScale(Actor* actor, bool isSmallOnly) { + const int32_t mode = CVAR_RANDO_ENEMY_SIZE_VALUE; + float randomNumber = 0.0f; + bool isBigActor = false; + + if (mode == ENEMY_SIZE_RANDOM_SEEDED) { + // Deterministic seed from actor spawn data + global seed, matching the pattern used by the enemy randomizer + // (EnemyRandomizer.cpp). The salt (0xDEAD) decorrelates the sequence from the enemy replacement RNG so that + // the size and enemy type don't share the same initial state when both systems use the same actor ID and + // position. + const uint32_t seed = gPlayState->sceneNum + actor->id + static_cast(actor->home.pos.x) + + static_cast(actor->home.pos.y) + static_cast(actor->home.pos.z) + + actor->home.rot.x + actor->home.rot.y + actor->home.rot.z + actor->params; + + uint64_t randomState = 0; + ShipUtils::RandInit( + (seed ^ 0xDEAD) + (IS_RANDO + ? Rando::Context::GetInstance()->GetSeed() + : gSaveContext.ship.stats.fileCreatedAt), + &randomState); + + isBigActor = !isSmallOnly && ShipUtils::Random(0, 2, &randomState) == 1; + if (isBigActor) { + // Between 100% and 300% size. + randomNumber = static_cast(ShipUtils::Random(0, 200, &randomState)); + return 1.0f + randomNumber / 100.0f; + } + + // Between 10% and 100% size. + randomNumber = static_cast(ShipUtils::Random(0, 90, &randomState)); + return 0.1f + randomNumber / 100.0f; + } + + // Unseeded random -- different every room load. + isBigActor = !isSmallOnly && rand() % 2; + if (isBigActor) { + randomNumber = rand() % 200; + return 1.0f + randomNumber / 100.0f; + } + + randomNumber = rand() % 90; + return 0.1f + randomNumber / 100.0f; +} + static void RandomizedEnemySizes(void* refActor) { // Randomized Enemy Sizes Actor* actor = static_cast(refActor); @@ -30,27 +77,11 @@ static void RandomizedEnemySizes(void* refActor) { 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); - } - + const float randomScale = ComputeRandomScale(actor, smallOnlyEnemy); Actor_SetScale(actor, actor->scale.z * randomScale); if (CVAR_ENEMY_SCALE_HEALTH_VALUE && (actor->category == ACTORCAT_ENEMY)) { diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h index 7b4b79a6f97..7b762a74eb3 100644 --- a/soh/soh/Enhancements/enhancementTypes.h +++ b/soh/soh/Enhancements/enhancementTypes.h @@ -45,6 +45,12 @@ typedef enum { ENEMY_RANDOMIZER_RANDOM_SEEDED, } EnemyRandomizerMode; +typedef enum { + ENEMY_SIZE_OFF, + ENEMY_SIZE_RANDOM, + ENEMY_SIZE_RANDOM_SEEDED, +} EnemySizeMode; + typedef enum { BOOTSEQUENCE_DEFAULT, BOOTSEQUENCE_AUTHENTIC, From b0554ba7f8c15400304325358279057d3e20107b Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 06:18:27 -0700 Subject: [PATCH 2/5] Update soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp Co-authored-by: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> --- soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp index 9c7fc6e846d..0b39b3c80a9 100644 --- a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp +++ b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp @@ -52,7 +52,7 @@ static float ComputeRandomScale(Actor* actor, bool isSmallOnly) { } // Unseeded random -- different every room load. - isBigActor = !isSmallOnly && rand() % 2; + isBigActor = !isSmallOnly && ShipUtils::Random(0, 2, &randomState) == 1; if (isBigActor) { randomNumber = rand() % 200; return 1.0f + randomNumber / 100.0f; From be09c4250e45ca04d0945bc4120fe365c0233056 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 06:18:40 -0700 Subject: [PATCH 3/5] Update soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp Co-authored-by: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> --- soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp index 0b39b3c80a9..98222a9b61d 100644 --- a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp +++ b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp @@ -22,6 +22,7 @@ static float ComputeRandomScale(Actor* actor, bool isSmallOnly) { const int32_t mode = CVAR_RANDO_ENEMY_SIZE_VALUE; float randomNumber = 0.0f; bool isBigActor = false; + static uint64_t randomState = 0; if (mode == ENEMY_SIZE_RANDOM_SEEDED) { // Deterministic seed from actor spawn data + global seed, matching the pattern used by the enemy randomizer From 75b5972c784b470f49456972b72d0ed2db5010c9 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 06:18:50 -0700 Subject: [PATCH 4/5] Update soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp Co-authored-by: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> --- soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp index 98222a9b61d..f4c24f47506 100644 --- a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp +++ b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp @@ -33,7 +33,6 @@ static float ComputeRandomScale(Actor* actor, bool isSmallOnly) { static_cast(actor->home.pos.y) + static_cast(actor->home.pos.z) + actor->home.rot.x + actor->home.rot.y + actor->home.rot.z + actor->params; - uint64_t randomState = 0; ShipUtils::RandInit( (seed ^ 0xDEAD) + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() From 07d54e43f5ed27c83ff62dce8c3bd04ecd69153d Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 06:28:05 -0700 Subject: [PATCH 5/5] fix(enemy-randomizer): ShipUtils::Random() over rand() --- soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp index f4c24f47506..7dc2f7ded6e 100644 --- a/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp +++ b/soh/soh/Enhancements/ExtraModes/RandomizedEnemySizes.cpp @@ -54,11 +54,11 @@ static float ComputeRandomScale(Actor* actor, bool isSmallOnly) { // Unseeded random -- different every room load. isBigActor = !isSmallOnly && ShipUtils::Random(0, 2, &randomState) == 1; if (isBigActor) { - randomNumber = rand() % 200; + randomNumber = ShipUtils::Random(0, 200, &randomState); return 1.0f + randomNumber / 100.0f; } - randomNumber = rand() % 90; + randomNumber = ShipUtils::Random(0, 90, &randomState); return 0.1f + randomNumber / 100.0f; }