Hookify fish, refactor fishsanity (#7091)

This commit is contained in:
Philip Dubé
2026-08-18 14:36:30 +00:00
committed by GitHub
parent 401605532f
commit 9baa114f6b
50 changed files with 550 additions and 704 deletions
+3 -3
View File
@@ -322,9 +322,9 @@ extern GraphicsContext* __gfxCtx;
#ifdef __cplusplus
#define DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(rsk) \
(OTRGlobals::Instance->gRandoContext->GetOption(rsk).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH) && \
OTRGlobals::Instance->gRandoContext->GetOption(rsk).IsNot(RO_DUNGEON_ITEM_LOC_VANILLA) && \
OTRGlobals::Instance->gRandoContext->GetOption(rsk).IsNot(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON))
(RAND_GET_OPTION(rsk).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH) && \
RAND_GET_OPTION(rsk).IsNot(RO_DUNGEON_ITEM_LOC_VANILLA) && \
RAND_GET_OPTION(rsk).IsNot(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON))
#endif
// #endregion
@@ -3,6 +3,7 @@
#include "soh/ShipUtils.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/Enhancements/enhancementTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "variables.h"
#include "soh/ResourceManagerHelpers.h"
+47 -1
View File
@@ -1,3 +1,5 @@
#include <cmath>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
#include "src/overlays/actors/ovl_Fishing/z_fishing.h"
@@ -8,7 +10,14 @@ extern "C" {
#include <macros.h>
extern PlayState* gPlayState;
extern SaveContext gSaveContext;
f32 Fishing_GetMinimumRequiredScore();
}
// RANDOTODO: update the enhancement sliders to not allow
// values above rando fish weight values when rando'd
static float Fishing_GetMinimumRequiredScore() {
int weight = LINK_IS_CHILD ? CVarGetInteger(CVAR_ENHANCEMENT("MinimumFishWeightChild"), 10)
: CVarGetInteger(CVAR_ENHANCEMENT("MinimumFishWeightAdult"), 13);
return sqrtf((weight - 0.5f) / 0.0036f);
}
void BuildFishingMessage(uint16_t* textId, bool* loadFromMessageTable) {
@@ -29,6 +38,37 @@ void RegisterFishingMessages() {
});
}
void RegisterCustomizeFishing() {
bool customized = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0);
COND_VB_SHOULD(VB_FISHING_USE_DEFAULT_RECORD_LENGTH, customized, {
f32* recordLength = va_arg(args, f32*);
*recordLength = Fishing_GetMinimumRequiredScore();
*should = false;
});
COND_VB_SHOULD(VB_FISHING_SPAWN_LOACHES, (customized && CVarGetInteger(CVAR_ENHANCEMENT("LoachesAlwaysAppear"), 0)),
{ *should = true; });
COND_VB_SHOULD(VB_FISHING_FISH_IS_LOACH, (customized && CVarGetInteger(CVAR_ENHANCEMENT("AllHyruleLoaches"), 0)),
{ *should = true; });
COND_VB_SHOULD(VB_FISHING_FISH_BITE, (customized && CVarGetInteger(CVAR_ENHANCEMENT("GuaranteeFishingBite"), 0)),
{ *should = true; });
COND_VB_SHOULD(VB_FISHING_FISH_ESCAPE, (customized && CVarGetInteger(CVAR_ENHANCEMENT("FishNeverEscape"), 0)),
{ *should = false; });
COND_VB_SHOULD(VB_FISHING_CATCH_FISH, (customized && CVarGetInteger(CVAR_ENHANCEMENT("InstantFishing"), 0)),
{ *should = true; });
COND_VB_SHOULD(VB_FISHING_INSTANT_CATCH, (customized && CVarGetInteger(CVAR_ENHANCEMENT("InstantFishing"), 0)),
{ *should = true; });
COND_VB_SHOULD(VB_FISHING_CONFIRM_KEEPING_SMALLER_FISH,
(customized && CVarGetInteger(CVAR_ENHANCEMENT("SkipKeepConfirmation"), 0)), { *should = false; });
}
void RegisterHoverFishing() {
COND_VB_SHOULD(VB_NOT_CAST_FISHING, (CVarGetInteger(CVAR_ENHANCEMENT("HoverFishing"), false)), {
Vec3f* rodCheckPos = va_arg(args, Vec3f*);
@@ -68,6 +108,12 @@ void RegisterAllowFishingBlankB() {
}
static RegisterShipInitFunc initFunc(RegisterFishingMessages, { CVAR_ENHANCEMENT("CustomizeFishing"), "IS_RANDO" });
static RegisterShipInitFunc
initCustomizeFishing(RegisterCustomizeFishing,
{ CVAR_ENHANCEMENT("CustomizeFishing"), CVAR_ENHANCEMENT("LoachesAlwaysAppear"),
CVAR_ENHANCEMENT("AllHyruleLoaches"), CVAR_ENHANCEMENT("GuaranteeFishingBite"),
CVAR_ENHANCEMENT("FishNeverEscape"), CVAR_ENHANCEMENT("InstantFishing"),
CVAR_ENHANCEMENT("SkipKeepConfirmation") });
static RegisterShipInitFunc initHoverFishing(RegisterHoverFishing, { CVAR_ENHANCEMENT("HoverFishing") });
static RegisterShipInitFunc initAllowFishingBlankB(RegisterAllowFishingBlankB,
{ CVAR_ENHANCEMENT("FishingBlankB"), "IS_RANDO" });
@@ -1,5 +1,8 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#include "soh/ShipInit.hpp"
#include "soh/cvar_prefixes.h"
extern "C" {
#include <variables.h>
@@ -1,5 +1,9 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/ShipInit.hpp"
#include "soh/cvar_prefixes.h"
extern "C" {
#include "variables.h"
@@ -1,4 +1,4 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -1,9 +1,8 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
#include <variables.h>
}
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#include "soh/ShipInit.hpp"
#include "soh/cvar_prefixes.h"
// TODO: Port the rest of the behavior for this enhancement here.
@@ -23,6 +22,9 @@ void BuildQuitFishingMessage(uint16_t* textId, bool* loadFromMessageTable) {
void QuitFishingAtDoor_Register() {
COND_ID_HOOK(OnOpenText, TEXT_FISHERMAN_LEAVE, CVarGetInteger(CVAR_ENHANCEMENT("QuitFishingAtDoor"), 0),
BuildQuitFishingMessage);
COND_VB_SHOULD(VB_FISHING_QUIT_AT_DOOR, CVarGetInteger(CVAR_ENHANCEMENT("QuitFishingAtDoor"), 0),
{ *should = true; });
}
static RegisterShipInitFunc initFunc(QuitFishingAtDoor_Register, { CVAR_ENHANCEMENT("QuitFishingAtDoor") });
@@ -757,6 +757,97 @@ typedef enum {
// - `*EnElf`
VB_FAIRY_HEAL,
// #### `result`
// ```c
// this->actor.xzDistToPlayer < (KREG(59) + 50.0f)
// ```
// Whether a hooked fish is reeled in close enough to be caught.
// #### `args`
// - None
VB_FISHING_CATCH_FISH,
// #### `result`
// ```c
// (this->isLoach == 0) && (sFishOnHandIsLoach == 0) && ((s16)this->fishLength < (s16)sFishOnHandLength)
// ```
// Whether keeping a fish smaller than the one held asks for confirmation.
// #### `args`
// - None
VB_FISHING_CONFIRM_KEEPING_SMALLER_FISH,
// #### `result`
// ```c
// ((this->timerArray[0] == 1) || (Rand_ZeroOne() < chance)) &&
// ((Rand_ZeroOne() < (this->perception * multiplier)) || ((this->isLoach + 1) == KREG(69)))
// ```
// Whether a fish bites the lure.
// #### `args`
// - None
VB_FISHING_FISH_BITE,
// #### `result`
// ```c
// ((sLureTimer & 0x7F) == 0) && (Rand_ZeroOne() < 0.05f) && (sLureEquipped != FS_LURE_SINKING) && (KREG(69) == 0)
// ```
// Whether a hooked fish randomly escapes the line.
// #### `args`
// - None
VB_FISHING_FISH_ESCAPE,
// #### `result`
// ```c
// sFishInits[thisx->params - EN_FISH_PARAM].isLoach
// ```
// Whether this pond fish is a loach.
// #### `args`
// - None
VB_FISHING_FISH_IS_LOACH,
// #### `result`
// ```c
// false
// ```
// Whether a caught fish snaps to Link's hand instead of drifting there.
// #### `args`
// - None
VB_FISHING_INSTANT_CATCH,
// #### `result`
// ```c
// false
// ```
// Whether the pond owner offers to quit fishing at the door.
// #### `args`
// - None
VB_FISHING_QUIT_AT_DOOR,
// #### `result`
// ```c
// (KREG(1) == 1) || ((sFishGameNumber & 3) == 3)
// ```
// Whether loaches spawn in the pond this game.
// #### `args`
// - None
VB_FISHING_SPAWN_LOACHES,
// #### `result`
// ```c
// true
// ```
// Whether the starting fishing record uses the vanilla length.
// A hook returning false should write its own length through the arg.
// #### `args`
// - `*f32` (sFishingRecordLength)
VB_FISHING_USE_DEFAULT_RECORD_LENGTH,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - None
VB_FISHING_ZERO_XZ,
// #### `result`
// True if the next text position must be beyond the current position; false otherwise
// #### `args`
@@ -2653,14 +2744,6 @@ typedef enum {
// - `*Vec3f`
VB_NOT_CAST_FISHING,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - None
VB_FISHING_ZERO_XZ,
// #### `result`
// ```c
// false
@@ -1,6 +1,7 @@
#include "playthrough.hpp"
#include <spdlog/spdlog.h>
#include <libultraship/bridge/consolevariablebridge.h>
#include "playthrough.hpp"
#include "fill.hpp"
#include "../location_access.h"
#include "../rng.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
+4 -4
View File
@@ -1,4 +1,4 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
#include "src/overlays/actors/ovl_En_GirlA/z_en_girla.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
@@ -21,7 +21,7 @@ void RegisterVBOverrides() {
}
int8_t capacity = 50;
if (bombchuBag.Is(RO_BOMBCHU_BAG_PROGRESSIVE)) {
capacity = OTRGlobals::Instance->gRandoContext->GetBombchuCapacity();
capacity = Rando::Context::GetInstance()->GetBombchuCapacity();
}
if (AMMO(ITEM_BOMBCHU) >= capacity) {
*canBuy = CANBUY_RESULT_CANT_GET_NOW;
@@ -31,7 +31,7 @@ void RegisterVBOverrides() {
COND_VB_SHOULD(VB_CHECK_BOMBCHU_CAPACITY, shouldRegister, {
*should = false;
uint8_t capacity = OTRGlobals::Instance->gRandoContext->GetBombchuCapacity();
uint8_t capacity = Rando::Context::GetInstance()->GetBombchuCapacity();
if (AMMO(ITEM_BOMBCHU) > capacity) {
AMMO(ITEM_BOMBCHU) = capacity;
}
@@ -40,7 +40,7 @@ void RegisterVBOverrides() {
COND_VB_SHOULD(VB_COLOR_AMMO_GREEN, shouldRegister, {
int16_t i = va_arg(args, int);
if (i == ITEM_BOMBCHU) {
uint8_t capacity = OTRGlobals::Instance->gRandoContext->GetBombchuCapacity();
uint8_t capacity = Rando::Context::GetInstance()->GetBombchuCapacity();
if (AMMO(i) == capacity) {
*should = true;
}
@@ -1,9 +1,9 @@
#include "soh/ResourceManagerHelpers.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include "soh/ShipInit.hpp"
#include "z64save.h"
#include "objects/object_gi_compass/object_gi_compass.h"
#include "objects/object_gi_map/object_gi_map.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
extern "C" {
@@ -1,4 +1,4 @@
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/randomizer/SeedContext.h"
@@ -1,7 +1,7 @@
#include "soh/Enhancements/randomizer/entrance.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer_entrance_tracker.h"
#include "soh/Enhancements/randomizer/randomizer.h"
#include <soh/OTRGlobals.h>
extern "C" {
#include <variables.h>
@@ -12,12 +12,12 @@ extern PlayState* gPlayState;
// clang-format attempts to format this strangely for some reason, so temporarily turning it off
#define ENTRANCES_SHUFFLED \
IS_RANDO && \
OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_ENTRANCES).Is(RO_GENERIC_ON) && \
Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_ENTRANCES).Is(RO_GENERIC_ON) && \
(CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("EntrancesOnSigns"), 0) == 1)
// clang-format on
void BuildEntranceHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
auto ctx = OTRGlobals::Instance->gRandoContext;
auto ctx = Rando::Context::GetInstance();
s16 entrance = -1;
switch (*textId) {
case TEXT_WATERFALL:
@@ -2,7 +2,7 @@
* Handles the randomized silly messages that the Fire Temple
* trapped Gorons have when you free them.
*/
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -7,6 +7,7 @@
* and Carpet Salesman)
*/
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "soh/Enhancements/randomizer/randomizer.h"
@@ -5,7 +5,7 @@
* text changes that don't really fit anywhere else.
*/
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -2,7 +2,7 @@
* This file is responsible for the messages
* for the Rando-Relevant Navi Hints enhancement.
*/
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -1,7 +1,7 @@
/**
* This file is for handling the Randomize Rupee Names enhancement
*/
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -38,7 +38,6 @@ Context::Context() {
mDungeons = std::make_shared<Dungeons>();
mLogic = std::make_shared<Logic>();
mTrials = std::make_shared<Trials>();
mFishsanity = std::make_shared<Fishsanity>();
}
RandomizerArea Context::GetAreaFromString(std::string str) {
@@ -185,7 +184,7 @@ void Context::GenerateLocationPool() {
(location.GetRCType() == RCTYPE_COW && mOptions[RSK_SHUFFLE_COWS].Is(RO_GENERIC_OFF)) ||
(location.GetRandomizerCheck() == RC_LH_HYRULE_LOACH &&
mOptions[RSK_FISHSANITY].IsNot(RO_FISHSANITY_HYRULE_LOACH)) ||
(location.GetRCType() == RCTYPE_FISH && !mFishsanity->GetFishLocationIncluded(&location)) ||
(location.GetRCType() == RCTYPE_FISH && !Fishsanity::GetFishLocationIncluded(&location)) ||
(location.GetRCType() == RCTYPE_POT && mOptions[RSK_SHUFFLE_POTS].Is(RO_SHUFFLE_POTS_OFF)) ||
(location.GetRCType() == RCTYPE_GRASS && mOptions[RSK_SHUFFLE_GRASS].Is(RO_SHUFFLE_GRASS_OFF)) ||
(location.GetRCType() == RCTYPE_CRATE && mOptions[RSK_SHUFFLE_CRATES].Is(RO_SHUFFLE_CRATES_OFF)) ||
@@ -496,10 +495,6 @@ std::shared_ptr<Dungeons> Context::GetDungeons() {
return mDungeons;
}
std::shared_ptr<Fishsanity> Context::GetFishsanity() {
return mFishsanity;
}
DungeonInfo* Context::GetDungeon(size_t key) const {
return mDungeons->GetDungeon(static_cast<DungeonKey>(key));
}
@@ -3,7 +3,6 @@
#include "randomizerTypes.h"
#include "item_override.h"
#include "hint.h"
#include "fishsanity.h"
#include "trial.h"
#include <memory>
@@ -86,7 +85,6 @@ class Context {
const std::set<RandomizerTrick>& enabledTricks);
std::shared_ptr<EntranceShuffler> GetEntranceShuffler();
std::shared_ptr<Dungeons> GetDungeons();
std::shared_ptr<Fishsanity> GetFishsanity();
DungeonInfo* GetDungeon(size_t key) const;
DungeonInfo* GetDungeonFromScene(SceneID key) const;
std::shared_ptr<Logic> GetLogic();
@@ -195,7 +193,6 @@ class Context {
std::shared_ptr<Dungeons> mDungeons;
std::shared_ptr<Logic> mLogic;
std::shared_ptr<Trials> mTrials;
std::shared_ptr<Fishsanity> mFishsanity;
std::shared_ptr<Kaleido> mKaleido;
bool mSeedGenerated = false;
bool mSpoilerLoaded = false;
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "static_data.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "soh/Enhancements/randomizer/randomizer.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/Enhancements/randomizer/RCToRandInf.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "static_data.h"
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/Enhancements/randomizer/RCToRandInf.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh_assets.h"
#include "static_data.h"
#include <libultraship/libultra.h>
@@ -1,4 +1,5 @@
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "randomizer_grotto.h"
#include "draw.h"
#include "static_data.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh_assets.h"
#include "static_data.h"
#include "item_category_adj.h"
@@ -1,4 +1,5 @@
#include "soh/ObjectExtension/ObjectExtension.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "item_category_adj.h"
#include "particle_cmc.h"
#include "soh/frame_interpolation.h"
@@ -1,4 +1,5 @@
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh_assets.h"
#include "static_data.h"
#include "item_category_adj.h"
@@ -1,4 +1,5 @@
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "item_category_adj.h"
#include "particle_cmc.h"
@@ -1,4 +1,5 @@
#include "ShuffleRocks.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "static_data.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "item_category_adj.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "item_category_adj.h"
#include "particle_cmc.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "dungeon.h"
#include "SeedContext.h"
#include "draw.h"
@@ -1,9 +1,9 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
extern "C" {
#include "z64.h"
#include "functions.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "overlays/actors/ovl_En_Ossan/z_en_ossan.h"
extern "C" {
extern PlayState* gPlayState;
}
@@ -1,4 +1,4 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/randomizer.h"
extern "C" {
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh_assets.h"
#include "static_data.h"
#include "soh/ObjectExtension/ObjectExtension.h"
@@ -1,4 +1,5 @@
#include <soh/OTRGlobals.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include <soh/GameVersions.h>
#include "soh/ResourceManagerHelpers.h"
#include "soh/ObjectExtension/ObjectExtension.h"
@@ -1,4 +1,5 @@
#include "Traps.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/Enhancements/randomizer/static_data.h"
#include "soh/ShipUtils.h"
@@ -1,4 +1,5 @@
#include "entrance.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "3drando/fill.hpp"
#include "3drando/pool_functions.hpp"
+302 -375
View File
@@ -1,7 +1,8 @@
#include "3drando/pool_functions.hpp"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "../../OTRGlobals.h"
#include "fishsanity.h"
#include "draw.h"
#include "soh/frame_interpolation.h"
#include "variables.h"
#include "functions.h"
#include "macros.h"
@@ -50,160 +51,35 @@ std::unordered_map<int8_t, RandomizerCheck> Rando::StaticData::randomizerGrottoF
{ 0x29, RC_ZR_OPEN_GROTTO_FISH }
};
ActorFunc drawFishing = NULL;
ActorFunc drawEnFish = NULL;
Color_RGB8 fsPulseColor = { 30, 240, 200 };
typedef struct {
u8 mode;
u8 numFish;
bool ageSplit;
} FishsanityPondOptions;
typedef enum {
FSC_NONE,
FSC_POND,
FSC_GROTTO,
FSC_ZD,
} FishsanityCheckType;
static const CheckIdentity defaultIdentity = { RAND_INF_MAX, RC_UNKNOWN_CHECK };
static CheckIdentity NextChildPondFish = defaultIdentity;
static CheckIdentity NextAdultPondFish = defaultIdentity;
static ActorFunc drawFishing = NULL;
static ActorFunc drawEnFish = NULL;
static Color_RGB8 fsPulseColor = { 30, 240, 200 };
static s16 fishGroupCounter = 0;
static bool enableAdvance = false;
static CheckIdentity IdentifyFish(s32 sceneNum, s32 actorParams) {
CheckIdentity fishIdentity;
fishIdentity.randomizerInf = RAND_INF_MAX;
fishIdentity.randomizerCheck = RC_UNKNOWN_CHECK;
// Fishsanity will determine what the identity of the fish should be
if (sceneNum == SCENE_FISHING_POND) {
return OTRGlobals::Instance->gRandoContext->GetFishsanity()->IdentifyPondFish(actorParams);
}
Rando::Location* location =
OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor(ACTOR_EN_FISH, sceneNum, actorParams);
if (location->GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
fishIdentity.randomizerInf = rcToRandomizerInf[location->GetRandomizerCheck()];
fishIdentity.randomizerCheck = location->GetRandomizerCheck();
}
return fishIdentity;
}
namespace Rando {
const CheckIdentity Fishsanity::defaultIdentity = { RAND_INF_MAX, RC_UNKNOWN_CHECK };
bool Fishsanity::fishsanityHelpersInit = false;
std::unordered_map<RandomizerCheck, LinkAge> Fishsanity::pondFishAgeMap;
std::vector<RandomizerCheck> Fishsanity::childPondFish;
std::vector<RandomizerCheck> Fishsanity::adultPondFish;
Fishsanity::Fishsanity() {
InitializeHelpers();
}
Fishsanity::~Fishsanity() {
}
bool Fishsanity::GetFishLocationIncluded(Rando::Location* loc, FishsanityOptionsSource optionsSource) {
auto [mode, numFish, ageSplit] = GetOptions(optionsSource);
if (loc->GetRCType() != RCTYPE_FISH || mode == RO_FISHSANITY_OFF || mode == RO_FISHSANITY_HYRULE_LOACH) {
return false;
}
RandomizerCheck rc = loc->GetRandomizerCheck();
// Are pond fish enabled, and is this a pond fish location?
if (mode != RO_FISHSANITY_OVERWORLD && numFish > 0 && loc->GetScene() == SCENE_FISHING_POND &&
loc->GetActorID() == ACTOR_FISHING) {
// Is this a child fish location? If so, is it within the defined number of pond fish checks?
if (rc >= RC_LH_CHILD_FISH_1 && rc <= RC_LH_CHILD_LOACH_2 && numFish > (loc->GetActorParams() - 100)) {
return true;
}
// Are adult fish available, and is this an adult fish location? If so, is it within the defined number of pond
// fish checks?
if (ageSplit && rc >= RC_LH_ADULT_FISH_1 && rc <= RC_LH_ADULT_LOACH &&
numFish > (loc->GetActorParams() - 100)) {
return true;
}
}
// Are overworld fish enabled, and is this an overworld fish location?
if (mode != RO_FISHSANITY_POND && (loc->GetScene() == SCENE_GROTTOS || loc->GetScene() == SCENE_ZORAS_DOMAIN) &&
loc->GetActorID() == ACTOR_EN_FISH && (loc->GetActorParams() >> 8)) {
return true;
}
// Must not be an included fish location!
return false;
}
std::pair<std::vector<RandomizerCheck>, std::vector<RandomizerCheck>>
Fishsanity::GetFishingPondLocations(FishsanityOptionsSource optionsSource) {
auto [mode, numFish, ageSplit] = GetOptions(optionsSource);
std::vector<RandomizerCheck> activeFish;
std::vector<RandomizerCheck> remainingFish;
std::vector<RandomizerCheck> pondFish = Rando::StaticData::GetPondFishLocations();
// Fishsanity_InitializeHelpers();
remainingFish.insert(remainingFish.end(), pondFish.begin(), pondFish.end());
// No pond fish shuffled
if (numFish == 0) {
return std::make_pair(activeFish, remainingFish);
}
// Every pond fish is shuffled, so we can save some time
if (numFish > 16) {
// Child and adult pond fish are both shuffled, set activeFish to remainingFish and return an empty vector for
// inactive fish.
if (ageSplit) {
return std::make_pair(remainingFish, activeFish);
}
// Activate all child fish only
activeFish = FilterAndEraseFromPool(
remainingFish, [](const RandomizerCheck loc) { return pondFishAgeMap[loc] == LINK_AGE_CHILD; });
return std::make_pair(activeFish, remainingFish);
}
// Only some pond fish are shuffled, so we have to only activate the requested number.
activeFish.insert(activeFish.end(), childPondFish.begin(), childPondFish.begin() + numFish);
// If pond is split, also add the requested number of adult fish.
if (ageSplit) {
activeFish.insert(activeFish.end(), adultPondFish.begin(),
adultPondFish.begin() + std::min<uint8_t>(numFish, 16));
}
// NOTE: This only works because we can assume activeFish is already sorted; changes that break this assumption will
// also break this
std::erase_if(remainingFish,
[&](RandomizerCheck loc) { return std::binary_search(activeFish.begin(), activeFish.end(), loc); });
return std::make_pair(activeFish, remainingFish);
}
std::pair<std::vector<RandomizerCheck>, std::vector<RandomizerCheck>>
Fishsanity::GetFishsanityLocations(FishsanityOptionsSource optionsSource) {
auto [mode, numFish, ageSplit] = GetOptions(optionsSource);
std::vector<RandomizerCheck> activeFish;
std::vector<RandomizerCheck> remainingFish;
// Add pond fish
if (mode == RO_FISHSANITY_POND || mode == RO_FISHSANITY_BOTH) {
auto pondLocations = GetFishingPondLocations(optionsSource);
activeFish.insert(activeFish.end(), pondLocations.first.begin(), pondLocations.first.end());
remainingFish.insert(remainingFish.end(), pondLocations.second.begin(), pondLocations.second.end());
}
// Add overworld fish
if (mode == RO_FISHSANITY_OVERWORLD || mode == RO_FISHSANITY_BOTH) {
std::vector<RandomizerCheck> overworldFish = Rando::StaticData::GetOverworldFishLocations();
activeFish.insert(activeFish.end(), overworldFish.begin(), overworldFish.end());
}
return std::make_pair(activeFish, remainingFish);
}
CheckIdentity Fishsanity::IdentifyPondFish(u8 fishParams) {
auto [mode, pondCount, ageSplit] = GetOptions();
CheckIdentity identity = defaultIdentity;
if (!GetPondFishShuffled()) {
return identity;
}
if (pondCount > 16) {
identity = GetPondFish(fishParams, IsAdultPond());
} else {
identity = LINK_IS_ADULT ? mCurrPondFish.second : mCurrPondFish.first;
}
return identity;
}
FishsanityPondOptions Fishsanity::GetOptions(FishsanityOptionsSource optionsSource) {
/**
* @brief Get fishsanity fishing pond options from the requested source
*/
static FishsanityPondOptions GetOptions(FishsanityOptionsSource optionsSource = FSO_SOURCE_RANDO) {
FishsanityPondOptions options{};
switch (optionsSource) {
// Used in check tracker
@@ -222,13 +98,101 @@ FishsanityPondOptions Fishsanity::GetOptions(FishsanityOptionsSource optionsSour
return options;
}
void Fishsanity::UpdateCurrentPondFish() {
auto [mode, pondCount, ageSplit] = GetOptions();
mCurrPondFish = std::pair<CheckIdentity, CheckIdentity>();
mCurrPondFish.first = defaultIdentity;
mCurrPondFish.second = defaultIdentity;
/**
* @brief Returns true if the fishing pond is shuffled
*/
static bool GetPondFishShuffled() {
u8 fsMode = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY);
return OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY_POND_COUNT) > 0 &&
(fsMode == RO_FISHSANITY_POND || fsMode == RO_FISHSANITY_BOTH);
}
// Initialize mCurrPondFish if we're shuffling pond fish, but if all fish are shuffled, we don't need to use this.
/**
* @brief Returns true if overworld fish are shuffled
*/
static bool GetOverworldFishShuffled() {
u8 fsMode = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY);
return fsMode == RO_FISHSANITY_OVERWORLD || fsMode == RO_FISHSANITY_BOTH;
}
/**
* @brief Returns true if the fishing pond is currently adult (i.e., age split is enabled and Link is adult)
*/
static bool IsAdultPond() {
return LINK_IS_ADULT && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY_AGE_SPLIT);
}
/**
* @brief Gets the type of a fishsanity check
*/
static FishsanityCheckType GetCheckType(RandomizerCheck rc) {
// if it's not RCTYPE_FISH, obviously it's not a fish
if (Rando::StaticData::GetLocation(rc)->GetRCType() != RCTYPE_FISH) {
return FSC_NONE;
}
auto loc = Rando::StaticData::GetLocation(rc);
switch (loc->GetScene()) {
case SCENE_FISHING_POND:
return FSC_POND;
case SCENE_ZORAS_DOMAIN:
return FSC_ZD;
case SCENE_GROTTOS:
return FSC_GROTTO;
default:
return FSC_NONE;
}
}
/**
* @brief Returns true if the given CheckIdentity represents an actual fish
*/
static bool IsFish(CheckIdentity* fish) {
if (fish->randomizerCheck == RC_UNKNOWN_CHECK || fish->randomizerInf == RAND_INF_MAX) {
return false;
}
return GetCheckType(fish->randomizerCheck) != FSC_NONE;
}
/**
* @brief Resolves a pond fish's CheckIdentity directly from params & pond age
*/
static CheckIdentity GetPondFish(s16 params, bool adultPond) {
auto pair = Rando::StaticData::randomizerFishingPondFish[params - 100];
RandomizerCheck rc = adultPond ? pair.second : pair.first;
return { OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(rc), rc };
}
/**
* @brief Returns the identity for a caught pond fish given its params
*/
static CheckIdentity IdentifyPondFish(u8 fishParams) {
auto [mode, pondCount, ageSplit] = GetOptions();
CheckIdentity identity = defaultIdentity;
if (!GetPondFishShuffled()) {
return identity;
}
if (pondCount > 16) {
identity = GetPondFish(fishParams, IsAdultPond());
} else {
identity = LINK_IS_ADULT ? NextAdultPondFish : NextChildPondFish;
}
return identity;
}
/**
* @brief Updates current pond fish according to save data
*/
static void UpdateCurrentPondFish() {
auto [mode, pondCount, ageSplit] = GetOptions();
NextChildPondFish = defaultIdentity;
NextAdultPondFish = defaultIdentity;
// Initialize next pond fish if we're shuffling pond fish, but if all fish are shuffled, we don't need to use this.
if ((mode == RO_FISHSANITY_BOTH || mode == RO_FISHSANITY_POND) && pondCount < 17) {
// find the first inf that isn't set yet for each age
// but don't go past the max number
@@ -239,47 +203,31 @@ void Fishsanity::UpdateCurrentPondFish() {
OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(tableEntry.first)) ||
i == pondCount - 1) {
// Found first child check
if (!IsFish(&mCurrPondFish.first)) {
mCurrPondFish.first = GetPondFish(params, false);
if (!IsFish(&NextChildPondFish)) {
NextChildPondFish = GetPondFish(params, false);
}
if (!ageSplit && !IsFish(&mCurrPondFish.second)) {
mCurrPondFish.second = GetPondFish(params, false);
if (!ageSplit && !IsFish(&NextAdultPondFish)) {
NextAdultPondFish = GetPondFish(params, false);
// both ages are resolved! we can quit here
break;
}
}
if (ageSplit && !IsFish(&mCurrPondFish.second) && tableEntry.second != RC_UNKNOWN_CHECK &&
if (ageSplit && !IsFish(&NextAdultPondFish) && tableEntry.second != RC_UNKNOWN_CHECK &&
(!Flags_GetRandomizerInf(
OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(tableEntry.second)) ||
i == pondCount - 1)) {
mCurrPondFish.second = GetPondFish(params, true);
NextAdultPondFish = GetPondFish(params, true);
}
}
}
}
void Fishsanity::InitializeFromSave() {
UpdateCurrentPondFish();
}
bool Fishsanity::GetPondFishShuffled() {
u8 fsMode = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY);
return OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY_POND_COUNT) > 0 &&
(fsMode == RO_FISHSANITY_POND || fsMode == RO_FISHSANITY_BOTH);
}
bool Fishsanity::GetOverworldFishShuffled() {
u8 fsMode = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY);
return fsMode == RO_FISHSANITY_OVERWORLD || fsMode == RO_FISHSANITY_BOTH;
}
bool Fishsanity::IsAdultPond() {
return LINK_IS_ADULT && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY_AGE_SPLIT);
}
bool Fishsanity::GetPondCleared() {
/**
* @brief Returns true if all available pond fish checks have been collected for the current age
*/
static bool GetPondCleared() {
auto [mode, pondCount, ageSplit] = GetOptions();
// no fish shuffled, so pond is always cleared :thumbsup:
if (pondCount == 0) {
@@ -307,187 +255,45 @@ bool Fishsanity::GetPondCleared() {
return true;
}
bool Fishsanity::GetDomainCleared() {
for (RandomizerInf i = RAND_INF_ZD_FISH_1; i <= RAND_INF_ZD_FISH_5; i = (RandomizerInf)(i + 1)) {
if (!Flags_GetRandomizerInf(i)) {
return false;
}
static CheckIdentity IdentifyFish(s32 sceneNum, s32 actorParams) {
CheckIdentity fishIdentity;
fishIdentity.randomizerInf = RAND_INF_MAX;
fishIdentity.randomizerCheck = RC_UNKNOWN_CHECK;
// Fishsanity will determine what the identity of the fish should be
if (sceneNum == SCENE_FISHING_POND) {
return IdentifyPondFish(actorParams);
}
return true;
Rando::Location* location =
OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor(ACTOR_EN_FISH, sceneNum, actorParams);
if (location->GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
fishIdentity.randomizerInf = rcToRandomizerInf[location->GetRandomizerCheck()];
fishIdentity.randomizerCheck = location->GetRandomizerCheck();
}
return fishIdentity;
}
void Fishsanity::InitializeHelpers() {
if (fishsanityHelpersInit) {
return;
}
for (auto pair : Rando::StaticData::randomizerFishingPondFish) {
pondFishAgeMap[pair.first] = LINK_AGE_CHILD;
pondFishAgeMap[pair.second] = LINK_AGE_ADULT;
childPondFish.push_back(pair.first);
adultPondFish.push_back(pair.second);
}
static void Fishsanity_OpenGreyscaleColor(PlayState* play, Color_RGB8* color, int16_t frameOffset) {
OPEN_DISPS(play->state.gfxCtx);
gDPSetGrayscaleColor(POLY_OPA_DISP++, color->r, color->g, color->b,
// Make color pulse, offset a bit by the actor params
ABS(255.0f * Math_CosS((play->gameplayFrames + frameOffset) * 1000)));
gSPGrayscale(POLY_OPA_DISP++, true);
CLOSE_DISPS(play->state.gfxCtx);
}
CheckIdentity Fishsanity::GetPondFish(s16 params, bool adultPond) {
auto pair = Rando::StaticData::randomizerFishingPondFish[params - 100];
RandomizerCheck rc = adultPond ? pair.second : pair.first;
return { OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(rc), rc };
static void Fishsanity_CloseGreyscaleColor(PlayState* play) {
OPEN_DISPS(play->state.gfxCtx);
gSPGrayscale(POLY_OPA_DISP++, false);
CLOSE_DISPS(play->state.gfxCtx);
}
CheckIdentity Fishsanity::AdvancePond() {
auto [mode, pondCount, ageSplit] = GetOptions();
// No need to update state with full pond shuffle
if (pondCount > 16) {
return defaultIdentity;
}
UpdateCurrentPondFish();
return IsAdultPond() ? mCurrPondFish.second : mCurrPondFish.first;
}
FishsanityCheckType Fishsanity::GetCheckType(RandomizerCheck rc) {
// if it's not RCTYPE_FISH, obviously it's not a fish
if (Rando::StaticData::GetLocation(rc)->GetRCType() != RCTYPE_FISH) {
return FSC_NONE;
}
auto loc = Rando::StaticData::GetLocation(rc);
switch (loc->GetScene()) {
case SCENE_FISHING_POND:
return FSC_POND;
case SCENE_ZORAS_DOMAIN:
return FSC_ZD;
case SCENE_GROTTOS:
return FSC_GROTTO;
default:
return FSC_NONE;
}
}
bool Fishsanity::IsFish(CheckIdentity* fish) {
if (fish->randomizerCheck == RC_UNKNOWN_CHECK || fish->randomizerInf == RAND_INF_MAX) {
return false;
}
return GetCheckType(fish->randomizerCheck) != FSC_NONE;
}
void Fishsanity::OnActorInitHandler(void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
auto fs = OTRGlobals::Instance->gRandoContext->GetFishsanity();
CheckIdentity fish;
if (actor->id == ACTOR_EN_FISH && fs->GetOverworldFishShuffled()) {
// Set fish ID for ZD fish
if (gPlayState->sceneNum == SCENE_ZORAS_DOMAIN && actor->params == -1) {
actor->params ^= fishGroupCounter++;
} else if (gPlayState->sceneNum == SCENE_GROTTOS && actor->params == 1) {
actor->params = 0x100 | gSaveContext.respawn[RESPAWN_MODE_RETURN].data;
}
fish = IdentifyFish(gPlayState->sceneNum, actor->params);
// Render fish as randomized item
if (Rando::Fishsanity::IsFish(&fish) && !Flags_GetRandomizerInf(fish.randomizerInf)) {
if (!drawEnFish) {
drawEnFish = actor->draw;
}
actor->draw = Fishsanity_DrawEnFish;
}
return;
}
if (actor->id == ACTOR_FISHING && gPlayState->sceneNum == SCENE_FISHING_POND && actor->params >= 100 &&
actor->params <= 117 && fs->GetPondFishShuffled()) {
// Initialize pond fish for fishsanity
// Initialize fishsanity metadata on this actor
Fishing* fishActor = static_cast<Fishing*>(refActor);
// fishActor->fishsanityParams = actor->params;
fish = IdentifyFish(gPlayState->sceneNum, actor->params);
// With every pond fish shuffled, caught fish will not spawn unless all fish have been caught.
if (RAND_GET_OPTION(RSK_FISHSANITY_POND_COUNT).Get() > 16 && !fs->GetPondCleared()) {
// Create effect for uncaught fish
if (!Flags_GetRandomizerInf(fish.randomizerInf)) {
actor->shape.shadowDraw = Fishsanity_DrawEffShadow;
if (!drawFishing) {
drawFishing = actor->draw;
}
actor->draw = Fishsanity_DrawFishing;
}
}
}
}
void Fishsanity::OnActorUpdateHandler(void* refActor) {
if (gPlayState->sceneNum != SCENE_GROTTOS && gPlayState->sceneNum != SCENE_ZORAS_DOMAIN &&
gPlayState->sceneNum != SCENE_FISHING_POND) {
return;
}
Actor* actor = static_cast<Actor*>(refActor);
auto fs = OTRGlobals::Instance->gRandoContext->GetFishsanity();
// Detect fish catch
if (actor->id == ACTOR_FISHING && fs->GetPondFishShuffled()) {
Fishing* fish = static_cast<Fishing*>(refActor);
// State 6 -> Fish caught and hoisted
if (fish->fishState == 6) {
CheckIdentity identity = IdentifyFish(gPlayState->sceneNum, actor->params);
if (identity.randomizerCheck != RC_UNKNOWN_CHECK) {
Flags_SetRandomizerInf(identity.randomizerInf);
enableAdvance = true;
// Remove uncaught effect
if (actor->shape.shadowDraw != NULL) {
actor->shape.shadowDraw = NULL;
actor->draw = drawFishing;
}
}
}
}
if (actor->id == ACTOR_EN_FISH && fs->GetOverworldFishShuffled()) {
CheckIdentity fish = IdentifyFish(gPlayState->sceneNum, actor->params);
EnFish* fishActor = static_cast<EnFish*>(refActor);
if (Rando::Fishsanity::IsFish(&fish) && Flags_GetRandomizerInf(fish.randomizerInf)) {
// Reset draw method
if (actor->draw == Fishsanity_DrawEnFish) {
actor->draw = drawEnFish;
}
}
if (((actor->params >> 8) > 0) && fishActor->respawnTimer > 0) {
Actor_Kill(actor);
}
}
// Reset fish group counter when the group gets culled
if (actor->id == ACTOR_OBJ_MURE && gPlayState->sceneNum == SCENE_ZORAS_DOMAIN && fishGroupCounter > 0 &&
!(actor->flags & ACTOR_FLAG_UPDATE_CULLING_DISABLED) && fs->GetOverworldFishShuffled()) {
fishGroupCounter = 0;
}
}
} // namespace Rando
// C interface
extern "C" {
bool Randomizer_GetPondFishShuffled() {
return Rando::Context::GetInstance()->GetFishsanity()->GetPondFishShuffled();
}
bool Randomizer_GetOverworldFishShuffled() {
return Rando::Context::GetInstance()->GetFishsanity()->GetOverworldFishShuffled();
}
bool Randomizer_IsAdultPond() {
return Rando::Context::GetInstance()->GetFishsanity()->IsAdultPond();
}
void Fishsanity_DrawEffShadow(Actor* actor, Lights* lights, PlayState* play) {
/// Custom shadow draw function to add effect to uncollected fish
static void Fishsanity_DrawEffShadow(Actor* actor, Lights* lights, PlayState* play) {
Vec3f pos, ripplePos;
static Vec3f velocity = { 0.0f, 0.0f, 0.0f };
static Vec3f accel = { 0.0f, 0.0f, 0.0f };
@@ -527,7 +333,8 @@ void Fishsanity_DrawEffShadow(Actor* actor, Lights* lights, PlayState* play) {
}
}
void Fishsanity_DrawEnFish(struct Actor* actor, struct PlayState* play) {
/// Overridden actor draw function for bottleable fish
static void Fishsanity_DrawEnFish(Actor* actor, PlayState* play) {
CheckIdentity fish = IdentifyFish(play->sceneNum, actor->params);
GetItemEntry randoItem = Rando::Context::GetInstance()->GetFinalGIEntry(fish.randomizerCheck, true, GI_FISH);
if (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0)) {
@@ -545,28 +352,149 @@ void Fishsanity_DrawEnFish(struct Actor* actor, struct PlayState* play) {
Matrix_Pop();
}
void Fishsanity_DrawFishing(struct Actor* actor, struct PlayState* play) {
/// Overridden actor draw function for the fishing pond
static void Fishsanity_DrawFishing(Actor* actor, PlayState* play) {
Fishsanity_OpenGreyscaleColor(play, &fsPulseColor, (actor->params - 100) * 20);
drawFishing(actor, play);
Fishsanity_CloseGreyscaleColor(play);
}
void Fishsanity_OpenGreyscaleColor(PlayState* play, Color_RGB8* color, int16_t frameOffset) {
OPEN_DISPS(play->state.gfxCtx);
gDPSetGrayscaleColor(POLY_OPA_DISP++, color->r, color->g, color->b,
// Make color pulse, offset a bit by the actor params
ABS(255.0f * Math_CosS((play->gameplayFrames + frameOffset) * 1000)));
gSPGrayscale(POLY_OPA_DISP++, true);
CLOSE_DISPS(play->state.gfxCtx);
/**
* @brief ActorInit hook handler for fishsanity
*/
static void OnActorInitHandler(void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
CheckIdentity fish;
if (actor->id == ACTOR_EN_FISH && GetOverworldFishShuffled()) {
// Set fish ID for ZD fish
if (gPlayState->sceneNum == SCENE_ZORAS_DOMAIN && actor->params == -1) {
actor->params ^= fishGroupCounter++;
} else if (gPlayState->sceneNum == SCENE_GROTTOS && actor->params == 1) {
actor->params = 0x100 | gSaveContext.respawn[RESPAWN_MODE_RETURN].data;
}
fish = IdentifyFish(gPlayState->sceneNum, actor->params);
// Render fish as randomized item
if (IsFish(&fish) && !Flags_GetRandomizerInf(fish.randomizerInf)) {
if (!drawEnFish) {
drawEnFish = actor->draw;
}
actor->draw = Fishsanity_DrawEnFish;
}
return;
}
if (actor->id == ACTOR_FISHING && gPlayState->sceneNum == SCENE_FISHING_POND && actor->params >= 100 &&
actor->params <= 117 && GetPondFishShuffled()) {
// Initialize pond fish for fishsanity
fish = IdentifyFish(gPlayState->sceneNum, actor->params);
// With every pond fish shuffled, caught fish will not spawn unless all fish have been caught.
if (RAND_GET_OPTION(RSK_FISHSANITY_POND_COUNT).Get() > 16 && !GetPondCleared()) {
// Create effect for uncaught fish
if (!Flags_GetRandomizerInf(fish.randomizerInf)) {
actor->shape.shadowDraw = Fishsanity_DrawEffShadow;
if (!drawFishing) {
drawFishing = actor->draw;
}
actor->draw = Fishsanity_DrawFishing;
}
}
}
}
void Fishsanity_CloseGreyscaleColor(PlayState* play) {
OPEN_DISPS(play->state.gfxCtx);
gSPGrayscale(POLY_OPA_DISP++, false);
CLOSE_DISPS(play->state.gfxCtx);
/**
* @brief ActorUpdate hook handler for fishsanity
*/
static void OnActorUpdateHandler(void* refActor) {
if (gPlayState->sceneNum != SCENE_GROTTOS && gPlayState->sceneNum != SCENE_ZORAS_DOMAIN &&
gPlayState->sceneNum != SCENE_FISHING_POND) {
return;
}
Actor* actor = static_cast<Actor*>(refActor);
// Detect fish catch
if (actor->id == ACTOR_FISHING && GetPondFishShuffled()) {
Fishing* fish = static_cast<Fishing*>(refActor);
// State 6 -> Fish caught and hoisted
if (fish->fishState == 6) {
CheckIdentity identity = IdentifyFish(gPlayState->sceneNum, actor->params);
if (identity.randomizerCheck != RC_UNKNOWN_CHECK) {
Flags_SetRandomizerInf(identity.randomizerInf);
enableAdvance = true;
// Remove uncaught effect
if (actor->shape.shadowDraw != NULL) {
actor->shape.shadowDraw = NULL;
actor->draw = drawFishing;
}
}
}
}
if (actor->id == ACTOR_EN_FISH && GetOverworldFishShuffled()) {
CheckIdentity fish = IdentifyFish(gPlayState->sceneNum, actor->params);
EnFish* fishActor = static_cast<EnFish*>(refActor);
if (IsFish(&fish) && Flags_GetRandomizerInf(fish.randomizerInf)) {
// Reset draw method
if (actor->draw == Fishsanity_DrawEnFish) {
actor->draw = drawEnFish;
}
}
if (((actor->params >> 8) > 0) && fishActor->respawnTimer > 0) {
Actor_Kill(actor);
}
}
// Reset fish group counter when the group gets culled
if (actor->id == ACTOR_OBJ_MURE && gPlayState->sceneNum == SCENE_ZORAS_DOMAIN && fishGroupCounter > 0 &&
!(actor->flags & ACTOR_FLAG_UPDATE_CULLING_DISABLED) && GetOverworldFishShuffled()) {
fishGroupCounter = 0;
}
}
namespace Rando::Fishsanity {
bool GetFishLocationIncluded(Rando::Location* loc, FishsanityOptionsSource optionsSource) {
auto [mode, numFish, ageSplit] = GetOptions(optionsSource);
if (loc->GetRCType() != RCTYPE_FISH || mode == RO_FISHSANITY_OFF || mode == RO_FISHSANITY_HYRULE_LOACH) {
return false;
}
RandomizerCheck rc = loc->GetRandomizerCheck();
// Are pond fish enabled, and is this a pond fish location?
if (mode != RO_FISHSANITY_OVERWORLD && numFish > 0 && loc->GetScene() == SCENE_FISHING_POND &&
loc->GetActorID() == ACTOR_FISHING) {
// Is this a child fish location? If so, is it within the defined number of pond fish checks?
if (rc >= RC_LH_CHILD_FISH_1 && rc <= RC_LH_CHILD_LOACH_2 && numFish > (loc->GetActorParams() - 100)) {
return true;
}
// Are adult fish available, and is this an adult fish location? If so, is it within the defined number of pond
// fish checks?
if (ageSplit && rc >= RC_LH_ADULT_FISH_1 && rc <= RC_LH_ADULT_LOACH &&
numFish > (loc->GetActorParams() - 100)) {
return true;
}
}
// Are overworld fish enabled, and is this an overworld fish location?
if (mode != RO_FISHSANITY_POND && (loc->GetScene() == SCENE_GROTTOS || loc->GetScene() == SCENE_ZORAS_DOMAIN) &&
loc->GetActorID() == ACTOR_EN_FISH && (loc->GetActorParams() >> 8)) {
return true;
}
// Must not be an included fish location!
return false;
}
void InitializeFromSave() {
UpdateCurrentPondFish();
}
} // namespace Rando::Fishsanity
void RegisterShuffleFish() {
bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_FISHSANITY).IsNot(RO_FISHSANITY_OFF);
COND_HOOK(OnSceneInit, shouldRegister, [](int16_t sceneNum) {
@@ -575,19 +503,18 @@ void RegisterShuffleFish() {
}
});
COND_HOOK(OnActorInit, shouldRegister, Rando::Fishsanity::OnActorInitHandler);
COND_HOOK(OnActorUpdate, shouldRegister, Rando::Fishsanity::OnActorUpdateHandler);
COND_HOOK(OnActorInit, shouldRegister, OnActorInitHandler);
COND_HOOK(OnActorUpdate, shouldRegister, OnActorUpdateHandler);
COND_HOOK(OnItemReceive, shouldRegister, [](GetItemEntry itemEntry) {
if (enableAdvance) {
enableAdvance = false;
OTRGlobals::Instance->gRandoContext->GetFishsanity()->AdvancePond();
UpdateCurrentPondFish();
}
});
COND_VB_SHOULD(VB_BOTTLE_ACTOR, shouldRegister, {
Actor* actor = va_arg(args, Actor*);
auto fs = OTRGlobals::Instance->gRandoContext->GetFishsanity();
if (actor->id == ACTOR_EN_FISH && fs->GetOverworldFishShuffled()) {
if (actor->id == ACTOR_EN_FISH && GetOverworldFishShuffled()) {
auto fish = IdentifyFish(gPlayState->sceneNum, actor->params);
if (fish.randomizerCheck != RC_UNKNOWN_CHECK && !Flags_GetRandomizerInf(fish.randomizerInf)) {
Flags_SetRandomizerInf(fish.randomizerInf);
+13 -196
View File
@@ -1,209 +1,26 @@
#pragma once
#include <z64.h>
#include "randomizerTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
typedef struct {
u8 mode;
u8 numFish;
bool ageSplit;
} FishsanityPondOptions;
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "soh/Enhancements/randomizer/location.h"
typedef enum {
FSO_SOURCE_RANDO,
FSO_SOURCE_CVARS,
} FishsanityOptionsSource;
typedef enum {
FSC_NONE,
FSC_POND,
FSC_GROTTO,
FSC_ZD,
} FishsanityCheckType;
#ifdef __cplusplus
#include "soh/Enhancements/randomizer/location.h"
namespace Rando {
namespace Rando::Fishsanity {
/**
* @brief Class to provide an interface for and direct Fishsanity features
* @brief Returns true if the given fish location is active
*
* @param loc The Location to check
* @param optionsSource Optionally declare from which source to pull settings
*/
class Fishsanity {
public:
Fishsanity();
~Fishsanity();
bool GetFishLocationIncluded(Rando::Location* loc, FishsanityOptionsSource optionsSource = FSO_SOURCE_RANDO);
static const CheckIdentity defaultIdentity;
/**
* @brief Initializes pond fish state from save
*/
void InitializeFromSave();
/**
* @brief Gets the type of a fishsanity check
* @param rc The RandomizerCheck to categorize
* @return The check's fishsanity type, or FSC_NONE
*/
static FishsanityCheckType GetCheckType(RandomizerCheck rc);
/**
* @brief Returns true if the given CheckIdentity represents an actual fish
* @param fish The fish to check
*/
static bool IsFish(CheckIdentity* fish);
/**
* @brief Returns true if the given fish location is active
*
* @param loc The Location to check
* @param optionsSource Optionally declare from which source to pull settings
*/
bool GetFishLocationIncluded(Rando::Location* loc, FishsanityOptionsSource optionsSource = FSO_SOURCE_RANDO);
/**
* @brief Get the active and inactive locations in the fishing pond.
*
* @param optionsSource Optionally declare from which source to pull settings
* @return A pair of vectors, where the fist is all active pond fish checks, and the second is all inactive pond
* fish checks.
*/
std::pair<std::vector<RandomizerCheck>, std::vector<RandomizerCheck>>
GetFishingPondLocations(FishsanityOptionsSource optionsSource = FSO_SOURCE_RANDO);
/**
* @brief Get all active fishsanity locations, and all inactive fishing pond locations.
*
* @param optionsSource Optionally declare from which source to pull settings
* @return A pair of vectors, where the first is all active fishsanity checks, and the second is all inactive
* fishsanity checks.
*/
std::pair<std::vector<RandomizerCheck>, std::vector<RandomizerCheck>>
GetFishsanityLocations(FishsanityOptionsSource optionsSource = FSO_SOURCE_RANDO);
/**
* @brief Returns the identity for a caught pond fish given its params.
* Not for use externally from rando
*
* @param fishParams Actor parameters for the fish to identify
*/
CheckIdentity IdentifyPondFish(u8 fishParams);
/**
* @brief Get fishsanity fishing pond options from the requested source
*/
FishsanityPondOptions GetOptions(FishsanityOptionsSource optionsSource = FSO_SOURCE_RANDO);
/**
* @brief Updates current pond fish according to save data
*/
void UpdateCurrentPondFish();
/**
* @brief Initializes internal state from save
*/
void InitializeFromSave();
/**
* @brief Returns true if the fishing pond is shuffled
*/
bool GetPondFishShuffled();
/**
* @brief Returns true if overworld fish are shuffled
*/
bool GetOverworldFishShuffled();
/**
* @brief Returns true if the fishing pond is currently adult (i.e., age split is enabled and Link is adult)
*/
bool IsAdultPond();
/**
* @brief Returns true if all available pond fish checks have been collected for the current age
*/
bool GetPondCleared();
/**
* @brief Returns true if all available Zora's Domain fish checks have been collected
*/
bool GetDomainCleared();
/**
* @brief Advances current fishing pond check; no effect if every fish is shuffled
* @return The new CheckIdentity for the current pond, or default identity if every fish is shuffled
*/
CheckIdentity AdvancePond();
/**
* @brief ActorInit hook handler for fishsanity
*/
static void OnActorInitHandler(void* refActor);
/**
* @brief ActorUpdate hook handler for fishsanity
*/
static void OnActorUpdateHandler(void* refActor);
private:
/**
* @brief Initialize helper statics if they have not been initialized yet
*/
void InitializeHelpers();
/**
* @brief Resolves a pond fish's CheckIdentity directly from params & pond age
*
* @param params Params for Fishing actor
* @param adultPond Whether to resolve this fish as an adult check
* @return The CheckIdentity for the described fish
*/
static CheckIdentity GetPondFish(s16 params, bool adultPond);
/**
* @brief Current pond fish when all pond fish are not randomized
*/
std::pair<CheckIdentity, CheckIdentity> mCurrPondFish;
/**
* @brief True if fishsanity helpers have been initialized
*/
static bool fishsanityHelpersInit;
/////////////////////////////////////////////////////////
//// Helper data structures derived from static data ////
/////////////////////////////////////////////////////////
/**
* @brief Mapping from pond fish check to the age where that check can be collected
*/
static std::unordered_map<RandomizerCheck, LinkAge> pondFishAgeMap;
/**
* @brief List of child pond fish checks
*/
static std::vector<RandomizerCheck> childPondFish;
/**
* @brief List of adult pond fish checks
*/
static std::vector<RandomizerCheck> adultPondFish;
};
} // namespace Rando
extern "C" {
#endif
/// Returns true if pond fish should be shuffled based on fishsanity settings.
bool Randomizer_GetPondFishShuffled();
/// Returns true if overworld fish should be shuffled based on fishsanity settings.
bool Randomizer_GetOverworldFishShuffled();
/// Returns true if the adult fishing pond should be used for fishsanity.
bool Randomizer_IsAdultPond();
/// Custom shadow draw function to add effect to uncollected fish
void Fishsanity_DrawEffShadow(Actor* actor, Lights* lights, PlayState* play);
/// Overridden actor draw function for bottleable fish
void Fishsanity_DrawEnFish(struct Actor* actor, struct PlayState* play);
/// Overridden actor draw function for the fishing pond
void Fishsanity_DrawFishing(struct Actor* actor, struct PlayState* play);
void Fishsanity_OpenGreyscaleColor(PlayState* play, Color_RGB8* color, int16_t frameOffset);
void Fishsanity_CloseGreyscaleColor(PlayState* play);
#ifdef __cplusplus
}
#endif
} // namespace Rando::Fishsanity
@@ -5,6 +5,7 @@
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "soh/Enhancements/randomizer/bean_patches.h"
#include "soh/Enhancements/randomizer/dungeon.h"
#include "soh/Enhancements/randomizer/fishsanity.h"
#include "soh/Enhancements/randomizer/static_data.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
@@ -2913,7 +2914,7 @@ static void RandomizerRegisterHooks() {
RandomizerOnKaleidoscopeUpdateHandler);
if (RAND_GET_OPTION(RSK_FISHSANITY).IsNot(RO_FISHSANITY_OFF)) {
OTRGlobals::Instance->gRandoContext->GetFishsanity()->InitializeFromSave();
Rando::Fishsanity::InitializeFromSave();
}
});
}
@@ -1,4 +1,5 @@
#include "item_location.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "SeedContext.h"
#include "logic.h"
#include "rng.h"
@@ -1,4 +1,5 @@
#include "randomizer_check_objects.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include "static_data.h"
#include "SeedContext.h"
#include <map>
@@ -208,7 +209,7 @@ void RandomizerCheckObjects::UpdateImGuiVisibility() {
(location.GetRCType() != RCTYPE_RED_ICE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleRedIce"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_FISH ||
ctx->GetFishsanity()->GetFishLocationIncluded(&location, FSO_SOURCE_CVARS)) &&
Rando::Fishsanity::GetFishLocationIncluded(&location, FSO_SOURCE_CVARS)) &&
(location.GetRCType() != RCTYPE_ADULT_TRADE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleAdultTrade"), RO_GENERIC_NO)) &&
(location.GetRandomizerCheck() != RC_KF_KOKIRI_SWORD_CHEST ||
@@ -12,6 +12,7 @@
#include "soh/SohGui/SohMenu.h"
#include "dungeon.h"
#include "entrance.h"
#include "fishsanity.h"
#include "location_access.h"
#include "3drando/fill.hpp"
#include "soh/Enhancements/debugger/performanceTimer.h"
@@ -1945,8 +1946,7 @@ bool IsCheckShuffled(RandomizerCheck rc) {
(showDungeonWonderItems && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) &&
(loc->GetRCType() != RCTYPE_ICICLE || showIcicles) &&
(loc->GetRCType() != RCTYPE_RED_ICE || showRedIce) &&
(loc->GetRCType() != RCTYPE_FISH ||
OTRGlobals::Instance->gRandoContext->GetFishsanity()->GetFishLocationIncluded(loc)) &&
(loc->GetRCType() != RCTYPE_FISH || Rando::Fishsanity::GetFishLocationIncluded(loc)) &&
(loc->GetRCType() != RCTYPE_FREESTANDING ||
(showOverworldFreestanding && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) ||
(showDungeonFreestanding && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) &&
+1
View File
@@ -1,4 +1,5 @@
#include "ResourceManagerHelpers.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include "OTRGlobals.h"
#include "variables.h"
#include "z64.h"
+4 -4
View File
@@ -1,11 +1,11 @@
#include <unordered_set>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "SohMenu.h"
#include "soh/Enhancements/enhancementTypes.h"
#include "soh/Enhancements/randomizer/randomizer_check_objects.h"
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "soh/Enhancements/randomizer/settings.h"
#include "soh/OTRGlobals.h"
#include "soh/ShipUtils.h"
#include "soh/SohGui/SohGui.hpp"
@@ -67,7 +67,7 @@ void SaveExcludedLocations() {
}
void DrawLocationsMenu(WidgetInfo& info) {
auto ctx = OTRGlobals::Instance->gRandoContext;
auto ctx = Rando::Context::GetInstance();
int32_t currMQDungeonSetting = CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), 0) |
CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeonCount"), 0) << 8;
static ImVec2 cellPadding(8.0f, 8.0f);
@@ -360,7 +360,7 @@ void UpdateMenuTricks() {
}
void DrawTricksMenu(WidgetInfo& info) {
auto ctx = OTRGlobals::Instance->gRandoContext;
auto ctx = Rando::Context::GetInstance();
auto randoSettings = Rando::Settings::GetInstance();
static ImVec2 cellPadding(8.0f, 8.0f);
bool generating = CVarGetInteger(CVAR_GENERAL("RandoGenerating"), 0);
@@ -717,7 +717,7 @@ void SohMenu::AddMenuRandomizer() {
});
AddWidget(path, "Generate Randomizer", WIDGET_BUTTON)
.Callback([](WidgetInfo& info) {
OTRGlobals::Instance->gRandoContext->SetSpoilerLoaded(false);
Rando::Context::GetInstance()->SetSpoilerLoaded(false);
GenerateRandomizer(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), 0) ? seedString : "");
})
.PreFunc([](WidgetInfo& info) {
+21 -72
View File
@@ -15,7 +15,6 @@
#define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED
#define WATER_SURFACE_Y(play) play->colCtx.colHeader->waterBoxes->ySurface
bool getShouldSpawnLoaches();
void Fishing_Init(Actor* thisx, PlayState* play);
void Fishing_Destroy(Actor* thisx, PlayState* play);
@@ -432,13 +431,6 @@ 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);
}
void Fishing_SetColliderElement(s32 index, ColliderJntSph* collider, Vec3f* pos, f32 scale) {
collider->elements[index].dim.worldSphere.center.x = pos->x;
collider->elements[index].dim.worldSphere.center.y = pos->y;
@@ -906,17 +898,13 @@ 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 {
} else if (GameInteractor_Should(VB_FISHING_USE_DEFAULT_RECORD_LENGTH, true, &sFishingRecordLength)) {
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 {
} else if (GameInteractor_Should(VB_FISHING_USE_DEFAULT_RECORD_LENGTH, true, &sFishingRecordLength)) {
sFishingRecordLength = 45.0f; // 7 lbs
}
}
@@ -991,8 +979,8 @@ void Fishing_Init(Actor* thisx, PlayState* play2) {
ENKANBAN_FISHING);
Actor_Spawn(&play->actorCtx, play, ACTOR_FISHING, 0.0f, 0.0f, 0.0f, 0, 0, 0, 200);
// Loach(es) will spawn every fourth game, or if "Loaches Always Appear" is enabled
if (getShouldSpawnLoaches()) {
// Loach(es) will spawn every fourth game
if (GameInteractor_Should(VB_FISHING_SPAWN_LOACHES, (KREG(1) == 1) || ((sFishGameNumber & 3) == 3))) {
// Fishes 16 and 17 are loaches. Only 16 is spawned as adult; child also spawns 17.
if (sLinkAge != LINK_AGE_CHILD) {
fishCount = 16;
@@ -1008,7 +996,8 @@ void Fishing_Init(Actor* thisx, PlayState* play2) {
sFishInits[i].pos.z, 0, Rand_ZeroFloat(0x10000), 0, 100 + i);
}
} else {
if ((thisx->params < (EN_FISH_PARAM + 15) && !AllHyruleLoaches()) || (thisx->params == EN_FISH_AQUARIUM)) {
if (!GameInteractor_Should(VB_FISHING_FISH_IS_LOACH, thisx->params >= (EN_FISH_PARAM + 15)) ||
(thisx->params == EN_FISH_AQUARIUM)) {
SkelAnime_InitFlex(play, &this->skelAnime, &gFishingFishSkel, &gFishingFishAnim, NULL, NULL, 0);
Animation_MorphToLoop(&this->skelAnime, &gFishingFishAnim, 0.0f);
} else {
@@ -1028,7 +1017,8 @@ void Fishing_Init(Actor* thisx, PlayState* play2) {
this->fishState = 10;
this->fishStateNext = 10;
this->isLoach = sFishInits[thisx->params - EN_FISH_PARAM].isLoach || AllHyruleLoaches();
this->isLoach =
GameInteractor_Should(VB_FISHING_FISH_IS_LOACH, sFishInits[thisx->params - EN_FISH_PARAM].isLoach);
this->perception = sFishInits[thisx->params - EN_FISH_PARAM].perception;
this->fishLength = sFishInits[thisx->params - EN_FISH_PARAM].baseLength;
@@ -2910,49 +2900,6 @@ void Fishing_HandleAquariumDialog(Fishing* this, PlayState* play) {
}
}
f32 Fishing_GetMinimumRequiredScore() {
int32_t weight;
// RANDOTODO: update the enhancement sliders to not allow
// values above rando fish weight values when rando'd
if (sLinkAge == 1) {
weight = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0)
? CVarGetInteger(CVAR_ENHANCEMENT("MinimumFishWeightChild"), 10)
: 10;
} else {
weight = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0)
? CVarGetInteger(CVAR_ENHANCEMENT("MinimumFishWeightAdult"), 13)
: 13;
}
return sqrt(((f32)weight - 0.5f) / 0.0036f);
}
bool getInstantFish() {
return CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) &&
CVarGetInteger(CVAR_ENHANCEMENT("InstantFishing"), 0);
}
bool getGuaranteeBite() {
return CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) &&
CVarGetInteger(CVAR_ENHANCEMENT("GuaranteeFishingBite"), 0);
}
bool getFishNeverEscape() {
return CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) &&
CVarGetInteger(CVAR_ENHANCEMENT("FishNeverEscape"), 0);
}
bool getShouldSpawnLoaches() {
return (CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) &&
CVarGetInteger(CVAR_ENHANCEMENT("LoachesAlwaysAppear"), 0)) ||
((KREG(1) == 1) || ((sFishGameNumber & 3) == 3));
}
bool getShouldConfirmKeep() {
return !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) ||
!CVarGetInteger(CVAR_ENHANCEMENT("SkipKeepConfirmation"), 0);
}
void Fishing_UpdateFish(Actor* thisx, PlayState* play2) {
s16 i;
s16 rotXYScale = 10;
@@ -3451,9 +3398,9 @@ void Fishing_UpdateFish(Actor* thisx, PlayState* play2) {
if (sLureEquipped == FS_LURE_SINKING) {
chance *= 5.0f;
}
if (getGuaranteeBite() == 1 ||
((this->timerArray[0] == 1) || (Rand_ZeroOne() < chance)) &&
((Rand_ZeroOne() < (this->perception * multiplier)) || ((this->isLoach + 1) == KREG(69)))) {
if (GameInteractor_Should(VB_FISHING_FISH_BITE, ((this->timerArray[0] == 1) || (Rand_ZeroOne() < chance)) &&
((Rand_ZeroOne() < (this->perception * multiplier)) ||
((this->isLoach + 1) == KREG(69))))) {
if (this->isLoach == 0) {
this->fishState = 3;
this->unk_190 = 1.2f;
@@ -3866,8 +3813,9 @@ void Fishing_UpdateFish(Actor* thisx, PlayState* play2) {
if ((sRodCastState < 3) || ((sReelLock != 0) && (sFishFightTime > 50)) || (sFishFightTime >= 6000) ||
((sLureBitTimer == 0) && (sLineHooked == 0)) || (sRodPullback == 0) ||
(((sLureTimer & 0x7F) == 0) && (Rand_ZeroOne() < 0.05f) && (sLureEquipped != FS_LURE_SINKING) &&
(KREG(69) == 0) && (getFishNeverEscape() == 0))) {
GameInteractor_Should(VB_FISHING_FISH_ESCAPE, ((sLureTimer & 0x7F) == 0) && (Rand_ZeroOne() < 0.05f) &&
(sLureEquipped != FS_LURE_SINKING) &&
(KREG(69) == 0))) {
sFishingCaughtTextDelay = 20;
if ((sLureBitTimer == 0) && (sLineHooked == 0)) {
@@ -3897,7 +3845,7 @@ void Fishing_UpdateFish(Actor* thisx, PlayState* play2) {
sFishingMusicDelay = 50;
sRodReelingSpeed = 0.5f;
this->unk_152 = 0;
} else if (this->actor.xzDistToPlayer < (KREG(59) + 50.0f) || getInstantFish() == 1) {
} else if (GameInteractor_Should(VB_FISHING_CATCH_FISH, this->actor.xzDistToPlayer < (KREG(59) + 50.0f))) {
this->fishState = 6;
this->timerArray[0] = 100;
player->unk_860 = 3;
@@ -3976,7 +3924,7 @@ void Fishing_UpdateFish(Actor* thisx, PlayState* play2) {
multiVecSrc.y = -10.0f;
multiVecSrc.z = 5.0f;
Matrix_MultVec3f(&multiVecSrc, &targetPosOffset);
if (getInstantFish() == 0) {
if (!GameInteractor_Should(VB_FISHING_INSTANT_CATCH, false)) {
Math_ApproachF(&this->actor.world.pos.x, player->bodyPartsPos[15].x + targetPosOffset.x, 1.0f, 6.0f);
Math_ApproachF(&this->actor.world.pos.y, player->bodyPartsPos[15].y + targetPosOffset.y, 1.0f, 6.0f);
Math_ApproachF(&this->actor.world.pos.z, player->bodyPartsPos[15].z + targetPosOffset.z, 1.0f, 6.0f);
@@ -4000,9 +3948,10 @@ void Fishing_UpdateFish(Actor* thisx, PlayState* play2) {
sFishOnHandIsLoach = this->isLoach;
sLureCaughtWith = sLureEquipped;
Actor_Kill(&this->actor);
} else if (getShouldConfirmKeep() && (this->isLoach == 0) &&
(sFishOnHandIsLoach == 0) &&
((s16)this->fishLength < (s16)sFishOnHandLength)) {
} else if (GameInteractor_Should(
VB_FISHING_CONFIRM_KEEPING_SMALLER_FISH,
(this->isLoach == 0) && (sFishOnHandIsLoach == 0) &&
((s16)this->fishLength < (s16)sFishOnHandLength))) {
this->keepState = 1;
this->timerArray[0] = 0x3C;
Message_StartTextbox(play, 0x4098, NULL);
@@ -5548,7 +5497,7 @@ void Fishing_UpdateOwner(Actor* thisx, PlayState* play2) {
player->actor.speedXZ = 0.0f;
// #region SOH [Enhancement]
if (CVarGetInteger(CVAR_ENHANCEMENT("QuitFishingAtDoor"), 0)) {
if (GameInteractor_Should(VB_FISHING_QUIT_AT_DOOR, false)) {
Fishing_QuitAtDoor(this, play);
}
// #endregion
@@ -3,7 +3,6 @@
#include <libultraship/libultra.h>
#include "global.h"
#include "soh/Enhancements/randomizer/fishsanity.h"
struct Fishing;