Shipwright/soh/soh/Enhancements/enemyrandomizer.cpp

411 lines
21 KiB
C++

#include "enemyrandomizer.h"
#include "functions.h"
#include "macros.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "variables.h"
extern "C" {
#include <z64.h>
}
extern "C" uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum);
static EnemyEntry randomizedEnemySpawnTable[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = {
{ ACTOR_EN_FIREFLY, 2 }, // Regular Keese
{ ACTOR_EN_FIREFLY, 1 }, // Fire Keese
{ ACTOR_EN_FIREFLY, 4 }, // Ice Keese
{ ACTOR_EN_TEST, 2 }, // Stalfos
{ ACTOR_EN_TITE, -1 }, // Tektite (red)
{ ACTOR_EN_TITE, -2 }, // Tektite (blue)
{ ACTOR_EN_WALLMAS, 1 }, // Wallmaster
{ ACTOR_EN_DODONGO, -1 }, // Dodongo
{ ACTOR_EN_PEEHAT, -1 }, // Flying Peahat (big grounded, doesn't spawn larva)
{ ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva
{ ACTOR_EN_ZF, -1 }, // Lizalfos
{ ACTOR_EN_ZF, -2 }, // Dinolfos
{ ACTOR_EN_GOMA, 7 }, // Gohma larva (non-gohma rooms)
{ ACTOR_EN_BUBBLE, 0 }, // Shabom (bubble)
{ ACTOR_EN_DODOJR, 0 }, // Baby Dodongo
{ ACTOR_EN_TORCH2, 0 }, // Dark Link
{ ACTOR_EN_BILI, 0 }, // Biri (jellyfish)
{ ACTOR_EN_TP, -1 }, // Electric Tailparasan
{ ACTOR_EN_ST, 0 }, // Skulltula (normal)
{ ACTOR_EN_ST, 1 }, // Skulltula (big)
{ ACTOR_EN_ST, 2 }, // Skulltula (invisible)
{ ACTOR_EN_BW, 0 }, // Torch Slug
{ ACTOR_EN_EIYER, 10 }, // Stinger (land) (One in formation, sink under floor and do not activate)
{ ACTOR_EN_MB, 0 }, // Moblins (Club)
{ ACTOR_EN_DEKUBABA, 0 }, // Deku Baba (small)
{ ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large)
{ ACTOR_EN_AM, -1 }, // Armos (enemy variant)
{ ACTOR_EN_DEKUNUTS, 768 }, // Mad Scrub (triple attack) (projectiles don't work)
{ ACTOR_EN_VALI, -1 }, // Bari (big jellyfish)
{ ACTOR_EN_BB, -1 }, // Bubble (flying skull enemy) (blue)
{ ACTOR_EN_YUKABYUN, 0 }, // Flying Floor Tile
{ ACTOR_EN_VM, 1280 }, // Beamos
{ ACTOR_EN_FLOORMAS, 0 }, // Floormaster
{ ACTOR_EN_RD, 1 }, // Redead (standing)
{ ACTOR_EN_RD, 32766 }, // Gibdo (standing)
{ ACTOR_EN_SB, 0 }, // Shell Blade
{ ACTOR_EN_KAREBABA, 0 }, // Withered Deku Baba
{ ACTOR_EN_RR, 0 }, // Like-Like
{ ACTOR_EN_NY, 0 }, // Spike (rolling enemy)
{ ACTOR_EN_IK, 2 }, // Iron Knuckle (black, standing)
{ ACTOR_EN_IK, 3 }, // Iron Knuckle (white, standing)
{ ACTOR_EN_TUBO_TRAP, 0 }, // Flying pot
{ ACTOR_EN_FZ, 0 }, // Freezard
{ ACTOR_EN_CLEAR_TAG, 1 }, // Arwing
{ ACTOR_EN_WF, 0 }, // Wolfos (normal)
{ ACTOR_EN_WF, 1 }, // Wolfos (white)
{ ACTOR_EN_SKB, 1 }, // Stalchild (small)
{ ACTOR_EN_SKB, 20 }, // Stalchild (big)
{ ACTOR_EN_CROW, 0 } // Guay
// Doesn't work {ACTOR_EN_POH, 0}, // Poe (Seems to rely on other objects?)
// Doesn't work {ACTOR_EN_POH, 2}, // Poe (composer Sharp) (Seems to rely on other objects?)
// Doesn't work {ACTOR_EN_POH, 3}, // Poe (composer Flat) (Seems to rely on other objects?)
// Doesn't work {ACTOR_EN_OKUTA, 0}, // Octorok (actor directly uses water box collision to handle hiding/popping up)
// Doesn't work {ACTOR_EN_REEBA, 0}, // Leever (reliant on surface and also normally used in tandem with a leever spawner, kills itself too quickly otherwise)
// Kinda doesn't work { ACTOR_EN_FD, 0 }, // Flare Dancer (jumps out of bounds a lot, and possible cause of crashes because of spawning a ton of flame actors)
};
static int enemiesToRandomize[] = {
ACTOR_EN_FIREFLY, // Keese (including fire/ice)
ACTOR_EN_TEST, // Stalfos
ACTOR_EN_TITE, // Tektite
ACTOR_EN_POH, // Poe (normal, blue rupee, composers
ACTOR_EN_OKUTA, // Octorok
ACTOR_EN_WALLMAS, // Wallmaster
ACTOR_EN_DODONGO, // Dodongo
// ACTOR_EN_REEBA, // Leever (reliant on spawner (z_e_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 Tailparasan
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 (can be randomized, but not randomized to, so keeping it in vanilla locations means it atleast shows up in the game
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
};
extern "C" uint8_t GetRandomizedEnemy(PlayState* play, int16_t *actorId, f32 *posX, f32 *posY, f32 *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_SPOT02) ||
(*actorId == ACTOR_EN_OKUTA && play->sceneNum == SCENE_SPOT10)) {
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_BDAN &&
play->roomCtx.curRoom.num == 12) {
*params = 92280;
}
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_JYASINZOU) {
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_BDAN && 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);
int8_t timesRandomized = 1;
// While randomized enemy isn't allowed in certain situations, randomize again.
while (!IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, randomEnemy)) {
randomEnemy = GetRandomizedEnemyEntry(seed + timesRandomized);
timesRandomized++;
}
*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;
}
EnemyEntry GetRandomizedEnemyEntry(uint32_t seed) {
if (CVarGetInteger("gSeededRandomizedEnemies", 0) && gSaveContext.n64ddFlag) {
uint32_t finalSeed = seed + gSaveContext.seedIcons[0] + gSaveContext.seedIcons[1] + gSaveContext.seedIcons[2] +
gSaveContext.seedIcons[3] + gSaveContext.seedIcons[4];
Random_Init(finalSeed);
uint32_t randomNumber = Random(0, RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE);
return randomizedEnemySpawnTable[randomNumber];
} else {
uint32_t randomNumber = rand() + seed;
return randomizedEnemySpawnTable[randomNumber % RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE];
}
}
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_MEN && 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_GANONTIKA && roomNum == 17));
// Only randomize the intitial 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 Doodong'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_HAKADAN && 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_JYASINZOU && 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_MIZUSIN && roomNum == 2));
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);
// Freezard - Child Link can only kill this with jump slash deku sticks or other equipment like bombs.
// Beamos - Needs bombs.
// Shell Blade & Spike - Child link can't kill these with sword or deku stick.
// 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.
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;
// 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_YDAN:
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 9)) &&
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10)));
// Dodongo's Cavern
case SCENE_DDAN:
return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 5 || roomNum == 13 || roomNum == 14)));
// Jabu Jabu
case SCENE_BDAN:
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 8 || roomNum == 9)) &&
!(!isMQ && enemiesToExcludeTimedRooms && roomNum == 12) &&
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 11 || roomNum == 14)));
// Forest Temple
case SCENE_BMORI1:
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_HIDAN:
return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 15 || roomNum == 17 || roomNum == 18)));
// Water Temple
case SCENE_MIZUSIN:
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18 || roomNum == 19)) &&
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18)));
// Spirit Temple
case SCENE_JYASINZOU:
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_HAKADAN:
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_GANONTIKA:
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 5 || roomNum == 9)) &&
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9)));
// Ice Caverns
case SCENE_ICE_DOUKUTO:
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_HAKADANCH:
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_MEN:
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 ceilling,
// becoming impossible to kill.
// Ganon's Tower.
case SCENE_GANON:
return (!(enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI || (enemy.id == ACTOR_EN_ZF && enemy.params == -1)));
// Ganon's Tower Escape.
case SCENE_GANON_SONOGO:
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 ceilling can't handle collision with other enemies.
case SCENE_YDAN_BOSS:
return (!enemiesToExcludeTimedRooms && !(enemy.id == ACTOR_EN_SKB && enemy.params == 20) &&
!(enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1));
// Grottos.
case SCENE_KAKUSIANA:
return (!(enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 7)));
// Royal Grave.
case SCENE_HAKAANA_OUKE:
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_SPOT17:
return (enemy.id != ACTOR_EN_TORCH2);
default:
return 1;
}
}