diff --git a/soh/soh/Enhancements/enemyrandomizer.cpp b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp similarity index 69% rename from soh/soh/Enhancements/enemyrandomizer.cpp rename to 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 @@ -25,226 +27,91 @@ extern "C" { extern PlayState* gPlayState; } +namespace SohGui { +extern std::shared_ptr 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,264 +122,45 @@ 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); - - *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; -} - -static std::vector selectedEnemyList; - -void GetSelectedEnemies() { - selectedEnemyList.clear(); - for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) { - selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); - } else if (CVarGetInteger(enemyCVarList[i], 1)) { - selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); - } - } - if (selectedEnemyList.size() == 0) { - selectedEnemyList.push_back(randomizedEnemySpawnTable[0]); - } -} - -EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play) { - std::vector filteredEnemyList = {}; - if (selectedEnemyList.size() == 0) { - GetSelectedEnemies(); - } - for (EnemyEntry enemy : selectedEnemyList) { - if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy)) { - filteredEnemyList.push_back(enemy); - } - } - if (filteredEnemyList.size() == 0) { - filteredEnemyList = selectedEnemyList; - } - if (CVAR_ENEMY_RANDOMIZER_VALUE == ENEMY_RANDOMIZER_RANDOM_SEEDED) { - uint32_t finalSeed = - seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt); - Random_Init(finalSeed); - uint32_t randomNumber = Random(0, filteredEnemyList.size()); - return filteredEnemyList[randomNumber]; - } else { - uint32_t randomSelectedEnemy = Random(0, filteredEnemyList.size()); - return filteredEnemyList[randomSelectedEnemy]; - } -} - -bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX) { - - uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum); - - for (int i = 0; i < ARRAY_COUNT(enemiesToRandomize); i++) { - if (actorId == enemiesToRandomize[i]) { - switch (actorId) { - // Only randomize the main component of Electric Tailparasans, not the tail segments they spawn. - case ACTOR_EN_TP: - return (params == -1); - // Only randomize the initial Deku Scrub actor (single and triple attack), not the flower they spawn. - case ACTOR_EN_DEKUNUTS: - return (params == -256 || params == 768); - // Don't randomize the OoB wallmaster in the Silver Rupee room because it's only there to - // not trigger unlocking the door after killing the other wallmaster in authentic gameplay. - case ACTOR_EN_WALLMAS: - return (!(!isMQ && sceneNum == SCENE_GERUDO_TRAINING_GROUND && roomNum == 2 && posX == -2345)); - // Only randomize initial Floormaster actor (it can split and does some spawning on init). - case ACTOR_EN_FLOORMAS: - return (params == 0 || params == -32768); - // Only randomize the initial eggs, not the enemies that spawn from them. - case ACTOR_EN_GOMA: - return (params >= 0 && params <= 9); - // Only randomize Skullwalltulas, not Golden Skulltulas. - case ACTOR_EN_SW: - return (params == 0); - // Don't randomize Nabooru because it'll break the cutscene and the door. - // Don't randomize Iron Knuckle in MQ Spirit Trial because it's needed to - // break the thrones in the room to access a button. - case ACTOR_EN_IK: - return (params != 1280 && !(isMQ && sceneNum == SCENE_INSIDE_GANONS_CASTLE && roomNum == 17)); - // Only randomize the initial spawn of the huge jellyfish. It spawns another copy when hit with a sword. - case ACTOR_EN_VALI: - return (params == -1); - // Don't randomize Lizalfos in Dodongo's Cavern because the gates won't work correctly otherwise. - case ACTOR_EN_ZF: - return (params != 1280 && params != 1281 && params != 1536 && params != 1537); - // Don't randomize the Wolfos in SFM because it's needed to open the gate. - case ACTOR_EN_WF: - return (params != 7936); - // Don't randomize the Stalfos in Forest Temple because other enemies fall through the hole and don't - // trigger the platform. Don't randomize the Stalfos spawning on the boat in Shadow Temple, as - // randomizing them places the new enemies down in the river. - case ACTOR_EN_TEST: - return (params != 1 && !(sceneNum == SCENE_SHADOW_TEMPLE && roomNum == 21)); - // Only randomize the enemy variant of Armos Statue. - // Leave one Armos unrandomized in the Spirit Temple room where an armos is needed to push down a - // button. - case ACTOR_EN_AM: - return ((params == -1 || params == 255) && !(sceneNum == SCENE_SPIRIT_TEMPLE && posX == 2141)); - // Don't randomize Shell Blades and Spikes in the underwater portion in Water Temple as it's impossible - // to kill most other enemies underwater with just hookshot and they're required to be killed for a - // grate to open. - case ACTOR_EN_SB: - case ACTOR_EN_NY: - return (!(!isMQ && sceneNum == SCENE_WATER_TEMPLE && roomNum == 2)); - case ACTOR_EN_SKJ: - return !(sceneNum == SCENE_LOST_WOODS && LINK_IS_CHILD); - default: - return 1; - } - } - } - - // If no enemy is found, don't randomize the actor. - return 0; -} - bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) { uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum); @@ -628,6 +276,225 @@ bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) { } } +static std::vector selectedEnemyList; + +void GetSelectedEnemies() { + selectedEnemyList.clear(); + for (int i = 0; i < ARRAY_COUNT(randomizedEnemySpawnTable); i++) { + if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) { + selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); + } else if (CVarGetInteger(randomizedEnemySpawnTable[i].cvar, 1)) { + selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); + } + } + if (selectedEnemyList.size() == 0) { + selectedEnemyList.push_back(randomizedEnemySpawnTable[0]); + } +} + +EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play) { + std::vector filteredEnemyList = {}; + if (selectedEnemyList.size() == 0) { + GetSelectedEnemies(); + } + for (EnemyEntry enemy : selectedEnemyList) { + if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy)) { + filteredEnemyList.push_back(enemy); + } + } + if (filteredEnemyList.size() == 0) { + filteredEnemyList = selectedEnemyList; + } + if (CVAR_ENEMY_RANDOMIZER_VALUE == ENEMY_RANDOMIZER_RANDOM_SEEDED) { + uint32_t finalSeed = + seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt); + Random_Init(finalSeed); + uint32_t randomNumber = Random(0, filteredEnemyList.size()); + return filteredEnemyList[randomNumber]; + } else { + uint32_t randomSelectedEnemy = Random(0, filteredEnemyList.size()); + return filteredEnemyList[randomSelectedEnemy]; + } +} + +bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX) { + + uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum); + + for (int i = 0; i < ARRAY_COUNT(enemiesToRandomize); i++) { + if (actorId == enemiesToRandomize[i]) { + switch (actorId) { + // Only randomize the main component of Electric Tailparasans, not the tail segments they spawn. + case ACTOR_EN_TP: + return (params == -1); + // Only randomize the initial Deku Scrub actor (single and triple attack), not the flower they spawn. + case ACTOR_EN_DEKUNUTS: + return (params == -256 || params == 768); + // Don't randomize the OoB wallmaster in the Silver Rupee room because it's only there to + // not trigger unlocking the door after killing the other wallmaster in authentic gameplay. + case ACTOR_EN_WALLMAS: + return (!(!isMQ && sceneNum == SCENE_GERUDO_TRAINING_GROUND && roomNum == 2 && posX == -2345)); + // Only randomize initial Floormaster actor (it can split and does some spawning on init). + case ACTOR_EN_FLOORMAS: + return (params == 0 || params == -32768); + // Only randomize the initial eggs, not the enemies that spawn from them. + case ACTOR_EN_GOMA: + return (params >= 0 && params <= 9); + // Only randomize Skullwalltulas, not Golden Skulltulas. + case ACTOR_EN_SW: + return (params == 0); + // Don't randomize Nabooru because it'll break the cutscene and the door. + // Don't randomize Iron Knuckle in MQ Spirit Trial because it's needed to + // break the thrones in the room to access a button. + case ACTOR_EN_IK: + return (params != 1280 && !(isMQ && sceneNum == SCENE_INSIDE_GANONS_CASTLE && roomNum == 17)); + // Only randomize the initial spawn of the huge jellyfish. It spawns another copy when hit with a sword. + case ACTOR_EN_VALI: + return (params == -1); + // Don't randomize Lizalfos in Dodongo's Cavern because the gates won't work correctly otherwise. + case ACTOR_EN_ZF: + return (params != 1280 && params != 1281 && params != 1536 && params != 1537); + // Don't randomize the Wolfos in SFM because it's needed to open the gate. + case ACTOR_EN_WF: + return (params != 7936); + // Don't randomize the Stalfos in Forest Temple because other enemies fall through the hole and don't + // trigger the platform. Don't randomize the Stalfos spawning on the boat in Shadow Temple, as + // randomizing them places the new enemies down in the river. + case ACTOR_EN_TEST: + return (params != 1 && !(sceneNum == SCENE_SHADOW_TEMPLE && roomNum == 21)); + // Only randomize the enemy variant of Armos Statue. + // Leave one Armos unrandomized in the Spirit Temple room where an armos is needed to push down a + // button. + case ACTOR_EN_AM: + return ((params == -1 || params == 255) && !(sceneNum == SCENE_SPIRIT_TEMPLE && posX == 2141)); + // Don't randomize Shell Blades and Spikes in the underwater portion in Water Temple as it's impossible + // to kill most other enemies underwater with just hookshot and they're required to be killed for a + // grate to open. + case ACTOR_EN_SB: + case ACTOR_EN_NY: + return (!(!isMQ && sceneNum == SCENE_WATER_TEMPLE && roomNum == 2)); + case ACTOR_EN_SKJ: + return !(sceneNum == SCENE_LOST_WOODS && LINK_IS_CHILD); + default: + return 1; + } + } + } + + // If no enemy is found, don't randomize the actor. + return 0; +} + +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); + + *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) { Actor* actor = (Actor*)ptr; if (actor->params == -1) { @@ -1047,4 +914,66 @@ void RegisterEnemyRandomizer() { }); } +static const std::map 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 -#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 #include #include -#include #include extern "C" { @@ -136,12 +135,6 @@ static const std::map mirroredWorldModes = { { MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED, "Dungeons Random (Seeded)" }, }; -static const std::map 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"