Enemy rando cleanup 2 (#6365)

Move stuff to main file, delete header & refactor lists
This commit is contained in:
Pepe20129
2026-03-21 19:46:15 +01:00
committed by GitHub
parent 2af5d21125
commit 4e82ea192a
4 changed files with 394 additions and 529 deletions

View File

@@ -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,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<EnemyEntry> 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<EnemyEntry> 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<EnemyEntry> 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<EnemyEntry> 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<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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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"