mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-04-23 08:14:31 +00:00
Enemy rando cleanup 2 (#6365)
Move stuff to main file, delete header & refactor lists
This commit is contained in:
parent
2af5d21125
commit
4e82ea192a
|
|
@ -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);
|
||||
|
|
@ -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();
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue