summaryrefslogtreecommitdiff
path: root/soh
diff options
context:
space:
mode:
authorPepe20129 <72659707+Pepe20129@users.noreply.github.com>2026-03-21 19:46:15 +0100
committerGitHub <noreply@github.com>2026-03-21 18:46:15 +0000
commit4e82ea192a0e0bb7bfb7a11ff2fa6ce499f72aab (patch)
treeaaf372f19c6b9b059f92d0a7402d00c07b066056 /soh
parent2af5d2112543f88c4476d5cf082aeca9e601f5f2 (diff)
Enemy rando cleanup 2 (#6365)
Move stuff to main file, delete header & refactor lists
Diffstat (limited to 'soh')
-rw-r--r--soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp (renamed from soh/soh/Enhancements/enemyrandomizer.cpp)851
-rw-r--r--soh/soh/Enhancements/enemyrandomizer.h10
-rw-r--r--soh/soh/SohGui/SohMenuEnhancements.cpp53
-rw-r--r--soh/src/code/z_actor.c1
4 files changed, 390 insertions, 525 deletions
diff --git a/soh/soh/Enhancements/enemyrandomizer.cpp b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp
index c9f343f6b..f03080e8f 100644
--- a/soh/soh/Enhancements/enemyrandomizer.cpp
+++ b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp
@@ -1,4 +1,3 @@
-#include "enemyrandomizer.h"
#include "functions.h"
#include "macros.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
@@ -9,6 +8,9 @@
#include "soh/OTRGlobals.h"
#include "soh/cvar_prefixes.h"
#include "soh/ResourceManagerHelpers.h"
+#include "soh/SohGui/MenuTypes.h"
+#include "soh/SohGui/SohMenu.h"
+#include "soh/SohGui/SohGui.hpp"
extern "C" {
#include <z64.h>
@@ -25,226 +27,91 @@ extern "C" {
extern PlayState* gPlayState;
}
+namespace SohGui {
+extern std::shared_ptr<SohMenu> mSohMenu;
+}
+
#define CVAR_ENEMY_RANDOMIZER_NAME CVAR_ENHANCEMENT("RandomizedEnemies")
#define CVAR_ENEMY_RANDOMIZER_DEFAULT ENEMY_RANDOMIZER_OFF
#define CVAR_ENEMY_RANDOMIZER_VALUE CVarGetInteger(CVAR_ENEMY_RANDOMIZER_NAME, CVAR_ENEMY_RANDOMIZER_DEFAULT)
#define ENEMY_RANDOMIZER_ENABLED CVAR_ENEMY_RANDOMIZER_VALUE != CVAR_ENEMY_RANDOMIZER_DEFAULT
typedef struct EnemyEntry {
+ const char* cvar;
+ const char* name;
int16_t id;
int16_t params;
} EnemyEntry;
-bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX);
-bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy);
-EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play);
-
-const char* enemyCVarList[] = {
- CVAR_ENHANCEMENT("RandomizedEnemyList.Anubis"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Armos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Arwing"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.BabyDodongo"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Bari"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Beamos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.BigSkulltula"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.BigStalchild"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Biri"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.BlackKnuckle"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.BlueTektite"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Bubble"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.ClubMoblin"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.DarkLink"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Dinolfos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Dodongo"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.FireKeese"),
- // CVAR_ENHANCEMENT("RandomizedEnemyList.FlareDancer"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.FloorTile"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Floormaster"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPeahat"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPot"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Freezard"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.GerudoFighter"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Gibdo"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.GohmaLarva"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Guay"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.IceKeese"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisFireKeese"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisFloormaster"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisIceKeese"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisKeese"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisRedead"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisSkulltula"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.InvisStalfos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Keese"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.LargeBaba"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.LikeLike"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Lizalfos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.MadScrub"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.NormalWolfos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.PeahatLarva"),
- // CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Redead"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.RedTektite"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Shabom"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.ShellBlade"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Skulltula"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.SkullKid"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.SmallBaba"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.SmallStalchild"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.SpearMoblin"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Spike"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Stalfos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Stinger"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Tailparasan"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.TorchSlug"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.Wallmaster"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteKnuckle"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteWolfos"),
- CVAR_ENHANCEMENT("RandomizedEnemyList.WitheredBaba"),
-};
-
-const char* enemyNameList[] = {
- "Anubis",
- "Armos",
- "Arwing",
- "Baby Dodongo",
- "Bari",
- "Beamos",
- "Big Skulltula",
- "Stalchild (Big)",
- "Biri",
- "Iron Knuckle (Black)",
- "Blue Tektite",
- "Bubble",
- "Club Moblin",
- "Dark Link",
- "Dinolfos",
- "Dodongo",
- "Fire Keese",
- //"Flare Dancer",
- "Floor Tile",
- "Floormaster",
- "Flying Peahat",
- "Flying Pot",
- "Freezard",
- "Gerudo Fighter",
- "Gibdo",
- "Gohma Larva",
- "Guay",
- "Ice Keese",
- "Invisible Fire Keese",
- "Invisible Floormaster",
- "Invisible Ice Keese",
- "Invisible Keese",
- "Invisible Redead",
- "Invisible Skulltula",
- "Invisible Stalfos",
- "Keese",
- "Large Deku Baba",
- "Like-Like",
- "Lizalfos",
- "Mad Scrub",
- "Wolfos (Normal)",
- "Peahat Larva",
- //"Poe",
- "Redead",
- "Red Tektite",
- "Shabom",
- "Shell Blade",
- "Skulltula",
- "Skull Kid",
- "Small Deku Baba",
- "Stalchild (Small)",
- "Spear Moblin",
- "Spike",
- "Stalfos",
- "Stinger",
- "Tailpasaran",
- "Torch Slug",
- "Wallmaster",
- "Iron Knuckle (White)",
- "Wolfos (White)",
- "Withered Deku Baba",
-};
-
+// clang-format off
static EnemyEntry randomizedEnemySpawnTable[] = {
- { ACTOR_EN_ANUBICE_TAG, 1 }, // Anubis
- { ACTOR_EN_AM, -1 }, // Armos
- { ACTOR_EN_CLEAR_TAG, 1 }, // Arwing
- { ACTOR_EN_DODOJR, 0 }, // Baby Dodongo
- { ACTOR_EN_VALI, -1 }, // Bari (big jellyfish)
- { ACTOR_EN_VM, 1280 }, // Beamos
- { ACTOR_EN_ST, 1 }, // Skulltula (big)
- { ACTOR_EN_SKB, 20 }, // Stalchild (big)
- { ACTOR_EN_BILI, 0 }, // Biri (jellyfish)
- { ACTOR_EN_IK, 2 }, // Iron Knuckle (black, standing)
- { ACTOR_EN_TITE, -2 }, // Tektite (blue)
- { ACTOR_EN_BB, -1 }, // Bubble (flying skull enemy) (blue)
- { ACTOR_EN_MB, 0 }, // Club Moblin
- { ACTOR_EN_TORCH2, 0 }, // Dark Link
- { ACTOR_EN_ZF, -2 }, // Dinolfos
- { ACTOR_EN_DODONGO, -1 }, // Dodongo
- { ACTOR_EN_FIREFLY, 1 }, // Fire Keese
- // { ACTOR_EN_FD, 0 }, // Flare Dancer (possible cause of crashes because of spawning flame actors on
- // sloped ground)
- { ACTOR_EN_YUKABYUN, 0 }, // Flying Floor Tile
- { ACTOR_EN_FLOORMAS, 0 }, // Floormaster
- { ACTOR_EN_PEEHAT, -1 }, // Flying Peahat (big grounded, doesn't spawn larva)
- { ACTOR_EN_TUBO_TRAP, 0 }, // Flying pot
- { ACTOR_EN_FZ, 0 }, // Freezard
- { ACTOR_EN_GELDB, 0 }, // Gerudo Fighter
- { ACTOR_EN_RD, 32766 }, // Gibdo (standing)
- { ACTOR_EN_GOMA, 7 }, // Gohma Larva (Non-Gohma rooms)
- { ACTOR_EN_CROW, 0 }, // Guay
- { ACTOR_EN_FIREFLY, 4 }, // Ice Keese
- { ACTOR_EN_FIREFLY, 0x8001 }, // Fire Keese (invisible)
- { ACTOR_EN_FLOORMAS, 0x8000 }, // Floormaster (invisible)
- { ACTOR_EN_FIREFLY, 0x8004 }, // Ice Keese (invisible)
- { ACTOR_EN_FIREFLY, 0x8002 }, // Keese (invisible)
- { ACTOR_EN_RD, 3 }, // Redead (invisible)
- { ACTOR_EN_ST, 2 }, // Skulltula (invisible)
- { ACTOR_EN_TEST, 0 }, // Stalfos (invisible)
- { ACTOR_EN_FIREFLY, 2 }, // Regular Keese
- { ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large)
- // Doesn't work (reliant on surface, without a spawner it kills itself too quickly)
- // { ACTOR_EN_REEBA, 0 }, // Leever
- { ACTOR_EN_RR, 0 }, // Like-Like
- { ACTOR_EN_ZF, -1 }, // Lizalfos
- { ACTOR_EN_DEKUNUTS, 768 }, // Mad Scrub (triple attack) (projectiles don't work)
- { ACTOR_EN_WF, 0 }, // Wolfos (normal)
- // Doesn't work (actor directly uses water box collision to handle hiding/popping up)
- // { ACTOR_EN_OKUTA, 0 }, // Octorok
- { ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva
- // Doesn't work (Seems to rely on other objects?)
- // { ACTOR_EN_POH, 0 }, // Poe
- // Doesn't work (Seems to rely on other objects?)
- // { ACTOR_EN_POH, 2 }, // Poe (composer Sharp)
- // Doesn't work (Seems to rely on other objects?)
- // { ACTOR_EN_POH, 3 }, // Poe (composer Flat)
- { ACTOR_EN_RD, 1 }, // Redead (standing)
- { ACTOR_EN_TITE, -1 }, // Tektite (red)
- { ACTOR_EN_BUBBLE, 0 }, // Shabom (bubble)
- { ACTOR_EN_SB, 0 }, // Shell Blade
- { ACTOR_EN_ST, 0 }, // Skulltula (normal)
- { ACTOR_EN_SKJ, 4159 }, // Skull Kid
- { ACTOR_EN_DEKUBABA, 0 }, // Deku Baba (small)
- { ACTOR_EN_SKB, 1 }, // Stalchild (small)
- { ACTOR_EN_MB, -1 }, // Spear Moblin
- { ACTOR_EN_NY, 0 }, // Spike (rolling enemy)
- { ACTOR_EN_TEST, 2 }, // Stalfos
- { ACTOR_EN_EIYER, 10 }, // Stinger (land) (One in formation, sink under floor and do not activate)
- { ACTOR_EN_TP, -1 }, // Electric Tailpasaran
- { ACTOR_EN_BW, 0 }, // Torch Slug
- { ACTOR_EN_WALLMAS, 1 }, // Wallmaster
- { ACTOR_EN_IK, 3 }, // Iron Knuckle (white, standing)
- { ACTOR_EN_WF, 1 }, // Wolfos (white)
- { ACTOR_EN_KAREBABA, 0 }, // Withered Deku Baba
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Anubis"), "Anubis", ACTOR_EN_ANUBICE_TAG, 1 }, // Anubis
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Armos"), "Armos", ACTOR_EN_AM, -1 }, // Armos
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Arwing"), "Arwing", ACTOR_EN_CLEAR_TAG, 1 }, // Arwing
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.BabyDodongo"), "Baby Dodongo", ACTOR_EN_DODOJR, 0 }, // Baby Dodongo
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Bari"), "Bari", ACTOR_EN_VALI, -1 }, // Bari (big jellyfish)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Beamos"), "Beamos", ACTOR_EN_VM, 1280 }, // Beamos
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.BigSkulltula"), "Big Skulltula", ACTOR_EN_ST, 1 }, // Skulltula (big)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.BigStalchild"), "Stalchild (Big)", ACTOR_EN_SKB, 20 }, // Stalchild (big)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Biri"), "Biri", ACTOR_EN_BILI, 0 }, // Biri (jellyfish)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.BlackKnuckle"), "Iron Knuckle (Black)", ACTOR_EN_IK, 2 }, // Iron Knuckle (black, standing)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.BlueTektite"), "Blue Tektite", ACTOR_EN_TITE, -2 }, // Tektite (blue)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Bubble"), "Bubble", ACTOR_EN_BB, -1 }, // Bubble (flying skull enemy) (blue)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.ClubMoblin"), "Club Moblin", ACTOR_EN_MB, 0 }, // Club Moblin
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.DarkLink"), "Dark Link", ACTOR_EN_TORCH2, 0 }, // Dark Link
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Dinolfos"), "Dinolfos", ACTOR_EN_ZF, -2 }, // Dinolfos
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Dodongo"), "Dodongo", ACTOR_EN_DODONGO, -1 }, // Dodongo
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.FireKeese"), "Fire Keese", ACTOR_EN_FIREFLY, 1 }, // Fire Keese
+ // { CVAR_ENHANCEMENT("RandomizedEnemyList.FlareDancer"), "Flare Dancer", ACTOR_EN_FD, 0 }, // Flare Dancer (possible cause of crashes because of spawning flame actors on sloped ground)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.FloorTile"), "Floor Tile", ACTOR_EN_YUKABYUN, 0 }, // Flying Floor Tile
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Floormaster"), "Floormaster", ACTOR_EN_FLOORMAS, 0 }, // Floormaster
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPeahat"), "Flying Peahat", ACTOR_EN_PEEHAT, -1 }, // Flying Peahat (big grounded, doesn't spawn larva)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPot"), "Flying Pot", ACTOR_EN_TUBO_TRAP, 0 }, // Flying pot
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Freezard"), "Freezard", ACTOR_EN_FZ, 0 }, // Freezard
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.GerudoFighter"), "Gerudo Fighter", ACTOR_EN_GELDB, 0 }, // Gerudo Fighter
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Gibdo"), "Gibdo", ACTOR_EN_RD, 32766 }, // Gibdo (standing)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.GohmaLarva"), "Gohma Larva", ACTOR_EN_GOMA, 7 }, // Gohma Larva (Non-Gohma rooms)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Guay"), "Guay", ACTOR_EN_CROW, 0 }, // Guay
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.IceKeese"), "Ice Keese", ACTOR_EN_FIREFLY, 4 }, // Ice Keese
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisFireKeese"), "Invisible Fire Keese", ACTOR_EN_FIREFLY, 0x8001 }, // Fire Keese (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisFloormaster"), "Invisible Floormaster", ACTOR_EN_FLOORMAS, 0x8000 }, // Floormaster (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisIceKeese"), "Invisible Ice Keese", ACTOR_EN_FIREFLY, 0x8004 }, // Ice Keese (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisKeese"), "Invisible Keese", ACTOR_EN_FIREFLY, 0x8002 }, // Keese (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisRedead"), "Invisible Redead", ACTOR_EN_RD, 3 }, // Redead (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisSkulltula"), "Invisible Skulltula", ACTOR_EN_ST, 2 }, // Skulltula (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.InvisStalfos"), "Invisible Stalfos", ACTOR_EN_TEST, 0 }, // Stalfos (invisible)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Keese"), "Keese", ACTOR_EN_FIREFLY, 2 }, // Regular Keese
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.LargeBaba"), "Large Deku Baba", ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large)
+ // { CVAR_ENHANCEMENT("RandomizedEnemyList.Leever"), "Leever", ACTOR_EN_REEBA, 0 }, // Leever Doesn't work (reliant on surface, without a spawner it kills itself too quickly)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.LikeLike"), "Like-Like", ACTOR_EN_RR, 0 }, // Like-Like
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Lizalfos"), "Lizalfos", ACTOR_EN_ZF, -1 }, // Lizalfos
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.MadScrub"), "Mad Scrub", ACTOR_EN_DEKUNUTS, 768 }, // Mad Scrub (triple attack) (projectiles don't work)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.NormalWolfos"), "Wolfos (Normal)", ACTOR_EN_WF, 0 }, // Wolfos (normal)
+ // { CVAR_ENHANCEMENT("RandomizedEnemyList.Octorok"), "Octorok", ACTOR_EN_OKUTA, 0 }, // Octorok Doesn't work (actor directly uses water box collision to handle hiding/popping up)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.PeahatLarva"), "Peahat Larva", ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva
+ // { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 0 }, // Poe Doesn't work (Seems to rely on other objects?)
+ // { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 2 }, // Poe (composer Sharp) Doesn't work (Seems to rely on other objects?)
+ // { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 3 }, // Poe (composer Flat) Doesn't work (Seems to rely on other objects?)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Redead"), "Redead", ACTOR_EN_RD, 1 }, // Redead (standing)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.RedTektite"), "Red Tektite", ACTOR_EN_TITE, -1 }, // Tektite (red)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Shabom"), "Shabom", ACTOR_EN_BUBBLE, 0 }, // Shabom (bubble)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.ShellBlade"), "Shell Blade", ACTOR_EN_SB, 0 }, // Shell Blade
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Skulltula"), "Skulltula", ACTOR_EN_ST, 0 }, // Skulltula (normal)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.SkullKid"), "Skull Kid", ACTOR_EN_SKJ, 4159 }, // Skull Kid
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.SmallBaba"), "Small Deku Baba", ACTOR_EN_DEKUBABA, 0 }, // Deku Baba (small)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.SmallStalchild"), "Stalchild (Small)", ACTOR_EN_SKB, 1 }, // Stalchild (small)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.SpearMoblin"), "Spear Moblin", ACTOR_EN_MB, -1 }, // Spear Moblin
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Spike"), "Spike", ACTOR_EN_NY, 0 }, // Spike (rolling enemy)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Stalfos"), "Stalfos", ACTOR_EN_TEST, 2 }, // Stalfos
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Stinger"), "Stinger", ACTOR_EN_EIYER, 10 }, // Stinger (land) (One in formation, sink under floor and do not activate)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Tailparasan"), "Tailpasaran", ACTOR_EN_TP, -1 }, // Electric Tailpasaran
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.TorchSlug"), "Torch Slug", ACTOR_EN_BW, 0 }, // Torch Slug
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.Wallmaster"), "Wallmaster", ACTOR_EN_WALLMAS, 1 }, // Wallmaster
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteKnuckle"), "Iron Knuckle (White)", ACTOR_EN_IK, 3 }, // Iron Knuckle (white, standing)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteWolfos"), "Wolfos (White)", ACTOR_EN_WF, 1 }, // Wolfos (white)
+ { CVAR_ENHANCEMENT("RandomizedEnemyList.WitheredBaba"), "Withered Deku Baba", ACTOR_EN_KAREBABA, 0 }, // Withered Deku Baba
};
-
-// assert sizes without accidental 0 initialization
-static_assert(ARRAY_COUNT(enemyCVarList) == ARRAY_COUNT(enemyNameList), "");
-static_assert(ARRAY_COUNT(enemyCVarList) == ARRAY_COUNT(randomizedEnemySpawnTable), "");
-static_assert(ARRAY_COUNT(enemyCVarList) == RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE, "");
+// clang-format on
static int enemiesToRandomize[] = {
ACTOR_EN_ANUBICE_TAG, // Anubis
@@ -255,163 +122,168 @@ static int enemiesToRandomize[] = {
ACTOR_EN_OKUTA, // Octorok
ACTOR_EN_WALLMAS, // Wallmaster
ACTOR_EN_DODONGO, // Dodongo
- // ACTOR_EN_REEBA, // Leever (reliant on spawner (z_en_encount1.c))
- ACTOR_EN_PEEHAT, // Flying Peahat, big one spawning larva, larva
- ACTOR_EN_ZF, // Lizalfos, Dinolfos
- ACTOR_EN_GOMA, // Gohma Larva (normal, eggs, gohma eggs)
- ACTOR_EN_BUBBLE, // Shabom (bubble)
- ACTOR_EN_DODOJR, // Baby Dodongo
- ACTOR_EN_TORCH2, // Dark Link
- ACTOR_EN_BILI, // Biri (small jellyfish)
- ACTOR_EN_TP, // Electric Tailpasaran
- ACTOR_EN_ST, // Skulltula (normal, big, invisible)
- ACTOR_EN_BW, // Torch Slug
- ACTOR_EN_EIYER, // Stinger (land)
- ACTOR_EN_MB, // Moblins (Club, spear)
- ACTOR_EN_DEKUBABA, // Deku Baba (small, large)
- ACTOR_EN_AM, // Armos (enemy variant)
- ACTOR_EN_DEKUNUTS, // Mad Scrub (single attack, triple attack)
- ACTOR_EN_VALI, // Bari (big jellyfish) (spawns very high up)
- ACTOR_EN_BB, // Bubble (flying skull enemy) (all colors)
- ACTOR_EN_YUKABYUN, // Flying Floor Tile
- ACTOR_EN_VM, // Beamos
- ACTOR_EN_FLOORMAS, // Floormaster
- ACTOR_EN_RD, // Redead, Gibdo
- ACTOR_EN_SW, // Skullwalltula
- ACTOR_EN_FD, // Flare Dancer
- ACTOR_EN_SB, // Shell Blade
- ACTOR_EN_KAREBABA, // Withered Deku Baba
- ACTOR_EN_RR, // Like-Like
- ACTOR_EN_NY, // Spike (rolling enemy)
- ACTOR_EN_IK, // Iron Knuckle
- ACTOR_EN_TUBO_TRAP, // Flying pot
- ACTOR_EN_FZ, // Freezard
- ACTOR_EN_WEIYER, // Stinger (Water)
- ACTOR_EN_HINTNUTS, // Hint Deku Scrubs
- ACTOR_EN_WF, // Wolfos
- ACTOR_EN_SKB, // Stalchild
- ACTOR_EN_CROW, // Guay
- ACTOR_EN_SKJ, // Skull Kid
+ ACTOR_EN_PEEHAT, // Flying Peahat, big one spawning larva, larva
+ ACTOR_EN_ZF, // Lizalfos, Dinolfos
+ ACTOR_EN_GOMA, // Gohma Larva (normal, eggs, gohma eggs)
+ ACTOR_EN_BUBBLE, // Shabom (bubble)
+ ACTOR_EN_DODOJR, // Baby Dodongo
+ ACTOR_EN_TORCH2, // Dark Link
+ ACTOR_EN_BILI, // Biri (small jellyfish)
+ ACTOR_EN_TP, // Electric Tailpasaran
+ ACTOR_EN_ST, // Skulltula (normal, big, invisible)
+ ACTOR_EN_BW, // Torch Slug
+ ACTOR_EN_EIYER, // Stinger (land)
+ ACTOR_EN_MB, // Moblins (Club, spear)
+ ACTOR_EN_DEKUBABA, // Deku Baba (small, large)
+ ACTOR_EN_AM, // Armos (enemy variant)
+ ACTOR_EN_DEKUNUTS, // Mad Scrub (single attack, triple attack)
+ ACTOR_EN_VALI, // Bari (big jellyfish) (spawns very high up)
+ ACTOR_EN_BB, // Bubble (flying skull enemy) (all colors)
+ ACTOR_EN_YUKABYUN, // Flying Floor Tile
+ ACTOR_EN_VM, // Beamos
+ ACTOR_EN_FLOORMAS, // Floormaster
+ ACTOR_EN_RD, // Redead, Gibdo
+ ACTOR_EN_SW, // Skullwalltula
+ ACTOR_EN_FD, // Flare Dancer
+ ACTOR_EN_SB, // Shell Blade
+ ACTOR_EN_KAREBABA, // Withered Deku Baba
+ ACTOR_EN_RR, // Like-Like
+ ACTOR_EN_NY, // Spike (rolling enemy)
+ ACTOR_EN_IK, // Iron Knuckle
+ ACTOR_EN_TUBO_TRAP, // Flying pot
+ ACTOR_EN_FZ, // Freezard
+ ACTOR_EN_WEIYER, // Stinger (Water)
+ ACTOR_EN_HINTNUTS, // Hint Deku Scrubs
+ ACTOR_EN_WF, // Wolfos
+ ACTOR_EN_SKB, // Stalchild
+ ACTOR_EN_CROW, // Guay
+ ACTOR_EN_SKJ, // Skull Kid
+ // ACTOR_EN_REEBA, // Leever (reliant on spawner (z_en_encount1.c))
};
-uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* posY, s16* posZ, int16_t* rotX,
- int16_t* rotY, int16_t* rotZ, int16_t* params) {
-
- uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(play->sceneNum);
-
- // Hack to remove enemies that wrongfully spawn because of bypassing object dependency with enemy randomizer on.
- // This should probably be handled on OTR generation in the future when object dependency is fully removed.
- // Remove bats and Skulltulas from graveyard.
- // Remove Octorok in Lost Woods.
- if (((*actorId == ACTOR_EN_FIREFLY || (*actorId == ACTOR_EN_SW && *params == 0)) &&
- play->sceneNum == SCENE_GRAVEYARD) ||
- (*actorId == ACTOR_EN_OKUTA && play->sceneNum == SCENE_LOST_WOODS)) {
- return 0;
- }
-
- // Hack to change a pot in Spirit Temple that holds a Deku Shield to not hold anything.
- // This should probably be handled on OTR generation in the future when object dependency is fully removed.
- // This Deku Shield doesn't normally spawn in authentic gameplay because of object dependency.
- if (*actorId == ACTOR_OBJ_TSUBO && *params == 24597) {
- *params = 24067;
- }
-
- // Lengthen timer in non-MQ Jabu Jabu bubble room.
- if (!isMQ && *actorId == ACTOR_OBJ_ROOMTIMER && *params == 30760 && play->sceneNum == SCENE_JABU_JABU &&
- play->roomCtx.curRoom.num == 12) {
- *params = (*params & ~0x3FF) | 120;
- }
-
- if (IsEnemyFoundToRandomize(play->sceneNum, play->roomCtx.curRoom.num, *actorId, *params, *posX)) {
-
- // When replacing Iron Knuckles in Spirit Temple, move them away from the throne because
- // some enemies can get stuck on the throne.
- if (*actorId == ACTOR_EN_IK && play->sceneNum == SCENE_SPIRIT_TEMPLE) {
- if (*params == 6657) {
- *posX = *posX + 150;
- } else if (*params == 6401) {
- *posX = *posX - 150;
- }
- }
-
- // Move like-likes in MQ Jabu Jabu down into the room as they otherwise get stuck on Song of Time blocks.
- if (*actorId == ACTOR_EN_RR && play->sceneNum == SCENE_JABU_JABU && play->roomCtx.curRoom.num == 11) {
- if (*posX == 1003) {
- *posX = *posX - 75;
- } else {
- *posX = *posX + 75;
- }
- *posY = *posY - 200;
- }
-
- // Do a raycast from the original position of the actor to find the ground below it, then try to place
- // the new actor on the ground. This way enemies don't spawn very high in the sky, and gives us control
- // over height offsets per enemy from a proven grounded position.
- CollisionPoly poly;
- Vec3f pos;
- f32 raycastResult;
-
- pos.x = *posX;
- pos.y = *posY + 50;
- pos.z = *posZ;
- raycastResult = BgCheck_AnyRaycastFloor1(&play->colCtx, &poly, &pos);
-
- // If ground is found below actor, move actor to that height.
- if (raycastResult > BGCHECK_Y_MIN) {
- *posY = raycastResult;
- }
-
- // Get randomized enemy ID and parameter.
- uint32_t seed =
- play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params;
- EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play);
+bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) {
+ uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
- *actorId = randomEnemy.id;
- *params = randomEnemy.params;
+ // Freezard - Child Link can only kill this with jump slash Deku Sticks or other equipment like bombs.
+ // Beamos - Needs bombs.
+ // Anubis - Needs fire.
+ // Shell Blade & Spike - Child Link can't kill these with sword or Deku Stick.
+ // Flare dancer, Arwing & Dark Link - Both go out of bounds way too easily, softlocking the player.
+ // Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies left.
+ // Club Moblin - Many issues with them falling or placing out of bounds. Maybe fixable in the future?
+ bool enemiesToExcludeClearRooms =
+ enemy.id == ACTOR_EN_FZ || enemy.id == ACTOR_EN_VM || enemy.id == ACTOR_EN_SB || enemy.id == ACTOR_EN_NY ||
+ enemy.id == ACTOR_EN_CLEAR_TAG || enemy.id == ACTOR_EN_WALLMAS || enemy.id == ACTOR_EN_TORCH2 ||
+ (enemy.id == ACTOR_EN_MB && enemy.params == 0) || enemy.id == ACTOR_EN_FD || enemy.id == ACTOR_EN_ANUBICE_TAG;
- // Straighten out enemies so they aren't flipped on their sides when the original spawn is.
- *rotX = 0;
+ // Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms.
+ bool enemiesToExcludeTimedRooms = enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI;
- switch (*actorId) {
- // When spawning big jellyfish, spawn it up high.
- case ACTOR_EN_VALI:
- *posY = *posY + 300;
- break;
- // Spawn Peahat off the ground, otherwise it kills itself by colliding with the ground.
- case ACTOR_EN_PEEHAT:
- if (*params == 1) {
- *posY = *posY + 100;
- }
- break;
- // Spawn Skulltulas off the ground.
- case ACTOR_EN_ST:
- *posY = *posY + 200;
- break;
- // Spawn flying enemies off the ground.
- case ACTOR_EN_FIREFLY:
- case ACTOR_EN_BILI:
- case ACTOR_EN_BB:
- case ACTOR_EN_CLEAR_TAG:
- case ACTOR_EN_CROW:
- *posY = *posY + 75;
- break;
- default:
- break;
- }
+ switch (sceneNum) {
+ // Deku Tree
+ case SCENE_DEKU_TREE:
+ return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 9)) &&
+ !(isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10)));
+ // Dodongo's Cavern
+ case SCENE_DODONGOS_CAVERN:
+ return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
+ !(isMQ && enemiesToExcludeClearRooms && (roomNum == 5 || roomNum == 13 || roomNum == 14)));
+ // Jabu Jabu
+ case SCENE_JABU_JABU:
+ return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 8 || roomNum == 9)) &&
+ !(!isMQ && enemiesToExcludeTimedRooms && roomNum == 12) &&
+ !(isMQ && enemiesToExcludeClearRooms && (roomNum == 11 || roomNum == 14)));
+ // Forest Temple
+ case SCENE_FOREST_TEMPLE:
+ return (!(!isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 6 || roomNum == 10 || roomNum == 18 || roomNum == 21)) &&
+ !(isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 5 || roomNum == 6 || roomNum == 18 || roomNum == 21)));
+ // Fire Temple
+ case SCENE_FIRE_TEMPLE:
+ return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
+ !(isMQ && enemiesToExcludeClearRooms && (roomNum == 15 || roomNum == 17 || roomNum == 18)));
+ // Water Temple
+ case SCENE_WATER_TEMPLE:
+ return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18 || roomNum == 19)) &&
+ !(isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18)));
+ // Spirit Temple
+ case SCENE_SPIRIT_TEMPLE:
+ return (!(!isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 1 || roomNum == 10 || roomNum == 17 || roomNum == 20)) &&
+ !(isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 1 || roomNum == 2 || roomNum == 4 || roomNum == 10 || roomNum == 15 ||
+ roomNum == 19 || roomNum == 20)));
+ // Shadow Temple
+ case SCENE_SHADOW_TEMPLE:
+ return (
+ !(!isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 1 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 16 || roomNum == 17 ||
+ roomNum == 19 || roomNum == 20)) &&
+ !(isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 1 || roomNum == 6 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 20)));
+ // Ganon's Castle Trials
+ case SCENE_INSIDE_GANONS_CASTLE:
+ return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 5 || roomNum == 9)) &&
+ !(isMQ && enemiesToExcludeClearRooms &&
+ (roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9)));
+ // Ice Caverns
+ case SCENE_ICE_CAVERN:
+ return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 7)) &&
+ !(isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 7)));
+ // Bottom of the Well
+ // Exclude Dark Link from room with holes in the floor because it can pull you in a like-like making the player
+ // fall down.
+ case SCENE_BOTTOM_OF_THE_WELL:
+ return (!(!isMQ && enemy.id == ACTOR_EN_TORCH2 && roomNum == 3));
+ // Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
+ // Gerudo Training Ground.
+ case SCENE_GERUDO_TRAINING_GROUND:
+ return (!(enemy.id == ACTOR_EN_TORCH2 && roomNum == 6) &&
+ !(!isMQ && enemiesToExcludeTimedRooms && (roomNum == 1 || roomNum == 7)) &&
+ !(!isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 5 || roomNum == 10)) &&
+ !(isMQ && enemiesToExcludeTimedRooms &&
+ (roomNum == 1 || roomNum == 3 || roomNum == 5 || roomNum == 7)) &&
+ !(isMQ && enemiesToExcludeClearRooms && roomNum == 10));
+ // Don't allow certain enemies in Ganon's Tower because they would spawn up on the ceiling,
+ // becoming impossible to kill.
+ // Ganon's Tower.
+ case SCENE_GANONS_TOWER:
+ return (!(enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI ||
+ (enemy.id == ACTOR_EN_ZF && enemy.params == -1)));
+ // Ganon's Tower Escape.
+ case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
+ return (!((enemiesToExcludeTimedRooms || (enemy.id == ACTOR_EN_ZF && enemy.params == -1)) && roomNum == 1));
+ // Don't allow big Stalchildren, big Peahats and the large Bari (jellyfish) during the Gohma fight because they
+ // can clip into Gohma and it crashes the game. Likely because Gohma on the ceiling can't handle collision with
+ // other enemies.
+ case SCENE_DEKU_TREE_BOSS:
+ return (!enemiesToExcludeTimedRooms && !(enemy.id == ACTOR_EN_SKB && enemy.params == 20) &&
+ !(enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1));
+ // Grottos.
+ case SCENE_GROTTOS:
+ return (!(enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 7)));
+ // Royal Grave.
+ case SCENE_ROYAL_FAMILYS_TOMB:
+ return (!(enemiesToExcludeClearRooms && roomNum == 0));
+ // Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
+ // Death Mountain Crater.
+ case SCENE_DEATH_MOUNTAIN_CRATER:
+ return (enemy.id != ACTOR_EN_TORCH2);
+ default:
+ return 1;
}
-
- // Enemy finished randomization process.
- return 1;
}
static std::vector<EnemyEntry> selectedEnemyList;
void GetSelectedEnemies() {
selectedEnemyList.clear();
- for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) {
+ for (int i = 0; i < ARRAY_COUNT(randomizedEnemySpawnTable); i++) {
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) {
selectedEnemyList.push_back(randomizedEnemySpawnTable[i]);
- } else if (CVarGetInteger(enemyCVarList[i], 1)) {
+ } else if (CVarGetInteger(randomizedEnemySpawnTable[i].cvar, 1)) {
selectedEnemyList.push_back(randomizedEnemySpawnTable[i]);
}
}
@@ -513,119 +385,114 @@ bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId,
return 0;
}
-bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) {
- uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
+uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* posY, s16* posZ, int16_t* rotX,
+ int16_t* rotY, int16_t* rotZ, int16_t* params) {
- // Freezard - Child Link can only kill this with jump slash Deku Sticks or other equipment like bombs.
- // Beamos - Needs bombs.
- // Anubis - Needs fire.
- // Shell Blade & Spike - Child Link can't kill these with sword or Deku Stick.
- // Flare dancer, Arwing & Dark Link - Both go out of bounds way too easily, softlocking the player.
- // Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies left.
- // Club Moblin - Many issues with them falling or placing out of bounds. Maybe fixable in the future?
- bool enemiesToExcludeClearRooms =
- enemy.id == ACTOR_EN_FZ || enemy.id == ACTOR_EN_VM || enemy.id == ACTOR_EN_SB || enemy.id == ACTOR_EN_NY ||
- enemy.id == ACTOR_EN_CLEAR_TAG || enemy.id == ACTOR_EN_WALLMAS || enemy.id == ACTOR_EN_TORCH2 ||
- (enemy.id == ACTOR_EN_MB && enemy.params == 0) || enemy.id == ACTOR_EN_FD || enemy.id == ACTOR_EN_ANUBICE_TAG;
+ uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(play->sceneNum);
- // Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms.
- bool enemiesToExcludeTimedRooms = enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI;
+ // Hack to remove enemies that wrongfully spawn because of bypassing object dependency with enemy randomizer on.
+ // This should probably be handled on OTR generation in the future when object dependency is fully removed.
+ // Remove bats and Skulltulas from graveyard.
+ // Remove Octorok in Lost Woods.
+ if (((*actorId == ACTOR_EN_FIREFLY || (*actorId == ACTOR_EN_SW && *params == 0)) &&
+ play->sceneNum == SCENE_GRAVEYARD) ||
+ (*actorId == ACTOR_EN_OKUTA && play->sceneNum == SCENE_LOST_WOODS)) {
+ return 0;
+ }
- switch (sceneNum) {
- // Deku Tree
- case SCENE_DEKU_TREE:
- return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 9)) &&
- !(isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10)));
- // Dodongo's Cavern
- case SCENE_DODONGOS_CAVERN:
- return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
- !(isMQ && enemiesToExcludeClearRooms && (roomNum == 5 || roomNum == 13 || roomNum == 14)));
- // Jabu Jabu
- case SCENE_JABU_JABU:
- return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 8 || roomNum == 9)) &&
- !(!isMQ && enemiesToExcludeTimedRooms && roomNum == 12) &&
- !(isMQ && enemiesToExcludeClearRooms && (roomNum == 11 || roomNum == 14)));
- // Forest Temple
- case SCENE_FOREST_TEMPLE:
- return (!(!isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 6 || roomNum == 10 || roomNum == 18 || roomNum == 21)) &&
- !(isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 5 || roomNum == 6 || roomNum == 18 || roomNum == 21)));
- // Fire Temple
- case SCENE_FIRE_TEMPLE:
- return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
- !(isMQ && enemiesToExcludeClearRooms && (roomNum == 15 || roomNum == 17 || roomNum == 18)));
- // Water Temple
- case SCENE_WATER_TEMPLE:
- return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18 || roomNum == 19)) &&
- !(isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18)));
- // Spirit Temple
- case SCENE_SPIRIT_TEMPLE:
- return (!(!isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 1 || roomNum == 10 || roomNum == 17 || roomNum == 20)) &&
- !(isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 1 || roomNum == 2 || roomNum == 4 || roomNum == 10 || roomNum == 15 ||
- roomNum == 19 || roomNum == 20)));
- // Shadow Temple
- case SCENE_SHADOW_TEMPLE:
- return (
- !(!isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 1 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 16 || roomNum == 17 ||
- roomNum == 19 || roomNum == 20)) &&
- !(isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 1 || roomNum == 6 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 20)));
- // Ganon's Castle Trials
- case SCENE_INSIDE_GANONS_CASTLE:
- return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 5 || roomNum == 9)) &&
- !(isMQ && enemiesToExcludeClearRooms &&
- (roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9)));
- // Ice Caverns
- case SCENE_ICE_CAVERN:
- return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 7)) &&
- !(isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 7)));
- // Bottom of the Well
- // Exclude Dark Link from room with holes in the floor because it can pull you in a like-like making the player
- // fall down.
- case SCENE_BOTTOM_OF_THE_WELL:
- return (!(!isMQ && enemy.id == ACTOR_EN_TORCH2 && roomNum == 3));
- // Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
- // Gerudo Training Ground.
- case SCENE_GERUDO_TRAINING_GROUND:
- return (!(enemy.id == ACTOR_EN_TORCH2 && roomNum == 6) &&
- !(!isMQ && enemiesToExcludeTimedRooms && (roomNum == 1 || roomNum == 7)) &&
- !(!isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 5 || roomNum == 10)) &&
- !(isMQ && enemiesToExcludeTimedRooms &&
- (roomNum == 1 || roomNum == 3 || roomNum == 5 || roomNum == 7)) &&
- !(isMQ && enemiesToExcludeClearRooms && roomNum == 10));
- // Don't allow certain enemies in Ganon's Tower because they would spawn up on the ceiling,
- // becoming impossible to kill.
- // Ganon's Tower.
- case SCENE_GANONS_TOWER:
- return (!(enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI ||
- (enemy.id == ACTOR_EN_ZF && enemy.params == -1)));
- // Ganon's Tower Escape.
- case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
- return (!((enemiesToExcludeTimedRooms || (enemy.id == ACTOR_EN_ZF && enemy.params == -1)) && roomNum == 1));
- // Don't allow big Stalchildren, big Peahats and the large Bari (jellyfish) during the Gohma fight because they
- // can clip into Gohma and it crashes the game. Likely because Gohma on the ceiling can't handle collision with
- // other enemies.
- case SCENE_DEKU_TREE_BOSS:
- return (!enemiesToExcludeTimedRooms && !(enemy.id == ACTOR_EN_SKB && enemy.params == 20) &&
- !(enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1));
- // Grottos.
- case SCENE_GROTTOS:
- return (!(enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 7)));
- // Royal Grave.
- case SCENE_ROYAL_FAMILYS_TOMB:
- return (!(enemiesToExcludeClearRooms && roomNum == 0));
- // Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
- // Death Mountain Crater.
- case SCENE_DEATH_MOUNTAIN_CRATER:
- return (enemy.id != ACTOR_EN_TORCH2);
- default:
- return 1;
+ // Hack to change a pot in Spirit Temple that holds a Deku Shield to not hold anything.
+ // This should probably be handled on OTR generation in the future when object dependency is fully removed.
+ // This Deku Shield doesn't normally spawn in authentic gameplay because of object dependency.
+ if (*actorId == ACTOR_OBJ_TSUBO && *params == 24597) {
+ *params = 24067;
+ }
+
+ // Lengthen timer in non-MQ Jabu Jabu bubble room.
+ if (!isMQ && *actorId == ACTOR_OBJ_ROOMTIMER && *params == 30760 && play->sceneNum == SCENE_JABU_JABU &&
+ play->roomCtx.curRoom.num == 12) {
+ *params = (*params & ~0x3FF) | 120;
+ }
+
+ if (IsEnemyFoundToRandomize(play->sceneNum, play->roomCtx.curRoom.num, *actorId, *params, *posX)) {
+
+ // When replacing Iron Knuckles in Spirit Temple, move them away from the throne because
+ // some enemies can get stuck on the throne.
+ if (*actorId == ACTOR_EN_IK && play->sceneNum == SCENE_SPIRIT_TEMPLE) {
+ if (*params == 6657) {
+ *posX = *posX + 150;
+ } else if (*params == 6401) {
+ *posX = *posX - 150;
+ }
+ }
+
+ // Move like-likes in MQ Jabu Jabu down into the room as they otherwise get stuck on Song of Time blocks.
+ if (*actorId == ACTOR_EN_RR && play->sceneNum == SCENE_JABU_JABU && play->roomCtx.curRoom.num == 11) {
+ if (*posX == 1003) {
+ *posX = *posX - 75;
+ } else {
+ *posX = *posX + 75;
+ }
+ *posY = *posY - 200;
+ }
+
+ // Do a raycast from the original position of the actor to find the ground below it, then try to place
+ // the new actor on the ground. This way enemies don't spawn very high in the sky, and gives us control
+ // over height offsets per enemy from a proven grounded position.
+ CollisionPoly poly;
+ Vec3f pos;
+ f32 raycastResult;
+
+ pos.x = *posX;
+ pos.y = *posY + 50;
+ pos.z = *posZ;
+ raycastResult = BgCheck_AnyRaycastFloor1(&play->colCtx, &poly, &pos);
+
+ // If ground is found below actor, move actor to that height.
+ if (raycastResult > BGCHECK_Y_MIN) {
+ *posY = raycastResult;
+ }
+
+ // Get randomized enemy ID and parameter.
+ uint32_t seed =
+ play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params;
+ EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play);
+
+ *actorId = randomEnemy.id;
+ *params = randomEnemy.params;
+
+ // Straighten out enemies so they aren't flipped on their sides when the original spawn is.
+ *rotX = 0;
+
+ switch (*actorId) {
+ // When spawning big jellyfish, spawn it up high.
+ case ACTOR_EN_VALI:
+ *posY = *posY + 300;
+ break;
+ // Spawn Peahat off the ground, otherwise it kills itself by colliding with the ground.
+ case ACTOR_EN_PEEHAT:
+ if (*params == 1) {
+ *posY = *posY + 100;
+ }
+ break;
+ // Spawn Skulltulas off the ground.
+ case ACTOR_EN_ST:
+ *posY = *posY + 200;
+ break;
+ // Spawn flying enemies off the ground.
+ case ACTOR_EN_FIREFLY:
+ case ACTOR_EN_BILI:
+ case ACTOR_EN_BB:
+ case ACTOR_EN_CLEAR_TAG:
+ case ACTOR_EN_CROW:
+ *posY = *posY + 75;
+ break;
+ default:
+ break;
+ }
}
+
+ // Enemy finished randomization process.
+ return 1;
}
void FixClubMoblinScale(void* ptr) {
@@ -1047,4 +914,66 @@ void RegisterEnemyRandomizer() {
});
}
+static const std::map<int32_t, const char*> enemyRandomizerModes = {
+ { ENEMY_RANDOMIZER_OFF, "Disabled" },
+ { ENEMY_RANDOMIZER_RANDOM, "Random" },
+ { ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)" },
+};
+
+void RegisterEnemyRandomizerWidgets() {
+ WidgetPath path = { "Enhancements", "Extra Modes", SECTION_COLUMN_2 };
+
+ SohGui::mSohMenu->AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX)
+ .CVar(CVAR_ENHANCEMENT("RandomizedEnemies"))
+ .Callback([](WidgetInfo& info) { GetSelectedEnemies(); })
+ .Options(
+ UIWidgets::ComboboxOptions()
+ .DefaultIndex(ENEMY_RANDOMIZER_OFF)
+ .ComboMap(enemyRandomizerModes)
+ .Tooltip("Replaces fixed enemies throughout the game with a random enemy. Bosses, Mini-Bosses and a "
+ "few specific regular enemies are excluded.\n"
+ "Enemies that need more than Deku Nuts & either Deku Sticks or a sword to kill are excluded "
+ "from spawning in \"clear enemy\" rooms.\n\n"
+ "- 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."));
+
+ SohGui::mSohMenu->AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth"))
+ .PreFunc([](WidgetInfo& info) {
+ info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0);
+ })
+ .Options(UIWidgets::CheckboxOptions().Tooltip("Scales normal enemies Health with their randomized size.\n"
+ "*This will NOT affect Bosses!*"));
+
+ SohGui::mSohMenu->AddWidget(path, "Enemy List", WIDGET_SEPARATOR_TEXT).PreFunc([](WidgetInfo& info) {
+ info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
+ });
+
+ SohGui::mSohMenu->AddWidget(path, "Select all Enemies", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All"))
+ .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); })
+ .Callback([](WidgetInfo& info) { GetSelectedEnemies(); });
+
+ SohGui::mSohMenu->AddWidget(path, "Enemy List", WIDGET_SEPARATOR).PreFunc([](WidgetInfo& info) {
+ info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
+ });
+
+ for (int i = 0; i < ARRAY_COUNT(randomizedEnemySpawnTable); i++) {
+ SohGui::mSohMenu->AddWidget(path, randomizedEnemySpawnTable[i].name, WIDGET_CVAR_CHECKBOX)
+ .CVar(randomizedEnemySpawnTable[i].cvar)
+ .Options(UIWidgets::CheckboxOptions().DefaultValue(true))
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
+ info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0);
+ info.options->disabledTooltip = "These options are disabled because \"Select All Enemies\" is enabled.";
+ })
+ .Callback([](WidgetInfo& info) { GetSelectedEnemies(); });
+ }
+}
+
static RegisterShipInitFunc initFunc(RegisterEnemyRandomizer, { CVAR_ENEMY_RANDOMIZER_NAME });
+static RegisterMenuInitFunc menuInitFunc(RegisterEnemyRandomizerWidgets);
diff --git a/soh/soh/Enhancements/enemyrandomizer.h b/soh/soh/Enhancements/enemyrandomizer.h
deleted file mode 100644
index 6e1708fbc..000000000
--- a/soh/soh/Enhancements/enemyrandomizer.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include <libultraship/libultra/types.h>
-#include "item-tables/ItemTableTypes.h"
-
-#define RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE 59
-
-extern const char* enemyCVarList[];
-extern const char* enemyNameList[];
-extern void GetSelectedEnemies();
diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp
index aab8c4d04..8e9a9059b 100644
--- a/soh/soh/SohGui/SohMenuEnhancements.cpp
+++ b/soh/soh/SohGui/SohMenuEnhancements.cpp
@@ -4,7 +4,6 @@
#include <soh/Enhancements/game-interactor/GameInteractor.h>
#include <soh/OTRGlobals.h>
#include <soh/Enhancements/cosmetics/authenticGfxPatches.h>
-#include <soh/Enhancements/enemyrandomizer.h>
#include <soh/Enhancements/TimeDisplay/TimeDisplay.h>
extern "C" {
@@ -136,12 +135,6 @@ static const std::map<int32_t, const char*> mirroredWorldModes = {
{ MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED, "Dungeons Random (Seeded)" },
};
-static const std::map<int32_t, const char*> enemyRandomizerModes = {
- { ENEMY_RANDOMIZER_OFF, "Disabled" },
- { ENEMY_RANDOMIZER_RANDOM, "Random" },
- { ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)" },
-};
-
void SohMenu::AddMenuEnhancements() {
// Add Enhancements Menu
AddMenuEntry("Enhancements", CVAR_SETTING("Menu.EnhancementsSidebarSection"));
@@ -1684,52 +1677,6 @@ void SohMenu::AddMenuEnhancements() {
.PreFunc(
[](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; });
- path.column = SECTION_COLUMN_2;
- AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX)
- .CVar(CVAR_ENHANCEMENT("RandomizedEnemies"))
- .Callback([](WidgetInfo& info) { GetSelectedEnemies(); })
- .Options(
- ComboboxOptions()
- .DefaultIndex(ENEMY_RANDOMIZER_OFF)
- .ComboMap(enemyRandomizerModes)
- .Tooltip("Replaces fixed enemies throughout the game with a random enemy. Bosses, Mini-Bosses and a "
- "few specific regular enemies are excluded.\n"
- "Enemies that need more than Deku Nuts & either Deku Sticks or a sword to kill are excluded "
- "from spawning in \"clear enemy\" rooms.\n\n"
- "- Random: Enemies are randomized every time you load a room.\n"
- "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file."));
- AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_CHECKBOX)
- .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes"))
- .Options(CheckboxOptions().Tooltip("Enemies and Bosses spawn with random sizes."));
- AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX)
- .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth"))
- .PreFunc([](WidgetInfo& info) {
- info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0);
- })
- .Options(CheckboxOptions().Tooltip(
- "Scales normal enemies Health with their randomized size. *This will NOT affect Bosses!*"));
- AddWidget(path, "Enemy List", WIDGET_SEPARATOR_TEXT).PreFunc([](WidgetInfo& info) {
- info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
- });
- AddWidget(path, "Select all Enemies", WIDGET_CVAR_CHECKBOX)
- .CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All"))
- .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); })
- .Callback([](WidgetInfo& info) { GetSelectedEnemies(); });
- AddWidget(path, "Enemy List", WIDGET_SEPARATOR).PreFunc([](WidgetInfo& info) {
- info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
- });
- for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) {
- AddWidget(path, enemyNameList[i], WIDGET_CVAR_CHECKBOX)
- .CVar(enemyCVarList[i])
- .Options(CheckboxOptions().DefaultValue(true))
- .PreFunc([](WidgetInfo& info) {
- info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
- info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0);
- info.options->disabledTooltip = "These options are disabled because \"Select All Enemies\" is enabled.";
- })
- .Callback([](WidgetInfo& info) { GetSelectedEnemies(); });
- }
-
// Cheats
path.sidebarName = "Cheats";
AddSidebarEntry("Enhancements", path.sidebarName, 3);
diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c
index 1fe489589..e999788a4 100644
--- a/soh/src/code/z_actor.c
+++ b/soh/src/code/z_actor.c
@@ -11,7 +11,6 @@
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
-#include "soh/Enhancements/enemyrandomizer.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/Enhancements/nametag.h"