Merge remote-tracking branch 'origin/develop' into actor-accessibility-experiments

This commit is contained in:
Demur Rumed 2025-06-12 20:26:14 +00:00
commit 14aa193d8c
36 changed files with 367 additions and 225 deletions

View file

@ -266,9 +266,6 @@ typedef struct Actor {
/* 0x134 */ ActorFunc draw; // Draw Routine. Called by `Actor_Draw`
/* 0x138 */ ActorResetFunc reset;
/* 0x13C */ char dbgPad[0x10]; // Padding that only exists in the debug rom
// #region SOH [General]
/* */ u8 maximumHealth; // Max health value for use with health bars, set on actor init
// #endregion
} Actor; // size = 0x14C
typedef enum {

View file

@ -0,0 +1,19 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/randomizer/context.h"
extern "C" {
extern PlayState* gPlayState;
#include "src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.h"
}
void RegisterCuccosToReturn() {
COND_VB_SHOULD(VB_SET_CUCCO_COUNT, CVarGetInteger(CVAR_ENHANCEMENT("CuccosToReturn"), 7) != 7, {
EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*);
// Override starting Cucco count using setting value
enNiwLady->cuccosInPen = 7 - CVarGetInteger(CVAR_ENHANCEMENT("CuccosToReturn"), 7);
*should = false;
});
}
static RegisterShipInitFunc initFunc(RegisterCuccosToReturn, { CVAR_ENHANCEMENT("CuccosToReturn") });

View file

