mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-07-05 05:40:32 -04:00
Enemy rando cleanup 3 (#6518)
* Fix bari's biris not respecting the seeded option * Randomize Peehat Larvas * Refactor `IsEnemyAllowedToSpawn` * Fix the issue where some enemies spawn above the ceiling * Partially fix twisted hallway issue * Prevent Baris from spawning Baris
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
#include "functions.h"
|
||||
#include "macros.h"
|
||||
#include "soh/Enhancements/randomizer/3drando/random.hpp"
|
||||
#include "soh/Enhancements/randomizer/SeedContext.h"
|
||||
#include "soh/Enhancements/enhancementTypes.h"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
@@ -19,6 +18,7 @@ extern "C" {
|
||||
#include "src/overlays/actors/ovl_En_Blkobj/z_en_blkobj.h"
|
||||
#include "src/overlays/actors/ovl_En_Encount1/z_en_encount1.h"
|
||||
#include "src/overlays/actors/ovl_En_GeldB/z_en_geldb.h"
|
||||
#include "src/overlays/actors/ovl_En_Peehat/z_en_peehat.h"
|
||||
#include "src/overlays/actors/ovl_En_Rr/z_en_rr.h"
|
||||
#include "src/overlays/actors/ovl_En_Vali/z_en_vali.h"
|
||||
|
||||
@@ -37,8 +37,8 @@ extern std::shared_ptr<SohMenu> mSohMenu;
|
||||
typedef struct EnemyEntry {
|
||||
const char* cvar;
|
||||
const char* name;
|
||||
int16_t id;
|
||||
int16_t params;
|
||||
s16 id;
|
||||
s16 params;
|
||||
} EnemyEntry;
|
||||
|
||||
// clang-format off
|
||||
@@ -60,7 +60,7 @@ static EnemyEntry randomizedEnemySpawnTable[] = {
|
||||
{ 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.FlareDancer"), "Flare Dancer", ACTOR_EN_FD, 0 }, // Flare Dancer (possible cause of crashes because of spawning flame actors on sloped ground or overloading)
|
||||
{ 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)
|
||||
@@ -80,16 +80,16 @@ static EnemyEntry randomizedEnemySpawnTable[] = {
|
||||
{ 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.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.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.Poe"), "Poe", ACTOR_EN_POH, 0 }, // Poe Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe.Sharp"), "Poe (Sharp)", ACTOR_EN_POH, 2 }, // Poe (composer Sharp) Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe.Flat"), "Poe (Flat)", 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)
|
||||
@@ -159,125 +159,205 @@ static int enemiesToRandomize[] = {
|
||||
// ACTOR_EN_REEBA, // Leever (reliant on spawner (z_en_encount1.c))
|
||||
};
|
||||
|
||||
bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) {
|
||||
uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
|
||||
// Freezard - Child Link can only kill this with Deku Stick jumpslash or other equipment like bombs.
|
||||
// Beamos - Needs bombs.
|
||||
// Anubis - Needs fire.
|
||||
// Shell Blade & Spike - Child Link can't kill these with sword or Deku Stick.
|
||||
// Flare dancer, Arwing & Dark Link - Both go out of bounds way too easily, softlocking the player.
|
||||
// Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies left.
|
||||
// Club Moblin - Many issues with them falling or placing out of bounds. Maybe fixable in the future?
|
||||
bool enemiesToExcludeClearRooms =
|
||||
enemy.id == ACTOR_EN_FZ || enemy.id == ACTOR_EN_VM || enemy.id == ACTOR_EN_SB || enemy.id == ACTOR_EN_NY ||
|
||||
enemy.id == ACTOR_EN_CLEAR_TAG || enemy.id == ACTOR_EN_WALLMAS || enemy.id == ACTOR_EN_TORCH2 ||
|
||||
(enemy.id == ACTOR_EN_MB && enemy.params == 0) || enemy.id == ACTOR_EN_FD || enemy.id == ACTOR_EN_ANUBICE_TAG;
|
||||
|
||||
// Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms.
|
||||
bool enemiesToExcludeTimedRooms = enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI;
|
||||
|
||||
switch (sceneNum) {
|
||||
// Deku Tree
|
||||
case SCENE_DEKU_TREE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 9)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10)));
|
||||
// Dodongo's Cavern
|
||||
case SCENE_DODONGOS_CAVERN:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 5 || roomNum == 13 || roomNum == 14)));
|
||||
// Jabu Jabu
|
||||
case SCENE_JABU_JABU:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 8 || roomNum == 9)) &&
|
||||
!(!isMQ && enemiesToExcludeTimedRooms && roomNum == 12) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 11 || roomNum == 14)));
|
||||
// Forest Temple
|
||||
case SCENE_FOREST_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 6 || roomNum == 10 || roomNum == 18 || roomNum == 21)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 5 || roomNum == 6 || roomNum == 18 || roomNum == 21)));
|
||||
// Fire Temple
|
||||
case SCENE_FIRE_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 15 || roomNum == 17 || roomNum == 18)));
|
||||
// Water Temple
|
||||
case SCENE_WATER_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18 || roomNum == 19)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18)));
|
||||
// Spirit Temple
|
||||
case SCENE_SPIRIT_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 10 || roomNum == 17 || roomNum == 20)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 2 || roomNum == 4 || roomNum == 10 || roomNum == 15 ||
|
||||
roomNum == 19 || roomNum == 20)));
|
||||
// Shadow Temple
|
||||
case SCENE_SHADOW_TEMPLE:
|
||||
return (
|
||||
!(!isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 16 || roomNum == 17 ||
|
||||
roomNum == 19 || roomNum == 20)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 6 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 20)));
|
||||
// Ganon's Castle Trials
|
||||
case SCENE_INSIDE_GANONS_CASTLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 5 || roomNum == 9)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9)));
|
||||
// Ice Caverns
|
||||
case SCENE_ICE_CAVERN:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 7)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 7)));
|
||||
// Bottom of the Well
|
||||
// Exclude Dark Link from room with holes in the floor because it can pull you in a like-like making the player
|
||||
// fall down.
|
||||
case SCENE_BOTTOM_OF_THE_WELL:
|
||||
return (!(!isMQ && enemy.id == ACTOR_EN_TORCH2 && roomNum == 3));
|
||||
// Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
|
||||
// Gerudo Training Ground.
|
||||
case SCENE_GERUDO_TRAINING_GROUND:
|
||||
return (!(enemy.id == ACTOR_EN_TORCH2 && roomNum == 6) &&
|
||||
!(!isMQ && enemiesToExcludeTimedRooms && (roomNum == 1 || roomNum == 7)) &&
|
||||
!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 5 || roomNum == 10)) &&
|
||||
!(isMQ && enemiesToExcludeTimedRooms &&
|
||||
(roomNum == 1 || roomNum == 3 || roomNum == 5 || roomNum == 7)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && roomNum == 10));
|
||||
// Don't allow certain enemies in Ganon's Tower because they would spawn up on the ceiling,
|
||||
// becoming impossible to kill.
|
||||
// Ganon's Tower.
|
||||
case SCENE_GANONS_TOWER:
|
||||
return (!(enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI ||
|
||||
(enemy.id == ACTOR_EN_ZF && enemy.params == -1)));
|
||||
// Ganon's Tower Escape.
|
||||
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
|
||||
return (!((enemiesToExcludeTimedRooms || (enemy.id == ACTOR_EN_ZF && enemy.params == -1)) && roomNum == 1));
|
||||
// Don't allow big Stalchildren, big Peahats and the large Bari (jellyfish) during the Gohma fight because they
|
||||
// can clip into Gohma and it crashes the game. Likely because Gohma on the ceiling can't handle collision with
|
||||
// other enemies.
|
||||
case SCENE_DEKU_TREE_BOSS:
|
||||
return (!enemiesToExcludeTimedRooms && !(enemy.id == ACTOR_EN_SKB && enemy.params == 20) &&
|
||||
!(enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1));
|
||||
// Grottos.
|
||||
case SCENE_GROTTOS:
|
||||
return (!(enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 7)));
|
||||
// Royal Grave.
|
||||
case SCENE_ROYAL_FAMILYS_TOMB:
|
||||
return (!(enemiesToExcludeClearRooms && roomNum == 0));
|
||||
// Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
|
||||
// Death Mountain Crater.
|
||||
case SCENE_DEATH_MOUNTAIN_CRATER:
|
||||
return (enemy.id != ACTOR_EN_TORCH2);
|
||||
static bool IsExcludedFromClearRooms(s16 enemyId, s16 enemyParams) {
|
||||
switch (enemyId) {
|
||||
// Freezard - Child Link can only kill this with Deku Stick jumpslash or other equipment like bombs
|
||||
case ACTOR_EN_FZ:
|
||||
// Beamos - Needs bombs
|
||||
case ACTOR_EN_VM:
|
||||
// Shell Blade - It's annoying to kill these as Child Link with sword or Deku Stick
|
||||
case ACTOR_EN_SB:
|
||||
// Spike - Child Link can't kill these with sword or Deku Stick
|
||||
case ACTOR_EN_NY:
|
||||
// Arwing - Goes out of bounds way too easily, softlocking the player
|
||||
case ACTOR_EN_CLEAR_TAG:
|
||||
// Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies
|
||||
// left
|
||||
case ACTOR_EN_WALLMAS:
|
||||
// Dark Link - Goes out of bounds way too easily, softlocking the player
|
||||
case ACTOR_EN_TORCH2:
|
||||
// Flare dancer - Goes out of bounds way too easily, softlocking the player
|
||||
case ACTOR_EN_FD:
|
||||
// Anubis - Needs fire
|
||||
case ACTOR_EN_ANUBICE_TAG:
|
||||
return true;
|
||||
case ACTOR_EN_MB:
|
||||
return enemyParams == 0;
|
||||
default:
|
||||
return 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsExcludedFromTimedRooms(s16 enemyId, s16 enemyParams) {
|
||||
switch (enemyId) {
|
||||
// Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms
|
||||
case ACTOR_EN_VALI:
|
||||
return true;
|
||||
default:
|
||||
return IsExcludedFromClearRooms(enemyId, enemyParams);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsClearRoom(bool mq, s16 sceneNum, s8 roomNum) {
|
||||
switch (sceneNum) {
|
||||
case SCENE_DEKU_TREE:
|
||||
if (mq) {
|
||||
return roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 9;
|
||||
}
|
||||
case SCENE_DODONGOS_CAVERN:
|
||||
if (mq) {
|
||||
return roomNum == 5 || roomNum == 6 || roomNum == 13 || roomNum == 14;
|
||||
} else {
|
||||
return roomNum == 15;
|
||||
}
|
||||
case SCENE_JABU_JABU:
|
||||
if (mq) {
|
||||
return roomNum == 11 || roomNum == 13 || roomNum == 14;
|
||||
} else {
|
||||
return roomNum == 8 || roomNum == 9;
|
||||
}
|
||||
case SCENE_FOREST_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 5 || roomNum == 6 || roomNum == 18 || roomNum == 21;
|
||||
} else {
|
||||
return roomNum == 6 || roomNum == 10 || roomNum == 18 || roomNum == 21;
|
||||
}
|
||||
case SCENE_FIRE_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 15 || roomNum == 17 || roomNum == 18;
|
||||
} else {
|
||||
return roomNum == 15;
|
||||
}
|
||||
case SCENE_WATER_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 13 || roomNum == 18;
|
||||
} else {
|
||||
return roomNum == 13 || roomNum == 18 || roomNum == 19;
|
||||
}
|
||||
case SCENE_SPIRIT_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 1 || roomNum == 2 || roomNum == 4 || roomNum == 10 || roomNum == 15 ||
|
||||
roomNum == 19 || roomNum == 20;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 10 || roomNum == 17 || roomNum == 20 || roomNum == 27;
|
||||
}
|
||||
case SCENE_SHADOW_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 1 || roomNum == 6 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 20;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 16 ||
|
||||
roomNum == 17 || roomNum == 19 || roomNum == 20;
|
||||
}
|
||||
case SCENE_INSIDE_GANONS_CASTLE:
|
||||
if (mq) {
|
||||
return roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9;
|
||||
} else {
|
||||
return roomNum == 2 || roomNum == 5 || roomNum == 9;
|
||||
}
|
||||
case SCENE_ICE_CAVERN:
|
||||
if (mq) {
|
||||
return roomNum == 3 || roomNum == 7;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 7;
|
||||
}
|
||||
case SCENE_GERUDO_TRAINING_GROUND:
|
||||
if (mq) {
|
||||
return roomNum == 10;
|
||||
} else {
|
||||
return roomNum == 3 || roomNum == 5 || roomNum == 10;
|
||||
}
|
||||
case SCENE_GANONS_TOWER:
|
||||
return true;
|
||||
case SCENE_GROTTOS:
|
||||
return roomNum == 2 || roomNum == 7;
|
||||
case SCENE_ROYAL_FAMILYS_TOMB:
|
||||
return roomNum == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsTimedRoom(bool mq, s16 sceneNum, s8 roomNum) {
|
||||
switch (sceneNum) {
|
||||
case SCENE_JABU_JABU:
|
||||
return !mq && roomNum == 12;
|
||||
case SCENE_GERUDO_TRAINING_GROUND:
|
||||
if (mq) {
|
||||
return roomNum == 1 || roomNum == 3 || roomNum == 5 || roomNum == 7;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 7;
|
||||
}
|
||||
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
|
||||
return roomNum == 1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsEnemyAllowedToSpawn(s16 sceneNum, s8 roomNum, EnemyEntry enemy, s16 posY, bool fromBari) {
|
||||
bool mq = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
|
||||
if (IsExcludedFromClearRooms(enemy.id, enemy.params) && IsClearRoom(mq, sceneNum, roomNum)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsExcludedFromTimedRooms(enemy.id, enemy.params) && IsTimedRoom(mq, sceneNum, roomNum)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow Lizalfos or Baris in Ganon's Tower because they would spawn up on the ceiling, becoming impossible to
|
||||
// kill.
|
||||
if (sceneNum == SCENE_GANONS_TOWER &&
|
||||
(enemy.id == ACTOR_EN_VALI || (enemy.id == ACTOR_EN_ZF && enemy.params == -1))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow Lizalfos in the first room of the interior of the castle collapse
|
||||
if (sceneNum == SCENE_GANONS_TOWER_COLLAPSE_INTERIOR && roomNum == 1 && enemy.id == ACTOR_EN_ZF &&
|
||||
enemy.params == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow big Stalchildren, big Peahats and Baris (big jellyfish) during the Gohma fight because they can clip
|
||||
// into Gohma and it crashes the game. Likely because Gohma on the ceiling can't handle collision with other
|
||||
// enemies.
|
||||
if (sceneNum == SCENE_DEKU_TREE_BOSS &&
|
||||
((enemy.id == ACTOR_EN_SKB && enemy.params == 20) || (enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1) ||
|
||||
(enemy.id == ACTOR_EN_VALI))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow the following enemies in the first spawn of the first room in MQ Fire Temple loop as when spawned and
|
||||
// they get stuck in the room above
|
||||
// - Lizalfos/Dinolfos, Bari: they drop in
|
||||
// - Skulltulla: they appear above
|
||||
// - Flying Peehat: they rise above the ceiling
|
||||
if (mq && sceneNum == SCENE_FIRE_TEMPLE && roomNum == 15 && posY == 64 &&
|
||||
(enemy.id == ACTOR_EN_ZF || enemy.id == ACTOR_EN_VALI || enemy.id == ACTOR_EN_ST ||
|
||||
enemy.id == ACTOR_EN_PEEHAT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow Stalfos in the child spirit clear room as they jump out of bounds frequently
|
||||
if (sceneNum == SCENE_SPIRIT_TEMPLE && roomNum == 1 && enemy.id == ACTOR_EN_TEST) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow baris to spawn another bari
|
||||
if (fromBari && enemy.id == ACTOR_EN_VALI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<EnemyEntry> selectedEnemyList;
|
||||
|
||||
void GetSelectedEnemies() {
|
||||
static void UpdateSelectedEnemies() {
|
||||
selectedEnemyList.clear();
|
||||
|
||||
for (int i = 0; i < ARRAY_COUNT(randomizedEnemySpawnTable); i++) {
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) {
|
||||
selectedEnemyList.push_back(randomizedEnemySpawnTable[i]);
|
||||
@@ -285,108 +365,119 @@ void GetSelectedEnemies() {
|
||||
selectedEnemyList.push_back(randomizedEnemySpawnTable[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedEnemyList.size() == 0) {
|
||||
selectedEnemyList.push_back(randomizedEnemySpawnTable[0]);
|
||||
}
|
||||
}
|
||||
|
||||
EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play) {
|
||||
static EnemyEntry GetRandomizedEnemyEntry(u32 seed, PlayState* play, s16 posY, bool fromBari) {
|
||||
std::vector<EnemyEntry> filteredEnemyList = {};
|
||||
|
||||
if (selectedEnemyList.size() == 0) {
|
||||
GetSelectedEnemies();
|
||||
UpdateSelectedEnemies();
|
||||
}
|
||||
|
||||
for (EnemyEntry enemy : selectedEnemyList) {
|
||||
if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy)) {
|
||||
if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy, posY, fromBari)) {
|
||||
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];
|
||||
uint64_t randomState = 0;
|
||||
|
||||
ShipUtils::RandInit(
|
||||
seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt),
|
||||
&randomState);
|
||||
|
||||
return ShipUtils::RandomElement(filteredEnemyList, false, &randomState);
|
||||
}
|
||||
|
||||
return ShipUtils::RandomElement(filteredEnemyList, false);
|
||||
}
|
||||
|
||||
bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX) {
|
||||
|
||||
uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
static bool IsEnemyFoundToRandomize(s16 sceneNum, s8 roomNum, s16 actorId, s16 params, f32 posX) {
|
||||
u32 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 (actorId != enemiesToRandomize[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 right baby dodongo on the first tunnel in Dodongo's Cavern as in vanilla you use them
|
||||
// isntead of bombs to blow up a wall
|
||||
case ACTOR_EN_DODOJR:
|
||||
return !(sceneNum == SCENE_DODONGOS_CAVERN && roomNum == 1 && posX == 1972);
|
||||
// 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);
|
||||
// Don't randomize Skull Kids in Lost Woods as child as they're not enemies
|
||||
case ACTOR_EN_SKJ:
|
||||
return !(sceneNum == SCENE_LOST_WOODS && LINK_IS_CHILD);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no enemy is found, don't randomize the actor.
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
static u8 GetRandomizedEnemy(PlayState* play, s16* actorId, s16* posX, s16* posY, s16* posZ, s16* rotX, s16* rotY,
|
||||
s16* rotZ, s16* params, s16 offset = 0, bool fromBari = false) {
|
||||
u32 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.
|
||||
@@ -412,7 +503,6 @@ uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* po
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -443,17 +533,27 @@ uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* po
|
||||
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;
|
||||
// the forest temple second twisted hallway spawns after the enemies so we need to "find the floor" manually
|
||||
if (play->sceneNum == SCENE_FOREST_TEMPLE && play->roomCtx.curRoom.num == 20 && *posZ > -3000) {
|
||||
// when hallway is twisted (play->actorCtx.flags.tempSwch & 1), one spawn has the floor at 1235.165 &
|
||||
// the other at 1239.094 but that changes based on the player position
|
||||
// when not twisted, the whole floor is at 1228
|
||||
|
||||
*posY = 1228.0;
|
||||
} else {
|
||||
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);
|
||||
u32 seed =
|
||||
play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params + offset;
|
||||
EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play, *posY, fromBari);
|
||||
|
||||
*actorId = randomEnemy.id;
|
||||
*params = randomEnemy.params;
|
||||
@@ -528,6 +628,25 @@ void CustomStalfosPairFightDestroy(Actor* thisx, PlayState* play) {
|
||||
ObjectExtension::GetInstance().Remove<CustomStalfosPairFightData>(thisx);
|
||||
}
|
||||
|
||||
struct CustomPeehatLarvaData {
|
||||
EnPeehat* peehat = nullptr;
|
||||
ActorFunc originalDestroy = nullptr;
|
||||
};
|
||||
|
||||
static ObjectExtension::Register<CustomPeehatLarvaData> CustomPeehatLarvaDataRegister;
|
||||
|
||||
void CustomPeehatLarvaDestroy(Actor* thisx, PlayState* play) {
|
||||
assert(ObjectExtension::GetInstance().Has<CustomPeehatLarvaData>(thisx));
|
||||
|
||||
CustomPeehatLarvaData* customPeehatLarvaData = ObjectExtension::GetInstance().Get<CustomPeehatLarvaData>(thisx);
|
||||
|
||||
customPeehatLarvaData->peehat->unk_2FA -= 1;
|
||||
|
||||
customPeehatLarvaData->originalDestroy(thisx, play);
|
||||
|
||||
ObjectExtension::GetInstance().Remove<CustomPeehatLarvaData>(thisx);
|
||||
}
|
||||
|
||||
void RegisterEnemyRandomizer() {
|
||||
COND_ID_HOOK(OnActorInit, ACTOR_EN_MB, ENEMY_RANDOMIZER_ENABLED, FixClubMoblinScale);
|
||||
|
||||
@@ -756,14 +875,18 @@ void RegisterEnemyRandomizer() {
|
||||
s16 rotZ = 0;
|
||||
s16 params = 0;
|
||||
|
||||
for (s32 i = 0; i < 3; i++) {
|
||||
// Offset small jellyfish with Enemy Randomizer, otherwise it gets
|
||||
// stuck in a loop spawning more big jellyfish with seeded spawns.
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0)) {
|
||||
rotY += rand() % 50;
|
||||
}
|
||||
s16 homePosX = vali->actor.home.pos.x;
|
||||
s16 homePosY = vali->actor.home.pos.y;
|
||||
s16 homePosZ = vali->actor.home.pos.z;
|
||||
|
||||
if (!GetRandomizedEnemy(play, &actorId, &posX, &posY, &posZ, &rotX, &rotY, &rotZ, ¶ms)) {
|
||||
s16 homeRotX = vali->actor.home.rot.x;
|
||||
s16 homeRotY = vali->actor.home.rot.y;
|
||||
s16 homeRotZ = vali->actor.home.rot.z;
|
||||
|
||||
for (s32 i = 0; i < 3; i++) {
|
||||
// use the home pos & rot to make it consistent
|
||||
if (!GetRandomizedEnemy(play, &actorId, &homePosX, &homePosY, &homePosZ, &homeRotX, &homeRotY, &homeRotZ,
|
||||
¶ms, i * 1000, true)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
@@ -919,9 +1042,43 @@ void RegisterEnemyRandomizer() {
|
||||
|
||||
*should = false;
|
||||
});
|
||||
|
||||
COND_VB_SHOULD(VB_PEEHAT_SPAWN_LARVAS, ENEMY_RANDOMIZER_ENABLED, {
|
||||
EnPeehat* peehat = va_arg(args, EnPeehat*);
|
||||
PlayState* play = va_arg(args, PlayState*);
|
||||
|
||||
s16 actorId = ACTOR_EN_PEEHAT;
|
||||
s16 homePosX = peehat->actor.home.pos.x;
|
||||
s16 homePosY = peehat->actor.home.pos.y + 50.0f;
|
||||
s16 homePosZ = peehat->actor.home.pos.z;
|
||||
s16 rotX = 0;
|
||||
s16 rotY = 0;
|
||||
s16 rotZ = 0;
|
||||
s16 params = PEAHAT_TYPE_LARVA;
|
||||
|
||||
// 3 is MAX_LARVA
|
||||
for (s32 i = 3 - peehat->unk_2FA; i > 0; i--) {
|
||||
if (!GetRandomizedEnemy(play, &actorId, &homePosX, &homePosY, &homePosZ, &rotX, &rotY, &rotZ, ¶ms,
|
||||
i * 1000)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
Actor* enemy =
|
||||
Actor_Spawn(&play->actorCtx, play, actorId, homePosX, homePosY, homePosZ, rotX, rotY, rotZ, params);
|
||||
|
||||
if (enemy == NULL) {
|
||||
assert(false);
|
||||
} else {
|
||||
peehat->unk_2FA++;
|
||||
ObjectExtension::GetInstance().Set<CustomPeehatLarvaData>(
|
||||
enemy, CustomPeehatLarvaData{ .peehat = peehat, .originalDestroy = enemy->destroy });
|
||||
enemy->destroy = CustomPeehatLarvaDestroy;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const std::map<int32_t, const char*> enemyRandomizerModes = {
|
||||
static const std::map<s32, const char*> enemyRandomizerModes = {
|
||||
{ ENEMY_RANDOMIZER_OFF, "Disabled" },
|
||||
{ ENEMY_RANDOMIZER_RANDOM, "Random" },
|
||||
{ ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)" },
|
||||
@@ -932,7 +1089,7 @@ void RegisterEnemyRandomizerWidgets() {
|
||||
|
||||
SohGui::mSohMenu->AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("RandomizedEnemies"))
|
||||
.Callback([](WidgetInfo& info) { GetSelectedEnemies(); })
|
||||
.Callback([](WidgetInfo& info) { UpdateSelectedEnemies(); })
|
||||
.Options(
|
||||
UIWidgets::ComboboxOptions()
|
||||
.DefaultIndex(ENEMY_RANDOMIZER_OFF)
|
||||
@@ -963,7 +1120,7 @@ void RegisterEnemyRandomizerWidgets() {
|
||||
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(); });
|
||||
.Callback([](WidgetInfo& info) { UpdateSelectedEnemies(); });
|
||||
|
||||
SohGui::mSohMenu->AddWidget(path, "Enemy List", WIDGET_SEPARATOR).PreFunc([](WidgetInfo& info) {
|
||||
info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
|
||||
@@ -978,7 +1135,7 @@ void RegisterEnemyRandomizerWidgets() {
|
||||
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(); });
|
||||
.Callback([](WidgetInfo& info) { UpdateSelectedEnemies(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2784,7 +2784,16 @@ typedef enum {
|
||||
// ```
|
||||
// #### `args`
|
||||
// - `*int32_t (camId)`
|
||||
VB_SHOULD_LOAD_BG_IMAGE
|
||||
VB_SHOULD_LOAD_BG_IMAGE,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// true
|
||||
// ```
|
||||
// #### `args`
|
||||
// - `*EnPeehat`
|
||||
// - `*PlayState`
|
||||
VB_PEEHAT_SPAWN_LARVAS,
|
||||
} GIVanillaBehavior;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -298,17 +298,19 @@ void EnPeehat_HitWhenGrounded(EnPeehat* this, PlayState* play) {
|
||||
s32 i;
|
||||
|
||||
this->colCylinder.base.acFlags &= ~AC_HIT;
|
||||
for (i = MAX_LARVA - this->unk_2FA; i > 0; i--) {
|
||||
Actor* larva =
|
||||
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_PEEHAT,
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.x,
|
||||
Rand_CenteredFloat(25.0f) + (this->actor.world.pos.y + 50.0f),
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.z, 0, 0, 0, PEAHAT_TYPE_LARVA);
|
||||
if (GameInteractor_Should(VB_PEEHAT_SPAWN_LARVAS, true, this, play)) {
|
||||
for (i = MAX_LARVA - this->unk_2FA; i > 0; i--) {
|
||||
Actor* larva =
|
||||
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_PEEHAT,
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.x,
|
||||
Rand_CenteredFloat(25.0f) + (this->actor.world.pos.y + 50.0f),
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.z, 0, 0, 0, PEAHAT_TYPE_LARVA);
|
||||
|
||||
if (larva != NULL) {
|
||||
larva->velocity.y = 6.0f;
|
||||
larva->shape.rot.y = larva->world.rot.y = Rand_CenteredFloat(0xFFFF);
|
||||
this->unk_2FA++;
|
||||
if (larva != NULL) {
|
||||
larva->velocity.y = 6.0f;
|
||||
larva->shape.rot.y = larva->world.rot.y = Rand_CenteredFloat(0xFFFF);
|
||||
this->unk_2FA++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->unk_2D4 = 8;
|
||||
|
||||
Reference in New Issue
Block a user