Merge pull request #6715 from serprex/ackbar

merge ackbar to develop
This commit is contained in:
Philip Dubé
2026-06-10 01:23:01 +00:00
committed by GitHub
47 changed files with 736 additions and 492 deletions
@@ -4,7 +4,7 @@
"gRandoSettings": {
"BigPoeTargetCount": 1,
"BlueFireArrows": 1,
"BombchuBag": 1,
"BombchuBag": 2,
"BossKeysanity": 5,
"ClosedForest": 2,
"CuccosToReturn": 1,
@@ -21,11 +21,13 @@
"LacsRewardCount": 10,
"LacsRewardOptions": 1,
"LockOverworldDoors": 1,
"MedallionLockedTrials": 1,
"MixBosses": 1,
"MixDungeons": 1,
"MixGrottos": 1,
"MixInteriors": 1,
"MixOverworld": 1,
"MixThievesHideout": 1,
"MixedEntrances": 1,
"RainbowBridge": 7,
"ScrubsPrices": 2,
@@ -35,12 +37,16 @@
"Shuffle100GSReward": 1,
"ShuffleAdultTrade": 1,
"ShuffleBeanFairies": 1,
"ShuffleBeanSouls": 1,
"ShuffleBeehives": 1,
"ShuffleBossEntrances": 2,
"ShuffleBossSouls": 2,
"ShuffleBushes": 1,
"ShuffleChildWallet": 1,
"ShuffleClimb": 1,
"ShuffleCows": 1,
"ShuffleCrates": 3,
"ShuffleCrawl": 1,
"ShuffleDekuNutBag": 1,
"ShuffleDekuStickBag": 1,
"ShuffleDungeonsEntrances": 2,
@@ -50,7 +56,9 @@
"ShuffleFreestanding": 3,
"ShuffleFrogSongRupees": 1,
"ShuffleGanonBossKey": 9,
"ShuffleGanonTowerEntrance": 1,
"ShuffleGerudoToken": 1,
"ShuffleGrab": 1,
"ShuffleGrass": 3,
"ShuffleGrottosEntrances": 1,
"ShuffleInteriorsEntrances": 2,
@@ -59,18 +67,21 @@
"ShuffleMerchants": 3,
"ShuffleOcarinaButtons": 1,
"ShuffleOcarinas": 1,
"ShuffleOpenChest": 1,
"ShuffleOverworldEntrances": 1,
"ShuffleOverworldSpawns": 1,
"ShuffleOwlDrops": 1,
"ShufflePots": 3,
"ShuffleScrubs": 2,
"ShuffleSongs": 2,
"ShuffleSpeak": 1,
"ShuffleStoneFairies": 1,
"ShuffleSwim": 1,
"ShuffleThievesHideoutEntrances": 1,
"ShuffleTrees": 1,
"ShuffleTokens": 3,
"ShuffleWarpSongs": 1,
"ShuffleWeirdEgg": 1,
"SkipEponaRace": 1,
"StartingAge": 2,
"StartingHearts": 0,
"StartingMapsCompasses": 5,
@@ -19,6 +19,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 +38,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 +61,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 +81,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 +160,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,24 +366,29 @@ 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) {
uint64_t randomState = 0;
@@ -316,78 +402,83 @@ EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play) {
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.
@@ -415,7 +506,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) {
@@ -446,17 +536,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;
@@ -531,6 +631,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);
@@ -759,14 +878,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 += ShipUtils::Random(0, 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, &params)) {
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,
&params, i * 1000, true)) {
assert(false);
}
@@ -922,9 +1045,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, &params,
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)" },
@@ -935,7 +1092,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)
@@ -966,7 +1123,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);
@@ -981,7 +1138,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(); });
}
}
+21
View File
@@ -0,0 +1,21 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include <variables.h>
f32 Fishing_GetMinimumRequiredScore();
}
void BuildFishingMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (gSaveContext.minigameScore == 0) {
gSaveContext.minigameScore = Fishing_GetMinimumRequiredScore();
}
}
void RegisterFishingMessages() {
COND_ID_HOOK(OnOpenText, 0x40AE, CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0), BuildFishingMessage);
COND_ID_HOOK(OnOpenText, 0x4080, CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0), BuildFishingMessage);
}
static RegisterShipInitFunc initFunc(RegisterFishingMessages, { CVAR_ENHANCEMENT("CustomizeFishing") });
@@ -36,7 +36,7 @@ void RegisterAdditionalReticles() {
bool shouldRegister = CVAR_BOW_RETICLE_VALUE || CVAR_BOOMERANG_RETICLE_VALUE;
COND_VB_SHOULD(VB_DRAW_ADDITIONAL_RETICLES, shouldRegister, {
Player* player = GET_PLAYER(gPlayState);
Player* player = va_arg(args, Player*);
Actor* heldActor = player->heldActor;
if (CVAR_BOW_RETICLE_VALUE &&
((player->heldItemAction >= PLAYER_IA_BOW && player->heldItemAction <= PLAYER_IA_BOW_LIGHT) ||
+3 -3
View File
@@ -43,11 +43,11 @@ static bool HasArrowType(PlayerItemAction itemAction) {
case PLAYER_IA_BOW:
return true;
case PLAYER_IA_BOW_FIRE:
return INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE;
return INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE && gSaveContext.magic >= sMagicArrowCosts[0];
case PLAYER_IA_BOW_ICE:
return INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE;
return INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE && gSaveContext.magic >= sMagicArrowCosts[1];
case PLAYER_IA_BOW_LIGHT:
return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT;
return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT && gSaveContext.magic >= sMagicArrowCosts[2];
default:
return false;
}
@@ -7,11 +7,11 @@ extern "C" {
void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) {
CustomMessage msg =
CustomMessage("You got a %rGold Skulltula Token%w!&You've collected %r[[gsCount]]%w tokens&in total!",
"Ein %rGoldenes Skulltula-Symbol%w!&Du hast nun insgesamt %r[[gsCount]]&%wGoldene "
"Skulltula-Symbole&gesammelt!",
"Vous obtenez un %rSymbole de&Skulltula d'or%w! Vous avez&collecté %r[[gsCount]]%w symboles en "
"tout!",
CustomMessage("You got a %rGold Skulltula Token%w!&You've collected %r[[d]]%w |token|tokens|&in total!",
"Ein %rGoldenes Skulltula-Symbol%w!&Du hast nun insgesamt %r[[d]]&%w|Goldenes "
"Skulltula-Symbol|Goldene Skulltula-Symbole|&gesammelt!",
"Vous obtenez un %rSymbole de&Skulltula d'or%w! Vous avez&collecté %r[[d]]%w |symbole|symboles| "
"en tout!",
TEXTBOX_TYPE_BLUE);
// The freeze text cannot be manually dismissed and must be auto-dismissed.
// This is fine and even wanted when skull tokens are not shuffled, but when
@@ -26,7 +26,7 @@ void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) {
msg = msg + "\x0E\x3C";
}
int16_t gsCount = gSaveContext.inventory.gsTokens;
msg.Replace("[[gsCount]]", std::to_string(gsCount));
msg.InsertNumber(gsCount);
msg.AutoFormat(ITEM_SKULL_TOKEN);
msg.LoadIntoFont();
*loadFromMessageTable = false;
@@ -86,8 +86,7 @@ void RegisterShouldPlayBlueWarp() {
* should also account for the difference between your first and following visits to the blue warp.
*/
REGISTER_VB_SHOULD(VB_PLAY_TRANSITION_CS, {
// Do nothing when in a boss rush
if (IS_BOSS_RUSH) {
if (IS_BOSS_RUSH || gSaveContext.gameMode == GAMEMODE_END_CREDITS) {
return;
}
@@ -24,7 +24,6 @@ extern "C" {
#include "src/overlays/actors/ovl_En_Jj/z_en_jj.h"
#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_Spot06_Objects/z_bg_spot06_objects.h"
#include "src/overlays/actors/ovl_Bg_Spot03_Taki/z_bg_spot03_taki.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"
@@ -1241,41 +1240,41 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
case FLAG_EVENT_CHECK_INF:
switch (flag) {
case EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FAIRY_OCARINA).GetGIEntry_Copy();
TimeSaverQueueItem(RG_FAIRY_OCARINA);
break;
case EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_KOKIRI_EMERALD).GetGIEntry_Copy();
TimeSaverQueueItem(RG_KOKIRI_EMERALD);
break;
case EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_GORON_RUBY).GetGIEntry_Copy();
TimeSaverQueueItem(RG_GORON_RUBY);
break;
case EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZORA_SAPPHIRE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_ZORA_SAPPHIRE);
break;
case EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FOREST_MEDALLION).GetGIEntry_Copy();
TimeSaverQueueItem(RG_FOREST_MEDALLION);
break;
case EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FIRE_MEDALLION).GetGIEntry_Copy();
TimeSaverQueueItem(RG_FIRE_MEDALLION);
break;
case EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_WATER_MEDALLION).GetGIEntry_Copy();
TimeSaverQueueItem(RG_WATER_MEDALLION);
break;
case EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_ARROWS).GetGIEntry_Copy();
TimeSaverQueueItem(RG_LIGHT_ARROWS);
break;
case EVENTCHKINF_TIME_TRAVELED_TO_ADULT:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_MEDALLION).GetGIEntry_Copy();
TimeSaverQueueItem(RG_LIGHT_MEDALLION);
break;
}
break;
case FLAG_RANDOMIZER_INF:
switch (flag) {
case RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SHADOW_MEDALLION).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SHADOW_MEDALLION);
break;
case RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SPIRIT_MEDALLION).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SPIRIT_MEDALLION);
break;
}
break;
@@ -1287,22 +1286,22 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
case FLAG_RANDOMIZER_INF:
switch (flag) {
case RAND_INF_ZF_GREAT_FAIRY_REWARD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FARORES_WIND).GetGIEntry_Copy();
TimeSaverQueueItem(RG_FARORES_WIND);
break;
case RAND_INF_HC_GREAT_FAIRY_REWARD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DINS_FIRE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_DINS_FIRE);
break;
case RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_NAYRUS_LOVE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_NAYRUS_LOVE);
break;
case RAND_INF_DMT_GREAT_FAIRY_REWARD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_SINGLE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_MAGIC_SINGLE);
break;
case RAND_INF_DMC_GREAT_FAIRY_REWARD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_DOUBLE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_MAGIC_DOUBLE);
break;
case RAND_INF_OGC_GREAT_FAIRY_REWARD:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DOUBLE_DEFENSE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_DOUBLE_DEFENSE);
break;
}
break;
@@ -1328,47 +1327,44 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
case FLAG_EVENT_CHECK_INF:
switch (flag) {
case EVENTCHKINF_LEARNED_ZELDAS_LULLABY:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZELDAS_LULLABY).GetGIEntry_Copy();
TimeSaverQueueItem(RG_ZELDAS_LULLABY);
break;
case EVENTCHKINF_LEARNED_MINUET_OF_FOREST:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MINUET_OF_FOREST).GetGIEntry_Copy();
TimeSaverQueueItem(RG_MINUET_OF_FOREST);
break;
case EVENTCHKINF_LEARNED_BOLERO_OF_FIRE:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_BOLERO_OF_FIRE).GetGIEntry_Copy();
TimeSaverQueueItem(RG_BOLERO_OF_FIRE);
break;
case EVENTCHKINF_LEARNED_SERENADE_OF_WATER:
vanillaQueuedItemEntry =
Rando::StaticData::RetrieveItem(RG_SERENADE_OF_WATER).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SERENADE_OF_WATER);
break;
case EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT:
vanillaQueuedItemEntry =
Rando::StaticData::RetrieveItem(RG_REQUIEM_OF_SPIRIT).GetGIEntry_Copy();
TimeSaverQueueItem(RG_REQUIEM_OF_SPIRIT);
break;
case EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL:
vanillaQueuedItemEntry =
Rando::StaticData::RetrieveItem(RG_NOCTURNE_OF_SHADOW).GetGIEntry_Copy();
TimeSaverQueueItem(RG_NOCTURNE_OF_SHADOW);
break;
case EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_PRELUDE_OF_LIGHT).GetGIEntry_Copy();
TimeSaverQueueItem(RG_PRELUDE_OF_LIGHT);
break;
case EVENTCHKINF_LEARNED_SARIAS_SONG:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SARIAS_SONG).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SARIAS_SONG);
break;
case EVENTCHKINF_LEARNED_SONG_OF_TIME:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_TIME).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SONG_OF_TIME);
break;
case EVENTCHKINF_LEARNED_SONG_OF_STORMS:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_STORMS).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SONG_OF_STORMS);
break;
case EVENTCHKINF_LEARNED_SUNS_SONG:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SUNS_SONG).GetGIEntry_Copy();
TimeSaverQueueItem(RG_SUNS_SONG);
break;
}
break;
case FLAG_RANDOMIZER_INF:
switch (flag) {
case RAND_INF_LEARNED_EPONA_SONG:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_EPONAS_SONG).GetGIEntry_Copy();
TimeSaverQueueItem(RG_EPONAS_SONG);
break;
}
break;
@@ -1415,12 +1411,10 @@ static void TimeSaverRegisterHooks() {
TimeSaverOnSceneInitHandler);
COND_HOOK(OnVanillaBehavior, true, TimeSaverOnVanillaBehaviorHandler);
COND_HOOK(OnActorInit, true, TimeSaverOnActorInitHandler);
COND_HOOK(OnSceneInit, true, [](int16_t sceneNum) { successChimeCooldown = 0; });
// item queue for use outside rando, rando has its own queue
COND_HOOK(OnLoadGame, !IS_RANDO, [](int32_t fileNum) {
vanillaQueuedItemEntry = GET_ITEM_NONE;
successChimeCooldown = 0;
});
COND_HOOK(OnLoadGame, !IS_RANDO, [](int32_t fileNum) { vanillaQueuedItemEntry = GET_ITEM_NONE; });
COND_HOOK(OnItemReceive, !IS_RANDO, TimeSaverOnItemReceiveHandler);
COND_HOOK(OnPlayerUpdate, !IS_RANDO, TimeSaverOnPlayerUpdateHandler);
COND_HOOK(OnFlagSet,
+5 -1
View File
@@ -55,6 +55,7 @@ extern std::shared_ptr<SohMenu> mSohMenu;
#define SEQ_COUNT_INSTRUMENT 6
#define SEQ_COUNT_SFX 57
#define SEQ_COUNT_VOICE 108
#define SEQ_COUNT_ENDING 5
size_t AuthenticCountBySequenceType(SeqType type) {
switch (type) {
@@ -76,6 +77,8 @@ size_t AuthenticCountBySequenceType(SeqType type) {
return SEQ_COUNT_INSTRUMENT;
case SEQ_VOICE:
return SEQ_COUNT_VOICE;
case SEQ_ENDING:
return SEQ_COUNT_ENDING;
default:
return 0;
}
@@ -808,7 +811,8 @@ void AudioEditor::DrawElement() {
}
std::vector<SeqType> allTypes = {
SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE,
SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE,
SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE, SEQ_ENDING,
};
void AudioEditor_RandomizeAll() {
@@ -2708,6 +2708,7 @@ void RegisterCosmeticHooks() {
[](s16 sceneNum) { CosmeticsEditor_AutoRandomizeAll(); });
COND_HOOK(OnGameFrameUpdate, true, CosmeticsUpdateTick);
COND_HOOK(OnAssetAltChange, true, []() { ApplyOrResetCustomGfxPatches(true); });
}
void RegisterCosmeticWidgets() {
+12 -2
View File
@@ -123,6 +123,8 @@ static bool IsDummyPlayer(const Player* player) {
return player != nullptr && player->actor.update == DummyPlayer_Update;
}
static bool sPrevAltAssetsEnabled = false;
void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, const char* dlist2, const char* dlist3,
const char* alternateDL) {
if (resource == NULL || gfx == NULL || dlist1 == NULL || dlist2 == NULL) {
@@ -130,6 +132,7 @@ void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, c
}
const bool altAssetsRuntime = ResourceMgr_IsAltAssetsEnabled();
const bool altAssetsChanged = (altAssetsRuntime != sPrevAltAssetsEnabled);
if (!altAssetsRuntime) {
// Alt assets are off; ensure any prior patches using these names are reverted.
@@ -138,11 +141,16 @@ void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, c
if (dlist3 != NULL) {
ResourceMgr_UnpatchGfxByName(resource, dlist3);
}
// Drop any cached version of the resource so it reloads clean (unpatched) next use.
ResourceMgr_UnloadResource(resource);
if (altAssetsChanged) {
ResourceMgr_UnloadResource(resource);
}
return;
}
if (altAssetsChanged) {
ResourceMgr_UnloadResource(resource);
}
if (!ResourceGetIsCustomByName(gfx)) {
return;
}
@@ -512,6 +520,8 @@ void UpdatePatchCustomEquipmentDlists() {
}
ApplyCommonEquipmentPatches();
sPrevAltAssetsEnabled = ResourceMgr_IsAltAssetsEnabled();
}
static void PatchCustomEquipment() {
@@ -573,10 +573,11 @@ typedef enum {
// #### `result`
// ```c
// true
// (this->heldItemAction == PLAYER_IA_HOOKSHOT) ||
// (this->heldItemAction == PLAYER_IA_LONGSHOT)
// ```
// #### `args`
// - None
// - '*Player'
VB_DRAW_ADDITIONAL_RETICLES,
// #### `result`
@@ -2991,7 +2992,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
@@ -1350,6 +1350,14 @@ void StaticData::HintTable_Init_Exclude_Dungeon() {
/*german*/ "Man erzählt sich, daß sich bewacht von einem #Ring der Flammen#, im Geistertempel #[[1]]# |befände|befänden|.",
/*french*/ "Selon moi, protégé par un #cercle de flammes# dans le Temple de l'Esprit se trouve #[[1]]#.", {QM_RED, QM_GREEN}));
hintTextTable[RHT_SPIRIT_TEMPLE_BOULDER_ROOM_SUN_FAIRY] = HintText(CustomMessage("They say that #calling the sun past rolling boulders in Spirit Temple# reveals #[[1]]#.",
/*german*/ TODO_TRANSLATE,
/*french*/ TODO_TRANSLATE, {QM_RED, QM_GREEN}));
hintTextTable[RHT_SPIRIT_TEMPLE_ARMOS_ROOM_SUN_FAIRY] = HintText(CustomMessage("They say that #calling the sun in the spotlight by statues# reveals #[[1]]#.",
/*german*/ TODO_TRANSLATE,
/*french*/ TODO_TRANSLATE, {QM_RED, QM_GREEN}));
hintTextTable[RHT_CRATE_SPIRIT_TEMPLE] = HintText(CustomMessage("They say that a #crate in Spirit Temple# contains #[[1]]#.",
/*german*/ "Man erzählt sich, daß eine #Kiste im Geistertempel# #[[1]]# enthielte.",
/*french*/ "Selon moi, une #caisse dans le Temple de l'Esprit# contient #[[1]]#.", {QM_RED, QM_GREEN}));
@@ -2249,7 +2249,7 @@ void StaticData::HintTable_Init_Exclude_Overworld() {
/*french*/ "Selon moi, un #arbre au Ranch Lon Lon# cache #[[1]]#.", { QM_RED, QM_GREEN }));
hintTextTable[RHT_BUSH_HYRULE_FIELD] =
HintText(CustomMessage("They say that a #bush in Hyrle Field# contains #[[1]]#.",
HintText(CustomMessage("They say that a #bush in Hyrule Field# contains #[[1]]#.",
/*german*/ TODO_TRANSLATE,
/*french*/ "Selon moi, un #buisson dans la Plaine d'Hyrule# cache #[[1]]#.", { QM_RED, QM_GREEN }));
hintTextTable[RHT_BUSH_ZORAS_FOUNTAIN] =
@@ -126,6 +126,7 @@ void GenerateStartingInventory() {
AddItemToInventory(RG_NOCTURNE_OF_SHADOW, ctx->GetOption(RSK_STARTING_NOCTURNE_OF_SHADOW) ? 1 : 0);
AddItemToInventory(RG_PRELUDE_OF_LIGHT, ctx->GetOption(RSK_STARTING_PRELUDE_OF_LIGHT) ? 1 : 0);
AddItemToInventory(RG_KOKIRI_SWORD, ctx->GetOption(RSK_STARTING_KOKIRI_SWORD) ? 1 : 0);
AddItemToInventory(RG_MAGIC_BEAN_PACK, ctx->GetOption(RSK_STARTING_BEANS) ? 1 : 0);
// if (ProgressiveGoronSword) {
// AddItemToInventory(RG_PROGRESSIVE_GORONSWORD, StartingBiggoronSword.Value<uint8_t>());
// } else {
@@ -14,7 +14,7 @@ extern PlayState* gPlayState;
void BuildHintStoneMessage(uint16_t* textId, bool* loadFromMessageTable) {
if ((RAND_GET_OPTION(RSK_GOSSIP_STONE_HINTS).Is(RO_GOSSIP_STONES_NEED_TRUTH) &&
Player_GetMask(gPlayState) == PLAYER_MASK_TRUTH) ||
Player_GetMask(gPlayState) != PLAYER_MASK_TRUTH) ||
(RAND_GET_OPTION(RSK_GOSSIP_STONE_HINTS).Is(RO_GOSSIP_STONES_NEED_STONE) &&
CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY) == 0)) {
return;
@@ -111,6 +111,8 @@ void BuildCarpetGuyMessage(uint16_t* textId, bool* loadFromMessageTable) {
BuildMerchantMessage(msg, RC_WASTELAND_BOMBCHU_SALESMAN,
!RAND_GET_OPTION(RSK_MERCHANT_TEXT_HINT) ||
CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0));
} else {
return;
}
msg.AutoFormat();
msg.LoadIntoFont();
@@ -141,7 +141,7 @@ void BuildSkulltulaPeopleMessage(uint16_t* textId, bool* loadFromMessageTable) {
"et j'aurai quelque chose à te donner! [[color]]([[1]])%w");
msg.InsertNumber(count);
msg.Replace("[[color]]", item.GetColor());
msg.InsertNames({ item.GetHint().GetHintMessage().GetForCurrentLanguage() });
msg.InsertNames({ item.GetHint().GetHintMessage() });
msg.AutoFormat();
msg.LoadIntoFont();
*loadFromMessageTable = false;
@@ -159,7 +159,7 @@ void Build100SkullsHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
Rando::Item& item =
Rando::StaticData::RetrieveItem(RAND_GET_ITEM_LOC(RC_KAK_100_GOLD_SKULLTULA_REWARD)->GetPlacedRandomizerGet());
msg.Replace("[[color]]", item.GetColor());
msg.InsertNames({ item.GetHint().GetHintMessage().GetForCurrentLanguage() });
msg.InsertNames({ item.GetHint().GetHintMessage() });
msg.AutoFormat();
msg.LoadIntoFont();
*loadFromMessageTable = false;
@@ -179,78 +179,43 @@ void BuildGregHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
}
}
void BuildMysteriousWarpMessage() {
CustomMessage msg = CustomMessage(
"Warp to&%ra mysterious place?%w&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
"Zu&%reinem mysteriösen Ort%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
"Se téléporter vers&%run endroit mystérieux%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%rOK!&Non%w");
msg.LoadIntoFont();
static void BuildWarpMessage(RandomizerHint rh, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
CustomMessage msg = CustomMessage(
"Warp to&%ra mysterious place?%w&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
"Zu&%reinem mysteriösen Ort%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
"Se téléporter vers&%run endroit mystérieux%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%rOK!&Non%w");
msg.AutoFormat();
msg.LoadIntoFont();
} else {
CustomMessage msg = RAND_GET_HINT(rh)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
}
*loadFromMessageTable = false;
}
void BuildMinuetWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
BuildMysteriousWarpMessage();
*loadFromMessageTable = false;
return;
}
CustomMessage msg = RAND_GET_HINT(RH_MINUET_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
*loadFromMessageTable = false;
BuildWarpMessage(RH_MINUET_WARP_LOC, loadFromMessageTable);
}
void BuildBoleroWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
BuildMysteriousWarpMessage();
*loadFromMessageTable = false;
return;
}
CustomMessage msg = RAND_GET_HINT(RH_BOLERO_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
*loadFromMessageTable = false;
BuildWarpMessage(RH_BOLERO_WARP_LOC, loadFromMessageTable);
}
void BuildSerenadeWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
BuildMysteriousWarpMessage();
*loadFromMessageTable = false;
return;
}
CustomMessage msg = RAND_GET_HINT(RH_SERENADE_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
*loadFromMessageTable = false;
BuildWarpMessage(RH_SERENADE_WARP_LOC, loadFromMessageTable);
}
void BuildRequiemWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
BuildMysteriousWarpMessage();
*loadFromMessageTable = false;
return;
}
CustomMessage msg = RAND_GET_HINT(RH_REQUIEM_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
*loadFromMessageTable = false;
BuildWarpMessage(RH_REQUIEM_WARP_LOC, loadFromMessageTable);
}
void BuildNocturneWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
BuildMysteriousWarpMessage();
*loadFromMessageTable = false;
return;
}
CustomMessage msg = RAND_GET_HINT(RH_NOCTURNE_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
*loadFromMessageTable = false;
BuildWarpMessage(RH_NOCTURNE_WARP_LOC, loadFromMessageTable);
}
void BuildPreludeWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
BuildMysteriousWarpMessage();
*loadFromMessageTable = false;
return;
}
CustomMessage msg = RAND_GET_HINT(RH_PRELUDE_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
msg.LoadIntoFont();
*loadFromMessageTable = false;
BuildWarpMessage(RH_PRELUDE_WARP_LOC, loadFromMessageTable);
}
void BuildFrogsHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
@@ -420,15 +385,17 @@ void RegisterStaticHints() {
COND_ID_HOOK(OnOpenText, TEXT_CHEST_GAME_REAL_GAMBLER, RAND_GET_OPTION(RSK_GREG_HINT), BuildGregHintMessage);
COND_ID_HOOK(OnOpenText, TEXT_CHEST_GAME_THANKS_A_LOT, RAND_GET_OPTION(RSK_GREG_HINT), BuildGregHintMessage);
// Warp
COND_ID_HOOK(OnOpenText, TEXT_WARP_MINUET_OF_FOREST, RAND_GET_OPTION(RSK_WARP_SONG_HINTS), BuildMinuetWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_BOLERO_OF_FIRE, RAND_GET_OPTION(RSK_WARP_SONG_HINTS), BuildBoleroWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_SERENADE_OF_WATER, RAND_GET_OPTION(RSK_WARP_SONG_HINTS),
COND_ID_HOOK(OnOpenText, TEXT_WARP_MINUET_OF_FOREST, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
BuildMinuetWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_BOLERO_OF_FIRE, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS), BuildBoleroWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_SERENADE_OF_WATER, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
BuildSerenadeWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_REQUIEM_OF_SPIRIT, RAND_GET_OPTION(RSK_WARP_SONG_HINTS),
COND_ID_HOOK(OnOpenText, TEXT_WARP_REQUIEM_OF_SPIRIT, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
BuildRequiemWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_NOCTURNE_OF_SHADOW, RAND_GET_OPTION(RSK_WARP_SONG_HINTS),
COND_ID_HOOK(OnOpenText, TEXT_WARP_NOCTURNE_OF_SHADOW, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
BuildNocturneWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_PRELUDE_OF_LIGHT, RAND_GET_OPTION(RSK_WARP_SONG_HINTS), BuildPreludeWarpMessage);
COND_ID_HOOK(OnOpenText, TEXT_WARP_PRELUDE_OF_LIGHT, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
BuildPreludeWarpMessage);
// Frogs
COND_ID_HOOK(OnOpenText, TEXT_FROGS_UNDERWATER, RAND_GET_OPTION(RSK_FROGS_HINT), BuildFrogsHintMessage);
// Loach
@@ -2686,7 +2686,16 @@ void RandomizerOnPlayerUpdateHandler() {
gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = respawn->second.yaw;
}
Play_TriggerVoidOut(gPlayState);
if (gPlayState->sceneNum == SCENE_GROTTOS) {
// RESPAWN_MODE_DOWN isn't refreshed on grotto entry, reload grotto instead
gPlayState->nextEntranceIndex = gSaveContext.entranceIndex;
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
gPlayState->transitionType = TRANS_TYPE_FADE_BLACK;
gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK;
gSaveContext.respawnFlag = 0;
} else {
Play_TriggerVoidOut(gPlayState);
}
}
}
@@ -2699,8 +2708,14 @@ void RandomizerOnPlayerUpdateHandler() {
}
if (!GameInteractor::IsGameplayPaused() && RAND_GET_OPTION(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) {
// Warp to credits
if (GameInteractor::State::TriforceHuntCreditsWarpActive) {
// Warp to credits once item queue has drained to avoid losing queued items
if (GameInteractor::State::TriforceHuntCreditsWarpActive && randomizerQueuedChecks.empty() &&
randomizerQueuedCheck == RC_UNKNOWN_CHECK) {
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] =
static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
gSaveContext.ship.stats.gameComplete = 1;
Play_PerformSave(gPlayState);
Notification::Emit({ .message = "Game autosaved" });
gPlayState->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0;
gSaveContext.nextCutsceneIndex = 0xFFF2;
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <string>
#include <variant>
#include <memory>
#include "soh/Enhancements/custom-message/text.h"
+1 -1
View File
@@ -64,7 +64,7 @@ bool Rando::Location::IsOverworld() const {
}
bool Rando::Location::IsShop() const {
return scene >= SCENE_BAZAAR && scene <= SCENE_BOMBCHU_SHOP;
return (scene >= SCENE_BAZAAR && scene <= SCENE_BOMBCHU_SHOP) || scene == SCENE_TEST01;
}
bool Rando::Location::IsVanillaCompletion() const {
@@ -136,7 +136,6 @@ void RegionTable_Init_SpiritTemple() {
LOCATION(RC_SPIRIT_TEMPLE_CHILD_CLIMB_EAST_CHEST, SpiritShared(RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_2F, []{return logic->CanHitSwitch(ED_BOMB_THROW) && logic->HasItem(RG_OPEN_CHEST);})),
LOCATION(RC_SPIRIT_TEMPLE_GS_SUN_ON_FLOOR_ROOM, SpiritShared(RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_2F, []{return logic->CanKillEnemy(RE_GOLD_SKULLTULA, logic->TakeDamage() ? ED_SHORT_JUMPSLASH : ED_BOMB_THROW);}, false,
RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_1F, []{return logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_BOOMERANG);})),
LOCATION(RC_SPIRIT_TEMPLE_BOULDER_ROOM_SUN_FAIRY, logic->CanUse(RG_SUNS_SONG) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_BOOMERANG) || logic->CanUse(RG_BOMBCHU_5) || (logic->CanUse(RG_BOMB_BAG) && logic->IsAdult && ctx->GetTrickOption(RT_SPIRIT_LOWER_ADULT_SWITCH))) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanJumpslash())),
}, {
//Exits
ENTRANCE(RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_1F, true),
@@ -102,7 +102,7 @@ void RegionTable_Init_DeathMountainTrail() {
//Exits
ENTRANCE(RR_DEATH_MOUNTAIN_ROCKFALL, true),
ENTRANCE(RR_DMC_UPPER_ENTRY, true),
ENTRANCE(RR_DMT_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
ENTRANCE(RR_DMT_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_KOKIRI) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
ENTRANCE(RR_DMT_GREAT_FAIRY_FOUNTAIN, AnyAgeTime([]{return logic->BlastOrSmash();})),
});
@@ -87,11 +87,11 @@ void RegionTable_Init_LakeHylia() {
//Exits
ENTRANCE(RR_HF_TO_LAKE_HYLIA, true),
ENTRANCE(RR_LH_FROM_SHORTCUT, true),
ENTRANCE(RR_LH_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
ENTRANCE(RR_LH_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_KOKIRI) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
ENTRANCE(RR_LH_FISHING_ISLAND, ((logic->IsChild || logic->Get(LOGIC_WATER_TEMPLE_CLEAR)) && logic->HasItem(RG_BRONZE_SCALE)) || (logic->IsAdult && (logic->ReachScarecrow() || CanPlantBean(RR_LAKE_HYLIA, RG_LAKE_HYLIA_BEAN_SOUL)))),
ENTRANCE(RR_LH_LAB, logic->CanOpenOverworldDoor(RG_HYLIA_LAB_KEY)),
ENTRANCE(RR_LH_FROM_WATER_TEMPLE, true),
ENTRANCE(RR_LH_GROTTO, logic->HasItem(RG_POWER_BRACELET) && (logic->IsAdult || logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
ENTRANCE(RR_LH_GROTTO, logic->HasItem(RG_POWER_BRACELET) && (logic->IsAdult || logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_KOKIRI) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
});
areaTable[RR_LH_FROM_SHORTCUT] = Region("LH From Shortcut", SCENE_LAKE_HYLIA, TIME_DOESNT_PASS, {RA_LAKE_HYLIA}, {}, {}, {
+14 -41
View File
@@ -1072,49 +1072,28 @@ static std::unordered_map<RandomizerGet, GameplayStatTimestamp> randomizerGetToS
// Gameplay stat tracking: Update time the item was acquired
// (special cases for rando items)
void Randomizer_GameplayStats_SetTimestamp(uint16_t item) {
u32 time = static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
// Have items in Link's pocket shown as being obtained at 0.1 seconds
if (time == 0) {
time = 1;
}
// Use ITEM_KEY_BOSS to timestamp Ganon's boss key
if (item == RG_GANONS_CASTLE_BOSS_KEY) {
gSaveContext.ship.stats.itemTimestamp[ITEM_KEY_BOSS] = time;
return;
}
if (randomizerGetToStatsTimeStamp.contains((RandomizerGet)item)) {
gSaveContext.ship.stats.itemTimestamp[randomizerGetToStatsTimeStamp[(RandomizerGet)item]] = time;
return;
}
// Count any bottled item as a bottle
if (item >= RG_EMPTY_BOTTLE && item <= RG_BOTTLE_WITH_BIG_POE) {
if (gSaveContext.ship.stats.itemTimestamp[ITEM_BOTTLE] == 0) {
if (gSaveContext.ship.stats.itemTimestamp[item] == 0) {
if (item == RG_GANONS_CASTLE_BOSS_KEY) {
gSaveContext.ship.stats.itemTimestamp[ITEM_KEY_BOSS] = time;
} else if (item == RG_MASTER_SWORD) {
gSaveContext.ship.stats.itemTimestamp[ITEM_SWORD_MASTER] = time;
} else if (randomizerGetToStatsTimeStamp.contains((RandomizerGet)item)) {
gSaveContext.ship.stats.itemTimestamp[randomizerGetToStatsTimeStamp[(RandomizerGet)item]] = time;
} else if (item >= RG_EMPTY_BOTTLE && item <= RG_BOTTLE_WITH_BIG_POE) {
gSaveContext.ship.stats.itemTimestamp[ITEM_BOTTLE] = time;
}
return;
}
// Count any bombchu pack as bombchus
if ((item >= RG_BOMBCHU_5 && item <= RG_BOMBCHU_20) || item == RG_PROGRESSIVE_BOMBCHU_BAG) {
if (gSaveContext.ship.stats.itemTimestamp[ITEM_BOMBCHU] = 0) {
} else if ((item >= RG_BOMBCHU_5 && item <= RG_BOMBCHU_20) || item == RG_PROGRESSIVE_BOMBCHU_BAG) {
gSaveContext.ship.stats.itemTimestamp[ITEM_BOMBCHU] = time;
} else if (item == RG_MAGIC_SINGLE) {
gSaveContext.ship.stats.itemTimestamp[ITEM_SINGLE_MAGIC] = time;
} else if (item == RG_DOUBLE_DEFENSE) {
gSaveContext.ship.stats.itemTimestamp[ITEM_DOUBLE_DEFENSE] = time;
}
return;
}
if (item == RG_MAGIC_SINGLE) {
gSaveContext.ship.stats.itemTimestamp[ITEM_SINGLE_MAGIC] = time;
return;
}
if (item == RG_DOUBLE_DEFENSE) {
gSaveContext.ship.stats.itemTimestamp[ITEM_DOUBLE_DEFENSE] = time;
return;
}
}
@@ -1385,13 +1364,7 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) ==
RO_TRIFORCE_HUNT_WIN) {
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] =
static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
gSaveContext.ship.stats.gameComplete = 1;
Play_PerformSave(play);
Notification::Emit({
.message = "Game autosaved",
});
// Save and warp are deferred until item queue drains
GameInteractor_SetTriforceHuntCreditsWarpActive(true);
}
}
@@ -1677,7 +1677,8 @@ bool IsCheckShuffled(RandomizerCheck rc) {
(loc->GetRCType() != RCTYPE_SCRUB || showScrubs ||
(showMajorScrubs && (rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized
rc == RC_HF_DEKU_SCRUB_GROTTO || rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT))) &&
(loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) &&
((loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) ||
(rc == RC_ZR_MAGIC_BEAN_SALESMAN && showBeans)) &&
(loc->GetRCType() != RCTYPE_BEGGAR || showBeggar) &&
(loc->GetRCType() != RCTYPE_SONG_LOCATION || showSongs) &&
(loc->GetRCType() != RCTYPE_BEEHIVE || showBeehives) &&
@@ -1728,8 +1729,7 @@ bool IsCheckShuffled(RandomizerCheck rc) {
rc == RC_DMT_TRADE_CLAIM_CHECK // even when shuffle adult trade is off
) &&
(rc != RC_KF_KOKIRI_SWORD_CHEST || showKokiriSword) && (rc != RC_TOT_MASTER_SWORD || showMasterSword) &&
(rc != RC_LH_HYRULE_LOACH || showHyruleLoach) && (rc != RC_ZR_MAGIC_BEAN_SALESMAN || showBeans) &&
(rc != RC_HC_MALON_EGG || showWeirdEgg) &&
(rc != RC_LH_HYRULE_LOACH || showHyruleLoach) && (rc != RC_HC_MALON_EGG || showWeirdEgg) &&
(loc->GetRCType() != RCTYPE_FROG_SONG || showFrogSongRupees) &&
((loc->GetRCType() != RCTYPE_MAP && loc->GetRCType() != RCTYPE_COMPASS) || showStartingMapsCompasses) &&
(loc->GetRCType() != RCTYPE_FOUNTAIN_FAIRY || showFountainFairies) &&
@@ -699,17 +699,18 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) {
modifiedLinkActorEntry.rot = gPlayState->linkActorEntry->rot;
modifiedLinkActorEntry.params = gPlayState->linkActorEntry->params;
if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON) {
// Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
// to prevent Link from falling if the bridge isn't spawned
if (sceneNum == SCENE_OUTSIDE_GANONS_CASTLE && spawn == 1) {
modifiedLinkActorEntry.pos.x = 0xFEA8;
modifiedLinkActorEntry.pos.y = 0x065C;
modifiedLinkActorEntry.pos.z = 0x0290;
modifiedLinkActorEntry.rot.y = 0x0700;
modifiedLinkActorEntry.params = 0x0DFF; // stationary spawn
gPlayState->linkActorEntry = &modifiedLinkActorEntry;
}
// Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
// to prevent Link from falling if the bridge isn't spawned
if (sceneNum == SCENE_OUTSIDE_GANONS_CASTLE && spawn == 1 &&
(Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON ||
Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF ||
Randomizer_GetSettingValue(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE))) {
modifiedLinkActorEntry.pos.x = 0xFEA8;
modifiedLinkActorEntry.pos.y = 0x065C;
modifiedLinkActorEntry.pos.z = 0x0290;
modifiedLinkActorEntry.rot.y = 0x0700;
modifiedLinkActorEntry.params = 0x0DFF; // stationary spawn
gPlayState->linkActorEntry = &modifiedLinkActorEntry;
}
if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
@@ -192,6 +192,9 @@ inline void from_json(const json& j, SaveContext& saveContext) {
j.at("swordHealth").get_to(saveContext.swordHealth);
std::vector<u32> sceneFlagsArray;
j.at("sceneFlags").get_to(sceneFlagsArray);
if (sceneFlagsArray.size() < 124 * 4) {
sceneFlagsArray.resize(124 * 4, 0);
}
for (int i = 0; i < 124; i++) {
saveContext.sceneFlags[i].chest = sceneFlagsArray[i * 4];
saveContext.sceneFlags[i].swch = sceneFlagsArray[i * 4 + 1];
@@ -3,6 +3,7 @@
#include <libultraship/libultraship.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h"
#include "soh/Enhancements/randomizer/randomizer.h"
static bool isResultOfHandling = false;
@@ -40,6 +41,10 @@ void Anchor::HandlePacket_SetCheckStatus(nlohmann::json payload) {
auto randoContext = Rando::Context::GetInstance();
RandomizerCheck rc = payload.at("rc").get<RandomizerCheck>();
if (rc < 0 || rc >= RC_MAX) {
SPDLOG_ERROR("[Anchor] SET_CHECK_STATUS: rc {} out of range", (int)rc);
return;
}
RandomizerCheckStatus status = payload.at("status").get<RandomizerCheckStatus>();
bool skipped = payload.at("skipped").get<bool>();
@@ -41,6 +41,13 @@ void Anchor::HandlePacket_SetFlag(nlohmann::json payload) {
s16 flagType = payload.at("flagType").get<s16>();
s16 flag = payload.at("flag").get<s16>();
// sceneNum == SCENE_ID_MAX is a sentinel meaning "global flag" (handled below); only larger
// values would index gSaveContext.sceneFlags out of bounds.
if (sceneNum < 0 || sceneNum > SCENE_ID_MAX) {
SPDLOG_ERROR("[Anchor] SET_FLAG: sceneNum {} out of range", sceneNum);
return;
}
if (sceneNum == SCENE_ID_MAX) {
auto effect = new GameInteractionEffect::SetFlag();
effect->parameters[0] = flagType;
@@ -39,6 +39,12 @@ void Anchor::HandlePacket_TeleportTo(nlohmann::json payload) {
s32 entranceIndex = payload.at("entranceIndex").get<s32>();
s8 roomIndex = payload.at("roomIndex").get<s8>();
if (entranceIndex < 0 || roomIndex < 0) {
SPDLOG_ERROR("[Anchor] TELEPORT_TO: invalid entranceIndex {} or roomIndex {}", entranceIndex, (int)roomIndex);
return;
}
PosRot posRot = payload.at("posRot").get<PosRot>();
gPlayState->nextEntranceIndex = entranceIndex;
@@ -41,6 +41,13 @@ void Anchor::HandlePacket_UnsetFlag(nlohmann::json payload) {
s16 flagType = payload.at("flagType").get<s16>();
s16 flag = payload.at("flag").get<s16>();
// sceneNum == SCENE_ID_MAX is a sentinel meaning "global flag" (handled below); only larger
// values would index gSaveContext.sceneFlags out of bounds.
if (sceneNum < 0 || sceneNum > SCENE_ID_MAX) {
SPDLOG_ERROR("[Anchor] UNSET_FLAG: sceneNum {} out of range", sceneNum);
return;
}
if (sceneNum == SCENE_ID_MAX) {
auto effect = new GameInteractionEffect::UnsetFlag();
effect->parameters[0] = flagType;
@@ -1,8 +1,11 @@
#include "soh/Network/Anchor/Anchor.h"
#include <nlohmann/json.hpp>
#include <libultraship/libultraship.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/OTRGlobals.h"
#include <spdlog/spdlog.h>
extern "C" {
#include "include/macros.h"
}
/**
* UPDATE_DUNGEON_ITEMS
@@ -33,6 +36,12 @@ void Anchor::HandlePacket_UpdateDungeonItems(nlohmann::json payload) {
}
u16 mapIndex = payload.at("mapIndex").get<u16>();
// dungeonKeys is shorter than dungeonItems (19 vs 20), so bound by the smaller of the two.
if (mapIndex >= ARRAY_COUNT(gSaveContext.inventory.dungeonItems) ||
mapIndex >= ARRAY_COUNT(gSaveContext.inventory.dungeonKeys)) {
SPDLOG_ERROR("[Anchor] UPDATE_DUNGEON_ITEMS: mapIndex {} out of range", mapIndex);
return;
}
gSaveContext.inventory.dungeonItems[mapIndex] = payload.at("dungeonItems").get<u8>();
gSaveContext.inventory.dungeonKeys[mapIndex] = payload.at("dungeonKeys").get<s8>();
}
+21 -14
View File
@@ -29,19 +29,19 @@ void CrowdControl::OnDisconnected() {
}
void CrowdControl::OnIncomingJson(nlohmann::json payload) {
Effect* incomingEffect = ParseMessage(payload);
std::unique_ptr<Effect> incomingEffect = ParseMessage(payload);
if (!incomingEffect) {
return;
}
// If effect is not a timed effect, execute and return result.
if (!incomingEffect->timeRemaining) {
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect);
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect.get());
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
} else {
// If another timed effect is already active that conflicts with the incoming effect.
bool isConflictingEffectActive = false;
for (Effect* effect : activeEffects) {
for (const auto& effect : activeEffects) {
if (effect != incomingEffect && effect->category == incomingEffect->category &&
effect->id < incomingEffect->id) {
isConflictingEffectActive = true;
@@ -52,14 +52,14 @@ void CrowdControl::OnIncomingJson(nlohmann::json payload) {
if (!isConflictingEffectActive) {
// Check if effect can be applied, if it can't, let CC know.
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect);
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect.get());
if (result == EffectResult::Retry || result == EffectResult::Failure) {
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
return;
}
activeEffectsMutex.lock();
activeEffects.push_back(incomingEffect);
activeEffects.push_back(std::move(incomingEffect));
activeEffectsMutex.unlock();
}
}
@@ -74,17 +74,15 @@ void CrowdControl::ProcessActiveEffects() {
auto it = activeEffects.begin();
while (it != activeEffects.end()) {
Effect* effect = *it;
Effect* effect = it->get();
EffectResult result = CrowdControl::ExecuteEffect(effect);
if (result == EffectResult::Success) {
// If time remaining has reached 0, we have finished the effect.
if (effect->timeRemaining <= 0) {
it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect),
activeEffects.end());
GameInteractor::RemoveEffect(
*dynamic_cast<RemovableGameInteractionEffect*>(effect->giEffect.get()));
delete effect;
it = activeEffects.erase(it);
} else {
// If we have a success after previously being paused, tell CC to resume timer.
if (effect->isPaused) {
@@ -176,7 +174,7 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu
return result;
}
CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
std::unique_ptr<CrowdControl::Effect> CrowdControl::ParseMessage(nlohmann::json dataReceived) {
if (!dataReceived.contains("id") || !dataReceived.contains("type")) {
SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived.dump());
return nullptr;
@@ -184,13 +182,16 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());
if (!dataReceived.contains("code")) {
// "parameters" is intentionally not required: most effects (spawn enemies, teleports, status
// effects, etc.) carry no parameters. Its absence is handled safely below, and any type error
// is caught by the guard in Network::HandleRemoteJson.
if (!dataReceived.contains("code") || !dataReceived.contains("viewer")) {
// This seems to happen when the CC session ends
SPDLOG_ERROR("[CrowdControl] Payload does not contain code, ignoring.");
SPDLOG_ERROR("[CrowdControl] Payload does not contain code or viewer, ignoring.");
return nullptr;
}
Effect* effect = new Effect();
auto effect = std::make_unique<Effect>();
effect->lastExecutionResult = EffectResult::Initiate;
effect->id = dataReceived["id"];
effect->viewerName = dataReceived["viewer"];
@@ -202,9 +203,15 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
receivedParameter = dataReceived["parameters"][0];
}
auto it = effectStringToEnum.find(effectName);
if (it == effectStringToEnum.end()) {
SPDLOG_ERROR("[CrowdControl] Unknown effect code: {}", effectName);
return nullptr;
}
// Assign GameInteractionEffect + values to CC effect.
// Categories are mostly used for checking for conflicting timed effects.
switch (effectStringToEnum[effectName]) {
switch (it->second) {
// Spawn Enemies and Objects
case kEffectSpawnCuccoStorm:
+2 -2
View File
@@ -64,14 +64,14 @@ class CrowdControl : public Network {
std::thread ccThreadProcess;
std::vector<Effect*> activeEffects;
std::vector<std::unique_ptr<Effect>> activeEffects;
std::mutex activeEffectsMutex;
void HandleRemoteData(nlohmann::json payload);
void ProcessActiveEffects();
void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status);
Effect* ParseMessage(nlohmann::json payload);
std::unique_ptr<Effect> ParseMessage(nlohmann::json payload);
EffectResult ExecuteEffect(Effect* effect);
EffectResult CanApplyEffect(Effect* effect);
EffectResult TranslateGiEnum(GameInteractionEffectQueryResult giResult);
+5 -1
View File
@@ -157,5 +157,9 @@ void Network::HandleRemoteJson(std::string payload) {
return;
}
OnIncomingJson(jsonPayload);
try {
OnIncomingJson(jsonPayload);
} catch (const std::exception& e) {
SPDLOG_ERROR("[Network] Exception handling incoming JSON: {}", e.what());
} catch (...) { SPDLOG_ERROR("[Network] Unknown exception handling incoming JSON"); }
}
+47 -49
View File
@@ -2,8 +2,6 @@
#include <libultraship/bridge.h>
#include <libultraship/libultraship.h>
#include <nlohmann/json.hpp>
#include "soh/OTRGlobals.h"
#include "soh/util.h"
#include "soh/ShipUtils.h"
template <class DstType, class SrcType> bool IsType(const SrcType* src) {
@@ -99,12 +97,12 @@ void Sail::OnIncomingJson(nlohmann::json payload) {
return;
}
GameInteractionEffectBase* giEffect = EffectFromJson(payload["effect"]);
auto giEffect = EffectFromJson(payload["effect"]);
if (giEffect) {
GameInteractionEffectQueryResult result;
if (effectType == "remove") {
if (IsType<RemovableGameInteractionEffect>(giEffect)) {
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect)->Remove();
if (IsType<RemovableGameInteractionEffect>(giEffect.get())) {
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect.get())->Remove();
} else {
result = GameInteractionEffectQueryResult::NotPossible;
}
@@ -134,7 +132,7 @@ void Sail::OnIncomingJson(nlohmann::json payload) {
} catch (...) { SPDLOG_ERROR("[Sail] Unknown exception handling remote JSON"); }
}
GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
std::unique_ptr<GameInteractionEffectBase> Sail::EffectFromJson(nlohmann::json payload) {
if (!payload.contains("name")) {
return nullptr;
}
@@ -142,7 +140,7 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
std::string name = payload["name"].get<std::string>();
if (name == "SetSceneFlag") {
auto effect = new GameInteractionEffect::SetSceneFlag();
auto effect = std::make_unique<GameInteractionEffect::SetSceneFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
@@ -150,7 +148,7 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
}
return effect;
} else if (name == "UnsetSceneFlag") {
auto effect = new GameInteractionEffect::UnsetSceneFlag();
auto effect = std::make_unique<GameInteractionEffect::UnsetSceneFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
@@ -158,171 +156,171 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
}
return effect;
} else if (name == "SetFlag") {
auto effect = new GameInteractionEffect::SetFlag();
auto effect = std::make_unique<GameInteractionEffect::SetFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "UnsetFlag") {
auto effect = new GameInteractionEffect::UnsetFlag();
auto effect = std::make_unique<GameInteractionEffect::UnsetFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "ModifyHeartContainers") {
auto effect = new GameInteractionEffect::ModifyHeartContainers();
auto effect = std::make_unique<GameInteractionEffect::ModifyHeartContainers>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "FillMagic") {
return new GameInteractionEffect::FillMagic();
return std::make_unique<GameInteractionEffect::FillMagic>();
} else if (name == "EmptyMagic") {
return new GameInteractionEffect::EmptyMagic();
return std::make_unique<GameInteractionEffect::EmptyMagic>();
} else if (name == "ModifyRupees") {
auto effect = new GameInteractionEffect::ModifyRupees();
auto effect = std::make_unique<GameInteractionEffect::ModifyRupees>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "NoUI") {
return new GameInteractionEffect::NoUI();
return std::make_unique<GameInteractionEffect::NoUI>();
} else if (name == "ModifyGravity") {
auto effect = new GameInteractionEffect::ModifyGravity();
auto effect = std::make_unique<GameInteractionEffect::ModifyGravity>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyHealth") {
auto effect = new GameInteractionEffect::ModifyHealth();
auto effect = std::make_unique<GameInteractionEffect::ModifyHealth>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetPlayerHealth") {
auto effect = new GameInteractionEffect::SetPlayerHealth();
auto effect = std::make_unique<GameInteractionEffect::SetPlayerHealth>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "FreezePlayer") {
return new GameInteractionEffect::FreezePlayer();
return std::make_unique<GameInteractionEffect::FreezePlayer>();
} else if (name == "BurnPlayer") {
return new GameInteractionEffect::BurnPlayer();
return std::make_unique<GameInteractionEffect::BurnPlayer>();
} else if (name == "ElectrocutePlayer") {
return new GameInteractionEffect::ElectrocutePlayer();
return std::make_unique<GameInteractionEffect::ElectrocutePlayer>();
} else if (name == "KnockbackPlayer") {
auto effect = new GameInteractionEffect::KnockbackPlayer();
auto effect = std::make_unique<GameInteractionEffect::KnockbackPlayer>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyLinkSize") {
auto effect = new GameInteractionEffect::ModifyLinkSize();
auto effect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "InvisibleLink") {
return new GameInteractionEffect::InvisibleLink();
return std::make_unique<GameInteractionEffect::InvisibleLink>();
} else if (name == "PacifistMode") {
return new GameInteractionEffect::PacifistMode();
return std::make_unique<GameInteractionEffect::PacifistMode>();
} else if (name == "DisableZTargeting") {
return new GameInteractionEffect::DisableZTargeting();
return std::make_unique<GameInteractionEffect::DisableZTargeting>();
} else if (name == "WeatherRainstorm") {
return new GameInteractionEffect::WeatherRainstorm();
return std::make_unique<GameInteractionEffect::WeatherRainstorm>();
} else if (name == "ReverseControls") {
return new GameInteractionEffect::ReverseControls();
return std::make_unique<GameInteractionEffect::ReverseControls>();
} else if (name == "ForceEquipBoots") {
auto effect = new GameInteractionEffect::ForceEquipBoots();
auto effect = std::make_unique<GameInteractionEffect::ForceEquipBoots>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyMovementSpeedMultiplier") {
auto effect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
auto effect = std::make_unique<GameInteractionEffect::ModifyMovementSpeedMultiplier>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "OneHitKO") {
return new GameInteractionEffect::OneHitKO();
return std::make_unique<GameInteractionEffect::OneHitKO>();
} else if (name == "ModifyDefenseModifier") {
auto effect = new GameInteractionEffect::ModifyDefenseModifier();
auto effect = std::make_unique<GameInteractionEffect::ModifyDefenseModifier>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "GiveOrTakeShield") {
auto effect = new GameInteractionEffect::GiveOrTakeShield();
auto effect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "TeleportPlayer") {
auto effect = new GameInteractionEffect::TeleportPlayer();
auto effect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ClearAssignedButtons") {
auto effect = new GameInteractionEffect::ClearAssignedButtons();
auto effect = std::make_unique<GameInteractionEffect::ClearAssignedButtons>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetTimeOfDay") {
auto effect = new GameInteractionEffect::SetTimeOfDay();
auto effect = std::make_unique<GameInteractionEffect::SetTimeOfDay>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetCollisionViewer") {
return new GameInteractionEffect::SetCollisionViewer();
return std::make_unique<GameInteractionEffect::SetCollisionViewer>();
} else if (name == "RandomizeCosmetics") {
return new GameInteractionEffect::RandomizeCosmetics();
return std::make_unique<GameInteractionEffect::RandomizeCosmetics>();
} else if (name == "PressButton") {
auto effect = new GameInteractionEffect::PressButton();
auto effect = std::make_unique<GameInteractionEffect::PressButton>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "PressRandomButton") {
auto effect = new GameInteractionEffect::PressRandomButton();
auto effect = std::make_unique<GameInteractionEffect::PressRandomButton>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "AddOrTakeAmmo") {
auto effect = new GameInteractionEffect::AddOrTakeAmmo();
auto effect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "RandomBombFuseTimer") {
return new GameInteractionEffect::RandomBombFuseTimer();
return std::make_unique<GameInteractionEffect::RandomBombFuseTimer>();
} else if (name == "DisableLedgeGrabs") {
return new GameInteractionEffect::DisableLedgeGrabs();
return std::make_unique<GameInteractionEffect::DisableLedgeGrabs>();
} else if (name == "RandomWind") {
return new GameInteractionEffect::RandomWind();
return std::make_unique<GameInteractionEffect::RandomWind>();
} else if (name == "RandomBonks") {
return new GameInteractionEffect::RandomBonks();
return std::make_unique<GameInteractionEffect::RandomBonks>();
} else if (name == "PlayerInvincibility") {
return new GameInteractionEffect::PlayerInvincibility();
return std::make_unique<GameInteractionEffect::PlayerInvincibility>();
} else if (name == "SlipperyFloor") {
return new GameInteractionEffect::SlipperyFloor();
return std::make_unique<GameInteractionEffect::SlipperyFloor>();
} else if (name == "SpawnEnemyWithOffset") {
auto effect = new GameInteractionEffect::SpawnEnemyWithOffset();
auto effect = std::make_unique<GameInteractionEffect::SpawnEnemyWithOffset>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "SpawnActor") {
auto effect = new GameInteractionEffect::SpawnActor();
auto effect = std::make_unique<GameInteractionEffect::SpawnActor>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
+3 -1
View File
@@ -2,12 +2,14 @@
#define NETWORK_SAIL_H
#ifdef __cplusplus
#include <memory>
#include "soh/Network/Network.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
class Sail : public Network {
private:
GameInteractionEffectBase* EffectFromJson(nlohmann::json payload);
std::unique_ptr<GameInteractionEffectBase> EffectFromJson(nlohmann::json payload);
void RegisterHooks();
public:
+1
View File
@@ -2454,6 +2454,7 @@ bool SoH_HandleConfigDrop(char* filePath) {
->ClearBindings();
Rando::Settings::GetInstance()->UpdateAllOptions();
SohGui::MarkRandomizerMenusDirty();
gui->SaveConsoleVariablesNextFrame();
ShipInit::Init("*");
+1
View File
@@ -28,6 +28,7 @@ static std::map<int32_t, const char*> languages = {
};
void UpdateMenuTricks();
void UpdateMenuLocations();
void MarkRandomizerMenusDirty();
class SohMenu : public Ship::Menu {
public:
+5
View File
@@ -183,6 +183,11 @@ void DrawLocationsMenu(WidgetInfo& info) {
ImGui::EndDisabled();
}
void MarkRandomizerMenusDirty() {
locationsDirty = true;
tricksDirty = true;
}
void UpdateMenuLocations() {
RandomizerCheckObjects::UpdateImGuiVisibility();
// todo: this efficiently when we build out cvar array support
+9 -6
View File
@@ -2032,8 +2032,10 @@ s32 GiveItemEntryWithoutActor(PlayState* play, GetItemEntry getItemEntry) {
PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON |
PLAYER_STATE1_CLIMBING_LADDER)) &&
Player_GetExplosiveHeld(player) < 0) {
if (((player->heldActor != NULL) && ((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX)) ||
(IS_RANDO && (getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))) ||
if (((player->heldActor != NULL && (getItemEntry.modIndex == MOD_NONE && getItemEntry.getItemId > GI_NONE &&
getItemEntry.getItemId < GI_MAX)) ||
(getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId > RG_NONE &&
getItemEntry.getItemId < RG_MAX)) ||
(!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
if ((getItemEntry.getItemId != GI_NONE)) {
player->getItemEntry = getItemEntry;
@@ -2073,8 +2075,10 @@ s32 GiveItemEntryFromActor(Actor* actor, PlayState* play, GetItemEntry getItemEn
PLAYER_STATE1_CLIMBING_LADDER)) &&
Player_GetExplosiveHeld(player) < 0) {
if ((((player->heldActor != NULL) || (actor == player->talkActor)) &&
((!IS_RANDO && ((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX))) ||
(IS_RANDO && ((getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))))) ||
((getItemEntry.getItemId == MOD_NONE &&
((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX))) ||
(getItemEntry.getItemId == MOD_RANDOMIZER &&
((getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))))) ||
(!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
if ((actor->xzDistToPlayer < xzRange) && (fabsf(actor->yDistToPlayer) < yRange)) {
s16 yawDiff = actor->yawTowardsPlayer - player->actor.shape.rot.y;
@@ -2118,8 +2122,7 @@ s32 Actor_OfferGetItem(Actor* actor, PlayState* play, s32 getItemId, f32 xzRange
PLAYER_STATE1_CLIMBING_LADDER)) &&
Player_GetExplosiveHeld(player) < 0) {
if ((((player->heldActor != NULL) || (actor == player->talkActor)) &&
((!IS_RANDO && ((getItemId > GI_NONE) && (getItemId < GI_MAX))) ||
(IS_RANDO && ((getItemId > RG_NONE) && (getItemId < RG_MAX))))) ||
((getItemId > GI_NONE) && (getItemId < GI_MAX))) ||
(!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
if ((actor->xzDistToPlayer < xzRange) && (fabsf(actor->yDistToPlayer) < yRange)) {
s16 yawDiff = actor->yawTowardsPlayer - player->actor.shape.rot.y;
+16 -11
View File
@@ -3,7 +3,6 @@
#include "objects/gameplay_field_keep/gameplay_field_keep.h"
#include "objects/object_link_boy/object_link_boy.h"
#include "objects/object_link_child/object_link_child.h"
#include "objects/object_triforce_spot/object_triforce_spot.h"
#include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
@@ -1317,12 +1316,14 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx**
if (limbIndex == PLAYER_LIMB_HEAD) {
if (CVarGetInteger(CVAR_COSMETIC("Link.HeadScale.Changed"), 0)) {
f32 scale = CVarGetFloat(CVAR_COSMETIC("Link.HeadScale.Value"), 1.0f);
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
if (scale > 1.2f) {
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * scale), 0.0f, 0.0f, MTXMODE_APPLY);
} else if (scale < 1.0f) {
Matrix_Translate((LINK_IS_ADULT ? 3600.0f : 2900.0f) * ABS(scale - 1.0f), 0.0f, 0.0f,
MTXMODE_APPLY);
if (scale != 1.0f) {
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
if (scale > 1.2f) {
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * scale), 0.0f, 0.0f, MTXMODE_APPLY);
} else if (scale < 1.0f) {
Matrix_Translate((LINK_IS_ADULT ? 3600.0f : 2900.0f) * ABS(scale - 1.0f), 0.0f, 0.0f,
MTXMODE_APPLY);
}
}
}
rot->x += this->headLimbRot.z;
@@ -1331,8 +1332,10 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx**
} else if (limbIndex == PLAYER_LIMB_L_HAND) {
if (CVarGetInteger(CVAR_COSMETIC("Link.SwordScale.Changed"), 0)) {
f32 scale = CVarGetFloat(CVAR_COSMETIC("Link.SwordScale.Value"), 1.0f);
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * scale), 0.0f, 0.0f, MTXMODE_APPLY);
if (scale != 1.0f) {
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * (scale - 1.0f)), 0.0f, 0.0f, MTXMODE_APPLY);
}
}
} else if (limbIndex == PLAYER_LIMB_UPPER) {
if (this->upperLimbYawSecondary != 0) {
@@ -1913,8 +1916,10 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve
}
if (this->actor.scale.y >= 0.0f) {
if (GameInteractor_Should(VB_DRAW_ADDITIONAL_RETICLES, (this->heldItemAction == PLAYER_IA_HOOKSHOT) ||
(this->heldItemAction == PLAYER_IA_LONGSHOT))) {
if (GameInteractor_Should(VB_DRAW_ADDITIONAL_RETICLES,
(this->heldItemAction == PLAYER_IA_HOOKSHOT) ||
(this->heldItemAction == PLAYER_IA_LONGSHOT),
this)) {
Matrix_MultVec3f(&D_80126184, &this->unk_3C8);
if (heldActor != NULL) {
+1 -1
View File
@@ -1183,7 +1183,7 @@ void func_80A99048(EnKo* this, PlayState* play) {
if (ENKO_TYPE == ENKO_TYPE_CHILD_5) {
this->collider.base.ocFlags1 |= 0x40;
}
if (GameInteractor_Should(VB_KOKIRI_GET_FOREST_QUEST_STATE2, false, this)) {
if (GameInteractor_Should(VB_KOKIRI_GET_FOREST_QUEST_STATE2, true, this)) {
this->forestQuestState = EnKo_GetForestQuestState2(this);
}
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, sOsAnimeLookup[ENKO_TYPE][this->forestQuestState]);
@@ -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;
@@ -432,6 +432,8 @@ static FishingEffect sFishingEffects[FISHING_EFFECT_COUNT];
static Vec3f sStreamSoundProjectedPos;
static s16 sFishOnHandParams;
f32 Fishing_GetMinimumRequiredScore();
u8 AllHyruleLoaches() {
return CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) &&
CVarGetInteger(CVAR_ENHANCEMENT("AllHyruleLoaches"), 0);
@@ -904,12 +906,16 @@ void Fishing_Init(Actor* thisx, PlayState* play2) {
if (sLinkAge == LINK_AGE_CHILD) {
if ((HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_CHILD) != 0) {
sFishingRecordLength = HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_CHILD;
} else if (CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0)) {
sFishingRecordLength = Fishing_GetMinimumRequiredScore();
} else {
sFishingRecordLength = 40.0f; // 6 lbs
}
} else {
if ((HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_ADULT) != 0) {
sFishingRecordLength = (HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_ADULT) >> 0x18;
} else if (CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0)) {
sFishingRecordLength = Fishing_GetMinimumRequiredScore();
} else {
sFishingRecordLength = 45.0f; // 7 lbs
}
@@ -7340,8 +7340,9 @@ s32 Player_ActionHandler_2(Player* this, PlayState* play) {
// getting bombchus need to show the cutscene) and whenever the player doesn't have the item yet. In
// rando, we're overruling this because we need to keep showing the cutscene because those items can be
// randomized and thus it's important to keep showing the cutscene.
uint8_t showItemCutscene = play->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY ||
Item_CheckObtainability(giEntry.itemId) == ITEM_NONE || IS_RANDO;
uint8_t showItemCutscene = play->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY || IS_RANDO ||
giEntry.modIndex == MOD_RANDOMIZER ||
Item_CheckObtainability(giEntry.itemId) == ITEM_NONE;
// Only skip cutscenes for drops when they're items/consumables from bushes/rocks/enemies.
uint8_t isDropToSkip =
@@ -7360,8 +7361,8 @@ s32 Player_ActionHandler_2(Player* this, PlayState* play) {
// the player already has because those items could be a randomized item coming from scrubs,
// freestanding PoH's and keys. So we need to once again overrule this specifically for items coming
// from bushes/rocks/enemies when the player has already picked that item up.
uint8_t skipItemCutsceneRando =
IS_RANDO && Item_CheckObtainability(giEntry.itemId) != ITEM_NONE && isDropToSkip;
uint8_t skipItemCutsceneRando = IS_RANDO && giEntry.modIndex == MOD_NONE &&
Item_CheckObtainability(giEntry.itemId) != ITEM_NONE && isDropToSkip;
// Show cutscene when picking up a item.
if (showItemCutscene && !skipItemCutscene && !skipItemCutsceneRando) {