@ -110,6 +110,7 @@ void applyPreset(std::string presetName, std::vector<PresetSection> includeSecti
}
}
}
ShipInit::InitAll();
}
void DrawPresetSelector(std::vector<PresetSection> includeSections, std::string presetLoc, bool disabled) {

View file

@ -0,0 +1,8 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
void RegisterSkipAmyPuzzle() {
COND_VB_SHOULD(VB_AMY_SOLVE, CVarGetInteger(CVAR_ENHANCEMENT("SkipAmyPuzzle"), 0), { *should = true; });
}
static RegisterShipInitFunc initFunc(RegisterSkipAmyPuzzle, { CVAR_ENHANCEMENT("SkipAmyPuzzle") });

View file

@ -10,8 +10,6 @@ extern "C" {
#include "variables.h"
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
static bool sEnteredBlueWarp = false;
/**

View file

@ -0,0 +1,31 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "src/overlays/actors/ovl_En_Wood02/z_en_wood02.h"
}
extern PlayState* gPlayState;
void RegisterTreesDropSticks() {
COND_VB_SHOULD(VB_TREE_DROP_COLLECTIBLE, CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0), {
if (INV_CONTENT(ITEM_STICK) != ITEM_NONE) {
EnWood02* tree = va_arg(args, EnWood02*);
Vec3f dropsSpawnPt = tree->actor.world.pos;
dropsSpawnPt.y += 200.0f;
*should = false;
for (s32 numDrops = Rand_Next() % 4; numDrops > 0; numDrops--) {
Item_DropCollectible(gPlayState, &dropsSpawnPt, ITEM00_STICK);
}
}
});
COND_VB_SHOULD(VB_PREVENT_ADULT_STICK, CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0), {
if (INV_CONTENT(ITEM_STICK) != ITEM_NONE) {
*should = false;
}
});
}
static RegisterShipInitFunc initFunc(RegisterTreesDropSticks, { CVAR_ENHANCEMENT("TreesDropSticks") });

View file

@ -13,15 +13,29 @@ extern "C" {
#include <z64.h>
}
const char* enemyCVarList[] = {
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"),
#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)
typedef struct EnemyEntry {
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);
const char* enemyCVarList[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = {
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.Gibdo"),
@ -30,18 +44,20 @@ const char* enemyCVarList[] = {
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.Redead"),
CVAR_ENHANCEMENT("RandomizedEnemyList.RedTektite"), CVAR_ENHANCEMENT("RandomizedEnemyList.Shabom"),
CVAR_ENHANCEMENT("RandomizedEnemyList.ShellBlade"), CVAR_ENHANCEMENT("RandomizedEnemyList.Skulltula"),
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.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"),
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[] = {
const char* enemyNameList[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = {
"Anubis",
"Armos",
"Arwing",
"Baby Dodongo",
@ -58,6 +74,7 @@ const char* enemyNameList[] = {
"Dinolfos",
"Dodongo",
"Fire Keese",
//"Flare Dancer",
"Floor Tile",
"Floormaster",
"Flying Peahat",
@ -75,13 +92,16 @@ const char* enemyNameList[] = {
"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",
@ -94,98 +114,105 @@ const char* enemyNameList[] = {
};
static EnemyEntry randomizedEnemySpawnTable[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = {
{ 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 }, // Moblins (Club)
{ ACTOR_EN_TORCH2, 0 }, // Dark Link
{ ACTOR_EN_ZF, -2 }, // Dinolfos
{ ACTOR_EN_DODONGO, -1 }, // Dodongo
{ ACTOR_EN_FIREFLY, 1 }, // Fire Keese
{ 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_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_ST, 2 }, // Skulltula (invisible)
{ ACTOR_EN_FIREFLY, 2 }, // Regular Keese
{ ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large)
{ 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_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_ST, 2 }, // Skulltula (invisible)
{ ACTOR_EN_FIREFLY, 2 }, // Regular Keese
{ ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large)
// Doesn't work (reliant on surface and also normally used in tandem with a leever spawner, kills itself too quickly
// otherwise) { 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)
{ ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva
{ 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_DEKUBABA, 0 }, // Deku Baba (small)
{ ACTOR_EN_SKB, 1 }, // Stalchild (small)
{ 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
// 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)
// 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
};
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 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 (can be randomized, but not randomized to, so keeping it in vanilla locations
// means it at least shows up in the game)
ACTOR_EN_ANUBICE_TAG, // Anubis
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_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
@ -198,6 +225,7 @@ static int enemiesToRandomize[] = {
ACTOR_EN_WF, // Wolfos
ACTOR_EN_SKB, // Stalchild
ACTOR_EN_CROW, // Guay
ACTOR_EN_SKJ, // Skull Kid
};
extern "C" uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, f32* posX, f32* posY, f32* posZ, int16_t* rotX,
@ -322,7 +350,7 @@ static std::vector<EnemyEntry> selectedEnemyList;
void GetSelectedEnemies() {
selectedEnemyList.clear();
for (int i = 0; i < 49; i++) {
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)) {
@ -408,6 +436,8 @@ bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId,
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;
}
@ -419,19 +449,19 @@ bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId,
}
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.
// Anubis - Needs fire.
// 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.
// 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;
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;
@ -532,3 +562,16 @@ bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) {
return 1;
}
}
void FixClubMoblinScale(void* ptr) {
Actor* actor = (Actor*)ptr;
if (actor->params == -1) {
Actor_SetScale(actor, 0.014f);
}
}
void RegisterEnemyRandomizer() {
COND_ID_HOOK(OnActorInit, ACTOR_EN_MB, CVAR_ENEMY_RANDOMIZER_VALUE, FixClubMoblinScale);
}
static RegisterShipInitFunc initFunc(RegisterEnemyRandomizer, { CVAR_ENEMY_RANDOMIZER_NAME });

View file

@ -1,23 +1,16 @@
#pragma once
#include <libultraship/bridge.h>
#include <libultraship/libultra/types.h>
typedef struct EnemyEntry {
int16_t id;
int16_t params;
} EnemyEntry;
#define RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE 49
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);
#define RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE 52
extern const char* enemyCVarList[];
extern const char* enemyNameList[];
extern void GetSelectedEnemies();
#ifndef __cplusplus
uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, f32* posX, f32* posY, f32* posZ, int16_t* rotX,
struct PlayState;
uint8_t GetRandomizedEnemy(struct PlayState* play, int16_t* actorId, f32* posX, f32* posY, f32* posZ, int16_t* rotX,
int16_t* rotY, int16_t* rotZ, int16_t* params);
#endif

View file

@ -20,6 +20,14 @@ typedef enum {
// - `int32_t` (entrance index) (promoted from `uint16_t` by va_arg)
VB_ALLOW_ENTRANCE_CS_FOR_EITHER_AGE,
// #### `result`
// ```c
// sBgPoEventPuzzleState == 0xF
// ```
// #### `args`
// - None
VB_AMY_SOLVE,
// #### `result`
// ```c
// this->actor.textId == 0x401A
@ -1689,6 +1697,15 @@ typedef enum {
// - `*ObjTsubo`
VB_POT_SETUP_DRAW,
// #### `result`
// ```c
// dropId == ITEM00_STICK
// ```
// #### `args`
// - None
VB_PREVENT_ADULT_STICK,
// #### `result`
// #### `result`
// ```c
// true
@ -2065,6 +2082,13 @@ typedef enum {
VB_TRANSITION_TO_SAVE_SCREEN_ON_DEATH,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - `*EnWood02`
VB_TREE_DROP_COLLECTIBLE,
// ```c
// true
// ```

View file

@ -9,8 +9,6 @@ extern PlayState* gPlayState;
#include "src/overlays/actors/ovl_En_Door/z_en_door.h"
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
using SceneDoorParamsPair = std::pair<int, int>;
std::map<SceneDoorParamsPair, RandomizerInf> lookupTable = {
// clang-format off

View file

@ -11,8 +11,7 @@ extern void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play);
void ObjComb_RandomizerChooseItemDrop(ObjComb* objComb, PlayState* play) {
s16 params = objComb->actor.params & 0x1F;
if (Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_BEEHIVES).Get() &&
!Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) {
if (RAND_GET_OPTION(RSK_SHUFFLE_BEEHIVES) && !Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) {
EnItem00* item00 = (EnItem00*)Item_DropCollectible2(play, &objComb->actor.world.pos, ITEM00_SOH_DUMMY);
item00->randoInf = objComb->beehiveIdentity.randomizerInf;
item00->itemEntry =
@ -41,8 +40,7 @@ void ObjComb_RandomizerWait(ObjComb* objComb, PlayState* play) {
s32 dmgFlags;
objComb->unk_1B0 -= 50;
if (Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_BEEHIVES).Get() &&
!Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) {
if (RAND_GET_OPTION(RSK_SHUFFLE_BEEHIVES) && !Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) {
if (objComb->unk_1B0 <= -5000) {
objComb->unk_1B0 = 1500;
}
@ -85,7 +83,7 @@ void ObjComb_RandomizerUpdate(void* actor) {
}
void RegisterShuffleBeehives() {
bool shouldRegister = IS_RANDO && Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_BEEHIVES).Get();
bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_BEEHIVES);
COND_ID_HOOK(OnActorInit, ACTOR_OBJ_COMB, shouldRegister, ObjComb_RandomizerInit);
COND_ID_HOOK(OnActorUpdate, ACTOR_OBJ_COMB, shouldRegister, ObjComb_RandomizerUpdate);

View file

@ -34,7 +34,7 @@ void EnCow_MoveForRandomizer(EnCow* enCow, PlayState* play) {
}
void RegisterShuffleCows() {
bool shouldRegister = IS_RANDO && Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_COWS).Get();
bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_COWS);
COND_VB_SHOULD(VB_GIVE_ITEM_FROM_COW, shouldRegister, {
EnCow* enCow = va_arg(args, EnCow*);

View file

@ -15,8 +15,6 @@ extern "C" {
extern PlayState* gPlayState;
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
extern void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play);
extern "C" void ObjKibako2_RandomizerDraw(Actor* thisx, PlayState* play) {
@ -158,7 +156,7 @@ extern "C" void ObjKibako_RandomizerDraw(Actor* thisx, PlayState* play) {
uint8_t ObjKibako2_RandomizerHoldsItem(ObjKibako2* crateActor, PlayState* play) {
RandomizerCheck rc = crateActor->crateIdentity.randomizerCheck;
uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon();
uint8_t crateSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_CRATES).Get();
uint8_t crateSetting = RAND_GET_OPTION(RSK_SHUFFLE_CRATES);
// Don't pull randomized item if crate isn't randomized or is already checked
if (!IS_RANDO || (crateSetting == RO_SHUFFLE_CRATES_OVERWORLD && isDungeon) ||
@ -174,7 +172,7 @@ uint8_t ObjKibako2_RandomizerHoldsItem(ObjKibako2* crateActor, PlayState* play)
uint8_t ObjKibako_RandomizerHoldsItem(ObjKibako* smallCrateActor, PlayState* play) {
RandomizerCheck rc = smallCrateActor->smallCrateIdentity.randomizerCheck;
uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon();
uint8_t crateSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_CRATES).Get();
uint8_t crateSetting = RAND_GET_OPTION(RSK_SHUFFLE_CRATES);
// Don't pull randomized item if crate isn't randomized or is already checked
if (!IS_RANDO || (crateSetting == RO_SHUFFLE_CRATES_OVERWORLD && isDungeon) ||
@ -211,7 +209,7 @@ void ObjKibako_RandomizerSpawnCollectible(ObjKibako* smallCrateActor, PlayState*
void ObjKibako2_RandomizerInit(void* actorRef) {
Actor* actor = static_cast<Actor*>(actorRef);
uint8_t logicSetting = Rando::Context::GetInstance()->GetOption(RSK_LOGIC_RULES).Get();
uint8_t logicSetting = RAND_GET_OPTION(RSK_LOGIC_RULES);
// don't shuffle two OOB crates in GF and don't shuffle child GV/GF crates when not in no logic
if (actor->id != ACTOR_OBJ_KIBAKO2 ||

View file

@ -23,7 +23,7 @@ void ShuffleFreestanding_OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* sh
Rando::Location* loc =
OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor(item00->actor.id, gPlayState->sceneNum, params);
uint8_t isDungeon = loc->IsDungeon();
uint8_t freestandingSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_FREESTANDING).Get();
uint8_t freestandingSetting = RAND_GET_OPTION(RSK_SHUFFLE_FREESTANDING);
RandomizerCheck randomizerCheck = loc->GetRandomizerCheck();
bool checkObtained = Rando::Context::GetInstance()->GetItemLocation(randomizerCheck)->HasObtained();

View file

@ -11,8 +11,6 @@ extern "C" {
extern PlayState* gPlayState;
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
extern void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play);
void DrawTypeOfGrass(EnKusa* grassActor, Gfx* bushDList, Gfx* grassDList, PlayState* play) {
@ -96,7 +94,7 @@ uint8_t EnKusa_RandomizerHoldsItem(EnKusa* grassActor, PlayState* play) {
RandomizerCheck rc = grassActor->grassIdentity.randomizerCheck;
uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon();
uint8_t grassSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_GRASS).Get();
uint8_t grassSetting = RAND_GET_OPTION(RSK_SHUFFLE_GRASS);
// Don't pull randomized item if grass isn't randomized or is already checked
if (!IS_RANDO || (grassSetting == RO_SHUFFLE_GRASS_OVERWORLD && isDungeon) ||

View file

@ -27,7 +27,7 @@ extern "C" void ObjTsubo_RandomizerDraw(Actor* thisx, PlayState* play) {
uint8_t ObjTsubo_RandomizerHoldsItem(ObjTsubo* potActor, PlayState* play) {
RandomizerCheck rc = potActor->potIdentity.randomizerCheck;
uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon();
uint8_t potSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_POTS).Get();
uint8_t potSetting = RAND_GET_OPTION(RSK_SHUFFLE_POTS);
// Don't pull randomized item if pot isn't randomized or is already checked
if (!IS_RANDO || (potSetting == RO_SHUFFLE_POTS_OVERWORLD && isDungeon) ||
@ -85,7 +85,7 @@ void ShufflePots_OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va
// Unlock early Ganon's Boss Key doors to allow access to the pots there when pots are shuffled in dungeon
if (id == VB_LOCK_BOSS_DOOR) {
DoorShutter* doorActor = va_arg(args, DoorShutter*);
uint8_t shufflePotSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_POTS).Get();
uint8_t shufflePotSetting = RAND_GET_OPTION(RSK_SHUFFLE_POTS);
if (gPlayState->sceneNum == SCENE_GANONS_TOWER && doorActor->dyna.actor.world.pos.y == 800 &&
(shufflePotSetting == RO_SHUFFLE_POTS_DUNGEONS || shufflePotSetting == RO_SHUFFLE_POTS_ALL)) {
*should = false;

View file

@ -14,6 +14,8 @@
#include <map>
#include <nlohmann/json.hpp>
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
/**
* @brief Singleton for storing and accessing dynamic Randomizer-related data
*

View file

@ -15,10 +15,6 @@ extern SaveContext gSaveContext;
extern PlayState* gPlayState;
}
#define FSi OTRGlobals::Instance->gRandoContext->GetFishsanity()
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
/**
* @brief Parallel list of pond fish checks for both ages
*/
@ -488,15 +484,15 @@ void Fishsanity::OnItemReceiveHandler(GetItemEntry itemEntry) {
// C interface
extern "C" {
bool Randomizer_GetPondFishShuffled() {
return FSi->GetPondFishShuffled();
return Rando::Context::GetInstance()->GetFishsanity()->GetPondFishShuffled();
}
bool Randomizer_GetOverworldFishShuffled() {
return FSi->GetOverworldFishShuffled();
return Rando::Context::GetInstance()->GetFishsanity()->GetOverworldFishShuffled();
}
bool Randomizer_IsAdultPond() {
return FSi->IsAdultPond();
return Rando::Context::GetInstance()->GetFishsanity()->IsAdultPond();
}
void Fishsanity_DrawEffShadow(Actor* actor, Lights* lights, PlayState* play) {

View file

@ -71,8 +71,6 @@ extern void EnGe1_Wait_Archery(EnGe1* enGe1, PlayState* play);
extern void EnGe1_SetAnimationIdle(EnGe1* enGe1);
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
bool LocMatchesQuest(Rando::Location loc) {
if (loc.GetQuest() == RCQUEST_BOTH) {
return true;
@ -927,13 +925,6 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
*should = Flags_GetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG);
break;
}
case VB_SET_CUCCO_COUNT: {
EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*);
// Override starting Cucco count using setting value
enNiwLady->cuccosInPen = 7 - RAND_GET_OPTION(RSK_CUCCO_COUNT);
*should = false;
break;
}
case VB_KING_ZORA_THANK_CHILD: {
// Allow turning in Ruto's letter even if you have already rescued her
if (!Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) {
@ -2144,7 +2135,7 @@ void RandomizerOnActorUpdateHandler(void* refActor) {
shutterDoor->unk_16E = 0;
}
} else if (actor->id == ACTOR_DOOR_GERUDO) {
DoorGerudo* gerudoDoor = (DoorGerudo*)actor;
DoorGerudo* gerudoDoor = reinterpret_cast<DoorGerudo*>(actor);
gerudoDoor->actionFunc = func_8099485C;
gerudoDoor->dyna.actor.world.pos.y = gerudoDoor->dyna.actor.home.pos.y + 200.0f;
}

View file

@ -595,7 +595,6 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of "
"rewards on slider does not change.";
mOptionDescriptions[RSK_CUCCO_COUNT] = "The amount of cuccos needed to claim the reward from Anju the Cucco Lady.";
mOptionDescriptions[RSK_BIG_POE_COUNT] = "The Poe collector will give a reward for turning in this many Big Poes.";
mOptionDescriptions[RSK_SKIP_CHILD_STEALTH] =
"The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards.";

View file

@ -4393,7 +4393,7 @@ CustomMessage Randomizer::GetFishingPondOwnerMessage(u16 originalTextId) {
"fischen!",
"Désolé, mais l'étang est fermé.&J'ai perdu ma bonne %rCanne à Pêche%w...&Impossible de pêcher sans elle!");
if (Rando::Context::GetInstance()->GetOption(RSK_FISHING_POLE_HINT)) {
if (GetRandoSettingValue(RSK_FISHING_POLE_HINT)) {
messageEntry = messageEntry + CustomMessage(ctx->GetHint(RH_FISHING_POLE)->GetHintMessage());
}

View file

@ -5805,7 +5805,6 @@ typedef enum {
RSK_STARTING_NUTS,
RSK_FULL_WALLETS,
RSK_SHUFFLE_CHEST_MINIGAME,
RSK_CUCCO_COUNT,
RSK_BIG_POE_COUNT,
RSK_SKIP_EPONA_RACE,
RSK_COMPLETE_MASK_QUEST,

View file

@ -269,7 +269,6 @@ void Settings::CreateOptions() {
OPT_BOOL(RSK_SKIP_EPONA_RACE, "Skip Epona Race", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipEponaRace"), mOptionDescriptions[RSK_SKIP_EPONA_RACE], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP);
OPT_BOOL(RSK_SKIP_SCARECROWS_SONG, "Skip Scarecrow's Song", CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"), mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG]);
OPT_U8(RSK_BIG_POE_COUNT, "Big Poe Target Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BigPoeTargetCount"), mOptionDescriptions[RSK_BIG_POE_COUNT], WidgetType::Slider, 10);
OPT_U8(RSK_CUCCO_COUNT, "Cuccos to return", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("CuccosToReturn"), mOptionDescriptions[RSK_CUCCO_COUNT], WidgetType::Slider, 7);
OPT_BOOL(RSK_COMPLETE_MASK_QUEST, "Complete Mask Quest", CVAR_RANDOMIZER_SETTING("CompleteMaskQuest"), mOptionDescriptions[RSK_COMPLETE_MASK_QUEST]);
OPT_U8(RSK_GOSSIP_STONE_HINTS, "Gossip Stone Hints", {"No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GossipStoneHints"), mOptionDescriptions[RSK_GOSSIP_STONE_HINTS], WidgetType::Combobox, RO_GOSSIP_STONES_NEED_NOTHING, false, IMFLAG_NONE);
OPT_U8(RSK_HINT_CLARITY, "Hint Clarity", {"Obscure", "Ambiguous", "Clear"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("HintClarity"), mOptionDescriptions[RSK_HINT_CLARITY], WidgetType::Combobox, RO_HINT_CLARITY_CLEAR, true, IMFLAG_INDENT);
@ -1316,8 +1315,8 @@ void Settings::CreateOptions() {
WidgetContainerType::TABLE);
mOptionGroups[RSG_TIMESAVERS_IMGUI] = OptionGroup::SubGroup(
"Timesavers",
{ &mOptions[RSK_CUCCO_COUNT], &mOptions[RSK_BIG_POE_COUNT], &mOptions[RSK_SKIP_CHILD_ZELDA],
&mOptions[RSK_SKIP_EPONA_RACE], &mOptions[RSK_COMPLETE_MASK_QUEST], &mOptions[RSK_SKIP_SCARECROWS_SONG] },
{ &mOptions[RSK_BIG_POE_COUNT], &mOptions[RSK_SKIP_CHILD_ZELDA], &mOptions[RSK_SKIP_EPONA_RACE],
&mOptions[RSK_COMPLETE_MASK_QUEST], &mOptions[RSK_SKIP_SCARECROWS_SONG] },
WidgetContainerType::COLUMN);
mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI] = OptionGroup::SubGroup("",
{
@ -1582,7 +1581,6 @@ void Settings::CreateOptions() {
&mOptions[RSK_SKIP_EPONA_RACE],
&mOptions[RSK_SKIP_SCARECROWS_SONG],
&mOptions[RSK_BIG_POE_COUNT],
&mOptions[RSK_CUCCO_COUNT],
&mOptions[RSK_COMPLETE_MASK_QUEST],
});
mOptionGroups[RSG_MISC] = OptionGroup("Miscellaneous Settings",
@ -2878,11 +2876,8 @@ void Context::FinalizeSettings(const std::set<RandomizerCheck>& excludedLocation
if (mOptions[RSK_FISHSANITY].IsNot(RO_FISHSANITY_HYRULE_LOACH)) {
mOptions[RSK_LOACH_HINT].Set(RO_GENERIC_OFF);
}
if (mOptions[RSK_CUCCO_COUNT].Is(0)) {
mOptions[RSK_CHICKENS_HINT].Set(RO_GENERIC_OFF);
}
}
void Settings::ParseJson(nlohmann::json spoilerFileJson) {
mContext->SetSeedString(spoilerFileJson["seed"].get<std::string>());
mContext->SetSeed(spoilerFileJson["finalSeed"].get<uint32_t>());

View file

@ -28,6 +28,7 @@ extern "C" {
#include "src/overlays/actors/ovl_En_Daiku/z_en_daiku.h"
#include "src/overlays/actors/ovl_Bg_Spot02_Objects/z_bg_spot02_objects.h"
#include "src/overlays/actors/ovl_Bg_Spot03_Taki/z_bg_spot03_taki.h"
#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h"
#include "src/overlays/actors/ovl_Bg_Hidan_Kousi/z_bg_hidan_kousi.h"
#include "src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.h"
#include "src/overlays/actors/ovl_En_Dnt_Demo/z_en_dnt_demo.h"
@ -49,8 +50,6 @@ extern void EnRu2_SetEncounterSwitchFlag(EnRu2* enRu2, PlayState* play);
extern void EnDaiku_EscapeSuccess(EnDaiku* enDaiku, PlayState* play);
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get()
void EnMa1_EndTeachSong(EnMa1* enMa1, PlayState* play) {
if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) {
Flags_SetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG);
@ -1386,3 +1385,15 @@ void TimeSaverRegisterHooks() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>(TimeSaverOnItemReceiveHandler);
});
}
void RegisterSkipWaterTempleGateDelay() {
COND_ID_HOOK(OnActorUpdate, ACTOR_BG_SPOT06_OBJECTS,
CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO), [](void* actor) {
BgSpot06Objects* spot06 = static_cast<BgSpot06Objects*>(actor);
if (spot06->dyna.actor.params == 0) {
spot06->timer = 0;
}
})
}
static RegisterShipInitFunc skipWaterTempleGateDelay(RegisterSkipWaterTempleGateDelay);

View file

@ -147,28 +147,25 @@ void RegisterOnInterfaceUpdateHook() {
timer = gSaveContext.subTimerSeconds;
}
if (timer > 0) {
if (timer > prevTimer || (timer % 30 == 0 && prevTimer != timer)) {
uint32_t minutes = timer / 60;
uint32_t seconds = timer % 60;
char* announceBuf = ttsAnnounceBuf;
char arg[8]; // at least big enough where no s8 string will overflow
if (minutes > 0) {
snprintf(arg, sizeof(arg), "%d", minutes);
auto translation = GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular",
TEXT_BANK_MISC, arg);
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str());
}
if (seconds > 0) {
snprintf(arg, sizeof(arg), "%d", seconds);
auto translation = GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular",
TEXT_BANK_MISC, arg);
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str());
}
assert(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf));
SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode());
prevTimer = timer;
if (timer > 0 && timer % (timer < 60 ? 10 : 30) == 0 && timer != prevTimer) {
uint32_t minutes = timer / 60;
uint32_t seconds = timer % 60;
char* announceBuf = ttsAnnounceBuf;
char arg[8]; // at least big enough where no s8 string will overflow
if (minutes > 0) {
snprintf(arg, sizeof(arg), "%d", minutes);
auto translation =
GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", TEXT_BANK_MISC, arg);
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str());
}
if (seconds > 0) {
snprintf(arg, sizeof(arg), "%d", seconds);
auto translation =
GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", TEXT_BANK_MISC, arg);
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str());
}
assert(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf));
SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode());
}
prevTimer = timer;

View file

@ -0,0 +1,29 @@
#include "ActorMaximumHealth.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
struct ActorMaximumHealth {
u8 maximumHealth = 0;
};
static ObjectExtension::Register<ActorMaximumHealth> ActorMaximumHealthRegister;
u8 GetActorMaximumHealth(const Actor* actor) {
const ActorMaximumHealth* maxHealth = ObjectExtension::GetInstance().Get<ActorMaximumHealth>(actor);
return maxHealth != nullptr ? maxHealth->maximumHealth : ActorMaximumHealth{}.maximumHealth;
}
void SetActorMaximumHealth(const Actor* actor, u8 maximumHealth) {
ObjectExtension::GetInstance().Set<ActorMaximumHealth>(actor, ActorMaximumHealth{ maximumHealth });
}
static void ActorMaximumHealth_Register() {
COND_HOOK(OnActorInit, true, [](void* ptr) {
Actor* actor = static_cast<Actor*>(ptr);
if (actor->category == ACTORCAT_ENEMY) {
SetActorMaximumHealth(actor, actor->colChkInfo.health);
}
});
}
RegisterShipInitFunc actorMaximumHealthInit(ActorMaximumHealth_Register);

View file

@ -0,0 +1,17 @@
#ifndef ACTOR_MAXIMUM_HEALTH_H
#define ACTOR_MAXIMUM_HEALTH_H
#ifdef __cplusplus
extern "C" {
#include "z64actor.h"
#endif
// Max health value for use with health bars, set on actor init
u8 GetActorMaximumHealth(const Actor* actor);
void SetActorMaximumHealth(const Actor* actor, u8 maximumHealth);
#ifdef __cplusplus
}
#endif
#endif // ACTOR_MAXIMUM_HEALTH_H

View file

@ -1138,6 +1138,10 @@ void SohMenu::AddMenuEnhancements() {
.CVar(CVAR_ENHANCEMENT("CuccoStayDurationMult"))
.Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip(
"Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider."));
AddWidget(path, "Cuccos Needed By Anju: %d", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR_ENHANCEMENT("CuccosToReturn"))
.Options(IntSliderOptions().Min(0).Max(7).DefaultValue(7).Format("%d").Tooltip(
"The amount of cuccos needed to receive bottle from Anju the Cucco Lady."));
path.column = SECTION_COLUMN_3;
AddWidget(path, "Enemies", WIDGET_SEPARATOR_TEXT);
@ -1312,6 +1316,11 @@ void SohMenu::AddMenuEnhancements() {
.Format("%d notes")
.Tooltip("Adjust the number of notes you need to play to end the third round."));
AddWidget(path, "Forest Temple", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Solve Amy's Puzzle", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_ENHANCEMENT("SkipAmyPuzzle"))
.Options(CheckboxOptions().Tooltip("Amy's block pushing puzzle instantly solved."));
path.column = SECTION_COLUMN_3;
AddWidget(path, "Fishing", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Customize Behavior##Fishing", WIDGET_CVAR_CHECKBOX)
@ -1581,9 +1590,6 @@ void SohMenu::AddMenuEnhancements() {
.Options(CheckboxOptions().Tooltip(
"Keese and Guay no longer target you and simply ignore you as if you were wearing the "
"Skull Mask."));
AddWidget(path, "No Dampe Fire", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_CHEAT("NoDampeFire"))
.Options(CheckboxOptions().Tooltip("Dampe won't drop fireballs during race."));
AddWidget(path, "Glitch Aids", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Easy Frame Advancing with Pause", WIDGET_CVAR_CHECKBOX)

View file

@ -1261,11 +1261,6 @@ void Actor_Init(Actor* actor, PlayState* play) {
actor->init = NULL;
GameInteractor_ExecuteOnActorInit(actor);
// For enemy health bar we need to know the max health during init
if (actor->category == ACTORCAT_ENEMY) {
actor->maximumHealth = actor->colChkInfo.health;
}
}
}
@ -2633,11 +2628,6 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) {
actor->init = NULL;
GameInteractor_ExecuteOnActorInit(actor);
// For enemy health bar we need to know the max health during init
if (actor->category == ACTORCAT_ENEMY) {
actor->maximumHealth = actor->colChkInfo.health;
}
}
actor = actor->next;
} else if (!Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) {

View file

@ -4,8 +4,8 @@
#include "overlays/effects/ovl_Effect_Ss_Dead_Sound/z_eff_ss_dead_sound.h"
#include "textures/icon_item_static/icon_item_static.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/OTRGlobals.h"
#define FLAGS 0
@ -1536,7 +1536,7 @@ s16 func_8001F404(s16 dropId) {
if (LINK_IS_ADULT) {
if (dropId == ITEM00_SEEDS) {
dropId = ITEM00_ARROWS_SMALL;
} else if ((dropId == ITEM00_STICK) && !(CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0))) {
} else if (GameInteractor_Should(VB_PREVENT_ADULT_STICK, dropId == ITEM00_STICK)) {
dropId = ITEM00_RUPEE_GREEN;
}
} else {

View file

@ -24,6 +24,7 @@
#include "soh/OTRGlobals.h"
#include "soh/ResourceManagerHelpers.h"
#include "soh/Enhancements/gameplaystats.h"
#include "soh/ObjectExtension/ActorMaximumHealth.h"
#include "message_data_static.h"
extern MessageTableEntry* sNesMessageEntryTablePtr;
@ -3643,7 +3644,7 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {
f32 scaleY = -0.75f;
f32 scaledHeight = -texHeight * scaleY;
f32 halfBarWidth = endTexWidth + ((f32)healthbar_fillWidth / 2);
s16 healthBarFill = ((f32)actor->colChkInfo.health / actor->maximumHealth) * healthbar_fillWidth;
s16 healthBarFill = ((f32)actor->colChkInfo.health / GetActorMaximumHealth(actor)) * healthbar_fillWidth;
if (anchorType == ENEMYHEALTH_ANCHOR_ACTOR) {
// Get actor projected position

View file

@ -6,6 +6,8 @@
#include "z_bg_po_event.h"
#include "objects/object_po_sisters/object_po_sisters.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#define FLAGS 0
@ -333,7 +335,7 @@ void BgPoEvent_BlockIdle(BgPoEvent* this, PlayState* play) {
Player* player = GET_PLAYER(play);
Actor* amy;
if (sBgPoEventPuzzleState == 0xF) {
if (GameInteractor_Should(VB_AMY_SOLVE, sBgPoEventPuzzleState == 0xF)) {
this->actionFunc = BgPoEvent_BlockSolved;
if ((this->type == 0) && (this->index == 0)) {
amy = Actor_Spawn(&play->actorCtx, play, ACTOR_EN_PO_SISTERS, this->dyna.actor.world.pos.x + 30.0f,

View file

@ -2,6 +2,7 @@
#include "objects/object_fz/object_fz.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorMaximumHealth.h"
#define FLAGS \
(ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \
@ -725,7 +726,7 @@ void EnFz_Draw(Actor* thisx, PlayState* play) {
// displayLists, so we need to recompute the index based on the scaled health (using the maximum health value) and
// clamp the final result for safety.
if (CVarGetInteger(CVAR_ENHANCEMENT("EnemySizeScalesHealth"), 0)) {
u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / this->actor.maximumHealth) * 6);
u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / GetActorMaximumHealth(this)) * 6);
index = (6 - scaledHealth) >> 1;
index = CLAMP(index, 0, 2);
}

View file

@ -1,6 +1,7 @@
#include "z_en_skj.h"
#include "overlays/actors/ovl_En_Skjneedle/z_en_skjneedle.h"
#include "objects/object_skj/object_skj.h"
#include "soh/Enhancements/enhancementTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/OTRGlobals.h"
#include "soh/ResourceManagerHelpers.h"
@ -404,7 +405,9 @@ void EnSkj_Init(Actor* thisx, PlayState* play2) {
default:
this->actor.params = type;
if (((this->actor.params != 0) && (this->actor.params != 1)) && (this->actor.params != 2)) {
if (INV_CONTENT(ITEM_TRADE_ADULT) < ITEM_SAW) {
if (INV_CONTENT(ITEM_TRADE_ADULT) < ITEM_SAW &&
CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), ENEMY_RANDOMIZER_OFF) ==
ENEMY_RANDOMIZER_OFF) {
Actor_Kill(&this->actor);
return;
}

View file

@ -6,6 +6,7 @@
#include "z_en_wood02.h"
#include "objects/object_wood02/object_wood02.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#define FLAGS 0
@ -327,7 +328,6 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) {
Vec3f dropsSpawnPt;
s32 i;
s32 leavesParams;
s32 numDrops;
// Despawn extra trees in a group if out of range
if ((this->spawnType == WOOD_SPAWN_SPAWNED) && (this->actor.parent != NULL)) {
@ -358,12 +358,7 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) {
dropsSpawnPt.y += 200.0f;
if ((this->unk_14C >= 0) && (this->unk_14C < 0x64)) {
if (CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0) && INV_CONTENT(ITEM_STICK) != ITEM_NONE) {
numDrops = Rand_ZeroOne() * 4;
for (i = 0; i < numDrops; ++i) {
Item_DropCollectible(play, &dropsSpawnPt, ITEM00_STICK);
}
} else {
if (GameInteractor_Should(VB_TREE_DROP_COLLECTIBLE, true, this)) {
Item_DropCollectibleRandom(play, &this->actor, &dropsSpawnPt, this->unk_14C << 4);
}
} else if (this->actor.home.rot.z != 0) {

View file

@ -1374,7 +1374,8 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) {
} else {
defaultName = &emptyNameNES;
}
} else { // GAME_REGION_NTSC
this->charPage = FS_CHAR_PAGE_HIRA; // Default to Hiragana Keyboard
} else { // GAME_REGION_NTSC
defaultName = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? &linkNameNES : &emptyNameNES;
}
memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, defaultName, 8);
@ -1575,7 +1576,8 @@ void FileChoose_UpdateRandomizerMenu(GameState* thisx) {
} else {
defaultName = &emptyNameNES;
}
} else { // GAME_REGION_NTSC
this->charPage = FS_CHAR_PAGE_HIRA; // Default to Hiragana Keyboard
} else { // GAME_REGION_NTSC
defaultName = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? &linkNameNES : &emptyNameNES;
}
memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, defaultName, 8);