Merge pull request from garrettjoecox/let-it-snow

develop -> let-it-now
This commit is contained in:
Garrett Cox 2024-12-04 21:35:44 -06:00 committed by GitHub
commit 2808279f00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 2896 additions and 1505 deletions

@ -9,7 +9,7 @@ Official Website: https://www.shipofharkinian.com/
Official Discord: https://discord.com/invite/shipofharkinian
If you're having any trouble after reading through this `README`, feel free ask for help in the Support text channels. Please keep in mind that we do not condone piracy.
If you're having any trouble after reading through this `README`, feel free to ask for help in the Support text channels. Please keep in mind that we do not condone piracy.
# Quick Start
@ -73,7 +73,7 @@ Congratulations, you are now sailing with the Ship of Harkinian! Have fun!
# Project Overview
Ship of Harkinian (SOH) is built atop a custom library dubbed libultraship (LUS). Back in the N64 days, there was an SDK distributed to developers named libultra; LUS is designed to mimic the functionality of libultra on modern hardware. In addition, we are dependant on the source code provided by the OOT decompilation project.
In order for the game to function, you will require a **legally aquired** ROM for Ocarina of Time. Click [here](https://ship.equipment/) to check the compatability of your specific rom. Any copyrighted assets are extracted from the ROM and reformated as a .otr archive file which the code uses.
In order for the game to function, you will require a **legally acquired** ROM for Ocarina of Time. Click [here](https://ship.equipment/) to check the compatibility of your specific rom. Any copyrighted assets are extracted from the ROM and reformatted as a .otr archive file which the code uses.
### Graphics Backends
Currently, there are three rendering APIs supported: DirectX11 (Windows), OpenGL (all platforms), and Metal (MacOS). You can change which API to use in the `Settings` menu of the menubar, which requires a restart. If you're having an issue with crashing, you can change the API in the `shipofharkinian.json` file by finding the line `gfxbackend:""` and changing the value to `sdl` for OpenGL. DirectX 11 is the default on Windows.
@ -99,13 +99,13 @@ If you want to playtest a continuous integration build, you can find them at the
* [Linux](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux.zip)
### Further Reading
More detailed documentation can be found in the 'docs' directory, including the afformentioned [building instructions](docs/BUILDING.md).
More detailed documentation can be found in the 'docs' directory, including the aforementioned [building instructions](docs/BUILDING.md).
*[Credits](docs/CREDITS.md)
*[Custom Music](docs/CUSTOM_MUSIC.md)
*[Controler Maping](docs/GAME_CONTROLLER_DB.md)
*[Modding](docs/MODDING.md)
*[Versioning](docs/VERSIONING.md)
* [Credits](docs/CREDITS.md)
* [Custom Music](docs/CUSTOM_MUSIC.md)
* [Controller Mapping](docs/GAME_CONTROLLER_DB.md)
* [Modding](docs/MODDING.md)
* [Versioning](docs/VERSIONING.md)
<a href="https://github.com/Kenix3/libultraship/">
<picture>

Binary file not shown.

After

(image error) Size: 6.0 KiB

Binary file not shown.

After

(image error) Size: 6.1 KiB

Binary file not shown.

After

(image error) Size: 6.3 KiB

@ -178,6 +178,15 @@ static const ALIGN_ASSET(2) char gSplitEntranceTex[] = dgSplitEntrance;
#define dgBossSoul "__OTR__textures/parameter_static/gBossSoul"
static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul;
#define dgMoonIcon "__OTR__textures/parameter_static/gMoon"
static const ALIGN_ASSET(2) char gMoonIconTex[] = dgMoonIcon;
#define dgSunIcon "__OTR__textures/parameter_static/gSun"
static const ALIGN_ASSET(2) char gSunIconTex[] = dgSunIcon;
#define dgNaviIcon "__OTR__textures/parameter_static/gNavi"
static const ALIGN_ASSET(2) char gNaviIconTex[] = dgNaviIcon;
#define dgFileSelMQButtonTex "__OTR__textures/title_static/gFileSelMQButtonTex"
static const ALIGN_ASSET(2) char gFileSelMQButtonTex[] = dgFileSelMQButtonTex;

@ -306,6 +306,7 @@ typedef enum {
/* 0x99 */ ITEM_STICK_UPGRADE_30,
/* 0x9A */ ITEM_NUT_UPGRADE_30,
/* 0x9B */ ITEM_NUT_UPGRADE_40,
/* 0x9C */ ITEM_SHIP, // SOH [Enhancement] Added to enable custom item gives
/* 0xFC */ ITEM_LAST_USED = 0xFC,
/* 0xFE */ ITEM_NONE_FE = 0xFE,
/* 0xFF */ ITEM_NONE = 0xFF
@ -455,9 +456,10 @@ typedef enum {
/* 0x79 */ GI_NUT_UPGRADE_30,
/* 0x7A */ GI_NUT_UPGRADE_40,
/* 0x7B */ GI_BULLET_BAG_50,
/* 0x7C */ GI_ICE_TRAP, // freezes link when opened from a chest
/* 0x7D */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg)
/* 0x84 */ GI_MAX
/* 0x7C */ GI_SHIP, // SOH [Enhancement] Added to enable custom item gives
/* 0x7D */ GI_ICE_TRAP, // freezes link when opened from a chest
/* 0x7E */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg)
/* 0x7F */ GI_MAX
} GetItemID;
typedef enum {

@ -735,14 +735,14 @@ typedef enum {
#define INFTABLE_12A 0x12A
#define INFTABLE_138 0x138
#define INFTABLE_139 0x139
#define INFTABLE_140 0x140
#define INFTABLE_RUTO_IN_JJ_MEET_RUTO 0x141
#define INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME 0x142
#define INFTABLE_143 0x143
#define INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE 0x144
#define INFTABLE_145 0x145
#define INFTABLE_146 0x146
#define INFTABLE_147 0x147
#define INFTABLE_140 0x140 // Left her on blue switch in fork room (causes her to spawn in fork room)
#define INFTABLE_RUTO_IN_JJ_MEET_RUTO 0x141 // Jumped down hole from hole room
#define INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME 0x142 // in the basement
#define INFTABLE_143 0x143 // Sat down in basement (causes her to get upset if this is set when actor is spawned)
#define INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE 0x144 // Entered the room with the sapphire
#define INFTABLE_145 0x145 // Thrown to sapphire (not kidnapped yet)
#define INFTABLE_146 0x146 // Kidnapped
#define INFTABLE_147 0x147 // Brought ruto back up to holes room, causes her to spawn in holes room instead of basement
#define INFTABLE_160 0x160
#define INFTABLE_161 0x161
#define INFTABLE_162 0x162

@ -19,7 +19,7 @@ uint64_t GetUnixTimestamp();
bool isFeverDisabled = false;
bool isExchangeDisabled = false;
float fontScale = 1.0f;
static float fontScale = 1.0f;
extern GetItemEntry vanillaQueuedItemEntry;

@ -0,0 +1,262 @@
#include "TimeDisplay.h"
#include "soh/Enhancements/gameplaystats.h"
#include <global.h>
#include "assets/textures/parameter_static/parameter_static.h"
#include "assets/soh_assets.h"
#include "soh/ImGuiUtils.h"
extern "C" {
#include "macros.h"
#include "functions.h"
#include "variables.h"
extern PlayState* gPlayState;
uint64_t GetUnixTimestamp();
}
float fontScale = 1.0f;
std::string timeDisplayTime = "";
ImTextureID textureDisplay = 0;
ImVec4 windowBG = ImVec4(0, 0, 0, 0.5f);
ImVec4 textColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
// ImVec4 Colors
#define COLOR_WHITE ImVec4(1.0f, 1.0f, 1.0f, 1.0f)
#define COLOR_LIGHT_RED ImVec4(1.0f, 0.05f, 0, 1.0f)
#define COLOR_LIGHT_BLUE ImVec4(0, 0.88f, 1.0f, 1.0f)
#define COLOR_LIGHT_GREEN ImVec4(0.52f, 1.0f, 0.23f, 1.0f)
#define COLOR_GREY ImVec4(0.78f, 0.78f, 0.78f, 1.0f)
const static std::vector<std::pair<std::string, const char*>> digitList = {
{ "DIGIT_0_TEXTURE", gCounterDigit0Tex }, { "DIGIT_1_TEXTURE", gCounterDigit1Tex },
{ "DIGIT_2_TEXTURE", gCounterDigit2Tex }, { "DIGIT_3_TEXTURE", gCounterDigit3Tex },
{ "DIGIT_4_TEXTURE", gCounterDigit4Tex }, { "DIGIT_5_TEXTURE", gCounterDigit5Tex },
{ "DIGIT_6_TEXTURE", gCounterDigit6Tex }, { "DIGIT_7_TEXTURE", gCounterDigit7Tex },
{ "DIGIT_8_TEXTURE", gCounterDigit8Tex }, { "DIGIT_9_TEXTURE", gCounterDigit9Tex },
{ "COLON_TEXTURE", gCounterColonTex },
};
const std::vector<TimeObject> timeDisplayList = {
{ DISPLAY_IN_GAME_TIMER, "Display Gameplay Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.InGameTimer") },
{ DISPLAY_TIME_OF_DAY, "Display Time of Day", CVAR_ENHANCEMENT("TimeDisplay.Timers.TimeofDay") },
{ DISPLAY_CONDITIONAL_TIMER, "Display Conditional Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.HotWater") },
{ DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.NaviTimer") }
};
static std::vector<TimeObject> activeTimers;
std::string convertDayTime(uint32_t dayTime) {
uint32_t totalSeconds = 24 * 60 * 60;
uint32_t ss = static_cast<uint32_t>(static_cast<double>(dayTime) * (totalSeconds - 1) / 65535);
uint32_t hh = ss / 3600;
uint32_t mm = (ss % 3600) / 60;
return fmt::format("{:0>2}:{:0>2}", hh, mm);
}
std::string convertNaviTime(uint32_t value) {
uint32_t totalSeconds = value * 0.05;
uint32_t ss = totalSeconds % 60;
uint32_t mm = totalSeconds / 60;
return fmt::format("{:0>2}:{:0>2}", mm, ss);
}
std::string formatHotWaterDisplay(uint32_t value) {
uint32_t ss = value % 60;
uint32_t mm = value / 60;
return fmt::format("{:0>2}:{:0>2}", mm, ss);
}
std::string formatTimeDisplay(uint32_t value) {
uint32_t sec = value / 10;
uint32_t hh = sec / 3600;
uint32_t mm = (sec - hh * 3600) / 60;
uint32_t ss = sec - hh * 3600 - mm * 60;
uint32_t ds = value % 10;
return fmt::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds);
}
static void TimeDisplayGetTimer(uint32_t timeID) {
timeDisplayTime = "";
textureDisplay = 0;
textColor = COLOR_WHITE;
Player* player = GET_PLAYER(gPlayState);
uint32_t timer1 = gSaveContext.timer1Value;
switch (timeID) {
case DISPLAY_IN_GAME_TIMER:
textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("GAMEPLAY_TIMER");
timeDisplayTime = formatTimeDisplay(GAMEPLAYSTAT_TOTAL_TIME).c_str();
break;
case DISPLAY_TIME_OF_DAY:
if (gSaveContext.dayTime >= DAY_BEGINS && gSaveContext.dayTime < NIGHT_BEGINS) {
textureDisplay =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("DAY_TIME_TIMER");
} else {
textureDisplay =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("NIGHT_TIME_TIMER");
}
timeDisplayTime = convertDayTime(gSaveContext.dayTime).c_str();
break;
case DISPLAY_CONDITIONAL_TIMER:
if (gSaveContext.timer1State > 0) {
timeDisplayTime = formatHotWaterDisplay(gSaveContext.timer1Value).c_str();
textColor =
gSaveContext.timer1State <= 4
? (gPlayState->roomCtx.curRoom.behaviorType2 == ROOM_BEHAVIOR_TYPE2_3 ? COLOR_LIGHT_RED
: COLOR_LIGHT_BLUE)
: COLOR_WHITE;
if (gSaveContext.timer1State <= 4) {
textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
gPlayState->roomCtx.curRoom.behaviorType2 == ROOM_BEHAVIOR_TYPE2_3
? itemMapping[ITEM_TUNIC_GORON].name
: itemMapping[ITEM_TUNIC_ZORA].name);
}
if (gSaveContext.timer1State >= 6) {
textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
itemMapping[ITEM_SWORD_MASTER].name);
}
} else {
textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
itemMapping[ITEM_TUNIC_KOKIRI].name);
timeDisplayTime = "-:--";
}
break;
case DISPLAY_NAVI_TIMER:
if (gSaveContext.naviTimer <= NAVI_PREPARE) {
timeDisplayTime = convertNaviTime(NAVI_PREPARE - gSaveContext.naviTimer).c_str();
} else if (gSaveContext.naviTimer <= NAVI_ACTIVE) {
timeDisplayTime = convertNaviTime(NAVI_ACTIVE - gSaveContext.naviTimer).c_str();
textColor = COLOR_LIGHT_GREEN;
} else {
timeDisplayTime = convertNaviTime(NAVI_COOLDOWN - gSaveContext.naviTimer).c_str();
textColor = COLOR_GREY;
}
textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("NAVI_TIMER");
break;
default:
break;
}
}
void TimeDisplayUpdateDisplayOptions() {
activeTimers.clear();
for (auto& timer : timeDisplayList) {
if (CVarGetInteger(timer.timeEnable, 0)) {
activeTimers.push_back(timer);
}
}
//if (pushBack) {
// activeTimers.push_back(timeDisplayList[timeID]);
//} else {
// uint32_t index = 0;
// for (auto& check : activeTimers) {
// if (check.timeID == timeID) {
// activeTimers.erase(activeTimers.begin() + index);
// return;
// }
// index++;
// }
//}
}
void TimeDisplayWindow::Draw() {
if (!gPlayState) {
return;
}
if (!CVarGetInteger(CVAR_WINDOW("TimeDisplayEnabled"), 0)) {
return;
}
ImGui::PushStyleColor(ImGuiCol_WindowBg, windowBG);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
ImGui::Begin("TimerDisplay", nullptr,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar);
ImGui::SetWindowFontScale(fontScale);
if (activeTimers.size() == 0) {
ImGui::Text("No Enabled Timers...");
} else {
ImGui::BeginTable("Timer List", 2, ImGuiTableFlags_NoClip);
for (auto& timers : activeTimers) {
ImGui::PushID(timers.timeID);
TimeDisplayGetTimer(timers.timeID);
ImGui::TableNextColumn();
ImGui::Image(textureDisplay, ImVec2(16.0f * fontScale, 16.0f * fontScale));
ImGui::TableNextColumn();
if (timeDisplayTime != "-:--") {
char* textToDecode = new char[timeDisplayTime.size() + 1];
textToDecode = std::strcpy(textToDecode, timeDisplayTime.c_str());
size_t textLength = timeDisplayTime.length();
uint16_t textureIndex = 0;
for (size_t i = 0; i < textLength; i++) {
ImVec2 originalCursorPos = ImGui::GetCursorPos();
if (textToDecode[i] == ':' || textToDecode[i] == '.') {
textureIndex = 10;
} else {
textureIndex = textToDecode[i] - '0';
}
if (textToDecode[i] == '.') {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (8.0f * fontScale));
ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
digitList[textureIndex].first),
ImVec2(8.0f * fontScale, 8.0f * fontScale), ImVec2(0, 0.5f), ImVec2(1, 1),
textColor, ImVec4(0, 0, 0, 0));
} else {
ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(
digitList[textureIndex].first),
ImVec2(8.0f * fontScale, 16.0f * fontScale), ImVec2(0, 0), ImVec2(1, 1), textColor,
ImVec4(0, 0, 0, 0));
}
ImGui::SameLine(0, 0);
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(1);
}
void TimeDisplayInitSettings() {
fontScale = CVarGetFloat(CVAR_ENHANCEMENT("TimeDisplay.FontScale"), 1.0f);
if (fontScale < 1.0f) {
fontScale = 1.0f;
}
if (CVarGetInteger(CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"), 0)) {
windowBG = ImVec4(0, 0, 0, 0);
} else {
windowBG = ImVec4(0, 0, 0, 0.5f);
}
}
static void TimeDisplayInitTimers() {
for (auto& update : timeDisplayList) {
if (CVarGetInteger(update.timeEnable, 0)) {
activeTimers.push_back(update);
}
}
}
void TimeDisplayWindow::InitElement() {
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("GAMEPLAY_TIMER", gClockIconTex, ImVec4(1, 1, 1, 1));
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("DAY_TIME_TIMER", gSunIconTex, ImVec4(1, 1, 1, 1));
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("NIGHT_TIME_TIMER", gMoonIconTex, ImVec4(1, 1, 1, 1));
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("NAVI_TIMER", gNaviIconTex, ImVec4(1, 1, 1, 1));
for (auto& load : digitList) {
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(load.first.c_str(), load.second, ImVec4(1, 1, 1, 1));
}
TimeDisplayInitSettings();
TimeDisplayInitTimers();
}

@ -0,0 +1,37 @@
#include <libultraship/libultraship.h>
class TimeDisplayWindow : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void InitElement() override;
void DrawElement() override {};
void Draw() override;
void UpdateElement() override {};
};
void TimeDisplayUpdateDisplayOptions();
void TimeDisplayInitSettings();
typedef enum {
DISPLAY_IN_GAME_TIMER,
DISPLAY_TIME_OF_DAY,
DISPLAY_CONDITIONAL_TIMER,
DISPLAY_NAVI_TIMER
};
typedef enum {
NAVI_PREPARE = 600,
NAVI_ACTIVE = 3000,
NAVI_COOLDOWN = 25800,
DAY_BEGINS = 17759,
NIGHT_BEGINS = 49155
};
typedef struct {
uint32_t timeID;
std::string timeLabel;
const char* timeEnable;
} TimeObject;
extern const std::vector<TimeObject> timeDisplayList;

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

@ -0,0 +1,27 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/OTRGlobals.h"
extern "C" {
#include "src/overlays/actors/ovl_Bg_Bdan_Objects/z_bg_bdan_objects.h"
}
/**
* Adjusts the behavior of the elevator to start near the bottom if you are entering the room from the bottom
*/
void MoveJabuJabuElevator_Register() {
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnActorInit>(ACTOR_BG_BDAN_OBJECTS, [](void* actorRef) {
Player* player = GET_PLAYER(gPlayState);
BgBdanObjects* bgBdanObjects = static_cast<BgBdanObjects*>(actorRef);
if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) {
return;
}
if (bgBdanObjects->dyna.actor.params == 1) {
if (player->actor.world.pos.y < -500.0f) {
bgBdanObjects->timer = 220;
}
}
});
}

@ -0,0 +1,93 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/OTRGlobals.h"
extern "C" {
#include "overlays/actors/ovl_En_Ru1/z_en_ru1.h"
#include "assets/objects/object_ru1/object_ru1.h"
Actor* func_80AEB124(PlayState* play);
}
void SkipChildRutoInteractions_Register() {
// Skips the Child Ruto introduction cutscene, where she drops down into the hole in Jabu-Jabu's Belly
REGISTER_VB_SHOULD(VB_PLAY_CHILD_RUTO_INTRO, {
EnRu1* enRu1 = va_arg(args, EnRu1*);
if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) {
return;
}
Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO);
Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME);
Flags_SetInfTable(INFTABLE_143);
enRu1->drawConfig = 1;
enRu1->actor.world.pos.x = 127.0f;
enRu1->actor.world.pos.y = -340.0f;
enRu1->actor.world.pos.z = -3041.0f;
enRu1->actor.shape.rot.y = enRu1->actor.world.rot.y = -5098;
if (*should) {
Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildTurnAroundAnim, 1.0f, 0,
Animation_GetLastFrame((void*)&gRutoChildTurnAroundAnim), ANIMMODE_ONCE, -8.0f);
enRu1->action = 10;
}
*should = false;
});
// Skips a short dialogue sequence where Ruto tells you to throw her to the Sapphire
REGISTER_VB_SHOULD(VB_RUTO_WANT_TO_BE_TOSSED_TO_SAPPHIRE, {
if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) {
return;
}
if (*should) {
Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE);
*should = false;
}
});
// Prevents Ruto from running to the Sapphire when she wants to be tossed to it, instead she just stands up and waits for link to get closer
REGISTER_VB_SHOULD(VB_RUTO_RUN_TO_SAPPHIRE, {
EnRu1* enRu1 = va_arg(args, EnRu1*);
DynaPolyActor* dynaPolyActor = va_arg(args, DynaPolyActor*);
if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) {
return;
}
if (*should) {
enRu1->unk_28C = (BgBdanObjects*)dynaPolyActor;
Flags_SetInfTable(INFTABLE_145);
Flags_SetSwitch(gPlayState, 0x02);
Flags_SetSwitch(gPlayState, 0x1F);
enRu1->action = 42;
Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildWait2Anim, 1.0f, 0,
Animation_GetLastFrame((void*)&gRutoChildWait2Anim), ANIMMODE_LOOP, -8.0f);
enRu1->unk_28C->cameraSetting = 1;
Actor* sapphire = func_80AEB124(gPlayState);
if (sapphire != NULL) {
Actor_Kill(sapphire);
}
enRu1->actor.room = gPlayState->roomCtx.curRoom.num;
*should = false;
}
});
// This overrides the behavior that causes Ruto to get upset at you before sitting back down again when INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME is set
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnActorInit>(ACTOR_EN_RU1, [](void* actorRef) {
EnRu1* enRu1 = static_cast<EnRu1*>(actorRef);
if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) {
return;
}
if (enRu1->action == 22) {
enRu1->action = 27;
enRu1->drawConfig = 1;
enRu1->actor.flags |= ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY;
Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildSittingAnim, 1.0f, 0.0f,
Animation_GetLastFrame((void*)&gRutoChildSittingAnim), ANIMMODE_LOOP, 0.0f);
}
});
}

@ -10,7 +10,9 @@ void TimeSavers_Register() {
SkipZeldaFleeingCastle_Register();
SkipIntro_Register();
// SkipMiscInteractions
MoveJabuJabuElevator_Register();
MoveMidoInKokiriForest_Register();
SkipChildRutoInteractions_Register();
FasterHeavyBlockLift_Register();
FasterRupeeAccumulator_Register();
}

@ -12,7 +12,9 @@ void TimeSavers_Register();
void SkipZeldaFleeingCastle_Register();
void SkipIntro_Register();
// SkipMiscInteractions
void MoveJabuJabuElevator_Register();
void MoveMidoInKokiriForest_Register();
void SkipChildRutoInteractions_Register();
void FasterHeavyBlockLift_Register();
void FasterRupeeAccumulator_Register();

File diff suppressed because it is too large Load Diff

@ -1,13 +1,6 @@
#pragma once
#include <libultraship/libultraship.h>
#define PATCH_GFX(path, name, cvar, index, instruction) \
if (CVarGetInteger(cvar, 0)) { \
ResourceMgr_PatchGfxByName(path, name, index, instruction); \
} else { \
ResourceMgr_UnpatchGfxByName(path, name); \
}
// Not to be confused with tabs, groups are 1:1 with the boxes shown in the UI, grouping them allows us to reset/randomize
// every item in a group at once. If you are looking for tabs they are rendered manually in ImGui in `DrawCosmeticsEditor`
typedef enum {
@ -28,9 +21,19 @@ typedef enum {
COSMETICS_GROUP_TRAILS,
COSMETICS_GROUP_NAVI,
COSMETICS_GROUP_IVAN,
COSMETICS_GROUP_MESSAGE,
COSMETICS_GROUP_MAX
} CosmeticGroup;
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
Color_RGBA8 CosmeticsEditor_GetDefaultValue(const char* id);
#ifdef __cplusplus
}
typedef struct {
const std::string Name;
const std::string ToolTip;
@ -46,8 +49,7 @@ static float TablesCellsWidth = 300.0f;
static ImGuiTableColumnFlags FlagsTable = ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV;
static ImGuiTableColumnFlags FlagsCell = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort;
void InitCosmeticsEditor();//Init the menu itself
ImVec4 GetRandomValue(int MaximumPossible);
ImVec4 GetRandomValue();
void CosmeticsEditor_RandomizeAll();
void CosmeticsEditor_RandomizeGroup(CosmeticGroup group);
void CosmeticsEditor_ResetAll();
@ -61,4 +63,5 @@ class CosmeticsEditorWindow : public Ship::GuiWindow {
void InitElement() override;
void DrawElement() override;
void UpdateElement() override {};
};
};
#endif //__cplusplus

@ -0,0 +1,209 @@
#include "CustomCollectible.h"
#include <libultraship/libultraship.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
extern "C" {
#include "z64actor.h"
#include "functions.h"
#include "variables.h"
#include "macros.h"
#include "objects/object_md/object_md.h"
extern PlayState* gPlayState;
}
EnItem00* CustomCollectible::Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc,
ActorFunc drawFunc) {
if (!gPlayState) {
return nullptr;
}
Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ITEM00, posX, posY, posZ, flags, rot, params, ITEM00_NONE, 0);
EnItem00* enItem00 = (EnItem00*)actor;
if (actionFunc != NULL) {
enItem00->actionFunc = (EnItem00ActionFunc)actionFunc;
}
if (drawFunc != NULL) {
actor->draw = drawFunc;
}
return enItem00;
}
void CustomCollectible_Init(Actor* actor, PlayState* play) {
EnItem00* enItem00 = (EnItem00*)actor;
if (CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_BOBBING) {
actor->shape.yOffset = 1250.0f;
} else {
actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f;
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::HIDE_TILL_OVERHEAD) {
Actor_SetScale(actor, 0.0f);
} else {
Actor_SetScale(actor, 0.015f);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::KEEP_ON_PLAYER) {
Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::TOSS_ON_SPAWN) {
actor->velocity.y = 8.0f;
actor->speedXZ = 2.0f;
actor->gravity = -1.4f;
actor->world.rot.y = Rand_ZeroOne() * 40000.0f;
}
enItem00->unk_15A = -1;
}
// By default this will just assume the GID was passed in as the rot z, if you want different functionality you should
// override the draw
void CustomCollectible_Draw(Actor* actor, PlayState* play) {
Matrix_Scale(30.0f, 30.0f, 30.0f, MTXMODE_APPLY);
GetItem_Draw(play, CUSTOM_ITEM_PARAM);
}
void CustomCollectible_Update(Actor* actor, PlayState* play) {
EnItem00* enItem00 = (EnItem00*)actor;
Player* player = GET_PLAYER(play);
if (!(CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_SPINNING)) {
actor->shape.rot.y += 960;
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_BOBBING) {
actor->shape.yOffset = 1250.0f;
} else {
actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f;
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::HIDE_TILL_OVERHEAD) {
Actor_SetScale(actor, 0.0f);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::KEEP_ON_PLAYER) {
actor->gravity = 0.0f;
Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::KILL_ON_TOUCH) {
// Pretty self explanatory, if the player is within range, kill the actor and call the action function
if ((actor->xzDistToPlayer <= 50.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) {
if (enItem00->actionFunc != NULL) {
enItem00->actionFunc(enItem00, play);
CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION;
}
Actor_Kill(actor);
}
} else if (CUSTOM_ITEM_FLAGS & CustomCollectible::GIVE_OVERHEAD) {
// If the item hasn't been picked up (unk_15A == -1) and the player is within range
if (enItem00->unk_15A == -1 && (actor->xzDistToPlayer <= 50.0f) &&
(fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) {
// Fire the action function
if (enItem00->actionFunc != NULL) {
enItem00->actionFunc(enItem00, play);
CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION;
}
Sfx_PlaySfxCentered(NA_SE_SY_GET_ITEM);
// Set the unk_15A to 15, this indicates the item has been picked up and will start the overhead animation
enItem00->unk_15A = 15;
CUSTOM_ITEM_FLAGS |= CustomCollectible::STOP_BOBBING;
CUSTOM_ITEM_FLAGS |= CustomCollectible::KEEP_ON_PLAYER;
}
// If the item has been picked up
if (enItem00->unk_15A > 0) {
// Reduce the size a bit, but also makes it visible for HIDE_TILL_OVERHEAD
Actor_SetScale(actor, 0.010f);
// Decrement the unk_15A, which will be used to bob the item up and down
enItem00->unk_15A--;
// Account for the different heights of the player forms
f32 height = 45.0f;
// TODO: Check for adult?
// Bob the item up and down
actor->world.pos.y += (height + (Math_SinS(enItem00->unk_15A * 15000) * (enItem00->unk_15A * 0.3f)));
}
// Finally, once the bobbing animation is done, kill the actor
if (enItem00->unk_15A == 0) {
Actor_Kill(actor);
}
} else if (CUSTOM_ITEM_FLAGS & CustomCollectible::GIVE_ITEM_CUTSCENE) {
// If the item hasn't been picked up and the player is within range
if (!Actor_HasParent(actor, play) && enItem00->unk_15A == -1) {
Actor_OfferGetItem(actor, play, GI_SHIP, 50.0f, 20.0f);
} else {
if (enItem00->unk_15A == -1) {
CUSTOM_ITEM_FLAGS |= CustomCollectible::STOP_BOBBING;
CUSTOM_ITEM_FLAGS |= CustomCollectible::KEEP_ON_PLAYER;
CUSTOM_ITEM_FLAGS |= CustomCollectible::HIDE_TILL_OVERHEAD;
}
// Begin incrementing the unk_15A, indicating the item has been picked up
enItem00->unk_15A++;
// For the first 20 frames, wait while the player's animation plays
if (enItem00->unk_15A >= 20) {
// After the first 20 frames, show the item and call the action function
if (enItem00->unk_15A == 20 && enItem00->actionFunc != NULL) {
enItem00->actionFunc(enItem00, play);
CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION;
}
// Override the bobbing animation to be a fixed height
actor->shape.yOffset = 900.0f;
Actor_SetScale(actor, 0.007f);
f32 height = 45.0f;
// TODO: Check for adult?
actor->world.pos.y += height;
}
// Once the player is no longer in the "Give Item" state, kill the actor
if (!(player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM)) {
Actor_Kill(actor);
}
}
}
if (actor->gravity != 0.0f) {
Actor_MoveForward(actor);
Actor_UpdateBgCheckInfo(play, actor, 20.0f, 15.0f, 15.0f, 0x1D);
}
if (actor->bgCheckFlags & 0x0003) {
actor->speedXZ = 0.0f;
}
Collider_UpdateCylinder(actor, &enItem00->collider);
CollisionCheck_SetAC(play, &play->colChkCtx, &enItem00->collider.base);
}
void CustomCollectible::RegisterHooks() {
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::ShouldActorInit>(
ACTOR_EN_ITEM00, [](void* actorRef, bool* should) {
Actor* actor = (Actor*)actorRef;
if (actor->params != ITEM00_NONE) {
return;
}
actor->init = CustomCollectible_Init;
actor->update = CustomCollectible_Update;
actor->draw = CustomCollectible_Draw;
actor->destroy = NULL;
// Set the rotX/rotZ back to 0, the original values can be accessed from actor->home
actor->world.rot.x = 0;
actor->world.rot.z = 0;
});
}

@ -0,0 +1,24 @@
extern "C" {
#include "z64actor.h"
}
#define CUSTOM_ITEM_FLAGS (actor->home.rot.x)
#define CUSTOM_ITEM_PARAM (actor->home.rot.z)
namespace CustomCollectible {
enum CustomCollectibleFlags : int16_t {
KILL_ON_TOUCH = 1 << 0, // 0000 0000 0000 0001
GIVE_OVERHEAD = 1 << 1, // 0000 0000 0000 0010
GIVE_ITEM_CUTSCENE = 1 << 2, // 0000 0000 0000 0100
HIDE_TILL_OVERHEAD = 1 << 3, // 0000 0000 0000 1000
KEEP_ON_PLAYER = 1 << 4, // 0000 0000 0001 0000
STOP_BOBBING = 1 << 5, // 0000 0000 0010 0000
STOP_SPINNING = 1 << 6, // 0000 0000 0100 0000
CALLED_ACTION = 1 << 7, // 0000 0000 1000 0000
TOSS_ON_SPAWN = 1 << 8, // 0000 0001 0000 0000
};
void RegisterHooks();
EnItem00* Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc = NULL,
ActorFunc drawFunc = NULL);
}; // namespace CustomCollectible

@ -357,6 +357,15 @@ static size_t NextLineLength(const std::string* textStr, const size_t lastNewlin
nextPosJump = 1;
// Assume worst case for player name 12 * 8 (widest character * longest name length)
totalPixelWidth += 96;
} else if (textStr->at(currentPos) == '\x05') {
// Skip colour control characters.
nextPosJump = 2;
} else if (textStr->at(currentPos) == '\x1E') {
//For the high score char, we have to take the next Char, then use that to get a worst case scenario.
if (textStr->at(currentPos+1) == '\x01'){
totalPixelWidth += 28;
}
nextPosJump = 2;
} else {
// Some characters only one byte while others are two bytes
// So check both possibilities when checking for a character
@ -382,6 +391,65 @@ static size_t NextLineLength(const std::string* textStr, const size_t lastNewlin
}
}
size_t CustomMessage::FindNEWLINE(std::string& str, size_t lastNewline) const {
size_t newLine = str.find(NEWLINE()[0], lastNewline);
bool done;
// Bail out early
if (newLine == std::string::npos) {
return newLine;
}
do {
done = true;
if (newLine != 0) {
switch (str[newLine - 1]) {
case '\x05': // COLOR
case '\x06': // SHIFT
case '\x07': // TEXTID
case '\x0C': // BOX_BREAK_DELAYED
case '\x0E': // FADE
case '\x11': // FADE2
case '\x12': // SFX
case '\x13': // ITEM_ICON
case '\x14': // TEXT_SPEED
case '\x15': // BACKGROUND
case '\x1E': // POINTS/HIGH_SCORE
done = false;
break;
default:
break;
}
if (newLine > 1) {
switch (str[newLine - 2]) {
case '\x07': // TEXTID
case '\x11': // FADE2
case '\x12': // SFX
case '\x15': // BACKGROUND
done = false;
break;
default:
break;
}
if (newLine > 2) {
if (str[newLine - 3] == '\x15') { // BACKGROUND
done = false;
}
}
}
}
if (!done) {
newLine = str.find(NEWLINE()[0], newLine + 1);
if (newLine == std::string::npos) {
// if we reach the end of the string, quit now to save a loop
done = true;
}
}
} while (!done);
return newLine;
}
void CustomMessage::AutoFormatString(std::string& str) const {
ReplaceAltarIcons(str);
ReplaceColors(str);
@ -396,7 +464,7 @@ void CustomMessage::AutoFormatString(std::string& str) const {
const size_t ampersand = str.find('&', lastNewline);
const size_t lastSpace = str.rfind(' ', lastNewline + lineLength);
size_t waitForInput = str.find(WAIT_FOR_INPUT()[0], lastNewline);
size_t newLine = str.find(NEWLINE()[0], lastNewline);
size_t newLine = FindNEWLINE(str, lastNewline);
if (carrot < waitForInput){
waitForInput = carrot;
}

@ -181,6 +181,12 @@ class CustomMessage {
*/
void FormatString(std::string& str) const;
/**
* @brief finds NEWLINEs in a string, while filtering
* /x01's that are used as opperands
*/
size_t FindNEWLINE(std::string& str, size_t lastNewline) const;
/**
* @brief formats the string specifically to fit in OoT's
* textboxes, and use it's formatting.

@ -1312,6 +1312,7 @@ static constexpr std::array<std::pair<const char*, CosmeticGroup>, COSMETICS_GRO
{"trials", COSMETICS_GROUP_TRAILS},
{"navi", COSMETICS_GROUP_NAVI},
{"ivan", COSMETICS_GROUP_IVAN},
{"message", COSMETICS_GROUP_MESSAGE},
}};
static bool CosmeticsHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args, std::string* output) {

@ -4,14 +4,14 @@
#include <string>
#include <version>
static std::unordered_map<const char*, std::unordered_map<HOOK_ID, HookInfo>> hookData;
static std::unordered_map<const char*, std::unordered_map<HOOK_ID, HookInfo>*> hookData;
const ImVec4 grey = ImVec4(0.75, 0.75, 0.75, 1);
const ImVec4 yellow = ImVec4(1, 1, 0, 1);
const ImVec4 red = ImVec4(1, 0, 0, 1);
void DrawHookRegisteringInfos(const char* hookName) {
if (hookData[hookName].size() == 0) {
if ((*hookData[hookName]).size() == 0) {
ImGui::TextColored(grey, "No hooks found");
return;
}
@ -27,7 +27,7 @@ void DrawHookRegisteringInfos(const char* hookName) {
//ImGui::TableSetupColumn("Stub");
ImGui::TableSetupColumn("Number of Calls");
ImGui::TableHeadersRow();
for (auto& [id, hookInfo] : hookData[hookName]) {
for (auto& [id, hookInfo] : (*hookData[hookName])) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -100,12 +100,10 @@ void HookDebuggerWindow::DrawElement() {
}
}
void HookDebuggerWindow::UpdateElement() {
hookData.clear();
void HookDebuggerWindow::InitElement() {
#define DEFINE_HOOK(name, _) hookData.insert({#name, GameInteractor::Instance->GetHookData<GameInteractor::name>()});
#include "../game-interactor/GameInteractor_HookTable.h"
#undef DEFINE_HOOK
}
}

@ -4,7 +4,7 @@ class HookDebuggerWindow : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void InitElement() override {};
void InitElement() override;
void DrawElement() override;
void UpdateElement() override;
void UpdateElement() override {};
};

@ -345,6 +345,19 @@ typedef enum {
VB_INFLICT_VOID_DAMAGE,
VB_GANONDORF_DECIDE_TO_FIGHT,
VB_USE_ITEM,
// Vanilla condition: Close enough & various cutscene checks
// Opt: *EnRu1
VB_PLAY_CHILD_RUTO_INTRO,
// Vanilla condition: !INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE && in the big okto room
// Opt: *EnRu1
VB_RUTO_WANT_TO_BE_TOSSED_TO_SAPPHIRE,
// Vanilla condition: Landed on the platform in the big okto room
// Opt: *EnRu1
VB_RUTO_RUN_TO_SAPPHIRE,
// Vanilla condition: !Flags_GetInfTable(INFTABLE_145)
// Opt: *EnRu1
VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED,
/*** Give Items ***/
@ -658,8 +671,8 @@ public:
inline static std::vector<HOOK_ID> hooksForFilter;
};
template <typename H> std::unordered_map<uint32_t, HookInfo> GetHookData() {
return RegisteredGameHooks<H>::hookData;
template <typename H> std::unordered_map<uint32_t, HookInfo>* GetHookData() {
return &RegisteredGameHooks<H>::hookData;
}
// General Hooks

@ -125,8 +125,8 @@ namespace Rando {
std::make_shared<KaleidoEntryIconCountRequired>(
gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255,255,255,255 }, 0,
yOffset, reinterpret_cast<int*>(&gSaveContext.triforcePiecesCollected),
ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).GetSelectedOptionIndex() + 1,
ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).GetSelectedOptionIndex() + 1));
ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).GetContextOptionIndex() + 1,
ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).GetContextOptionIndex() + 1));
yOffset += 18;
}
if (ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS)) {

@ -1038,6 +1038,9 @@ std::vector<AltTrapType> getEnabledAddTraps () {
std::vector<AltTrapType> enabledAddTraps;
for (int i = 0; i < ADD_TRAP_MAX; i++) {
if (CVarGetInteger(altTrapTypeCvars[i], 0)) {
if (gSaveContext.equips.buttonItems[0] == ITEM_FISHING_POLE && (i == ADD_VOID_TRAP || i == ADD_TELEPORT_TRAP)) {
continue; // don't add void or teleport if you're holding the fishing pole, as this causes issues
}
enabledAddTraps.push_back(static_cast<AltTrapType>(i));
}
}

@ -242,6 +242,8 @@ const std::vector<const char*> enhancementsCvars = {
CVAR_ENHANCEMENT("AuthenticLogo"),
CVAR_ENHANCEMENT("PauseLiveLinkRotationSpeed"),
CVAR_ENHANCEMENT("BowReticle"),
CVAR_ENHANCEMENT("BoomerangFirstPerson"),
CVAR_ENHANCEMENT("BoomerangReticle"),
CVAR_ENHANCEMENT("FixTexturesOOB"),
CVAR_ENHANCEMENT("IvanCoopModeEnabled"),
CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes"),
@ -304,6 +306,7 @@ const std::vector<const char*> enhancementsCvars = {
CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"),
CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"),
CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"),
CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"),
CVAR_ENHANCEMENT("SlowTextSpeed"),
};
@ -549,6 +552,7 @@ const std::vector<const char*> randomizerCvars = {
CVAR_RANDOMIZER_SETTING("SkipChildZelda"),
CVAR_RANDOMIZER_SETTING("SkipEponaRace"),
CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"),
CVAR_RANDOMIZER_SETTING("SleepingWaterfall"),
CVAR_RANDOMIZER_SETTING("StartingAge"),
CVAR_RANDOMIZER_SETTING("StartingBoleroOfFire"),
CVAR_RANDOMIZER_SETTING("StartingConsumables"),
@ -1175,12 +1179,13 @@ const std::vector<PresetEntry> s6PresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipChildZelda"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipEponaRace"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipTowerEscape"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), RO_WATERFALL_CLOSED),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingAge"), RO_AGE_RANDOM),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingConsumables"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingDekuShield"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingMapsCompasses"), RO_DUNGEON_ITEM_LOC_STARTWITH),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingOcarina"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), 0),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), RO_ZF_CLOSED),
};
const std::vector<PresetEntry> hellModePresetEntries = {
@ -1235,16 +1240,18 @@ const std::vector<PresetEntry> hellModePresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipEponaRace"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipTowerEscape"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), RO_WATERFALL_OPEN),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingAge"), RO_AGE_RANDOM),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingMapsCompasses"), RO_DUNGEON_ITEM_LOC_ANYWHERE),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SunlightArrows"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), 2),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), RO_ZF_OPEN),
};
const std::vector<PresetEntry> BenchmarkPresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("Forest"), RO_FOREST_CLOSED_DEKU),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("KakarikoGate"), RO_KAK_GATE_OPEN),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("DoorOfTime"), RO_DOOROFTIME_SONGONLY),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), RO_WATERFALL_CLOSED),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), RO_ZF_CLOSED),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("GerudoFortress"), RO_GF_NORMAL),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("RainbowBridge"), RO_BRIDGE_DUNGEON_REWARDS),

@ -238,10 +238,10 @@ static int GetMaxGSCount() {
int maxBridge = 0;
int maxLACS = 0;
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) {
maxBridge = ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Value<uint8_t>();
maxBridge = ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).GetContextOptionIndex();
}
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) {
maxLACS = ctx->GetOption(RSK_LACS_TOKEN_COUNT).Value<uint8_t>();
maxLACS = ctx->GetOption(RSK_LACS_TOKEN_COUNT).GetContextOptionIndex();
}
maxBridge = std::max(maxBridge, maxLACS);
//Get the max amount of GS which could be useful from token reward locations
@ -266,7 +266,7 @@ static int GetMaxGSCount() {
maxUseful = 10;
}
//Return max of the two possible reasons tokens could be important, minus the tokens in the starting inventory
return std::max(maxUseful, maxBridge) - ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Value<uint8_t>();
return std::max(maxUseful, maxBridge) - ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).GetContextOptionIndex();
}
std::string GetShopItemBaseName(std::string itemName) {
@ -856,11 +856,6 @@ static void AssumedFill(const std::vector<RandomizerGet>& items, const std::vect
SPDLOG_DEBUG(Rando::StaticData::RetrieveItem(item).GetName().GetEnglish());
SPDLOG_DEBUG(". TRYING AGAIN...\n");
#ifdef ENABLE_DEBUG
Regions::DumpWorldGraph(Rando::StaticData::RetrieveItem(item).GetName().GetEnglish());
PlacementLog_Write();
#endif
// reset any locations that got an item
for (RandomizerCheck loc : attemptedLocations) {
ctx->GetItemLocation(loc)->SetPlacedItem(RG_NONE);

@ -1177,7 +1177,7 @@ void StaticData::HintTable_Init() {
// /*spanish*/la tienda de pociones de la abuela
hintTextTable[RHT_GRAVEYARD_DAMPES_HOUSE] = HintText(CustomMessage("Dampé's Hut",
/*german*/ "der #Hut von Boris#",
/*german*/ "die #Hütte von Boris#",
/*french*/ "la #Cabane du Fossoyeur#"));
// /*spanish*/la cabaña de Dampé
@ -1277,12 +1277,12 @@ void StaticData::HintTable_Init() {
// /*spanish*/la #tumba de la Canción del Sol#
hintTextTable[RHT_GRAVEYARD_COMPOSERS_GRAVE] = HintText(CustomMessage("the #Composers' Grave#",
/*german*/ "das Königsgrab",
/*german*/ "das #Königsgrab#",
/*french*/ "la #Tombe royale#"));
// /*spanish*/el #Panteón Real#
hintTextTable[RHT_GRAVEYARD_DAMPES_GRAVE] = HintText(CustomMessage("Dampé's Grave",
/*german*/ "das Grab von Boris",
/*german*/ "das #Grab von Boris#",
/*french*/ "la #Tombe d'Igor#"));
// /*spanish*/la #tumba de Dampé#
@ -2893,7 +2893,7 @@ void StaticData::HintTable_Init() {
{QM_RED, QM_GREEN, QM_GREEN}));
hintTextTable[RHT_HBA_HINT_HAVE_1000] = HintText(CustomMessage("Hey, newcomer!&Want to take on the #Horseback Archery# challenge?^"
"Prove yourself to be a horsemaster by scoring 1500 points to win my #[[1]]#!\x0B",
"Prove yourself to be a horsemaster by scoring 1500 points to win my #[[1]]#!\x0B",
{QM_RED, QM_GREEN}));
hintTextTable[RHT_MALON_HINT_HOW_IS_EPONA] = HintText(CustomMessage("@! You should come back with Epona and try to beat my time on the #Obstacle Course#!^"

@ -222,18 +222,18 @@ uint8_t StonesRequiredBySettings() {
auto ctx = Rando::Context::GetInstance();
uint8_t stones = 0;
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_STONES)) {
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).Value<uint8_t>();
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).GetContextOptionIndex();
} else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS)) {
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Value<uint8_t>() - 6;
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).GetContextOptionIndex() - 6;
} else if ((ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS)) && (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON))) {
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Value<uint8_t>() - 6;
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).GetContextOptionIndex() - 6;
}
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_STONES)) {
stones = std::max<uint8_t>({ stones, ctx->GetOption(RSK_LACS_STONE_COUNT).Value<uint8_t>() });
stones = std::max<uint8_t>({ stones, ctx->GetOption(RSK_LACS_STONE_COUNT).GetContextOptionIndex() });
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_STONES)) {
stones = std::max<uint8_t>({ stones, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).Value<uint8_t>() - 6 )});
stones = std::max<uint8_t>({ stones, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).GetContextOptionIndex() - 6 )});
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS)) {
stones = std::max<uint8_t>({ stones, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Value<uint8_t>() - 6 )});
stones = std::max<uint8_t>({ stones, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).GetContextOptionIndex() - 6 )});
}
return stones;
}
@ -242,18 +242,18 @@ uint8_t MedallionsRequiredBySettings() {
auto ctx = Rando::Context::GetInstance();
uint8_t medallions = 0;
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_MEDALLIONS)) {
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Value<uint8_t>();
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).GetContextOptionIndex();
} else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS)) {
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Value<uint8_t>() - 3;
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).GetContextOptionIndex() - 3;
} else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS) && ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) {
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Value<uint8_t>() - 3;
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).GetContextOptionIndex() - 3;
}
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_MEDALLIONS)) {
medallions = std::max({ medallions, ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Value<uint8_t>() });
medallions = std::max({ medallions, ctx->GetOption(RSK_LACS_MEDALLION_COUNT).GetContextOptionIndex() });
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_REWARDS)) {
medallions = std::max({ medallions, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).Value<uint8_t>() - 3 )});
medallions = std::max({ medallions, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).GetContextOptionIndex() - 3 )});
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS) && ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) {
medallions = std::max({ medallions, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Value<uint8_t>() - 3 )});
medallions = std::max({ medallions, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).GetContextOptionIndex() - 3 )});
}
return medallions;
}
@ -262,10 +262,10 @@ uint8_t TokensRequiredBySettings() {
auto ctx = Rando::Context::GetInstance();
uint8_t tokens = 0;
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) {
tokens = ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Value<uint8_t>();
tokens = ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).GetContextOptionIndex();
}
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) {
tokens = std::max<uint8_t>({ tokens, ctx->GetOption(RSK_LACS_TOKEN_COUNT).Value<uint8_t>() });
tokens = std::max<uint8_t>({ tokens, ctx->GetOption(RSK_LACS_TOKEN_COUNT).GetContextOptionIndex() });
}
return tokens;
}
@ -273,7 +273,7 @@ uint8_t TokensRequiredBySettings() {
std::vector<std::pair<RandomizerCheck, std::function<bool()>>> conditionalAlwaysHints = {
std::make_pair(RC_MARKET_10_BIG_POES, []() {
auto ctx = Rando::Context::GetInstance();
return ctx->GetOption(RSK_BIG_POE_COUNT).Value<uint8_t>() >= 3 && !ctx->GetOption(RSK_BIG_POES_HINT);
return ctx->GetOption(RSK_BIG_POE_COUNT).GetContextOptionIndex() >= 3 && !ctx->GetOption(RSK_BIG_POES_HINT);
}), // Remember, the option's value being 3 means 4 are required
std::make_pair(RC_DEKU_THEATER_MASK_OF_TRUTH, []() {
auto ctx = Rando::Context::GetInstance();
@ -483,7 +483,7 @@ static void CreateTrialHints(uint8_t copies) {
AddGossipStoneHintCopies(copies, HINT_TYPE_HINT_KEY, "Trial", {RHT_ZERO_TRIALS});
} else {
std::vector<TrialInfo*> trials = ctx->GetTrials()->GetTrialList(); //there's probably a way to remove this assignment
if (ctx->GetOption(RSK_TRIAL_COUNT).Value<uint8_t>() >= 4) {//4 or 5 required trials, get skipped trials
if (ctx->GetOption(RSK_TRIAL_COUNT).GetContextOptionIndex() >= 4) {//4 or 5 required trials, get skipped trials
trials = FilterFromPool(trials, [](TrialInfo* trial){return trial->IsSkipped();});
} else {//1 to 3 trials, get requried trials
auto requiredTrials = FilterFromPool(trials, [](TrialInfo* trial){return trial->IsRequired();});
@ -611,7 +611,7 @@ uint8_t PlaceHints(std::vector<uint8_t>& selectedHints, std::vector<HintDistribu
void CreateStoneHints() {
auto ctx = Rando::Context::GetInstance();
SPDLOG_DEBUG("\nNOW CREATING HINTS\n");
const HintSetting& hintSetting = hintSettingTable[ctx->GetOption(RSK_HINT_DISTRIBUTION).Value<uint8_t>()];
const HintSetting& hintSetting = hintSettingTable[ctx->GetOption(RSK_HINT_DISTRIBUTION).GetContextOptionIndex()];
std::vector<HintDistributionSetting> distTable = hintSetting.distTable;
// Apply impa's song exclusions when zelda is skipped

@ -655,7 +655,7 @@ static void SetMinimalItemPool() {
ReplaceMaxItem(RG_PROGRESSIVE_BOMB_BAG, 1);
ReplaceMaxItem(RG_PIECE_OF_HEART, 0);
// Need an extra heart container when starting with 1 heart to be able to reach 3 hearts
ReplaceMaxItem(RG_HEART_CONTAINER, (ctx->GetOption(RSK_STARTING_HEARTS).Value<uint8_t>() == 18)? 1 : 0);
ReplaceMaxItem(RG_HEART_CONTAINER, (ctx->GetOption(RSK_STARTING_HEARTS).GetContextOptionIndex() == 18)? 1 : 0);
}
void GenerateItemPool() {
@ -721,7 +721,7 @@ void GenerateItemPool() {
if (ctx->GetOption(RSK_TRIFORCE_HUNT)) {
ctx->possibleIceTrapModels.push_back(RG_TRIFORCE_PIECE);
AddItemToMainPool(RG_TRIFORCE_PIECE, (ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Value<uint8_t>() + 1));
AddItemToMainPool(RG_TRIFORCE_PIECE, (ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).GetContextOptionIndex() + 1));
ctx->PlaceItemInLocation(RC_TRIFORCE_COMPLETED, RG_TRIFORCE); // Win condition
ctx->PlaceItemInLocation(RC_GANON, GetJunkItem(), false, true);
} else {
@ -821,7 +821,7 @@ void GenerateItemPool() {
if (fsMode.IsNot(RO_FISHSANITY_OFF)) {
if (fsMode.Is(RO_FISHSANITY_POND) || fsMode.Is(RO_FISHSANITY_BOTH)) {
// 17 max child pond fish
uint8_t pondCt = ctx->GetOption(RSK_FISHSANITY_POND_COUNT).GetSelectedOptionIndex();
uint8_t pondCt = ctx->GetOption(RSK_FISHSANITY_POND_COUNT).GetContextOptionIndex();
for (uint8_t i = 0; i < pondCt; i++) {
AddItemToMainPool(GetJunkItem());
}
@ -1348,7 +1348,7 @@ void GenerateItemPool() {
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) {
ctx->PlaceItemInLocation(RC_KAK_100_GOLD_SKULLTULA_REWARD, RG_GANONS_CASTLE_BOSS_KEY);
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Value<uint8_t>() >= RO_GANON_BOSS_KEY_LACS_VANILLA && ctx->GetOption(RSK_GANONS_BOSS_KEY).IsNot(RO_GANON_BOSS_KEY_TRIFORCE_HUNT)) {
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).GetContextOptionIndex() >= RO_GANON_BOSS_KEY_LACS_VANILLA && ctx->GetOption(RSK_GANONS_BOSS_KEY).IsNot(RO_GANON_BOSS_KEY_TRIFORCE_HUNT)) {
ctx->PlaceItemInLocation(RC_TOT_LIGHT_ARROWS_CUTSCENE, RG_GANONS_CASTLE_BOSS_KEY);
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA)) {
ctx->PlaceItemInLocation(RC_GANONS_TOWER_BOSS_KEY_CHEST, RG_GANONS_CASTLE_BOSS_KEY);

@ -260,7 +260,7 @@ void RegionTable_Init() {
areaTable[RR_ROOT] = Region("Root", "", {RA_LINKS_POCKET}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
LOCATION(RC_LINKS_POCKET, true),
LOCATION(RC_TRIFORCE_COMPLETED, logic->GetSaveContext()->triforcePiecesCollected >= ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).Value<uint8_t>();),
LOCATION(RC_TRIFORCE_COMPLETED, logic->GetSaveContext()->triforcePiecesCollected >= ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).GetContextOptionIndex();),
LOCATION(RC_SARIA_SONG_HINT, logic->CanUse(RG_SARIAS_SONG)),
}, {
//Exits
@ -395,7 +395,7 @@ void ReplaceAllInString(std::string& s, std::string const& toReplace, std::strin
std::string CleanCheckConditionString(std::string condition) {
ReplaceAllInString(condition, "logic->", "");
ReplaceAllInString(condition, "ctx->", "");
ReplaceAllInString(condition, ".Value<uint8_t>()", "");
ReplaceAllInString(condition, ".GetContextOptionIndex()", "");
ReplaceAllInString(condition, "GetSaveContext()->", "");
return condition;
}

@ -21,138 +21,140 @@ void RegionTable_Init_JabuJabusBelly() {
if (ctx->GetDungeon(JABU_JABUS_BELLY)->IsVanilla()) {
areaTable[RR_JABU_JABUS_BELLY_BEGINNING] = Region("Jabu Jabus Belly Beginning", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_ENTRYWAY, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_LIFT_MIDDLE, {[]{return logic->CanUseProjectile();}}),
Entrance(RR_JABU_JABUS_BELLY_ENTRYWAY, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return logic->CanUseProjectile();}}),
});
areaTable[RR_JABU_JABUS_BELLY_LIFT_MIDDLE] = Region("Jabu Jabus Belly Lift Middle", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
//Combines Lift room middle and lower, 1F holes room, the forked corridor, and it's side rooms
areaTable[RR_JABU_JABUS_BELLY_MAIN] = Region("Jabu Jabus Belly Main", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
//Events
EventAccess(&logic->JabuRutoInB1, {[]{return true;}}),
EventAccess(&logic->JabuWestTentacle, {[]{return logic->JabuRutoIn1F && logic->CanKillEnemy(RE_TENTACLE, ED_BOOMERANG);}}),
}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_DEKU_SCRUB, logic->HasItem(RG_BRONZE_SCALE) && (logic->IsChild || logic->HasItem(RG_SILVER_SCALE) || ctx->GetTrickOption(RT_JABU_ALCOVE_JUMP_DIVE) || logic->CanUse(RG_IRON_BOOTS)) && logic->CanStunDeku()),
//We can kill the Stingers with ruto
LOCATION(RC_JABU_JABUS_BELLY_BOOMERANG_CHEST, logic->JabuRutoIn1F),
LOCATION(RC_JABU_JABUS_BELLY_MAP_CHEST, logic->JabuWestTentacle),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_BEGINNING, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN_UPPER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_LIFT_LOWER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_NEAR_BOSS_ROOM, {[]{return HasAccessTo(RR_JABU_JABUS_BELLY_LIFT_UPPER) || (ctx->GetTrickOption(RT_JABU_BOSS_HOVER) && logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS));}}),
});
Entrance(RR_JABU_JABUS_BELLY_B1_NORTH, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_COMPASS_ROOM, {[]{return logic->JabuWestTentacle;}}),
Entrance(RR_JABU_JABUS_BELLY_BLUE_TENTACLE, {[]{return logic->JabuWestTentacle;}}),
Entrance(RR_JABU_JABUS_BELLY_GREEN_TENTACLE, {[]{return logic->JabuEastTentacle;}}),
Entrance(RR_JABU_JABUS_BELLY_BIGOCTO_LEDGE, {[]{return logic->JabuNorthTentacle;}}),
Entrance(RR_JABU_JABUS_BELLY_NEAR_BOSS_ROOM, {[]{return logic->LoweredJabuPath || (ctx->GetTrickOption(RT_JABU_BOSS_HOVER) && logic->CanUse(RG_HOVER_BOOTS));}}),
});
areaTable[RR_JABU_JABUS_BELLY_MAIN_UPPER] = Region("Jabu Jabus Belly Main Upper", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_LIFT_MIDDLE, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN_LOWER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_FORKED_CORRIDOR, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_BIGOCTO_ROOM, {[]{return Here(RR_JABU_JABUS_BELLY_GREEN_TENTACLE, []{return logic->CanUse(RG_BOOMERANG);});}}),
});
areaTable[RR_JABU_JABUS_BELLY_MAIN_LOWER] = Region("Jabu Jabus Belly Main Lower", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//contains B1 of hole room (aside from the ledge leading to big octo), 2 octorock room and north water switch room
areaTable[RR_JABU_JABUS_BELLY_B1_NORTH] = Region("Jabu Jabus Belly B1 North", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
//Events
EventAccess(&logic->JabuRutoIn1F, {[]{return logic->IsAdult || logic->HasItem(RG_BRONZE_SCALE);}}),
EventAccess(&logic->FairyPot, {[]{return logic->CanUse(RG_BOOMERANG) || logic->CanUse(RG_HOVER_BOOTS);}}),
}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_LOWER, logic->HookshotOrBoomerang()),
LOCATION(RC_JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_UPPER, logic->HookshotOrBoomerang()),
}, {
LOCATION(RC_JABU_JABUS_BELLY_GS_WATER_SWITCH_ROOM, logic->HookshotOrBoomerang()),
//PUT 2 OCTO ROOM POTS HERE
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_MAIN_UPPER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_SHABOMB_CORRIDOR, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_LOWER_SIDE_ROOM, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return true;}}),
//there's tricks for getting here with bunny-jumps or just side-hops
Entrance(RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_LEDGE, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->HasItem(RG_HOVER_BOOTS);}}),
Entrance(RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_SOUTH, {[]{return logic->IsAdult || logic->HasItem(RG_BRONZE_SCALE);}}),
});
areaTable[RR_JABU_JABUS_BELLY_SHABOMB_CORRIDOR] = Region("Jabu Jabus Belly Shabomb Corridor", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
areaTable[RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_LEDGE] = Region("Jabu Jabus Belly Water Switch Room Ledge", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
//Events
EventAccess(&logic->FairyPot, {[]{return true;}}),
}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_GS_WATER_SWITCH_ROOM, true),
//this is the logic for climbing back and forth to use the pots to kill the skull...
LOCATION(RC_JABU_JABUS_BELLY_GS_WATER_SWITCH_ROOM, logic->HasItem(RG_BRONZE_SCALE) ||
(logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS)) ||
//or killing the skull before climbing to grab the token
logic->CanKillEnemy(RE_GOLD_SKULLTULA, ED_BOMB_THROW)),
//PUT WATER SWITCH POTS HERE
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_MAIN_LOWER, {[]{return logic->HasItem(RG_BRONZE_SCALE);}}),
Entrance(RR_JABU_JABUS_BELLY_LIFT_LOWER, {[]{return logic->HasItem(RG_BRONZE_SCALE) && logic->CanUseProjectile();}}),
Entrance(RR_JABU_JABUS_BELLY_B1_NORTH, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_SOUTH, {[]{return true;}}),
});
areaTable[RR_JABU_JABUS_BELLY_LOWER_SIDE_ROOM] = Region("Jabu Jabus Belly Lower Side Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
//Events
EventAccess(&logic->FairyPot, {[]{return logic->FairyPot || (logic->CanUse(RG_BOOMERANG) || logic->CanUse(RG_HOVER_BOOTS));}}),
}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_MAIN_LOWER, {[]{return true;}}),
});
areaTable[RR_JABU_JABUS_BELLY_LIFT_LOWER] = Region("Jabu Jabus Belly Lift Lower", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
areaTable[RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_SOUTH] = Region("Jabu Jabus Belly Water Switch Room South", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_DEKU_SCRUB, logic->HasItem(RG_BRONZE_SCALE) && (logic->IsChild || logic->HasItem(RG_SILVER_SCALE) || ctx->GetTrickOption(RT_JABU_ALCOVE_JUMP_DIVE) || logic->CanUse(RG_IRON_BOOTS)) && logic->CanStunDeku()),
LOCATION(RC_JABU_JABUS_BELLY_GS_WATER_SWITCH_ROOM, logic->HookshotOrBoomerang()),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_SHABOMB_CORRIDOR, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_LIFT_MIDDLE, {[]{return true;}}),
});
areaTable[RR_JABU_JABUS_BELLY_FORKED_CORRIDOR] = Region("Jabu Jabus Belly Forked Corridor", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_MAIN_UPPER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_BOOMERANG_ROOM, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAP_ROOM, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_COMPASS_ROOM, {[]{return Here(RR_JABU_JABUS_BELLY_MAP_ROOM, []{return logic->CanUse(RG_BOOMERANG);});}}),
Entrance(RR_JABU_JABUS_BELLY_BLUE_TENTACLE, {[]{return Here(RR_JABU_JABUS_BELLY_MAP_ROOM, []{return logic->CanUse(RG_BOOMERANG);});}}),
Entrance(RR_JABU_JABUS_BELLY_GREEN_TENTACLE, {[]{return Here(RR_JABU_JABUS_BELLY_BLUE_TENTACLE, []{return logic->CanUse(RG_BOOMERANG);});}}),
});
areaTable[RR_JABU_JABUS_BELLY_BOOMERANG_ROOM] = Region("Jabu Jabus Belly Boomerang Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_BOOMERANG_CHEST, true),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_FORKED_CORRIDOR, {[]{return true;}}),
});
areaTable[RR_JABU_JABUS_BELLY_MAP_ROOM] = Region("Jabu Jabus Belly Map Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_MAP_CHEST, logic->CanUse(RG_BOOMERANG)),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_FORKED_CORRIDOR, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_B1_NORTH, {[]{return logic->IsAdult || logic->HasItem(RG_BRONZE_SCALE);}}),
Entrance(RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_LEDGE, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->HasItem(RG_HOVER_BOOTS);}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return logic->CanUseProjectile();}}),
});
areaTable[RR_JABU_JABUS_BELLY_COMPASS_ROOM] = Region("Jabu Jabus Belly Compass Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_COMPASS_CHEST, logic->CanAttack()),
//ruto could theoretically clear this room, but it's hard because of the timer and she doesn't appear with you when you respawn after failing, which would force a savewarp
LOCATION(RC_JABU_JABUS_BELLY_COMPASS_CHEST, logic->CanKillEnemy(RE_SHABOM)),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_FORKED_CORRIDOR, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return Here(RR_JABU_JABUS_BELLY_COMPASS_ROOM, []{return logic->CanKillEnemy(RE_SHABOM);});}}),
});
areaTable[RR_JABU_JABUS_BELLY_BLUE_TENTACLE] = Region("Jabu Jabus Belly Blue Tentacle", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
areaTable[RR_JABU_JABUS_BELLY_BLUE_TENTACLE] = Region("Jabu Jabus Belly Blue Tentacle", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
EventAccess(&logic->JabuEastTentacle, {[]{return logic->CanKillEnemy(RE_TENTACLE, ED_BOOMERANG);}}),
}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_FORKED_CORRIDOR, {[]{return Here(RR_JABU_JABUS_BELLY_BLUE_TENTACLE, []{return logic->CanUse(RG_BOOMERANG);});}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return logic->JabuEastTentacle;}}),
});
areaTable[RR_JABU_JABUS_BELLY_GREEN_TENTACLE] = Region("Jabu Jabus Belly Green Tentacle", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
areaTable[RR_JABU_JABUS_BELLY_GREEN_TENTACLE] = Region("Jabu Jabus Belly Green Tentacle", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
EventAccess(&logic->JabuNorthTentacle, {[]{return logic->CanKillEnemy(RE_TENTACLE, ED_BOOMERANG);}}),
}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_FORKED_CORRIDOR, {[]{return Here(RR_JABU_JABUS_BELLY_GREEN_TENTACLE, []{return logic->CanUse(RG_BOOMERANG);});}}),
//implied logic->CanKillEnemy(RE_BARI)
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return logic->JabuNorthTentacle;}}),
});
areaTable[RR_JABU_JABUS_BELLY_BIGOCTO_ROOM] = Region("Jabu Jabus Belly Bigocto Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
areaTable[RR_JABU_JABUS_BELLY_BIGOCTO_LEDGE] = Region("Jabu Jabus Belly Bigocto Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
//Only adult can get the token without assistance
LOCATION(RC_JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_UPPER, logic->IsAdult && logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH)),
//You can get the LOWER skull token from here as aduly with hovers backwalk and a backflip, but it's trickworthy and not relevant unless you can beat tentacles without rang
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_MAIN_LOWER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_ABOVE_BIGOCTO, {[]{return Here(RR_JABU_JABUS_BELLY_BIGOCTO_ROOM, []{return (logic->CanUse(RG_BOOMERANG) || logic->CanUse(RG_NUTS)) && (logic->CanUse(RG_KOKIRI_SWORD) || logic->CanUse(RG_STICKS));});}}),
Entrance(RR_JABU_JABUS_BELLY_B1_NORTH, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_ABOVE_BIGOCTO, {[]{return logic->JabuRutoIn1F && Here(RR_JABU_JABUS_BELLY_BIGOCTO_LEDGE, []{return logic->CanKillEnemy(RE_BIG_OCTO);});}}),
});
areaTable[RR_JABU_JABUS_BELLY_ABOVE_BIGOCTO] = Region("Jabu Jabus Belly Above Bigocto", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
//Events
EventAccess(&logic->FairyPot, {[]{return true;}}),
EventAccess(&logic->NutPot, {[]{return true;}}),
}, {}, {
}, {
//Locations
//PUT AFTER OCTO POTS HERE
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_LIFT_UPPER, {[]{return logic->CanUse(RG_BOOMERANG);}}),
});
areaTable[RR_JABU_JABUS_BELLY_LIFT_UPPER] = Region("Jabu Jabus Belly Lift Upper", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, {
areaTable[RR_JABU_JABUS_BELLY_LIFT_UPPER] = Region("Jabu Jabus Belly Lift Upper", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {
//Events
EventAccess(&logic->LoweredJabuPath, {[]{return true;}}),
}, {}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_LIFT_MIDDLE, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_LIFT_LOWER, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return true;}}),
});
areaTable[RR_JABU_JABUS_BELLY_NEAR_BOSS_ROOM] = Region("Jabu Jabus Belly Near Boss Room", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {
//Locations
LOCATION(RC_JABU_JABUS_BELLY_GS_NEAR_BOSS, logic->CanAttack()),
LOCATION(RC_JABU_JABUS_BELLY_GS_NEAR_BOSS, logic->CanKillEnemy(RE_GOLD_SKULLTULA, ED_BOMB_THROW)),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_LIFT_MIDDLE, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, {[]{return logic->CanUse(RG_BOOMERANG) || (ctx->GetTrickOption(RT_JABU_NEAR_BOSS_RANGED) && ((logic->IsAdult && (logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_FAIRY_BOW))) || (logic->IsChild && logic->CanUse(RG_FAIRY_SLINGSHOT)))) || (ctx->GetTrickOption(RT_JABU_NEAR_BOSS_EXPLOSIVES) && (logic->CanUse(RG_BOMBCHU_5) || (logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS) && logic->CanUse(RG_BOMB_BAG))));}}),
Entrance(RR_JABU_JABUS_BELLY_MAIN, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, {[]{return logic->CanUse(RG_BOOMERANG) || (ctx->GetTrickOption(RT_JABU_NEAR_BOSS_RANGED) && logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_FAIRY_SLINGSHOT)) || (ctx->GetTrickOption(RT_JABU_NEAR_BOSS_EXPLOSIVES) && (logic->CanUse(RG_BOMBCHU_5) || (logic->CanUse(RG_HOVER_BOOTS) && logic->CanUse(RG_BOMB_BAG))));}}),
});
}
@ -205,7 +207,7 @@ void RegionTable_Init_JabuJabusBelly() {
LOCATION(RC_JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_SWITCHES_CHEST, logic->CanUse(RG_FAIRY_SLINGSHOT)),
}, {
//Exits
Entrance(RR_JABU_JABUS_BELLY_MQ_BEGINNING, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MQ_LIFT_ROOM, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MQ_WATER_SWITCH_ROOM, {[]{return true;}}),
Entrance(RR_JABU_JABUS_BELLY_MQ_FORKED_CORRIDOR, {[]{return logic->CanUse(RG_BOOMERANG) && logic->HasExplosives() && Here(RR_JABU_JABUS_BELLY_MQ_HOLES_ROOM, []{return logic->CanUse(RG_FAIRY_SLINGSHOT);});}}),
Entrance(RR_JABU_JABUS_BELLY_MQ_INVISIBLE_KEESE_ROOM, {[]{return logic->JabuNorthTentacle;}}),

@ -43,7 +43,12 @@ void RegionTable_Init_ZorasDomain() {
Entrance(RR_ZR_FAIRY_GROTTO, {[]{return Here(RR_ZORAS_RIVER, []{return logic->BlastOrSmash();});}}),
Entrance(RR_THE_LOST_WOODS, {[]{return logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS);}}),
Entrance(RR_ZR_STORMS_GROTTO, {[]{return logic->CanOpenStormsGrotto();}}),
Entrance(RR_ZR_BEHIND_WATERFALL, {[]{return logic->CanUse(RG_ZELDAS_LULLABY) || (logic->IsChild && ctx->GetTrickOption(RT_ZR_CUCCO)) || (logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_ZR_HOVERS));}}),
Entrance(RR_ZR_BEHIND_WATERFALL, {[]{
return ctx->GetOption(RSK_SLEEPING_WATERFALL).Is(RO_WATERFALL_OPEN) ||
Here(RR_ZORAS_RIVER, []{return logic->CanUse(RG_ZELDAS_LULLABY);}) ||
(logic->IsChild && ctx->GetTrickOption(RT_ZR_CUCCO)) ||
(logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_ZR_HOVERS));
}}),
});
areaTable[RR_ZR_BEHIND_WATERFALL] = Region("ZR Behind Waterfall", "Zora River", {RA_ZORAS_RIVER}, DAY_NIGHT_CYCLE, {}, {}, {

@ -80,14 +80,6 @@ int Playthrough_Init(uint32_t seed, std::set<RandomizerCheck> excludedLocations,
SPDLOG_ERROR("Writing Spoiler Log Failed");
}
StopPerformanceTimer(PT_SPOILER_LOG);
#ifdef ENABLE_DEBUG
SPDLOG_INFO("Writing Placement Log...");
if (PlacementLog_Write()) {
SPDLOG_INFO("Writing Placement Log Done");
} else {
SPDLOG_ERROR("Writing Placement Log Failed");
}
#endif
}
ctx->playthroughLocations.clear();

@ -156,7 +156,7 @@ int GetPriceFromMax(int max) {
uint16_t GetPriceFromSettings(Rando::Location *loc, PriceSettingsStruct priceSettings) {
auto ctx = Rando::Context::GetInstance();
switch (ctx->GetOption(priceSettings.main).Value<uint8_t>()){
switch (ctx->GetOption(priceSettings.main).GetContextOptionIndex()){
case RO_PRICE_VANILLA:
return loc->GetVanillaPrice();
case RO_PRICE_CHEAP_BALANCED:
@ -172,19 +172,19 @@ uint16_t GetPriceFromSettings(Rando::Location *loc, PriceSettingsStruct priceSet
return 150;
}
case RO_PRICE_FIXED:
return (uint16_t)ctx->GetOption(priceSettings.fixedPrice).Value<uint8_t>() * 5;
return (uint16_t)ctx->GetOption(priceSettings.fixedPrice).GetContextOptionIndex() * 5;
case RO_PRICE_RANGE:{
uint16_t range1 = (uint16_t)ctx->GetOption(priceSettings.range1).Value<uint8_t>() * 5;
uint16_t range2 = (uint16_t)ctx->GetOption(priceSettings.range2).Value<uint8_t>() * 5;
uint16_t range1 = (uint16_t)ctx->GetOption(priceSettings.range1).GetContextOptionIndex() * 5;
uint16_t range2 = (uint16_t)ctx->GetOption(priceSettings.range2).GetContextOptionIndex() * 5;
return range1 < range2 ? Random(range1, range2+1) : Random(range2, range1+1);
}
case RO_PRICE_SET_BY_WALLET:{
bool isTycoon = ctx->GetOption(RSK_INCLUDE_TYCOON_WALLET).Value<bool>();
uint16_t noWeight = ctx->GetOption(priceSettings.noWallet).Value<uint8_t>();
uint16_t childWeight = ctx->GetOption(priceSettings.childWallet).Value<uint8_t>();
uint16_t adultWeight = ctx->GetOption(priceSettings.adultWallet).Value<uint8_t>();
uint16_t giantWeight = ctx->GetOption(priceSettings.giantWallet).Value<uint8_t>();
uint16_t tycoonWeight = isTycoon ? ctx->GetOption(priceSettings.tycoonWallet).Value<uint8_t>() : 0;
bool isTycoon = ctx->GetOption(RSK_INCLUDE_TYCOON_WALLET).GetContextOptionIndex();
uint16_t noWeight = ctx->GetOption(priceSettings.noWallet).GetContextOptionIndex();
uint16_t childWeight = ctx->GetOption(priceSettings.childWallet).GetContextOptionIndex();
uint16_t adultWeight = ctx->GetOption(priceSettings.adultWallet).GetContextOptionIndex();
uint16_t giantWeight = ctx->GetOption(priceSettings.giantWallet).GetContextOptionIndex();
uint16_t tycoonWeight = isTycoon ? ctx->GetOption(priceSettings.tycoonWallet).GetContextOptionIndex() : 0;
uint16_t totalWeight = noWeight + childWeight + adultWeight + giantWeight + tycoonWeight;
if (totalWeight == 0){ //if no weight, return from sane range
return Random(0, 501);

@ -6,7 +6,6 @@
#include "../entrance.h"
#include "random.hpp"
#include "../trial.h"
#include "tinyxml2.h"
#include "utils.hpp"
#include "hints.hpp"
#include "pool_functions.hpp"
@ -56,9 +55,6 @@ void GenerateHash() {
int number = std::stoi(hash.substr(j, 2));
ctx->hashIconIndexes[i] = number;
}
// Clear out spoiler log data here, in case we aren't going to re-generate it
// spoilerData = { 0 };
}
static auto GetGeneralPath() {
@ -79,7 +75,6 @@ static void WriteLocation(
Rando::Location* location = Rando::StaticData::GetLocation(locationKey);
Rando::ItemLocation* itemLocation = Rando::Context::GetInstance()->GetItemLocation(locationKey);
// auto node = parentNode->InsertNewChildElement("location");
switch (gSaveContext.language) {
case LANGUAGE_ENG:
default:
@ -89,35 +84,6 @@ static void WriteLocation(
jsonData["playthrough"][sphere][location->GetName()] = itemLocation->GetPlacedItemName().GetFrench();
break;
}
// node->SetAttribute("name", location->GetName().c_str());
// node->SetText(location->GetPlacedItemName().GetEnglish().c_str());
// if (withPadding) {
// constexpr int16_t LONGEST_NAME = 56; // The longest name of a location.
// constexpr int16_t PRICE_ATTRIBUTE = 12; // Length of a 3-digit price attribute.
// // Insert a padding so we get a kind of table in the XML document.
// int16_t requiredPadding = LONGEST_NAME - location->GetName().length();
// if (location->GetRCType() == RCTYPE_SHOP) {
// // Shop items have short location names, but come with an additional price attribute.
// requiredPadding -= PRICE_ATTRIBUTE;
// }
// if (requiredPadding >= 0) {
// std::string padding(requiredPadding, ' ');
// node->SetAttribute("_", padding.c_str());
// }
// }
// if (location->GetRCType() == RCTYPE_SHOP) {
// char price[6];
// sprintf(price, "%03d", location->GetPrice());
// node->SetAttribute("price", price);
// }
// if (!location->IsAddedToPool()) {
// #ifdef ENABLE_DEBUG
// node->SetAttribute("not-added", true);
// #endif
// }
}
//Writes a shuffled entrance to the specified node
@ -176,7 +142,7 @@ static void WriteSettings() {
auto allOptionGroups = ctx->GetSettings()->GetOptionGroups();
for (const Rando::OptionGroup& optionGroup : allOptionGroups) {
if (optionGroup.GetContainsType() == Rando::OptionGroupType::DEFAULT && optionGroup.PrintInSpoiler()) {
for (const Rando::Option* option : optionGroup.GetOptions()) {
for (Rando::Option* option : optionGroup.GetOptions()) {
std::string settingName = optionGroup.GetName() + ":" + option->GetName();
jsonData["settings"][settingName] = option->GetSelectedOptionText();
}
@ -186,26 +152,18 @@ static void WriteSettings() {
// Writes the excluded locations to the spoiler log, if there are any.
static void WriteExcludedLocations() {
// auto parentNode = spoilerLog.NewElement("excluded-locations");
auto ctx = Rando::Context::GetInstance();
for (size_t i = 1; i < ctx->GetSettings()->GetExcludeLocationsOptions().size(); i++) {
for (const auto& location : ctx->GetSettings()->GetExcludeLocationsOptions()[i]) {
if (location->GetSelectedOptionIndex() == RO_LOCATION_INCLUDE) {
if (location->GetContextOptionIndex() == RO_LOCATION_INCLUDE) {
continue;
}
jsonData["excludedLocations"].push_back(RemoveLineBreaks(location->GetName()));
// tinyxml2::XMLElement* node = spoilerLog.NewElement("location");
// node->SetAttribute("name", RemoveLineBreaks(location->GetName()).c_str());
// parentNode->InsertEndChild(node);
}
}
// if (!parentNode->NoChildren()) {
// spoilerLog.RootElement()->InsertEndChild(parentNode);
// }
}
// Writes the starting inventory to the spoiler log, if there is any.
@ -214,63 +172,27 @@ static void WriteStartingInventory() {
const Rando::OptionGroup& optionGroup = ctx->GetSettings()->GetOptionGroup(RSG_STARTING_INVENTORY);
for (const Rando::OptionGroup* subGroup : optionGroup.GetSubGroups()) {
if (subGroup->GetContainsType() == Rando::OptionGroupType::DEFAULT) {
for (const Rando::Option* option : subGroup->GetOptions()) {
for (Rando::Option* option : subGroup->GetOptions()) {
jsonData["settings"][option->GetName()] = option->GetSelectedOptionText();
}
}
}
}
// Writes the enabled tricks to the spoiler log, if there are any.
static void WriteEnabledTricks(tinyxml2::XMLDocument& spoilerLog) {
//auto parentNode = spoilerLog.NewElement("enabled-tricks");
//Writes the enabled tricks to the spoiler log, if there are any.
static void WriteEnabledTricks() {
auto ctx = Rando::Context::GetInstance();
for (const auto& setting : ctx->GetSettings()->GetOptionGroup(RSG_TRICKS).GetOptions()) {
if (setting->GetSelectedOptionIndex() != RO_GENERIC_ON/* || !setting->IsCategory(OptionCategory::Setting)*/) {
if (setting->GetContextOptionIndex() != RO_GENERIC_ON) {
continue;
}
jsonData["enabledTricks"].push_back(RemoveLineBreaks(setting->GetName()).c_str());
//auto node = parentNode->InsertNewChildElement("trick");
//node->SetAttribute("name", RemoveLineBreaks(setting->GetName()).c_str());
}
// if (!parentNode->NoChildren()) {
// spoilerLog.RootElement()->InsertEndChild(parentNode);
//}
}
// Writes the enabled glitches to the spoiler log, if there are any.
// TODO: Implement Glitches
// static void WriteEnabledGlitches(tinyxml2::XMLDocument& spoilerLog) {
// auto parentNode = spoilerLog.NewElement("enabled-glitches");
// for (const auto& setting : Settings::glitchCategories) {
// if (setting->Value<uint8_t>() == 0) {
// continue;
// }
// auto node = parentNode->InsertNewChildElement("glitch-category");
// node->SetAttribute("name", setting->GetName().c_str());
// node->SetText(setting->GetSelectedOptionText().c_str());
// }
// for (const auto& setting : Settings::miscGlitches) {
// if (!setting->Value<bool>()) {
// continue;
// }
// auto node = parentNode->InsertNewChildElement("misc-glitch");
// node->SetAttribute("name", RemoveLineBreaks(setting->GetName()).c_str());
// }
// if (!parentNode->NoChildren()) {
// spoilerLog.RootElement()->InsertEndChild(parentNode);
// }
// }
// Writes the Master Quest dungeons to the spoiler log, if there are any.
static void WriteMasterQuestDungeons(tinyxml2::XMLDocument& spoilerLog) {
static void WriteMasterQuestDungeons() {
auto ctx = Rando::Context::GetInstance();
for (const auto* dungeon : ctx->GetDungeons()->GetDungeonList()) {
std::string dungeonName;
@ -294,7 +216,6 @@ static void WriteRequiredTrials() {
// Writes the intended playthrough to the spoiler log, separated into spheres.
static void WritePlaythrough() {
// auto playthroughNode = spoilerLog.NewElement("playthrough");
auto ctx = Rando::Context::GetInstance();
for (uint32_t i = 0; i < ctx->playthroughLocations.size(); ++i) {
@ -306,8 +227,6 @@ static void WritePlaythrough() {
WriteLocation(sphereString, key, true);
}
}
// spoilerLog.RootElement()->InsertEndChild(playthroughNode);
}
//Write the randomized entrance playthrough to the spoiler log, if applicable
@ -389,11 +308,6 @@ static void WriteAllLocations() {
const char* SpoilerLog_Write() {
auto ctx = Rando::Context::GetInstance();
auto spoilerLog = tinyxml2::XMLDocument(false);
spoilerLog.InsertEndChild(spoilerLog.NewDeclaration());
auto rootNode = spoilerLog.NewElement("spoiler-log");
spoilerLog.InsertEndChild(rootNode);
jsonData.clear();
@ -413,11 +327,8 @@ const char* SpoilerLog_Write() {
WriteSettings();
WriteExcludedLocations();
WriteStartingInventory();
WriteEnabledTricks(spoilerLog); //RANDOTODO clean up spoilerLog refernces
//if (Settings::Logic.Is(LOGIC_GLITCHED)) {
// WriteEnabledGlitches(spoilerLog);
//}
WriteMasterQuestDungeons(spoilerLog);
WriteEnabledTricks();
WriteMasterQuestDungeons();
WriteRequiredTrials();
WritePlaythrough();
@ -466,30 +377,3 @@ void PlacementLog_Clear() {
placementtxt = "";
}
// RANDOTODO: Do we even use this?
bool PlacementLog_Write() {
auto placementLog = tinyxml2::XMLDocument(false);
placementLog.InsertEndChild(placementLog.NewDeclaration());
auto rootNode = placementLog.NewElement("placement-log");
placementLog.InsertEndChild(rootNode);
// rootNode->SetAttribute("version", Settings::version.c_str());
// rootNode->SetAttribute("seed", Settings::seed);
// WriteSettings(placementLog, true); // Include hidden settings.
// WriteExcludedLocations(placementLog);
// WriteStartingInventory(placementLog);
WriteEnabledTricks(placementLog);
//WriteEnabledGlitches(placementLog);
WriteMasterQuestDungeons(placementLog);
//WriteRequiredTrials(placementLog);
placementtxt = "\n" + placementtxt;
auto node = rootNode->InsertNewChildElement("log");
auto contentNode = node->InsertNewText(placementtxt.c_str());
contentNode->SetCData(true);
return true;
}

@ -112,7 +112,7 @@ void GenerateStartingInventory() {
// AddItemToInventory(RG_EMPTY_BOTTLE, 1);
// }
// AddItemToInventory(RG_RUTOS_LETTER, StartingRutoBottle.Value<uint8_t>());
AddItemToInventory(RG_PROGRESSIVE_OCARINA, ctx->GetOption(RSK_STARTING_OCARINA).Value<uint8_t>());
AddItemToInventory(RG_PROGRESSIVE_OCARINA, ctx->GetOption(RSK_STARTING_OCARINA).GetContextOptionIndex());
AddItemToInventory(RG_ZELDAS_LULLABY, ctx->GetOption(RSK_STARTING_ZELDAS_LULLABY) ? 1 : 0);
AddItemToInventory(RG_EPONAS_SONG, ctx->GetOption(RSK_STARTING_EPONAS_SONG) ? 1 : 0);
AddItemToInventory(RG_SARIAS_SONG, ctx->GetOption(RSK_STARTING_SARIAS_SONG) ? 1 : 0);
@ -153,21 +153,21 @@ void GenerateStartingInventory() {
// AddItemToInventory(RG_SPIRIT_MEDALLION, StartingSpiritMedallion.Value<uint8_t>());
// AddItemToInventory(RG_SHADOW_MEDALLION, StartingShadowMedallion.Value<uint8_t>());
// AddItemToInventory(RG_LIGHT_MEDALLION, StartingLightMedallion.Value<uint8_t>());
AddItemToInventory(RG_GOLD_SKULLTULA_TOKEN, ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Value<uint8_t>());
AddItemToInventory(RG_GOLD_SKULLTULA_TOKEN, ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).GetContextOptionIndex());
int8_t hearts = ctx->GetOption(RSK_STARTING_HEARTS).Value<uint8_t>() - 2;
int8_t hearts = ctx->GetOption(RSK_STARTING_HEARTS).GetContextOptionIndex() - 2;
AdditionalHeartContainers = 0;
if (hearts < 0) {
AddItemToInventory(RG_PIECE_OF_HEART, 4);
// Plentiful and minimal have less than 4 standard pieces of heart so also replace the winner heart
if (ctx->GetOption(RSK_ITEM_POOL).Value<uint8_t>() == 0 || ctx->GetOption(RSK_ITEM_POOL).Value<uint8_t>() == 3) {
if (ctx->GetOption(RSK_ITEM_POOL).GetContextOptionIndex() == 0 || ctx->GetOption(RSK_ITEM_POOL).GetContextOptionIndex() == 3) {
AddItemToInventory(RG_TREASURE_GAME_HEART);
}
AdditionalHeartContainers = 1 - hearts;
} else if (hearts > 0) {
// 16 containers in plentiful, 8 in balanced and 0 in the others
uint8_t maxContainers = 8 * std::max(0, 2 - ctx->GetOption(RSK_ITEM_POOL).Value<uint8_t>());
uint8_t maxContainers = 8 * std::max(0, 2 - ctx->GetOption(RSK_ITEM_POOL).GetContextOptionIndex());
if (hearts <= maxContainers) {
AddItemToInventory(RG_HEART_CONTAINER, hearts);

@ -270,6 +270,16 @@ Rando::Item plandomizerRandoRetrieveItem(RandomizerGet randoGetItem) {
return randoGetItemEntry;
}
void PlandoPushImageButtonStyle(){
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));
}
void PlandoPopImageButtonStyle(){
ImGui::PopStyleColor(3);
}
ImVec4 plandomizerGetItemColor(Rando::Item randoItem) {
itemColor = ImVec4( 1.0f, 1.0f, 1.0f, 1.0f );
if (randoItem.GetItemType() == ITEMTYPE_SMALLKEY || randoItem.GetItemType() == ITEMTYPE_FORTRESS_SMALLKEY
@ -698,6 +708,7 @@ void PlandomizerOverlayText(std::pair<Rando::Item, uint32_t> drawObject ) {
void PlandomizerDrawItemPopup(uint32_t index) {
if (shouldPopup && ImGui::BeginPopup("ItemList")) {
PlandoPushImageButtonStyle();
ImGui::SeparatorText("Resources");
ImGui::BeginTable("Infinite Item Table", 7);
for (auto& item : infiniteItemList) {
@ -759,6 +770,7 @@ void PlandomizerDrawItemPopup(uint32_t index) {
PlandomizerRemoveFromItemList(drawnItemsList[temporaryItemIndex].first);
PlandomizerAddToItemList(temporaryItem);
}
PlandoPopImageButtonStyle();
ImGui::EndTable();
ImGui::EndPopup();
}
@ -767,6 +779,7 @@ void PlandomizerDrawItemPopup(uint32_t index) {
void PlandomizerDrawIceTrapPopUp(uint32_t index) {
if (shouldTrapPopup && ImGui::BeginPopup("TrapList")) {
ImGui::BeginTable("Ice Trap Table", 8);
PlandoPushImageButtonStyle();
for (auto& items : itemImageMap) {
if (items.first == RG_ICE_TRAP) {
continue;
@ -785,6 +798,7 @@ void PlandomizerDrawIceTrapPopUp(uint32_t index) {
ImGui::PopID();
}
PlandoPopImageButtonStyle();
ImGui::EndTable();
ImGui::EndPopup();
}
@ -792,12 +806,14 @@ void PlandomizerDrawIceTrapPopUp(uint32_t index) {
void PlandomizerDrawItemSlots(uint32_t index) {
ImGui::PushID(index);
PlandoPushImageButtonStyle();
PlandomizerItemImageCorrection(plandoLogData[index].checkRewardItem);
if (ImGui::ImageButton(textureID, imageSize, textureUV0, textureUV1, imagePadding, ImVec4(0, 0, 0, 0), itemColor)) {
shouldPopup = true;
temporaryItem = plandoLogData[index].checkRewardItem;
ImGui::OpenPopup("ItemList");
};
PlandoPopImageButtonStyle();
UIWidgets::Tooltip(plandoLogData[index].checkRewardItem.GetName().english.c_str());
PlandomizerOverlayText(std::make_pair(plandoLogData[index].checkRewardItem, 1));
PlandomizerDrawItemPopup(index);
@ -808,9 +824,19 @@ void PlandomizerDrawShopSlider(uint32_t index) {
ImGui::PushID(index);
ImGui::Text("Price:");
ImGui::SameLine();
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 20.0f);
std::string MinusBTNName = " - ##Price";
if (ImGui::Button(MinusBTNName.c_str()) && plandoLogData[index].shopPrice > 0) {
plandoLogData[index].shopPrice--;
}
ImGui::SameLine();
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 40.0f);
ImGui::SliderInt("", &plandoLogData[index].shopPrice, 0, 999, "%d Rupees");
ImGui::PopItemWidth();
ImGui::SameLine();
std::string PlusBTNName = " + ##Price";
if (ImGui::Button(PlusBTNName.c_str()) && plandoLogData[index].shopPrice < 999) {
plandoLogData[index].shopPrice++;
}
ImGui::PopID();
}
@ -825,10 +851,12 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) {
ImGui::TableNextColumn();
PlandomizerItemImageCorrection(plandoLogData[index].iceTrapModel);
PlandoPushImageButtonStyle();
if (ImGui::ImageButton(textureID, imageSize, textureUV0, textureUV1, imagePadding, ImVec4(0, 0, 0, 0), itemColor)) {
shouldTrapPopup = true;
ImGui::OpenPopup("TrapList");
};
PlandoPopImageButtonStyle();
UIWidgets::Tooltip(plandoLogData[index].iceTrapModel.GetName().english.c_str());
PlandomizerDrawIceTrapPopUp(index);
ImGui::SameLine();
@ -847,7 +875,7 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) {
plandoLogData[index].iceTrapName = trapTextInput.c_str();
}
if (plandoLogData[index].shopPrice > -1) {
if (plandoLogData[index].shopPrice >= 0) {
PlandomizerDrawShopSlider(index);
}
ImGui::EndTable();
@ -860,7 +888,7 @@ void PlandomizerDrawOptions() {
ImGui::TableNextColumn();
ImGui::SeparatorText("Load/Save Spoiler Log");
PlandomizerPopulateSeedList();
static int32_t selectedList = 0;
static size_t selectedList = 0;
if (existingSeedList.size() != 0) {
if (ImGui::BeginCombo("##JsonFiles", existingSeedList[selectedList].c_str())) {
for (size_t i = 0; i < existingSeedList.size(); i++) {
@ -895,15 +923,13 @@ void PlandomizerDrawOptions() {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetContentRegionAvail().x * 0.5f) - (34.0f * 5.0f));
if (spoilerLogData.size() > 0) {
ImGui::BeginTable("HashIcons", 5);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));
for (int i = 0; i < 5; i++) {
ImGui::TableSetupColumn("Icon", ImGuiTableColumnFlags_WidthFixed, 34.0f);
}
ImGui::TableNextColumn();
int32_t index = 0;
size_t index = 0;
PlandoPushImageButtonStyle();
for (auto& hash : plandoHash) {
ImGui::PushID(index);
textureID = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(gSeedTextures[hash].tex);
@ -930,7 +956,7 @@ void PlandomizerDrawOptions() {
ImGui::PopID();
index++;
}
ImGui::PopStyleColor(3);
PlandoPopImageButtonStyle();
ImGui::EndTable();
} else {
ImGui::Text("No Spoiler Log Loaded");
@ -1039,9 +1065,6 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) {
ImGui::TableSetupColumn("Additional Options");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));
for (auto& spoilerData : spoilerLogData) {
auto checkID = Rando::StaticData::locationNameToEnum[spoilerData.checkName];
@ -1070,7 +1093,6 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) {
}
index++;
}
ImGui::PopStyleColor(3);
ImGui::EndTable();
ImGui::EndChild();
}

@ -672,11 +672,8 @@ bool EntranceShuffler::PlaceOneWayPriorityEntrance(
}
}
}
#ifdef ENABLE_DEBUG
auto message = "ERROR: Unable to place priority one-way entrance for " + priorityName + "\n";
SPDLOG_DEBUG(message);
PlacementLog_Write();
#endif
SPDLOG_DEBUG("ERROR: Unable to place priority one-way entrance for " + priorityName + "\n");
assert(false);
return false;
}
@ -1333,12 +1330,12 @@ int EntranceShuffler::ShuffleAllEntrances() {
(ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES) ? 1 : 0) + (ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES) ? 1 : 0) +
(ctx->GetOption(RSK_MIX_GROTTO_ENTRANCES) ? 1 : 0);
if (totalMixedPools < 2) {
ctx->GetOption(RSK_MIXED_ENTRANCE_POOLS).SetSelectedIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_BOSS_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_GROTTO_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIXED_ENTRANCE_POOLS).SetContextIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES).SetContextIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_BOSS_ENTRANCES).SetContextIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES).SetContextIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES).SetContextIndex(RO_GENERIC_OFF);
ctx->GetOption(RSK_MIX_GROTTO_ENTRANCES).SetContextIndex(RO_GENERIC_OFF);
}
if (ctx->GetOption(RSK_MIXED_ENTRANCE_POOLS)) {
std::set<EntranceType> poolsToMix = {};

@ -17,7 +17,7 @@ extern PlayState* gPlayState;
#define FSi OTRGlobals::Instance->gRandoContext->GetFishsanity()
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex()
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetContextOptionIndex()
/**
* @brief Parallel list of pond fish checks for both ages
@ -59,6 +59,7 @@ namespace Rando {
const FishIdentity Fishsanity::defaultIdentity = { RAND_INF_MAX, RC_UNKNOWN_CHECK };
bool Fishsanity::fishsanityHelpersInit = false;
s16 Fishsanity::fishGroupCounter = 0;
bool Fishsanity::enableAdvance = false;
std::unordered_map<RandomizerCheck, LinkAge> Fishsanity::pondFishAgeMap;
std::vector<RandomizerCheck> Fishsanity::childPondFish;
std::vector<RandomizerCheck> Fishsanity::adultPondFish;
@ -395,22 +396,6 @@ namespace Rando {
}
}
void Fishsanity::OnFlagSetHandler(int16_t flagType, int16_t flag) {
if (flagType != FLAG_RANDOMIZER_INF) {
return;
}
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromRandomizerInf((RandomizerInf)flag);
FishsanityCheckType fsType = Rando::Fishsanity::GetCheckType(rc);
if (fsType == FSC_NONE) {
return;
}
// When a pond fish is caught, advance the pond.
if (fsType == FSC_POND) {
OTRGlobals::Instance->gRandoContext->GetFishsanity()->AdvancePond();
}
}
void Fishsanity::OnActorUpdateHandler(void* refActor) {
if (gPlayState->sceneNum != SCENE_GROTTOS && gPlayState->sceneNum != SCENE_ZORAS_DOMAIN && gPlayState->sceneNum != SCENE_FISHING_POND) {
return;
@ -428,6 +413,7 @@ namespace Rando {
FishIdentity identity = OTRGlobals::Instance->gRandomizer->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;
@ -483,6 +469,13 @@ namespace Rando {
}
}
}
void Fishsanity::OnItemReceiveHandler(GetItemEntry itemEntry) {
if (enableAdvance) {
enableAdvance = false;
OTRGlobals::Instance->gRandoContext->GetFishsanity()->AdvancePond();
}
}
} // namespace Rando
// C interface

@ -133,11 +133,6 @@ class Fishsanity {
*/
static void OnActorInitHandler(void* refActor);
/**
* @brief FlagSet hook handler for fishsanity
*/
static void OnFlagSetHandler(int16_t flagType, int16_t flag);
/**
* @brief PlayerUpdate hook handler for fishsanity
*/
@ -158,6 +153,8 @@ class Fishsanity {
*/
static void OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs);
static void OnItemReceiveHandler(GetItemEntry itemEntry);
private:
/**
* @brief Initialize helper statics if they have not been initialized yet
@ -184,6 +181,7 @@ class Fishsanity {
static bool fishsanityHelpersInit;
static s16 fishGroupCounter;
static bool enableAdvance;
/////////////////////////////////////////////////////////
//// Helper data structures derived from static data ////

@ -559,23 +559,23 @@ CustomMessage Hint::GetBridgeReqsText() {
}
else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_STONES)) {
bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_STONES_HINT].GetHintMessage();
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).Value<uint8_t>());
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_MEDALLIONS)) {
bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_MEDALLIONS_HINT].GetHintMessage();
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Value<uint8_t>());
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS)) {
bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_REWARDS_HINT].GetHintMessage();
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Value<uint8_t>());
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS)) {
bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_DUNGEONS_HINT].GetHintMessage();
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Value<uint8_t>());
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) {
bridgeMessage = StaticData::hintTextTable[RHT_BRIDGE_TOKENS_HINT].GetHintMessage();
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Value<uint8_t>());
bridgeMessage.InsertNumber(ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG)) {
return StaticData::hintTextTable[RHT_BRIDGE_GREG_HINT].GetHintMessage();
@ -613,23 +613,23 @@ CustomMessage Hint::GetGanonBossKeyText() {
}
else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_STONES)) {
ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_STONES_HINT].GetHintMessage();
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_STONE_COUNT).Value<uint8_t>());
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_STONE_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_MEDALLIONS)) {
ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_MEDALLIONS_HINT].GetHintMessage();
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Value<uint8_t>());
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_MEDALLION_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_REWARDS)) {
ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_REWARDS_HINT].GetHintMessage();
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_REWARD_COUNT).Value<uint8_t>());
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_REWARD_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS)) {
ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_DUNGEONS_HINT].GetHintMessage();
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Value<uint8_t>());
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) {
ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_TOKENS_HINT].GetHintMessage();
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_TOKEN_COUNT).Value<uint8_t>());
ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_TOKEN_COUNT).GetContextOptionIndex());
}
else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_TRIFORCE_HUNT)) {
return StaticData::hintTextTable[RHT_GANON_BK_TRIFORCE_HINT].GetHintMessage();

@ -51,6 +51,7 @@ extern "C" {
#include "src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.h"
#include "src/overlays/actors/ovl_En_Xc/z_en_xc.h"
#include "src/overlays/actors/ovl_Fishing/z_fishing.h"
#include "src/overlays/actors/ovl_En_Mk/z_en_mk.h"
#include "adult_trade_shuffle.h"
#include "draw.h"
@ -60,9 +61,10 @@ extern void func_8084DFAC(PlayState* play, Player* player);
extern void Player_SetupActionPreserveAnimMovement(PlayState* play, Player* player, PlayerActionFunc actionFunc, s32 flags);
extern s32 Player_SetupWaitForPutAway(PlayState* play, Player* player, AfterPutAwayFunc func);
extern void Play_InitEnvironment(PlayState * play, s16 skyboxId);
extern void EnMk_Wait(EnMk* enMk, PlayState* play);
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex()
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetContextOptionIndex()
bool LocMatchesQuest(Rando::Location loc) {
if (loc.GetQuest() == RCQUEST_BOTH) {
@ -619,11 +621,11 @@ void func_8083A434_override(PlayState* play, Player* player) {
bool ShouldGiveFishingPrize(f32 sFishOnHandLength){
// RANDOTODO: update the enhancement sliders to not allow
// values above rando fish weight values when rando'd
if(LINK_IS_CHILD) {
if(LINK_IS_CHILD) {
int32_t weight = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) ? CVarGetInteger(CVAR_ENHANCEMENT("MinimumFishWeightChild"), 10) : 10;
f32 score = sqrt(((f32)weight - 0.5f) / 0.0036f);
return sFishOnHandLength >= score && (IS_RANDO ? !Flags_GetRandomizerInf(RAND_INF_CHILD_FISHING) : !(HIGH_SCORE(HS_FISHING) & HS_FISH_PRIZE_CHILD));
} else
} else
{
int32_t weight = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) ? CVarGetInteger(CVAR_ENHANCEMENT("MinimumFishWeightAdult"), 13) : 13;
f32 score = sqrt(((f32)weight - 0.5f) / 0.0036f);
@ -631,6 +633,147 @@ bool ShouldGiveFishingPrize(f32 sFishOnHandLength){
}
}
void RandomizerOnDialogMessageHandler() {
MessageContext *msgCtx = &gPlayState->msgCtx;
Actor *actor = msgCtx->talkActor;
auto ctx = Rando::Context::GetInstance();
bool revealMerchant = ctx->GetOption(RSK_MERCHANT_TEXT_HINT).GetContextOptionIndex() != RO_GENERIC_OFF;
bool nonBeanMerchants = ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL_BUT_BEANS) ||
ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL);
RandomizerCheck reveal = RC_UNKNOWN_CHECK;
if (ctx->GetOption(RSK_CHICKENS_HINT) && (msgCtx->textId >= TEXT_ANJU_PLEASE_BRING_MY_CUCCOS_BACK && msgCtx->textId <= TEXT_ANJU_PLEASE_BRING_1_CUCCO)) {
reveal = RC_KAK_ANJU_AS_CHILD;
} else {
switch (msgCtx->textId) {
case TEXT_SKULLTULA_PEOPLE_IM_CURSED:
if (actor->params == 1 && ctx->GetOption(RSK_KAK_10_SKULLS_HINT)){
reveal = RC_KAK_10_GOLD_SKULLTULA_REWARD;
} else if (actor->params == 2 && ctx->GetOption(RSK_KAK_20_SKULLS_HINT)){
reveal = RC_KAK_20_GOLD_SKULLTULA_REWARD;
} else if (actor->params == 3 && ctx->GetOption(RSK_KAK_30_SKULLS_HINT)){
reveal = RC_KAK_30_GOLD_SKULLTULA_REWARD;
} else if (actor->params == 4 && ctx->GetOption(RSK_KAK_40_SKULLS_HINT)){
reveal = RC_KAK_40_GOLD_SKULLTULA_REWARD;
} else if (ctx->GetOption(RSK_KAK_50_SKULLS_HINT)){
reveal = RC_KAK_50_GOLD_SKULLTULA_REWARD;
}
break;
case TEXT_SKULLTULA_PEOPLE_MAKE_YOU_VERY_RICH:
if (ctx->GetOption(RSK_KAK_100_SKULLS_HINT)) {
reveal = RC_KAK_100_GOLD_SKULLTULA_REWARD;
}
break;
case TEXT_MASK_SHOP_SIGN:
if (ctx->GetOption(RSK_MASK_SHOP_HINT)) {
auto itemSkull_loc = ctx->GetItemLocation(RC_DEKU_THEATER_SKULL_MASK);
if (itemSkull_loc->GetCheckStatus() == RCSHOW_UNCHECKED) {
itemSkull_loc->SetCheckStatus(RCSHOW_IDENTIFIED);
}
reveal = RC_DEKU_THEATER_MASK_OF_TRUTH;
}
break;
case TEXT_GHOST_SHOP_EXPLAINATION:
case TEXT_GHOST_SHOP_CARD_HAS_POINTS:
if (ctx->GetOption(RSK_BIG_POES_HINT)) {
reveal = RC_MARKET_10_BIG_POES;
}
break;
case TEXT_MALON_EVERYONE_TURNING_EVIL:
case TEXT_MALON_I_SING_THIS_SONG:
case TEXT_MALON_HOW_IS_EPONA_DOING:
case TEXT_MALON_OBSTICLE_COURSE:
case TEXT_MALON_INGO_MUST_HAVE_BEEN_TEMPTED:
if (ctx->GetOption(RSK_MALON_HINT)) {
reveal = RC_KF_LINKS_HOUSE_COW;
}
break;
case TEXT_FROGS_UNDERWATER:
if (ctx->GetOption(RSK_FROGS_HINT)) {
reveal = RC_ZR_FROGS_OCARINA_GAME;
}
break;
case TEXT_GF_HBA_SIGN:
case TEXT_HBA_NOT_ON_HORSE:
case TEXT_HBA_INITIAL_EXPLAINATION:
case TEXT_HBA_ALREADY_HAVE_1000:
if (ctx->GetOption(RSK_HBA_HINT)) {
auto item1000_loc = ctx->GetItemLocation(RC_GF_HBA_1000_POINTS);
if (item1000_loc->GetCheckStatus() == RCSHOW_UNCHECKED) {
item1000_loc->SetCheckStatus(RCSHOW_IDENTIFIED);
}
reveal = RC_GF_HBA_1500_POINTS;
}
break;
case TEXT_SCRUB_RANDOM:
if (ctx->GetOption(RSK_SCRUB_TEXT_HINT).GetContextOptionIndex() != RO_GENERIC_OFF) {
EnDns* enDns = (EnDns*)actor;
reveal = OTRGlobals::Instance->gRandomizer->GetCheckFromRandomizerInf((RandomizerInf)enDns->sohScrubIdentity.randomizerInf);
}
break;
case TEXT_BEAN_SALESMAN_BUY_FOR_10:
if (revealMerchant && (ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_BEANS_ONLY) ||
ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL))) {
reveal = RC_ZR_MAGIC_BEAN_SALESMAN;
}
break;
case TEXT_GRANNYS_SHOP:
if (revealMerchant && nonBeanMerchants &&
(ctx->GetOption(RSK_SHUFFLE_ADULT_TRADE) || INV_CONTENT(ITEM_CLAIM_CHECK) == ITEM_CLAIM_CHECK)) {
reveal = RC_KAK_GRANNYS_SHOP;
}
break;
case TEXT_MEDIGORON:
if (revealMerchant && nonBeanMerchants) {
reveal = RC_GC_MEDIGORON;
}
break;
case TEXT_CARPET_SALESMAN_1:
if (revealMerchant && nonBeanMerchants) {
reveal = RC_WASTELAND_BOMBCHU_SALESMAN;
}
break;
case TEXT_BIGGORON_BETTER_AT_SMITHING:
case TEXT_BIGGORON_WAITING_FOR_YOU:
case TEXT_BIGGORON_RETURN_AFTER_A_FEW_DAYS:
case TEXT_BIGGORON_I_MAAAADE_THISSSS:
if (ctx->GetOption(RSK_BIGGORON_HINT)) {
reveal = RC_DMT_TRADE_CLAIM_CHECK;
}
break;
case TEXT_SHEIK_NEED_HOOK:
case TEXT_SHEIK_HAVE_HOOK:
if (ctx->GetOption(RSK_OOT_HINT) && gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME &&
!ctx->GetItemLocation(RC_SONG_FROM_OCARINA_OF_TIME)->HasObtained()) {
auto itemoot_loc = ctx->GetItemLocation(RC_HF_OCARINA_OF_TIME_ITEM);
if (itemoot_loc->GetCheckStatus() == RCSHOW_UNCHECKED) {
itemoot_loc->SetCheckStatus(RCSHOW_IDENTIFIED);
}
reveal = RC_SONG_FROM_OCARINA_OF_TIME;
}
break;
case TEXT_FISHING_CLOUDY:
case TEXT_FISHING_TRY_ANOTHER_LURE:
case TEXT_FISHING_SECRETS:
case TEXT_FISHING_GOOD_FISHERMAN:
case TEXT_FISHING_DIFFERENT_POND:
case TEXT_FISHING_SCRATCHING:
case TEXT_FISHING_TRY_ANOTHER_LURE_WITH_SINKING_LURE:
if (ctx->GetOption(RSK_LOACH_HINT)) {
reveal = RC_LH_HYRULE_LOACH;
}
break;
}
}
if (reveal != RC_UNKNOWN_CHECK) {
auto item_loc = ctx->GetItemLocation(reveal);
if (item_loc->GetCheckStatus() == RCSHOW_UNCHECKED) {
item_loc->SetCheckStatus(RCSHOW_IDENTIFIED);
}
}
}
void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) {
va_list args;
va_copy(args, originalArgs);
@ -860,7 +1003,8 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
// This is typically called when you close the text box after getting an item, in case a previous
// function hid the interface.
Interface_ChangeAlpha(gSaveContext.unk_13EE);
gSaveContext.unk_13EA = 0;
Interface_ChangeAlpha(0x32);
// EnItem00_SetupAction(item00, func_8001E5C8);
// *should = false;
} else if (item00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY_GI) {
@ -1399,9 +1543,23 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
}
break;
}
case VB_TRADE_TIMER_EYEDROPS:{
EnMk* enMk = va_arg(args, EnMk*);
Flags_SetRandomizerInf(RAND_INF_ADULT_TRADES_LH_TRADE_FROG);
enMk->actor.flags &= ~ACTOR_FLAG_WILL_TALK;
enMk->actionFunc = EnMk_Wait;
enMk->flags |= 1;
*should = false;
break;
}
// We need to override the vanilla behavior here because the player might sequence break and get Ruto kidnapped before accessing other
// checks that require Ruto. So if she's kidnapped we allow her to spawn again
case VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED: {
*should = !Flags_GetInfTable(INFTABLE_145) || Flags_GetInfTable(INFTABLE_146);
break;
}
case VB_FREEZE_ON_SKULL_TOKEN:
case VB_TRADE_TIMER_ODD_MUSHROOM:
case VB_TRADE_TIMER_EYEDROPS:
case VB_TRADE_TIMER_FROG:
case VB_ANJU_SET_OBTAINED_TRADE_ITEM:
case VB_GIVE_ITEM_FROM_TARGET_IN_WOODS:
@ -2107,6 +2265,7 @@ void RandomizerRegisterHooks() {
static uint32_t onPlayerUpdateForRCQueueHook = 0;
static uint32_t onPlayerUpdateForItemQueueHook = 0;
static uint32_t onItemReceiveHook = 0;
static uint32_t onDialogMessageHook = 0;
static uint32_t onVanillaBehaviorHook = 0;
static uint32_t onSceneInitHook = 0;
static uint32_t onActorInitHook = 0;
@ -2119,10 +2278,10 @@ void RandomizerRegisterHooks() {
static uint32_t onKaleidoUpdateHook = 0;
static uint32_t fishsanityOnActorInitHook = 0;
static uint32_t fishsanityOnFlagSetHook = 0;
static uint32_t fishsanityOnActorUpdateHook = 0;
static uint32_t fishsanityOnSceneInitHook = 0;
static uint32_t fishsanityOnVanillaBehaviorHook = 0;
static uint32_t fishsanityOnItemReceiveHook = 0;
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) {
randomizerQueuedChecks = std::queue<RandomizerCheck>();
@ -2134,6 +2293,7 @@ void RandomizerRegisterHooks() {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(onPlayerUpdateForRCQueueHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(onPlayerUpdateForItemQueueHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnItemReceive>(onItemReceiveHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnItemReceive>(onDialogMessageHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnVanillaBehavior>(onVanillaBehaviorHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(onSceneInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorInit>(onActorInitHook);
@ -2146,16 +2306,17 @@ void RandomizerRegisterHooks() {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnKaleidoscopeUpdate>(onKaleidoUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorInit>(fishsanityOnActorInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnFlagSet>(fishsanityOnFlagSetHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(fishsanityOnActorUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(fishsanityOnSceneInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnVanillaBehavior>(fishsanityOnVanillaBehaviorHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnItemReceive>(fishsanityOnItemReceiveHook);
onFlagSetHook = 0;
onSceneFlagSetHook = 0;
onPlayerUpdateForRCQueueHook = 0;
onPlayerUpdateForItemQueueHook = 0;
onItemReceiveHook = 0;
onDialogMessageHook = 0;
onVanillaBehaviorHook = 0;
onSceneInitHook = 0;
onActorInitHook = 0;
@ -2168,10 +2329,10 @@ void RandomizerRegisterHooks() {
onKaleidoUpdateHook = 0;
fishsanityOnActorInitHook = 0;
fishsanityOnFlagSetHook = 0;
fishsanityOnActorUpdateHook = 0;
fishsanityOnSceneInitHook = 0;
fishsanityOnVanillaBehaviorHook = 0;
fishsanityOnItemReceiveHook = 0;
if (!IS_RANDO) return;
@ -2188,6 +2349,7 @@ void RandomizerRegisterHooks() {
onPlayerUpdateForRCQueueHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(RandomizerOnPlayerUpdateForRCQueueHandler);
onPlayerUpdateForItemQueueHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(RandomizerOnPlayerUpdateForItemQueueHandler);
onItemReceiveHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>(RandomizerOnItemReceiveHandler);
onDialogMessageHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnDialogMessage>(RandomizerOnDialogMessageHandler);
onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnVanillaBehavior>(RandomizerOnVanillaBehaviorHandler);
onSceneInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>(RandomizerOnSceneInitHandler);
onActorInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>(RandomizerOnActorInitHandler);
@ -2203,10 +2365,10 @@ void RandomizerRegisterHooks() {
OTRGlobals::Instance->gRandoContext->GetFishsanity()->InitializeFromSave();
fishsanityOnActorInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>(Rando::Fishsanity::OnActorInitHandler);
fishsanityOnFlagSetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>(Rando::Fishsanity::OnFlagSetHandler);
fishsanityOnActorUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>(Rando::Fishsanity::OnActorUpdateHandler);
fishsanityOnSceneInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>(Rando::Fishsanity::OnSceneInitHandler);
fishsanityOnVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnVanillaBehavior>(Rando::Fishsanity::OnVanillaBehaviorHandler);
fishsanityOnItemReceiveHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>(Rando::Fishsanity::OnItemReceiveHandler);
}
});
}

@ -176,8 +176,8 @@ void ItemLocation::SetHidden(const bool hidden_) {
hidden = hidden_;
}
bool ItemLocation::IsExcluded() const {
return excludedOption.Value<bool>();
bool ItemLocation::IsExcluded() {
return excludedOption.GetContextOptionIndex();
}
Option* ItemLocation::GetExcludedOption() {
@ -197,7 +197,7 @@ void ItemLocation::AddExcludeOption() {
// RANDOTODO: this without string compares and loops
bool alreadyAdded = false;
const Location* loc = StaticData::GetLocation(rc);
for (const Option* location : Context::GetInstance()->GetSettings()->GetExcludeOptionsForArea(loc->GetArea())) {
for (Option* location : Context::GetInstance()->GetSettings()->GetExcludeOptionsForArea(loc->GetArea())) {
if (location->GetName() == excludedOption.GetName()) {
alreadyAdded = true;
}

@ -45,7 +45,7 @@ class ItemLocation {
const std::vector<RandomizerHint>& GetHintedBy() const;
void AddHintedBy(RandomizerHint hintKey);
bool IsHidden() const;
bool IsExcluded() const;
bool IsExcluded();
void AddExcludeOption();
Option* GetExcludedOption();
void SetHidden(bool hidden_);

@ -415,6 +415,38 @@ namespace Rando {
bool killed = false;
switch(enemy) {
case RE_GOLD_SKULLTULA:
switch (distance){
case ED_CLOSE:
//hammer jumpslash cannot damage these, but hammer swing can
killed = CanUse(RG_MEGATON_HAMMER);
[[fallthrough]];
case ED_SHORT_JUMPSLASH:
killed = killed || CanUse(RG_KOKIRI_SWORD);
[[fallthrough]];
case ED_MASTER_SWORD_JUMPSLASH:
killed = killed || CanUse(RG_MASTER_SWORD);
[[fallthrough]];
case ED_LONG_JUMPSLASH:
killed = killed || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
[[fallthrough]];
case ED_BOMB_THROW:
killed = killed || CanUse(RG_BOMB_BAG);
[[fallthrough]];
case ED_BOOMERANG:
killed = killed || CanUse(RG_BOOMERANG) || CanUse(RG_DINS_FIRE);
[[fallthrough]];
case ED_HOOKSHOT:
//RANDOTODO test dins and chu range in a practical example
killed = killed || CanUse(RG_HOOKSHOT) || (wallOrFloor && CanUse(RG_BOMBCHU_5));
[[fallthrough]];
case ED_LONGSHOT:
killed = killed || CanUse(RG_LONGSHOT);
[[fallthrough]];
case ED_FAR:
killed = killed || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
break;
}
return killed;
case RE_GOHMA_LARVA:
case RE_MAD_SCRUB:
case RE_DEKU_BABA:
@ -618,8 +650,15 @@ namespace Rando {
case RE_PURPLE_LEEVER:
//dies on it's own, so this is the conditions to spawn it (killing 10 normal leevers)
//Sticks and Ice arrows work but will need ammo capacity logic
//other mothods can damage them but not kill them, and they run when hit, making them impractical
return CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD);
//other methods can damage them but not kill them, and they run when hit, making them impractical
return CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD);
case RE_TENTACLE:
return CanUse(RG_BOOMERANG);
case RE_BARI:
return HookshotOrBoomerang() || CanUse(RG_FAIRY_BOW) || HasExplosives() || CanUse(RG_MEGATON_HAMMER) || CanUse(RG_STICKS) || CanUse(RG_DINS_FIRE) || (TakeDamage() && (CanUse(RG_KOKIRI_SWORD) || CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD)));
case RE_SHABOM:
//RANDOTODO when you add better damage logic, you can kill this by taking hits
return CanUse(RG_BOOMERANG) || CanUse(RG_NUTS) || CanJumpslash() || CanUse(RG_DINS_FIRE) || CanUse(RG_ICE_ARROWS);
default:
SPDLOG_ERROR("CanKillEnemy reached `default`.");
assert(false);
@ -974,7 +1013,7 @@ namespace Rando {
10 for OHKO.
This is the number of shifts to apply, not a real multiplier
*/
uint8_t Multiplier = (ctx->GetOption(RSK_DAMAGE_MULTIPLIER).Value<uint8_t>() < 6) ? ctx->GetOption(RSK_DAMAGE_MULTIPLIER).Value<uint8_t>() : 10;
uint8_t Multiplier = (ctx->GetOption(RSK_DAMAGE_MULTIPLIER).GetContextOptionIndex() < 6) ? ctx->GetOption(RSK_DAMAGE_MULTIPLIER).GetContextOptionIndex() : 10;
//(Hearts() << (2 + HasItem(RG_DOUBLE_DEFENSE))) is quarter hearts after DD
//>> Multiplier halves on normal and does nothing on half, meaning we're working with half hearts on normal damage
return ((Hearts() << (2 + HasItem(RG_DOUBLE_DEFENSE))) >> Multiplier) +
@ -1103,21 +1142,21 @@ namespace Rando {
bool Logic::CanBuildRainbowBridge(){
return ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_ALWAYS_OPEN) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_VANILLA) && HasItem(RG_SHADOW_MEDALLION) && HasItem(RG_SPIRIT_MEDALLION) && CanUse(RG_LIGHT_ARROWS)) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_STONES) && StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).Value<uint8_t>()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_MEDALLIONS) && MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Value<uint8_t>()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS) && StoneCount() + MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Value<uint8_t>()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS) && DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Value<uint8_t>()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) && GetGSCount() >= ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Value<uint8_t>()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_STONES) && StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).GetContextOptionIndex()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_MEDALLIONS) && MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).GetContextOptionIndex()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS) && StoneCount() + MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).GetContextOptionIndex()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS) && DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >= ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).GetContextOptionIndex()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) && GetGSCount() >= ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).GetContextOptionIndex()) ||
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG) && HasItem(RG_GREG_RUPEE));
}
bool Logic::CanTriggerLACS(){
return (ctx->GetSettings()->LACSCondition() == RO_LACS_VANILLA && HasItem(RG_SHADOW_MEDALLION) && HasItem(RG_SPIRIT_MEDALLION)) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_STONES && StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_STONE_COUNT).Value<uint8_t>()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_MEDALLIONS && MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Value<uint8_t>()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_REWARDS && StoneCount() + MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_REWARD_COUNT).Value<uint8_t>()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_DUNGEONS && DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Value<uint8_t>()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_TOKENS && GetGSCount() >= ctx->GetOption(RSK_LACS_TOKEN_COUNT).Value<uint8_t>());
(ctx->GetSettings()->LACSCondition() == RO_LACS_STONES && StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_STONE_COUNT).GetContextOptionIndex()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_MEDALLIONS && MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_MEDALLION_COUNT).GetContextOptionIndex()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_REWARDS && StoneCount() + MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_REWARD_COUNT).GetContextOptionIndex()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_DUNGEONS && DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >= ctx->GetOption(RSK_LACS_DUNGEON_COUNT).GetContextOptionIndex()) ||
(ctx->GetSettings()->LACSCondition() == RO_LACS_TOKENS && GetGSCount() >= ctx->GetOption(RSK_LACS_TOKEN_COUNT).GetContextOptionIndex());
}
bool Logic::SmallKeys(RandomizerRegion dungeon, uint8_t requiredAmount) {
@ -2089,7 +2128,7 @@ namespace Rando {
//CanPlantBean = false;
BigPoeKill = false;
BaseHearts = ctx->GetOption(RSK_STARTING_HEARTS).Value<uint8_t>() + 1;
BaseHearts = ctx->GetOption(RSK_STARTING_HEARTS).GetContextOptionIndex() + 1;
//Bridge Requirements
@ -2145,6 +2184,7 @@ namespace Rando {
GTGPlatformSilverRupees = false;
MQJabuHolesRoomDoor = false;
JabuWestTentacle = false;
JabuEastTentacle = false;
JabuNorthTentacle = false;
LoweredJabuPath = false;
MQJabuLiftRoomCow = false;
@ -2158,6 +2198,9 @@ namespace Rando {
MQSpiritCrawlBoulder = false;
MQSpiritMapRoomEnemies = false;
MQSpirit3SunsEnemies = false;
Spirit1FSilverRupees = false;
JabuRutoInB1 = false;
JabuRutoIn1F = false;
StopPerformanceTimer(PT_LOGIC_RESET);
}

@ -156,6 +156,7 @@ class Logic {
bool GTGPlatformSilverRupees = false;
bool MQJabuHolesRoomDoor = false;
bool JabuWestTentacle = false;
bool JabuEastTentacle = false;
bool JabuNorthTentacle = false;
bool LoweredJabuPath = false;
bool MQJabuLiftRoomCow = false;
@ -171,6 +172,8 @@ class Logic {
bool MQSpiritTimeTravelChest = false;
bool MQSpirit3SunsEnemies = false;
bool Spirit1FSilverRupees = false;
bool JabuRutoInB1 = false;
bool JabuRutoIn1F = false;
/* --- END OF HELPERS AND LOCATION ACCESS --- */

@ -31,10 +31,7 @@ Option Option::LogicTrick(std::string name_) {
}
Option::operator bool() const {
if (std::holds_alternative<bool>(var)) {
return Value<bool>();
}
return Value<uint8_t>() != 0;
return contextSelection != 0;
}
size_t Option::GetOptionCount() const {
@ -49,12 +46,16 @@ const std::string& Option::GetDescription() const {
return description;
}
uint8_t Option::GetSelectedOptionIndex() const {
return selectedOption;
uint8_t Option::GetMenuOptionIndex() const {
return menuSelection;
}
uint8_t Option::GetContextOptionIndex() const {
return contextSelection;
}
const std::string& Option::GetSelectedOptionText() const {
return options[selectedOption];
return options[contextSelection];
}
const std::string& Option::GetCVarName() const {
@ -63,39 +64,45 @@ const std::string& Option::GetCVarName() const {
void Option::SetVariable() {
if (std::holds_alternative<bool>(var)) {
var.emplace<bool>(selectedOption != 0);
var.emplace<bool>(menuSelection != 0);
} else {
var.emplace<uint8_t>(selectedOption);
var.emplace<uint8_t>(menuSelection);
}
}
void Option::SetCVar() const {
void Option::SaveCVar() const {
if (!cvarName.empty()) {
CVarSetInteger(cvarName.c_str(), GetSelectedOptionIndex());
CVarSetInteger(cvarName.c_str(), GetMenuOptionIndex());
}
}
void Option::SetFromCVar() {
if (!cvarName.empty()) {
SetSelectedIndex(CVarGetInteger(cvarName.c_str(), defaultOption));
SetMenuIndex(CVarGetInteger(cvarName.c_str(), defaultOption));
}
}
void Option::SetDelayedOption() {
delayedOption = selectedOption;
delayedSelection = contextSelection;
}
void Option::RestoreDelayedOption() {
selectedOption = delayedOption;
contextSelection = delayedSelection;
}
void Option::SetMenuIndex(size_t idx) {
menuSelection = idx;
if (menuSelection > options.size() - 1) {
menuSelection = options.size() - 1;
}
SetVariable();
}
void Option::SetSelectedIndex(size_t idx) {
selectedOption = idx;
if (selectedOption > options.size() - 1) {
selectedOption = options.size() - 1;
void Option::SetContextIndex(size_t idx) {
contextSelection = idx;
if (contextSelection > options.size() - 1) {
contextSelection = options.size() - 1;
}
SetVariable();
}
void Option::Hide() {
@ -111,8 +118,8 @@ bool Option::IsHidden() const {
}
void Option::ChangeOptions(std::vector<std::string> opts) {
if (selectedOption >= opts.size()) {
selectedOption = opts.size() - 1;
if (menuSelection >= opts.size()) {
menuSelection = opts.size() - 1;
}
options = std::move(opts);
}
@ -177,7 +184,7 @@ Option::Option(uint8_t var_, std::string name_, std::vector<std::string> options
: var(var_), name(std::move(name_)), options(std::move(options_)), category(category_),
cvarName(std::move(cvarName_)), description(std::move(description_)), widgetType(widgetType_),
defaultOption(defaultOption_), defaultHidden(defaultHidden_), imFlags(imFlags_) {
selectedOption = defaultOption;
menuSelection = contextSelection = defaultOption;
hidden = defaultHidden;
SetFromCVar();
}
@ -187,7 +194,7 @@ Option::Option(bool var_, std::string name_, std::vector<std::string> options_,
: var(var_), name(std::move(name_)), options(std::move(options_)), category(category_),
cvarName(std::move(cvarName_)), description(std::move(description_)), widgetType(widgetType_),
defaultOption(defaultOption_), defaultHidden(defaultHidden_), imFlags(imFlags_) {
selectedOption = defaultOption;
menuSelection = contextSelection = defaultOption;
hidden = defaultHidden;
SetFromCVar();
}
@ -270,7 +277,7 @@ bool Option::RenderCombobox() {
bool Option::RenderSlider() {
bool changed = false;
int val = GetSelectedOptionIndex();
int val = GetMenuOptionIndex();
if (val > options.size() - 1) {
val = options.size() - 1;
CVarSetInteger(cvarName.c_str(), val);

@ -126,42 +126,25 @@ class Option {
*/
static Option LogicTrick(std::string name_);
/**
* @brief Gets the selected index or boolean value of the Option.
*
* @tparam T uint8_t or bool, depending on how the option was constructed.
* @return T
*/
template <typename T> T Value() const {
return std::get<T>(var);
}
/**
* @brief Determines if the value/selected index of this Option matches the provided value.
*
* @tparam T uint8_t, bool, or an enum (which will be cast to uint8_t).
* @param other The value to compare.
* @return true
* @return false
*/
template <typename T> bool Is(T other) const {
static_assert(std::is_integral_v<T> || std::is_enum_v<T>, "T must be an integral type or an enum.");
if constexpr ((std::is_integral_v<T> && !std::is_same_v<bool, T>) || std::is_enum_v<T>) {
return Value<uint8_t>() == static_cast<uint8_t>(other);
} else {
return Value<bool>() == static_cast<bool>(other);
}
bool Is(uint32_t other) const {
return contextSelection == other;
}
/**
* @brief Determines if the value/selected index of this Option does not match the provided value.
*
* @tparam T uint8_t, book, or an enum (which will be cast to uint8_t).
* @param other The value to compare.
* @return true
* @return false
*/
template <typename T> bool IsNot(T other) const {
bool IsNot(uint32_t other) const {
return !Is(other);
}
@ -203,11 +186,18 @@ class Option {
const std::string& GetCVarName() const;
/**
* @brief Get the selected index for this Option.
* @brief Get the menu index for this Option.
*
* @return uint8_t
*/
uint8_t GetSelectedOptionIndex() const;
uint8_t GetMenuOptionIndex() const;
/**
* @brief Get the rando context index for this Option.
*
* @return uint8_t
*/
uint8_t GetContextOptionIndex() const;
/**
* @brief Sets the variable to the currently selected index for this Option.
@ -218,7 +208,7 @@ class Option {
* @brief Sets the CVar corresponding to the property `cvarName` equal to the value
* of the property `selectedValue`.
*/
void SetCVar() const;
void SaveCVar() const;
/**
* @brief Sets the value of property `selectedValue` equal to the CVar corresponding
@ -237,11 +227,18 @@ class Option {
void RestoreDelayedOption();
/**
* @brief Set the selected index for this Option. Also calls `SetVariable()`.
* @brief Set the menu index for this Option. Also calls `SetVariable()`.
*
* @param idx the index to set as the selected index.
*/
void SetSelectedIndex(size_t idx);
void SetMenuIndex(size_t idx);
/**
* @brief Set the rando context index for this Option. Also calls `SetVariable()`.
*
* @param idx the index to set as the selected index.
*/
void SetContextIndex(size_t idx);
/**
* @brief Hides this Option in the menu. (Not currently being used afaik, we prefer to
@ -324,8 +321,9 @@ protected:
std::variant<bool, uint8_t> var;
std::string name;
std::vector<std::string> options;
uint8_t selectedOption = 0;
uint8_t delayedOption = 0;
uint8_t menuSelection = 0;
uint8_t contextSelection = 0;
uint8_t delayedSelection = 0;
bool hidden = false;
OptionCategory category = OptionCategory::Setting;
std::string cvarName;

@ -2,30 +2,30 @@
namespace Rando {
void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_FOREST] = "Closed - Kokiri sword & shield are required to access "
mOptionDescriptions[RSK_FOREST] = "Closed - Kokiri Sword & Deku Shield are required to access "
"the Deku Tree, and completing the Deku Tree is required to "
"access the Hyrule Field exit.\n"
"\n"
"Closed Deku - Kokiri boy no longer blocks the path to Hyrule "
"Field but Mido still requires the Kokiri sword and Deku shield "
"Field but Mido still requires the Kokiri Sword and Deku Shield "
"to access the tree.\n"
"\n"
"Open - Mido no longer blocks the path to the Deku Tree. Kokiri "
"boy no longer blocks the path out of the forest.";
mOptionDescriptions[RSK_KAK_GATE] = "Closed - The gate will remain closed until Zelda's letter "
mOptionDescriptions[RSK_KAK_GATE] = "Closed - The gate will remain closed until Zelda's Letter "
"is shown to the guard.\n"
"\n"
"Open - The gate is always open. The happy mask shop "
"will open immediately after obtaining Zelda's letter.";
"Open - The gate is always open. The Happy Mask Shop "
"will open immediately after obtaining Zelda's Letter.";
mOptionDescriptions[RSK_DOOR_OF_TIME] = "Closed - The Ocarina of Time, the Song of Time and all "
"three spiritual stones are required to open the Door of Time.\n"
"three Spiritual Stones are required to open the Door of Time.\n"
"\n"
"Song only - Play the Song of Time in front of the Door of "
"Time to open it.\n"
"\n"
"Open - The Door of Time is permanently open with no requirements.";
mOptionDescriptions[RSK_ZORAS_FOUNTAIN] = "Closed - King Zora obstructs the way to Zora's Fountain. "
"Ruto's letter must be shown as child Link in order to move "
"Ruto's Letter must be shown as child Link in order to move "
"him in both time periods.\n"
"\n"
"Closed as child - Ruto's Letter is only required to move King Zora "
@ -33,6 +33,12 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Open - King Zora has already mweeped out of the way in both "
"time periods. Ruto's Letter is removed from the item pool.";
mOptionDescriptions[RSK_SLEEPING_WATERFALL] = "Closed - Sleeping Waterfall obstructs the entrance to Zora's "
"Domain. Zelda's Lullaby must be played in order to open it "
"(but only once; then it stays open in both time periods).\n"
"\n"
"Open - Sleeping Waterfall is always open. "
"Link may always enter Zora's Domain.";
mOptionDescriptions[RSK_STARTING_AGE] =
"Choose which age Link will start as.\n\n"
"Starting as adult means you start with the Master Sword in your inventory.\n"
@ -54,12 +60,12 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Always open - No requirements.\n"
"\n"
"Stones - Obtain the specified amount of spiritual stones.\n"
"Stones - Obtain the specified amount of Spiritual Stones.\n"
"\n"
"Medallions - Obtain the specified amount of medallions.\n"
"\n"
"Dungeon rewards - Obtain the specified total sum of spiritual "
"stones or medallions.\n"
"Dungeon rewards - Obtain the specified total sum of Spiritual "
"Stones or medallions.\n"
"\n"
"Dungeons - Complete the specified amount of dungeons. Dungeons "
"are considered complete after stepping in to the blue warp after "
@ -83,10 +89,10 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Skip - No Trials are required and the barrier is already dispelled.\n"
"\n"
"Set Number - Select a number of trials that will be required from the"
"Set Number - Select a number of trials that will be required from the "
"slider below. Which specific trials you need to complete will be random.\n"
"\n"
"Random Number - A Random number and set of trials will be required.";
"Random Number - A random number and set of trials will be required.";
mOptionDescriptions[RSK_TRIAL_COUNT] = "Set the number of trials required to enter Ganon's Tower.";
mOptionDescriptions[RSK_MQ_DUNGEON_RANDOM] =
"Sets the number of Master Quest Dungeons that are shuffled into the pool.\n"
@ -96,7 +102,7 @@ void Settings::CreateOptionDescriptions() {
"Set Number - Select a number of dungeons that will be their Master Quest versions "
"using the slider below. Which dungeons are set to be the Master Quest variety will be random.\n"
"\n"
"Random Number - A Random number and set of dungeons will be their Master Quest varieties.\n"
"Random Number - A random number and set of dungeons will be their Master Quest varieties.\n"
"\n"
"Selection Only - Specify which dungeons are Vanilla, Master Quest or a 50/50 between the two.\n"
"Differs from Random Number in that they are rolled individually, making the exact total a bell curve.";
@ -118,14 +124,14 @@ void Settings::CreateOptionDescriptions() {
"Keep in mind seed generation can fail if more ornaments are placed than there are junk items in the item pool.";
mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = "The amount of Ornaments required to win the game.";
mOptionDescriptions[RSK_SHUFFLE_DUNGEON_ENTRANCES] =
"Shuffle the pool of dungeon entrances, including Bottom of the Well, Ice Cavern and Gerudo Training Grounds.\n"
"Shuffle the pool of dungeon entrances, including Bottom of the Well, Ice Cavern and Gerudo Training Ground.\n"
"\n"
"Shuffling Ganon's Castle can be enabled separately.\n"
"\n"
"Additionally, the entrances of Deku Tree, Fire Temple, Bottom of the Well and Gerudo Training Ground are "
"opened for both child and adult.\n"
"\n"
"- Deku Tree will be open for adult after Mido has seen child Link with a sword and shield.\n"
"- Deku Tree will be open for adult after Mido has seen child Link with a sword and a shield.\n"
"- Bottom of the Well will be open for adult after playing Song of Storms to the Windmill guy as child.\n"
"- Gerudo Training Ground will be open for child after adult has paid to open the gate once.";
mOptionDescriptions[RSK_SHUFFLE_BOSS_ENTRANCES] =
@ -171,23 +177,23 @@ void Settings::CreateOptionDescriptions() {
"This also adds the one-way entrance from Gerudo Valley to Lake Hylia in the pool of "
"overworld entrances when they are shuffled.";
mOptionDescriptions[RSK_MIXED_ENTRANCE_POOLS] =
"Shuffle entrances into a mixed pool instead of separate ones. Has no affect on pools whose "
"Shuffle entrances into a mixed pool instead of separate ones. Has no effect on pools whose "
"entrances aren't shuffled, and \"Shuffle Boss Entrances\" must be set to \"Full\" to include them.\n"
"\n"
"For example, enabling the settings to shuffle grotto, dungeon, and overworld entrances and "
"selecting grotto and dungeon entrances here will allow a dungeon to be inside a grotto or "
"vice versa, while overworld entrances are shuffled in their own separate pool and indoors stay vanilla.";
mOptionDescriptions[RSK_MIX_DUNGEON_ENTRANCES] = "Dungeon entrances will be part of the mixed pool";
mOptionDescriptions[RSK_MIX_BOSS_ENTRANCES] = "Boss entrances will be part of the mixed pool";
mOptionDescriptions[RSK_MIX_OVERWORLD_ENTRANCES] = "Overworld entrances will be part of the mixed pool";
mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES] = "Interior entrances will be part of the mixed pool";
mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES] = "Grotto entrances will be part of the mixed pool";
mOptionDescriptions[RSK_MIX_DUNGEON_ENTRANCES] = "Dungeon entrances will be part of the mixed pool.";
mOptionDescriptions[RSK_MIX_BOSS_ENTRANCES] = "Boss entrances will be part of the mixed pool.";
mOptionDescriptions[RSK_MIX_OVERWORLD_ENTRANCES] = "Overworld entrances will be part of the mixed pool.";
mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES] = "Interior entrances will be part of the mixed pool.";
mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES] = "Grotto entrances will be part of the mixed pool.";
mOptionDescriptions[RSK_SHUFFLE_SONGS] =
"Song locations - Songs will only appear at locations that normally teach songs.\n"
"\n"
"Dungeon rewards - Songs appear after beating a major dungeon boss.\n"
"The 4 remaining songs are located at:\n"
" - Zelda's lullaby location\n"
" - Zelda's Lullaby location\n"
" - Ice Cavern's Serenade of Water location\n"
" - Bottom of the Well Lens of Truth location\n"
" - Gerudo Training Ground's Ice Arrows location\n"
@ -240,33 +246,33 @@ void Settings::CreateOptionDescriptions() {
"\n"
"The Weird Egg is required to unlock several events:\n"
" - Zelda's Lullaby from Impa\n"
" - Saria's song in Sacred Forest Meadow\n"
" - Epona's song and chicken minigame at Lon Lon Ranch\n"
" - Zelda's letter for Kakariko gate (if set to closed)\n"
" - Saria's Song in Sacred Forest Meadow\n"
" - Epona's Song and chicken minigame at Lon Lon Ranch\n"
" - Zelda's Letter for Kakariko gate (if set to closed)\n"
" - Happy Mask Shop sidequest\n";
mOptionDescriptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] =
"Shuffles the Gerudo Membership Card into the item pool.\n"
"\n"
"The Gerudo Card is required to enter the Gerudo Training Grounds, opening "
"The Gerudo Card is required to enter the Gerudo Training Ground, opening "
"the gate to Haunted Wasteland and the Horseback Archery minigame.";
mOptionDescriptions[RSK_SHUFFLE_FISHING_POLE] = "Shuffles the fishing pole into the item pool.\n"
"\n"
"The fishing pole is required to play the fishing pond minigame.";
mOptionDescriptions[RSK_INFINITE_UPGRADES] = "Adds upgrades that hold infinite quanities of items (bombs, arrows, etc.)\n"
mOptionDescriptions[RSK_INFINITE_UPGRADES] = "Adds upgrades that hold infinite quantities of items (bombs, arrows, etc.).\n"
"\n"
"Progressive - The infinite upgrades are obtained after getting the last normal capacity upgrade\n"
"Progressive - The infinite upgrades are obtained after getting the last normal capacity upgrade.\n"
"\n"
"Condensed Progressive - The infinite upgrades are obtained as the first capacity upgrade (doesn't apply to the infinite wallet or to infinite magic)";
mOptionDescriptions[RSK_SHUFFLE_DEKU_STICK_BAG] = "Shuffles the deku stick bag into the item pool.\n"
"Condensed Progressive - The infinite upgrades are obtained as the first capacity upgrade (doesn't apply to the infinite wallet or to infinite magic).";
mOptionDescriptions[RSK_SHUFFLE_DEKU_STICK_BAG] = "Shuffles the Deku Stick bag into the item pool.\n"
"\n"
"The deku stick bag is required to hold deku sticks.";
mOptionDescriptions[RSK_SHUFFLE_DEKU_NUT_BAG] = "Shuffles the deku nut bag into the item pool.\n"
"The Deku Stick bag is required to hold Deku Sticks.";
mOptionDescriptions[RSK_SHUFFLE_DEKU_NUT_BAG] = "Shuffles the Deku Nut bag into the item pool.\n"
"\n"
"The deku nut bag is required to hold deku nuts.";
"The Deku Nut bag is required to hold Deku Nuts.";
mOptionDescriptions[RSK_SHOPSANITY] = "Off - All shop items will be the same as vanilla.\n"
"\n"
"Specifc Count - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain a specifc number (0-7) of non-vanilla shop items.\n"
"Specific Count - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain a specific number (0-7) of non-vanilla shop items.\n"
"\n"
"Random - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain a random number (1-7) of non-vanilla shop items.";
@ -279,12 +285,12 @@ void Settings::CreateOptionDescriptions() {
"8 Items - All shops will contain 8 non-vanilla shop items.\n"
*/;
mOptionDescriptions[RSK_SHOPSANITY_PRICES] =
"Vanilla - The same price as the item it replaced\n"
"Cheap Balanced - Prices will range between 0 to 95 rupees, favoring lower numbers\n"
"Balanced - Prices will range between 0 to 300 rupees, favoring lower numbers\n"
"Fixed - A fixed number\n"
"Range - A random point between specific ranges\n"
"Set By Wallet - Set weights that decide the choice of each wallet, and get a random price in that range if that wallet is chosen";
"Vanilla - The same price as the item it replaced.\n"
"Cheap Balanced - Prices will range between 0 to 95 rupees, favoring lower numbers.\n"
"Balanced - Prices will range between 0 to 300 rupees, favoring lower numbers.\n"
"Fixed - A fixed number.\n"
"Range - A random point between specific ranges.\n"
"Set By Wallet - Set weights that decide the choice of each wallet, and get a random price in that range if that wallet is chosen.";
mOptionDescriptions[RSK_SHOPSANITY_PRICES_FIXED_PRICE] =
"The price for Shopsanity checks.";
mOptionDescriptions[RSK_SHOPSANITY_PRICES_RANGE_1] =
@ -300,13 +306,13 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT] =
"The chance for Shopsanity checks to be purchasable with Giant's Wallet (201-500).";
mOptionDescriptions[RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT] =
"The chance for Shopsanity checks to be purchasable with Tycoon Wallet. (500+)";
"The chance for Shopsanity checks to be purchasable with Tycoon Wallet (500+).";
mOptionDescriptions[RSK_SHOPSANITY_PRICES_AFFORDABLE] =
"After choosing a price, set it to the affordable amount based on the wallet required.\n\n"
"Affordable prices per tier: starter = 1, adult = 100, giant = 201, tycoon = 501\n\n"
"Use this to enable wallet tier locking, but make shop items not as expensive as they could be.";
mOptionDescriptions[RSK_FISHSANITY] = "Off - Fish will not be shuffled. No changes will be made to fishing behavior.\n\n"
"Shuffle only Hyrule Loach - Allows you to earn an item by catching the hyrule loach at the fishing pond and giving it to the owner.\n\n"
"Shuffle only Hyrule Loach - Allows you to earn an item by catching the Hyrule Loach at the fishing pond and giving it to the owner.\n\n"
"Shuffle Fishing Pond - The fishing pond's fish will be shuffled. Catching a fish in the fishing pond will grant a reward.\n\n"
"Shuffle Overworld Fish - Fish in generic grottos and Zora's Domain will be shuffled. Catching a fish in a bottle will give a reward.\n\n"
"Shuffle Both - Both overworld fish and fish in the fishing pond will be shuffled.";
@ -326,12 +332,12 @@ void Settings::CreateOptionDescriptions() {
"\n"
"All - All Scrubs are shuffled.";
mOptionDescriptions[RSK_SCRUBS_PRICES] =
"Vanilla - The same price as the item it replaced\n"
"Cheap Balanced - Prices will range between 0 to 95 rupees, favoring lower numbers\n"
"Balanced - Prices will range between 0 to 300 rupees, favoring lower numbers\n"
"Fixed - A fixed number\n"
"Range - A random point between specific ranges\n"
"Set By Wallet - Set weights that decide the choice of each wallet, and get a random price in that range if that wallet is chosen";
"Vanilla - The same price as the item it replaced.\n"
"Cheap Balanced - Prices will range between 0 to 95 rupees, favoring lower numbers.\n"
"Balanced - Prices will range between 0 to 300 rupees, favoring lower numbers.\n"
"Fixed - A fixed number.\n"
"Range - A random point between specific ranges.\n"
"Set By Wallet - Set weights that decide the choice of each wallet, and get a random price in that range if that wallet is chosen.";
mOptionDescriptions[RSK_SCRUBS_PRICES_FIXED_PRICE] =
"The price for Scrub checks.";
mOptionDescriptions[RSK_SCRUBS_PRICES_RANGE_1] =
@ -347,7 +353,7 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_SCRUBS_PRICES_GIANT_WALLET_WEIGHT] =
"The chance for Scrub checks to be purchasable with Giant's Wallet (201-500).";
mOptionDescriptions[RSK_SCRUBS_PRICES_TYCOON_WALLET_WEIGHT] =
"The chance for Scrub checks to be purchasable with Tycoon Wallet. (500+)";
"The chance for Scrub checks to be purchasable with Tycoon Wallet (500+).";
mOptionDescriptions[RSK_SCRUBS_PRICES_AFFORDABLE] =
"After choosing a price, set it to the affordable amount based on the wallet required.\n\n"
"Affordable prices per tier: starter = 1, adult = 100, giant = 201, tycoon = 501\n\n"
@ -360,7 +366,7 @@ void Settings::CreateOptionDescriptions() {
"This setting governs if the Bean Salesman, Medigoron, Granny and the Carpet Salesman "
"sell a random item.\n"
"Beans Only - Only the Bean Salesman will have a check, and a pack of Magic Beans will be added "
" to the item pool."
"to the item pool."
"All But Beans - Medigoron, Granny and the Carpet Salesman will have checks, "
"A Giant's Knife and a pack of Bombchus will be added to the item pool, and "
"one of the bottles will contain a Blue Potion.\n\n"
@ -370,12 +376,12 @@ void Settings::CreateOptionDescriptions() {
"Otherwise when off, you will need to have found the Claim Check to buy her item (simulating the trade quest "
"is complete).";
mOptionDescriptions[RSK_MERCHANT_PRICES] =
"Vanilla - The same price as the Check in vanilla, 60 for the Bean Salesman\n"
"Cheap Balanced - Prices will range between 0 to 95 rupees, favoring lower numbers\n"
"Balanced - Prices will range between 0 to 300 rupees, favoring lower numbers\n"
"Fixed - A fixed number\n"
"Range - A random point between specific ranges\n"
"Set By Wallet - Set weights that decide the choice of each wallet, and get a random price in that range if that wallet is chosen";
"Vanilla - The same price as the Check in vanilla, 60 for the Bean Salesman.\n"
"Cheap Balanced - Prices will range between 0 to 95 rupees, favoring lower numbers.\n"
"Balanced - Prices will range between 0 to 300 rupees, favoring lower numbers.\n"
"Fixed - A fixed number.\n"
"Range - A random point between specific ranges.\n"
"Set By Wallet - Set weights that decide the choice of each wallet, and get a random price in that range if that wallet is chosen.";
mOptionDescriptions[RSK_MERCHANT_PRICES_FIXED_PRICE] =
"The price for Merchant checks.";
mOptionDescriptions[RSK_MERCHANT_PRICES_RANGE_1] =
@ -391,7 +397,7 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_MERCHANT_PRICES_GIANT_WALLET_WEIGHT] =
"The chance for Merchant checks to be purchasable with Giant's Wallet (201-500).";
mOptionDescriptions[RSK_MERCHANT_PRICES_TYCOON_WALLET_WEIGHT] =
"The chance for Merchant checks to be purchasable with Tycoon Wallet. (500+)";
"The chance for Merchant checks to be purchasable with Tycoon Wallet (500+).";
mOptionDescriptions[RSK_MERCHANT_PRICES_AFFORDABLE] =
"After choosing a price, set it to the affordable amount based on the wallet required.\n\n"
"Affordable prices per tier: starter = 1, adult = 100, giant = 201, tycoon = 501\n\n"
@ -416,16 +422,16 @@ void Settings::CreateOptionDescriptions() {
"\n"
"You can still talk to him multiple times to get Huge Rupees.";
mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS] =
"Shuffles the location of spiritual stones and medallions.\n"
"Shuffles the location of Spiritual Stones and medallions.\n"
"\n"
"End of dungeons - Spiritual stones and medallions will be given as rewards "
"End of dungeons - Spiritual Stones and medallions will be given as rewards "
"for beating major dungeons. Link will always start with one stone or medallion.\n"
"\n"
"Any dungeon - Spiritual stones and medallions can be found inside any dungeon.\n"
"Any dungeon - Spiritual Stones and medallions can be found inside any dungeon.\n"
"\n"
"Overworld - Spiritual stones and medallions can only be found outside of dungeons.\n"
"Overworld - Spiritual Stones and medallions can only be found outside of dungeons.\n"
"\n"
"Anywhere - Spiritual stones and medallions can appear anywhere.";
"Anywhere - Spiritual Stones and medallions can appear anywhere.";
mOptionDescriptions[RSK_SHUFFLE_MAPANDCOMPASS] =
"Start with - You will start with Maps & Compasses from all dungeons.\n"
"\n"
@ -433,7 +439,7 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Own dungeon - Maps & Compasses can only appear in their respective dungeon.\n"
"\n"
"Any dungeon - Maps & Compasses can only appear inside of any dungon.\n"
"Any dungeon - Maps & Compasses can only appear inside of any dungeon.\n"
"\n"
"Overworld - Maps & Compasses can only appear outside of dungeons.\n"
"\n"
@ -447,18 +453,18 @@ void Settings::CreateOptionDescriptions() {
"Own dungeon - Small Keys can only appear in their respective dungeon. "
"If Fire Temple is not a Master Quest dungeon, the door to the Boss Key chest will be unlocked.\n"
"\n"
"Any dungeon - Small Keys can only appear inside of any dungon.\n"
"Any dungeon - Small Keys can only appear inside of any dungeon.\n"
"\n"
"Overworld - Small Keys can only appear outside of dungeons.\n"
"\n"
"Anywhere - Small Keys can appear anywhere in the world.";
mOptionDescriptions[RSK_KEYRINGS] =
"Keyrings will replace all small keys from a particular dungeon with a single keyring that awards all keys for "
"it's associated dungeon\n"
"its associated dungeon.\n"
"\n"
"Off - No dungeons will have their keys replaced with keyrings.\n"
"\n"
"Random - A random amount of dungeons(0-8 or 9) will have their keys replaced with keyrings.\n"
"Random - A random amount of dungeons (0-8 or 9) will have their keys replaced with keyrings.\n"
"\n"
"Count - A specified amount of randomly selected dungeons will have their keys replaced with keyrings.\n"
"\n"
@ -470,20 +476,20 @@ void Settings::CreateOptionDescriptions() {
"If Gerudo Fortress Carpenters is set to Normal, and Gerudo Fortress Keys is set to anything "
"other than Vanilla, then the maximum amount of Key Rings that can be selected by Random or "
"Count will be 9. Otherwise, the maximum amount of Key Rings will be 8.";
mOptionDescriptions[RSK_GERUDO_KEYS] = "Vanilla - Thieve's Hideout Keys will appear in their vanilla locations.\n"
mOptionDescriptions[RSK_GERUDO_KEYS] = "Vanilla - Thieves' Hideout Keys will appear in their vanilla locations.\n"
"\n"
"Any dungeon - Thieve's Hideout Keys can only appear inside of any dungon.\n"
"Any dungeon - Thieves' Hideout Keys can only appear inside of any dungon.\n"
"\n"
"Overworld - Thieve's Hideout Keys can only appear outside of dungeons.\n"
"Overworld - Thieves' Hideout Keys can only appear outside of dungeons.\n"
"\n"
"Anywhere - Thieve's Hideout Keys can appear anywhere in the world.";
"Anywhere - Thieves' Hideout Keys can appear anywhere in the world.";
mOptionDescriptions[RSK_BOSS_KEYSANITY] = "Start with - You will start with Boss keys from all dungeons.\n"
"\n"
"Vanilla - Boss Keys will appear in their vanilla locations.\n"
"\n"
"Own dungeon - Boss Keys can only appear in their respective dungeon.\n"
"\n"
"Any dungeon - Boss Keys can only appear inside of any dungon.\n"
"Any dungeon - Boss Keys can only appear inside of any dungeon.\n"
"\n"
"Overworld - Boss Keys can only appear outside of dungeons.\n"
"\n"
@ -495,7 +501,7 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Start with - Places Ganon's Boss Key in your starting inventory."
"\n"
"Any dungeon - Ganon's Boss Key Key can only appear inside of any dungon.\n"
"Any dungeon - Ganon's Boss Key Key can only appear inside of any dungeon.\n"
"\n"
"Overworld - Ganon's Boss Key Key can only appear outside of dungeons.\n"
"\n"
@ -504,9 +510,9 @@ void Settings::CreateOptionDescriptions() {
"LACS - These settings put the boss key on the Light Arrow Cutscene location, from Zelda in Temple of Time as "
"adult, with differing requirements:\n"
"- Vanilla: Obtain the Shadow Medallion and Spirit Medallion\n"
"- Stones: Obtain the specified amount of spiritual stones.\n"
"- Stones: Obtain the specified amount of Spiritual Stones.\n"
"- Medallions: Obtain the specified amount of medallions.\n"
"- Dungeon rewards: Obtain the specified total sum of spiritual stones or medallions.\n"
"- Dungeon rewards: Obtain the specified total sum of Spiritual Stones or medallions.\n"
"- Dungeons: Complete the specified amount of dungeons. Dungeons are considered complete after stepping in to "
"the blue warp after the boss.\n"
"- Tokens: Obtain the specified amount of Skulltula tokens.\n"
@ -523,7 +529,7 @@ void Settings::CreateOptionDescriptions() {
"\n"
"Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of "
"rewards on slider does not change.";
mOptionDescriptions[RSK_CUCCO_COUNT] = "The amount of cuccos needed to claim the reward from Anju the cucco lady";
mOptionDescriptions[RSK_CUCCO_COUNT] = "The amount of cuccos needed to claim the reward from Anju the Cucco Lady.";
mOptionDescriptions[RSK_BIG_POE_COUNT] = "The Poe collector will give a reward for turning in this many Big Poes.";
mOptionDescriptions[RSK_SKIP_CHILD_STEALTH] =
"The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards.";
@ -532,11 +538,11 @@ void Settings::CreateOptionDescriptions() {
"until after meeting Zelda. Disables the ability to shuffle Weird Egg.";
mOptionDescriptions[RSK_SKIP_EPONA_RACE] = "Epona can be summoned with Epona's Song without needing to race Ingo.";
mOptionDescriptions[RSK_COMPLETE_MASK_QUEST] =
"Once the happy mask shop is opened, all masks will be available to be borrowed.";
"Once the Happy Mask Shop is opened, all masks will be available to be borrowed.";
mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG] =
"Start with the ability to summon Pierre the scarecrow. Pulling out an ocarina in the usual locations will "
"Start with the ability to summon Pierre the Scarecrow. Pulling out an Ocarina in the usual locations will "
"automatically summon him.\n"
"With \"Shuffle Ocarina Buttons\" enabled, you'll need at least two ocarina buttons to summon him.";
"With \"Shuffle Ocarina Buttons\" enabled, you'll need at least two Ocarina buttons to summon him.";
mOptionDescriptions[RSK_ITEM_POOL] = "Sets how many major items appear in the item pool.\n"
"\n"
"Plentiful - Extra major items are added to the pool.\n"
@ -592,12 +598,12 @@ void Settings::CreateOptionDescriptions() {
"Very Strong - Many powerful hints.";
mOptionDescriptions[RSK_TOT_ALTAR_HINT] =
"Reading the Temple of Time altar as child will tell you the locations of the Spiritual Stones.\n"
"Reading the Temple of Time altar as adult will tell you the locations of the Medallions, as well as the "
"Reading the Temple of Time altar as adult will tell you the locations of the medallions, as well as the "
"conditions for building the Rainbow Bridge and getting the Boss Key for Ganon's Castle.";
mOptionDescriptions[RSK_GANONDORF_HINT] =
"Talking to Ganondorf in his boss room will tell you the location of the Light Arrows and Master Sword."
"If this option is enabled and Ganondorf is reachable without these items, Gossip Stones will never hint the "
"appropriote items.";//RANDOTODO make this hint text about no dupe hints a global hint for static hints. Add to navi?
"appropriate items.";//RANDOTODO make this hint text about no dupe hints a global hint for static hints. Add to navi?
mOptionDescriptions[RSK_SHEIK_LA_HINT] =
"Talking to Sheik inside Ganon's Castle will tell you the location of the Light Arrows."
"If this option is enabled and Sheik is reachable without Light Arrows, Gossip Stones will never hint the "
@ -613,37 +619,37 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_FISHING_POLE_HINT] = "Talking to the fishing pond owner without the fishing pole will tell you its location.";
mOptionDescriptions[RSK_OOT_HINT] = "Sheik in the Temple of Time will tell you the item and song on the Ocarina of Time.";
mOptionDescriptions[RSK_FROGS_HINT] = "Standing near the pedestal for the frogs in Zora's River will tell you the "
"reward for the frogs' ocarina game.";
"reward for the frogs' Ocarina game.";
mOptionDescriptions[RSK_BIGGORON_HINT] = "Talking to Biggoron will tell you the item he will give you in exchange for the Claim Check.";
mOptionDescriptions[RSK_BIG_POES_HINT] = "Talking to the Poe Collector in the Market Guardhouse while adult will tell you what you receive for handing in Big Poes.";
mOptionDescriptions[RSK_CHICKENS_HINT] = "Talking to Anju as a child will tell you the item she will give you for delivering her Cuccos to the pen";
mOptionDescriptions[RSK_MALON_HINT] = "Talking to Malon as adult will tell you the item on \"Link's cow\", the cow you win from beating her time on the Lon Lon Obsticle Course.";
mOptionDescriptions[RSK_CHICKENS_HINT] = "Talking to Anju as a child will tell you the item she will give you for delivering her cuccos to the pen.";
mOptionDescriptions[RSK_MALON_HINT] = "Talking to Malon as adult will tell you the item on \"Link's cow\", the cow you win from beating her time on the Lon Lon Obstacle Course.";
mOptionDescriptions[RSK_HBA_HINT] = "Talking to the Horseback Archery gerudo in Gerudo Fortress, or the nearby sign, will tell you what you win for scoring 1000 and 1500 points on Horseback Archery.";
mOptionDescriptions[RSK_WARP_SONG_HINTS] = "Playing a warp song will tell you where it leads. (If warp song destinations are vanilla, this is always enabled.)";
mOptionDescriptions[RSK_SCRUB_TEXT_HINT] = "Business scrubs will reveal the identity of what they're selling.";
mOptionDescriptions[RSK_MERCHANT_TEXT_HINT] = "Merchants will reveal the identity of what they're selling (Shops are not affected by this setting).";
mOptionDescriptions[RSK_KAK_10_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 10 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_20_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 20 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_30_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 30 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_40_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 40 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_50_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 50 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_100_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 100 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_10_SKULLS_HINT] = "Talking to the Cursed Resident in the Skulltula House who is saved after 10 tokens will tell you the reward.";
mOptionDescriptions[RSK_KAK_20_SKULLS_HINT] = "Talking to the Cursed Resident in the Skulltula House who is saved after 20 tokens will tell you the reward.";
mOptionDescriptions[RSK_KAK_30_SKULLS_HINT] = "Talking to the Cursed Resident in the Skulltula House who is saved after 30 tokens will tell you the reward.";
mOptionDescriptions[RSK_KAK_40_SKULLS_HINT] = "Talking to the Cursed Resident in the Skulltula House who is saved after 40 tokens will tell you the reward.";
mOptionDescriptions[RSK_KAK_50_SKULLS_HINT] = "Talking to the Cursed Resident in the Skulltula House who is saved after 50 tokens will tell you the reward.";
mOptionDescriptions[RSK_KAK_100_SKULLS_HINT] = "Talking to the Cursed Resident in the Skulltula House who is saved after 100 tokens will tell you the reward.";
mOptionDescriptions[RSK_MASK_SHOP_HINT] = "Reading the mask shop sign will tell you rewards from showing masks at the Deku Theatre.";
mOptionDescriptions[RSK_FULL_WALLETS] = "Start with a full wallet. All wallet upgrades come filled with rupees.";
mOptionDescriptions[RSK_BOMBCHUS_IN_LOGIC] =
"Bombchus are properly considered in logic. Without this setting, any Bombchu requirement"
" is filled by Bomb Bag + a renewable source of Bombchus\n"
"Bombchus are properly considered in logic. Without this setting, any Bombchu requirement "
"is filled by Bomb Bag + a renewable source of Bombchus.\n"
"\n"
"The first Bombchu pack will always be 20, and subsequent packs will be "
"5 or 10 based on how many you have.\n"
"Once found, they can be replenished at the Bombchu shop.\n"
"\n"
"Bombchu Bowling is opened by obtaining Bombchus.";
mOptionDescriptions[RSK_ENABLE_BOMBCHU_DROPS] = "Once you obtain bombchus for the first time, refills can be found "
mOptionDescriptions[RSK_ENABLE_BOMBCHU_DROPS] = "Once you obtain Bombchus for the first time, refills can be found "
"in bushes and other places where bomb drops can normally spawn."
"\n"
"If you have Bombchus in Logic disabled, you will also need a"
"Bomb bag for bombchus to drop";
"If you have Bombchus in Logic disabled, you will also need a "
"Bomb Bag for Bombchus to drop.";
mOptionDescriptions[RSK_BLUE_FIRE_ARROWS] =
"Ice Arrows act like Blue Fire, making them able to melt red ice. "
"Item placement logic will respect this option, so it might be required to use this to progress.";
@ -671,4 +677,4 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_SHUFFLE_BOSS_SOULS] = "Shuffles 8 boss souls (one for each blue warp dungeon). A boss will not appear until you collect its respective soul."
"\n\"On + Ganon\" will also hide Ganon and Ganondorf behind a boss soul.";
}
} // namespace Rando
} // namespace Rando

@ -1270,7 +1270,7 @@ FishIdentity Randomizer::IdentifyFish(s32 sceneNum, s32 actorParams) {
}
u8 Randomizer::GetRandoSettingValue(RandomizerSettingKey randoSettingKey) {
return Rando::Context::GetInstance()->GetOption(randoSettingKey).GetSelectedOptionIndex();
return Rando::Context::GetInstance()->GetOption(randoSettingKey).GetContextOptionIndex();
}
GetItemEntry Randomizer::GetItemFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogItemId, bool checkObtainability) {

@ -568,19 +568,14 @@ typedef enum {
RR_DODONGOS_CAVERN_BOSS_ROOM,
RR_JABU_JABUS_BELLY_BEGINNING,
RR_JABU_JABUS_BELLY_LIFT_MIDDLE,
RR_JABU_JABUS_BELLY_MAIN_UPPER,
RR_JABU_JABUS_BELLY_MAIN_LOWER,
RR_JABU_JABUS_BELLY_SHABOMB_CORRIDOR,
RR_JABU_JABUS_BELLY_LOWER_SIDE_ROOM,
RR_JABU_JABUS_BELLY_LIFT_LOWER,
RR_JABU_JABUS_BELLY_FORKED_CORRIDOR,
RR_JABU_JABUS_BELLY_BOOMERANG_ROOM,
RR_JABU_JABUS_BELLY_MAP_ROOM,
RR_JABU_JABUS_BELLY_MAIN,
RR_JABU_JABUS_BELLY_B1_NORTH,
RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_SOUTH,
RR_JABU_JABUS_BELLY_WATER_SWITCH_ROOM_LEDGE,
RR_JABU_JABUS_BELLY_COMPASS_ROOM,
RR_JABU_JABUS_BELLY_BLUE_TENTACLE,
RR_JABU_JABUS_BELLY_GREEN_TENTACLE,
RR_JABU_JABUS_BELLY_BIGOCTO_ROOM,
RR_JABU_JABUS_BELLY_BIGOCTO_LEDGE,
RR_JABU_JABUS_BELLY_ABOVE_BIGOCTO,
RR_JABU_JABUS_BELLY_LIFT_UPPER,
RR_JABU_JABUS_BELLY_NEAR_BOSS_ROOM,
@ -3947,6 +3942,7 @@ typedef enum {
RSK_KAK_GATE,
RSK_DOOR_OF_TIME,
RSK_ZORAS_FOUNTAIN,
RSK_SLEEPING_WATERFALL,
RSK_STARTING_AGE,
RSK_GERUDO_FORTRESS,
RSK_RAINBOW_BRIDGE,
@ -4049,7 +4045,8 @@ typedef enum {
RSK_GANONS_BOSS_KEY,
RSK_SKIP_CHILD_STEALTH,
RSK_SKIP_CHILD_ZELDA,
RSK_STARTING_CONSUMABLES,
RSK_STARTING_STICKS,
RSK_STARTING_NUTS,
RSK_FULL_WALLETS,
RSK_SHUFFLE_CHEST_MINIGAME,
RSK_CUCCO_COUNT,
@ -4184,6 +4181,12 @@ typedef enum {
RO_ZF_OPEN,
} RandoOptionZorasFountain;
//Sleeping Waterfall settings (closed, open)
typedef enum {
RO_WATERFALL_CLOSED,
RO_WATERFALL_OPEN,
} RandoOptionSleepingWaterfall;
//Starting Age settings (child, adult, random)
typedef enum {
RO_AGE_CHILD,
@ -4630,6 +4633,9 @@ typedef enum {
RE_BEAMOS,
RE_WALLMASTER,
RE_PURPLE_LEEVER,
RE_TENTACLE,
RE_BARI,
RE_SHABOM,
} RandomizerEnemy;
//RANDOTODO compare child long jumpslash range with adult short

@ -1503,7 +1503,10 @@ void DrawLocation(RandomizerCheck rc) {
txt = itemLoc->GetPlacedItem().GetName().GetForLanguage(gSaveContext.language);
}
if (IsVisibleInCheckTracker(rc) && status == RCSHOW_IDENTIFIED && !mystery && !itemLoc->IsAddedToPool()) {
txt += fmt::format(" - {}", OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetPrice());
auto price = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetPrice();
if (price) {
txt += fmt::format(" - {}", price);
}
}
} else {
if (IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID())) {

@ -13,7 +13,10 @@ uint8_t Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey);
GetItemEntry Randomizer_GetItemFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId);
}
void StartingItemGive(GetItemEntry getItemEntry) {
void StartingItemGive(GetItemEntry getItemEntry, RandomizerCheck randomizerCheck) {
if (randomizerCheck != RC_MAX) {
OTRGlobals::Instance->gRandoContext->GetItemLocation(randomizerCheck)->SetCheckStatus(RCSHOW_SAVED);
}
if (getItemEntry.modIndex == MOD_NONE) {
if (getItemEntry.getItemId == GI_SWORD_BGS) {
gSaveContext.bgsFlag = true;
@ -96,8 +99,7 @@ void GiveLinkDekuNuts(int howManyNuts) {
void GiveLinksPocketItem() {
if (Randomizer_GetSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING) {
GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_LINKS_POCKET, (GetItemID)RG_NONE);
StartingItemGive(getItemEntry);
Rando::Context::GetInstance()->GetItemLocation(RC_LINKS_POCKET)->SetCheckStatus(RCSHOW_SAVED);
StartingItemGive(getItemEntry, RC_LINKS_POCKET);
// If we re-add the above, we'll get the item on save creation, now it's given on first load
Flags_SetRandomizerInf(RAND_INF_LINKS_POCKET);
}
@ -149,13 +151,11 @@ void SetStartingItems() {
INV_CONTENT(ITEM_OCARINA_FAIRY) = ITEM_OCARINA_FAIRY;
}
if (Randomizer_GetSettingValue(RSK_STARTING_CONSUMABLES)) {
if (!Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_STICK_BAG)) {
GiveLinkDekuSticks(10);
}
if (!Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_NUT_BAG)) {
GiveLinkDekuNuts(20);
}
if (Randomizer_GetSettingValue(RSK_STARTING_STICKS) && !Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_STICK_BAG)) {
GiveLinkDekuSticks(10);
}
if (Randomizer_GetSettingValue(RSK_STARTING_NUTS) && !Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_NUT_BAG)) {
GiveLinkDekuNuts(20);
}
if (Randomizer_GetSettingValue(RSK_FULL_WALLETS)) {
@ -251,10 +251,11 @@ extern "C" void Randomizer_InitSaveFile() {
// Flags_SetInfTable(INFTABLE_CHILD_MALON_SAID_EPONA_WAS_AFRAID_OF_YOU);
// Flags_SetInfTable(INFTABLE_SPOKE_TO_INGO_ONCE_AS_ADULT);
// Now handled by cutscene skips
// Ruto already met in jabu and spawns down the hole immediately
Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO);
Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME);
Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE);
// Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO);
// Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME);
// Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE);
// Now handled by cutscene skips
// Skip cutscenes before Nabooru fight
@ -381,7 +382,7 @@ extern "C" void Randomizer_InitSaveFile() {
if (Randomizer_GetSettingValue(RSK_SKIP_CHILD_ZELDA)) {
GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_SONG_FROM_IMPA, (GetItemID)RG_ZELDAS_LULLABY);
StartingItemGive(getItemEntry);
StartingItemGive(getItemEntry, RC_SONG_FROM_IMPA);
// malon/talon back at ranch
Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG);
@ -403,7 +404,7 @@ extern "C" void Randomizer_InitSaveFile() {
if (Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && startingAge == RO_AGE_ADULT) {
GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_TOT_MASTER_SWORD, GI_NONE);
StartingItemGive(getItemEntry);
StartingItemGive(getItemEntry, RC_TOT_MASTER_SWORD);
Flags_SetRandomizerInf(RAND_INF_TOT_MASTER_SWORD);
}

File diff suppressed because it is too large Load Diff

@ -13,7 +13,7 @@ std::unordered_map<uint32_t, CustomMessage> StaticData::hintTypeNames = {
{HINT_TYPE_ITEM_AREA, CustomMessage("Item Area")},
{HINT_TYPE_ALTAR_CHILD, CustomMessage("Child Altar")},
{HINT_TYPE_ALTAR_ADULT, CustomMessage("Adult Altar")},
{HINT_TYPE_WOTH, CustomMessage("Way of the Hero")},
{HINT_TYPE_WOTH, CustomMessage("Way of the Hero")},
{HINT_TYPE_FOOLISH, CustomMessage("Foolish")},
{HINT_TYPE_MESSAGE, CustomMessage("Hardcoded Message")}
};
@ -60,7 +60,7 @@ std::unordered_map<uint32_t, CustomMessage> StaticData::hintNames = {
{RH_LH_SOUTHEAST_GOSSIP_STONE, CustomMessage("LH Southeast Gossip Stone")},
{RH_LH_SOUTHWEST_GOSSIP_STONE, CustomMessage("LH Southwest Gossip Stone")},
{RH_GV_GOSSIP_STONE, CustomMessage("Gerudo Valley Gossip Stone")},
{RH_COLOSSUS_GOSSIP_STONE, CustomMessage("Desert Collosus Gossip Stone")},
{RH_COLOSSUS_GOSSIP_STONE, CustomMessage("Desert Colossus Gossip Stone")},
{RH_DODONGOS_CAVERN_GOSSIP_STONE, CustomMessage("Dodongo's Cavern Gossip Stone")},
{RH_GANONDORF_HINT, CustomMessage("Ganondorf Hint")},
{RH_GANONDORF_JOKE, CustomMessage("Ganondorf Joke")},
@ -189,7 +189,7 @@ std::unordered_map<uint32_t, RandomizerHintTextKey> StaticData::trialData = {
std::unordered_map<RandomizerHint, StaticHintInfo> StaticData::staticHintInfoMap = {
// RH_GANONDORF_HINT is special cased due to being different based on master sword shuffle
// Altar hints are special cased due to special hint marking rules
// warp song hints are special cased due to entrences not being done properly yet
// warp song hints are special cased due to entrances not being done properly yet
// Ganondorf Joke is special cased as the text is random
{RH_SHEIK_HINT, StaticHintInfo(HINT_TYPE_AREA, {RHT_SHEIK_HINT_LA_ONLY}, RSK_SHEIK_LA_HINT, true, {}, {RG_LIGHT_ARROWS}, {RC_SHEIK_HINT_GC, RC_SHEIK_HINT_MQ_GC}, true)},
{RH_DAMPES_DIARY, StaticHintInfo(HINT_TYPE_AREA, {RHT_DAMPE_DIARY}, RSK_DAMPES_DIARY_HINT, true, {}, {RG_PROGRESSIVE_HOOKSHOT}, {RC_DAMPE_HINT})},
@ -301,4 +301,4 @@ std::unordered_map<u32, RandomizerHint> StaticData::grottoChestParamsToHint{
};
std::array<HintText, RHT_MAX> StaticData::hintTextTable = {};
}
}

@ -35,10 +35,13 @@ extern SaveContext gSaveContext;
extern PlayState* gPlayState;
extern int32_t D_8011D3AC;
extern void func_808ADEF0(BgSpot03Taki* bgSpot03Taki, PlayState* play);
extern void BgSpot03Taki_ApplyOpeningAlpha(BgSpot03Taki* bgSpot03Taki, s32 bufferIndex);
extern void func_80AF36EC(EnRu2* enRu2, PlayState* play);
}
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex()
#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetContextOptionIndex()
void EnMa1_EndTeachSong(EnMa1* enMa1, PlayState* play) {
if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) {
@ -96,6 +99,9 @@ void EnDntDemo_JudgeSkipToReward(EnDntDemo* enDntDemo, PlayState* play) {
}
}
void BgSpot03Taki_KeepOpen(BgSpot03Taki* bgSpot03Taki, PlayState* play) {
}
static int successChimeCooldown = 0;
void RateLimitedSuccessChime() {
if (successChimeCooldown == 0) {
@ -263,7 +269,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
// The switch in jabu that you are intended to press with a box to reach barrinade
// can be skipped by either a frame perfect roll open or with OI
// The One Point for that switch is used in common setups for the former and is required for the latter to work
if (actor->params == 14848 && gPlayState->sceneNum == SCENE_JABU_JABU && !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)){
if (actor->params == 14848 && gPlayState->sceneNum == SCENE_JABU_JABU && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)){
break;
}
BgBdanSwitch* switchActor = (BgBdanSwitch*)actor;
@ -280,19 +286,11 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
RateLimitedSuccessChime();
break;
}
case ACTOR_BG_HIDAN_FWBIG: {
*should = false;
break;
}
case ACTOR_EN_EX_ITEM: {
*should = false;
break;
}
case ACTOR_EN_DNT_NOMAL: {
*should = false;
break;
}
case ACTOR_EN_DNT_DEMO: {
case ACTOR_BG_HIDAN_FWBIG:
case ACTOR_EN_EX_ITEM:
case ACTOR_EN_DNT_NOMAL:
case ACTOR_EN_DNT_DEMO:
case ACTOR_BG_HAKA_ZOU: {
*should = false;
break;
}
@ -311,6 +309,8 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
case ACTOR_BG_SPOT18_BASKET:
case ACTOR_BG_HIDAN_CURTAIN:
case ACTOR_BG_MORI_HINERI:
case ACTOR_BG_MIZU_SHUTTER:
case ACTOR_SHOT_SUN:
*should = false;
RateLimitedSuccessChime();
break;
@ -699,6 +699,8 @@ static uint32_t enFuUpdateHook = 0;
static uint32_t enFuKillHook = 0;
static uint32_t bgSpot02UpdateHook = 0;
static uint32_t bgSpot02KillHook = 0;
static uint32_t bgSpot03UpdateHook = 0;
static uint32_t bgSpot03KillHook = 0;
static uint32_t enPoSistersUpdateHook = 0;
static uint32_t enPoSistersKillHook = 0;
void TimeSaverOnActorInitHandler(void* actorRef) {
@ -754,6 +756,10 @@ void TimeSaverOnActorInitHandler(void* actorRef) {
});
}
if (actor->id == ACTOR_EN_OWL && gPlayState->sceneNum == SCENE_ZORAS_RIVER && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0) == 2) {
Actor_Kill(actor);
}
if (actor->id == ACTOR_BG_SPOT02_OBJECTS && actor->params == 2) {
bgSpot02UpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
@ -776,6 +782,61 @@ void TimeSaverOnActorInitHandler(void* actorRef) {
});
}
if (actor->id == ACTOR_BG_SPOT03_TAKI) {
bgSpot03UpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id != ACTOR_BG_SPOT03_TAKI) {
return;
}
bool shouldKeepOpen;
switch (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0)) {
case 1:
shouldKeepOpen = Flags_GetEventChkInf(EVENTCHKINF_OPENED_ZORAS_DOMAIN);
break;
case 2:
if (IS_RANDO && RAND_GET_OPTION(RSK_SLEEPING_WATERFALL) == RO_WATERFALL_OPEN) {
shouldKeepOpen = true;
} else {
shouldKeepOpen = CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) &&
(INV_CONTENT(ITEM_OCARINA_TIME) == ITEM_OCARINA_TIME ||
INV_CONTENT(ITEM_OCARINA_FAIRY) == ITEM_OCARINA_FAIRY);
}
break;
default:
shouldKeepOpen = false;
break;
}
if (!shouldKeepOpen) {
return;
}
BgSpot03Taki* bgSpot03 = static_cast<BgSpot03Taki*>(innerActorRef);
if (bgSpot03->actionFunc == func_808ADEF0) {
bgSpot03->actionFunc = BgSpot03Taki_KeepOpen;
bgSpot03->state = WATERFALL_OPENED;
bgSpot03->openingAlpha = 0.0f;
Flags_SetSwitch(gPlayState, bgSpot03->switchFlag);
func_8003EBF8(gPlayState, &gPlayState->colCtx.dyna, bgSpot03->dyna.bgId);
BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 0);
BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 1);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(bgSpot03UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(bgSpot03KillHook);
bgSpot03UpdateHook = 0;
bgSpot03KillHook = 0;
}
});
bgSpot03KillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(bgSpot03UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(bgSpot03KillHook);
bgSpot03UpdateHook = 0;
bgSpot03KillHook = 0;
});
}
if (actor->id == ACTOR_EN_DNT_DEMO && (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) {
EnDntDemo* enDntDemo = static_cast<EnDntDemo*>(actorRef);
enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward;
@ -786,7 +847,7 @@ void TimeSaverOnActorInitHandler(void* actorRef) {
// or poes from which the cutscene is triggered until we can have a "BeforeActorInit" hook.
// So for now we're just going to set the flag before they get to the room the cutscene is in
if (gPlayState->sceneNum == SCENE_FOREST_TEMPLE && actor->id == ACTOR_EN_ST && !Flags_GetSwitch(gPlayState, 0x1B)) {
if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) {
if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0) && !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) {
Flags_SetSwitch(gPlayState, 0x1B);
}
}
@ -812,7 +873,7 @@ void TimeSaverOnActorInitHandler(void* actorRef) {
// Fire Temple Darunia cutscene
if (actor->id == ACTOR_EN_DU && gPlayState->sceneNum == SCENE_FIRE_TEMPLE) {
if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) {
if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0) && !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) {
Flags_SetInfTable(INFTABLE_SPOKE_TO_DARUNIA_IN_FIRE_TEMPLE);
Actor_Kill(actor);
}

@ -301,6 +301,16 @@ void TimeSplitsGetImageSize(uint32_t item) {
}
}
void SplitsPushImageButtonStyle(){
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));
}
void SplitsPopImageButtonStyle(){
ImGui::PopStyleColor(3);
}
void TimeSplitsUpdateSplitStatus() {
uint32_t index = 0;
for (auto& data : splitList) {
@ -310,7 +320,7 @@ void TimeSplitsUpdateSplitStatus() {
}
index++;
}
for (int i = index; i < splitList.size(); i++) {
for (size_t i = index; i < splitList.size(); i++) {
if (splitList[i].splitTimeStatus != SPLIT_STATUS_ACTIVE && splitList[i].splitTimeStatus != SPLIT_STATUS_COLLECTED) {
splitList[i].splitTimeStatus = SPLIT_STATUS_INACTIVE;
}
@ -421,11 +431,28 @@ void TimeSplitsPopUpContext() {
if (popupID == ITEM_SKULL_TOKEN) {
ImGui::BeginTable("Token Table", 2);
ImGui::TableNextColumn();
SplitsPushImageButtonStyle();
ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("QUEST_SKULL_TOKEN"),
ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 2.0f, ImVec4(0, 0, 0, 0));
ImGui::TableNextColumn();
SplitsPopImageButtonStyle();
ImGui::PushItemWidth(150.0f);
ImGui::BeginGroup();
std::string MinusBTNName = " - ##Set Tokens";
ImGui::SameLine();
if (ImGui::Button(MinusBTNName.c_str()) && skullTokenCount > 0) {
skullTokenCount--;
}
ImGui::SameLine();
ImGui::SliderInt("##count", &skullTokenCount, 0, 100, "%d Tokens");
std::string PlusBTNName = " + ##Set Tokens";
ImGui::SameLine();
if (ImGui::Button(PlusBTNName.c_str()) && skullTokenCount < 100) {
skullTokenCount++;
}
ImGui::EndGroup();
ImGui::PopItemWidth();
if (ImGui::Button("Set Tokens")) {
auto findID = std::find_if(splitObjectList.begin(), splitObjectList.end(), [&](const SplitObject& obj) { return obj.splitID == ITEM_SKULL_TOKEN; });
@ -442,6 +469,7 @@ void TimeSplitsPopUpContext() {
ImGui::EndTable();
} else {
int rowIndex = 0;
SplitsPushImageButtonStyle();
for (auto item : popupList[popupID]) {
auto findID = std::find_if(splitObjectList.begin(), splitObjectList.end(), [&](const SplitObject& obj) { return obj.splitID == item; });
if (findID == splitObjectList.end()) {
@ -468,7 +496,7 @@ void TimeSplitsPopUpContext() {
if (popupID <= ITEM_SLINGSHOT && popupID != -1) {
ImVec2 imageMin = ImGui::GetItemRectMin();
ImVec2 imageMax = ImGui::GetItemRectMax();
ImVec2 imageSize = ImVec2(imageMax.x - imageMin.x, imageMax.y - imageMin.y);
//ImVec2 imageSize = ImVec2(imageMax.x - imageMin.x, imageMax.y - imageMin.y); UNUSED
ImVec2 textPos = ImVec2(imageMax.x - ImGui::CalcTextSize("00").x - 5,
imageMax.y - ImGui::CalcTextSize("00").y - 5);
@ -484,6 +512,7 @@ void TimeSplitsPopUpContext() {
}
rowIndex++;
}
SplitsPopImageButtonStyle();
}
ImGui::EndPopup();
}
@ -610,10 +639,8 @@ void TimeSplitsDrawSplitsList() {
ImGui::TableSetupColumn("Prev. Best");
ImGui::TableHeadersRow();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));
SplitsPushImageButtonStyle();
for (auto& split : splitList) {
ImGui::TableNextColumn();
TimeSplitsSplitBestTimeDisplay(split);
@ -648,10 +675,10 @@ void TimeSplitsDrawSplitsList() {
dragIndex++;
}
SplitsPopImageButtonStyle();
TimeSplitsPostDragAndDrop();
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(1);
ImGui::EndTable();
ImGui::EndChild();
@ -677,7 +704,7 @@ void TimeSplitsDrawItemList(uint32_t type) {
ImGui::BeginChild("Item Child");
ImGui::BeginTable("Item List", tableSize);
for (int i = 0; i < tableSize; i++) {
for (size_t i = 0; i < tableSize; i++) {
if (i == 0) {
ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 39.0f);
} else {
@ -689,15 +716,12 @@ void TimeSplitsDrawItemList(uint32_t type) {
}
}
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));
for (auto& split : splitObjectList) {
if (split.splitType == type) {
ImGui::TableNextColumn();
ImGui::PushID(split.splitID);
TimeSplitsGetImageSize(split.splitID);
SplitsPushImageButtonStyle();
if (ImGui::ImageButton(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage),
imageSize, ImVec2(0, 0), ImVec2(1, 1), imagePadding, ImVec4(0, 0, 0, 0), split.splitTint)) {
@ -715,6 +739,7 @@ void TimeSplitsDrawItemList(uint32_t type) {
}
}
}
SplitsPopImageButtonStyle();
TimeSplitsPopUpContext();
ImGui::PopID();
@ -729,7 +754,6 @@ void TimeSplitsDrawItemList(uint32_t type) {
}
}
ImGui::PopStyleColor(3);
ImGui::EndTable();
ImGui::EndChild();
}

@ -84,6 +84,7 @@ Sail* Sail::Instance;
#include "Enhancements/mods.h"
#include "Enhancements/game-interactor/GameInteractor.h"
#include "Enhancements/randomizer/draw.h"
#include "Enhancements/custom-collectible/CustomCollectible.h"
#include <libultraship/libultraship.h>
// Resource Types/Factories
@ -703,6 +704,7 @@ extern "C" void VanillaItemTable_Init() {
GET_ITEM(ITEM_NUT_UPGRADE_30, OBJECT_GI_NUTS, GID_NUTS, 0xA7, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_30),
GET_ITEM(ITEM_NUT_UPGRADE_40, OBJECT_GI_NUTS, GID_NUTS, 0xA8, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_40),
GET_ITEM(ITEM_BULLET_BAG_50, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG_50, 0x6C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_50),
GET_ITEM(ITEM_SHIP, OBJECT_UNSET_16E, GID_MAXIMUM, 0x00, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_SHIP),
GET_ITEM_NONE,
GET_ITEM_NONE,
GET_ITEM_NONE // GI_MAX - if you need to add to this table insert it before this entry.
@ -874,6 +876,7 @@ std::unordered_map<ItemID, RandomizerGet> ItemIDtoRandomizerGetMap {
{ ITEM_KOKIRI_EMERALD, RG_KOKIRI_EMERALD },
{ ITEM_GORON_RUBY, RG_GORON_RUBY },
{ ITEM_ZORA_SAPPHIRE, RG_ZORA_SAPPHIRE },
{ ITEM_SWORD_MASTER, RG_MASTER_SWORD },
};
extern "C" RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID) {
@ -1172,6 +1175,7 @@ extern "C" void InitOTR() {
DebugConsole_Init();
InitMods();
CustomCollectible::RegisterHooks();
ActorDB::AddBuiltInCustomActors();
// #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer
CVarClear(CVAR_GENERAL("RandomizerNewFileDropped"));
@ -1932,7 +1936,7 @@ extern "C" u32 SpoilerFileExists(const char* spoilerFileName) {
}
extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey) {
return OTRGlobals::Instance->gRandoContext->GetOption(randoSettingKey).GetSelectedOptionIndex();
return OTRGlobals::Instance->gRandoContext->GetOption(randoSettingKey).GetContextOptionIndex();
}
extern "C" RandomizerCheck Randomizer_GetCheckFromActor(s16 actorId, s16 sceneNum, s16 actorParams) {
@ -2152,7 +2156,6 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) {
} else if (textId >= TEXT_SHOP_ITEM_RANDOM_CONFIRM && textId <= TEXT_SHOP_ITEM_RANDOM_CONFIRM_END){
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromRandomizerInf((RandomizerInf)((textId - TEXT_SHOP_ITEM_RANDOM_CONFIRM) + RAND_INF_SHOP_ITEMS_KF_SHOP_ITEM_1));
messageEntry = OTRGlobals::Instance->gRandomizer->GetMerchantMessage(rc, TEXT_SHOP_ITEM_RANDOM_CONFIRM);
// textId: TEXT_SCRUB_RANDOM + (randomizerInf - RAND_INF_SCRUBS_PURCHASED_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT)
} else if (textId == TEXT_SCRUB_RANDOM) {
EnDns* enDns = (EnDns*)GET_PLAYER(play)->talkActor;
RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromRandomizerInf((RandomizerInf)enDns->sohScrubIdentity.randomizerInf);

@ -180,7 +180,7 @@ void SaveManager::LoadRandomizerVersion1() {
int key, value;
SaveManager::Instance->LoadData("sk" + std::to_string(i), key);
SaveManager::Instance->LoadData("sv" + std::to_string(i), value);
randoContext->GetOption(RandomizerSettingKey(key)).SetSelectedIndex(value);
randoContext->GetOption(RandomizerSettingKey(key)).SetContextIndex(value);
}
for (int i = 0; i < 50; i++) {
@ -286,7 +286,7 @@ void SaveManager::LoadRandomizerVersion2() {
SaveManager::Instance->LoadArray("randoSettings", RSK_MAX, [&](size_t i) {
int value = 0;
SaveManager::Instance->LoadData("", value);
randoContext->GetOption(RandomizerSettingKey(i)).SetSelectedIndex(value);
randoContext->GetOption(RandomizerSettingKey(i)).SetContextIndex(value);
});
SaveManager::Instance->LoadArray("hintLocations", RH_ZR_OPEN_GROTTO_GOSSIP_STONE + 1, [&](size_t i) {
@ -435,7 +435,7 @@ void SaveManager::LoadRandomizerVersion3() {
SaveManager::Instance->LoadArray("randoSettings", RSK_MAX, [&](size_t i) {
int value = 0;
SaveManager::Instance->LoadData("", value);
randoContext->GetOption(RandomizerSettingKey(i)).SetSelectedIndex(value);
randoContext->GetOption(RandomizerSettingKey(i)).SetContextIndex(value);
});
SaveManager::Instance->LoadArray("hintLocations", RH_MAX, [&](size_t i) {
@ -464,7 +464,7 @@ void SaveManager::LoadRandomizerVersion3() {
});
randoContext->GetTrials()->SkipAll();
SaveManager::Instance->LoadArray("requiredTrials", randoContext->GetOption(RSK_TRIAL_COUNT).GetSelectedOptionIndex()+1, [&](size_t i) {
SaveManager::Instance->LoadArray("requiredTrials", randoContext->GetOption(RSK_TRIAL_COUNT).GetContextOptionIndex() + 1, [&](size_t i) {
size_t trialId;
SaveManager::Instance->LoadData("", trialId);
randoContext->GetTrial(trialId)->SetAsRequired();
@ -513,7 +513,7 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f
SaveManager::Instance->SaveData("finalSeed", randoContext->GetSettings()->GetSeed());
SaveManager::Instance->SaveArray("randoSettings", RSK_MAX, [&](size_t i) {
SaveManager::Instance->SaveData("", randoContext->GetOption((RandomizerSettingKey(i))).GetSelectedOptionIndex());
SaveManager::Instance->SaveData("", randoContext->GetOption((RandomizerSettingKey(i))).GetContextOptionIndex());
});
SaveManager::Instance->SaveArray("hintLocations", RH_MAX, [&](size_t i) {

@ -39,6 +39,7 @@
#include "Enhancements/debugger/MessageViewer.h"
#include "soh/Notification/Notification.h"
#include "soh/Enhancements/Holiday/Caladius.h"
#include "soh/Enhancements/TimeDisplay/TimeDisplay.h"
bool isBetaQuestEnabled = false;
@ -138,6 +139,7 @@ namespace SohGui {
std::shared_ptr<SohModalWindow> mModalWindow;
std::shared_ptr<Notification::Window> mNotificationWindow;
std::shared_ptr<CaladiusWindow> mCaladiusWindow;
std::shared_ptr<TimeDisplayWindow> mTimeDisplayWindow;
void SetupGuiElements() {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
@ -226,6 +228,8 @@ namespace SohGui {
mCaladiusWindow = std::make_shared<CaladiusWindow>(CVAR_WINDOW("Holiday Cal"), "Holiday Cal");
gui->AddGuiWindow(mCaladiusWindow);
mCaladiusWindow->Show();
mTimeDisplayWindow = std::make_shared<TimeDisplayWindow>(CVAR_WINDOW("TimeDisplayEnabled"), "Additional Timers");
gui->AddGuiWindow(mTimeDisplayWindow);
}
void Destroy() {
@ -262,6 +266,7 @@ namespace SohGui {
mTimeSplitWindow = nullptr;
mCaladiusWindow = nullptr;
mPlandomizerWindow = nullptr;
mTimeDisplayWindow = nullptr;
}
void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function<void()> button1callback, std::function<void()> button2callback) {

@ -45,6 +45,7 @@
#include "Enhancements/timesplits/TimeSplits.h"
#include "Enhancements/Holiday/Holiday.hpp"
#include "Enhancements/randomizer/Plandomizer.h"
#include "Enhancements/TimeDisplay/TimeDisplay.h"
// FA icons are kind of wonky, if they worked how I expected them to the "+ 2.0f" wouldn't be needed, but
// they don't work how I expect them to so I added that because it looked good when I eyeballed it
@ -84,6 +85,7 @@ static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large
static const char* chestStyleMatchesContentsOptions[4] = { "Disabled", "Both", "Texture Only", "Size Only" };
static const char* skipGetItemAnimationOptions[3] = { "Disabled", "Junk Items", "All Items" };
static const char* skipForcedDialogOptions[4] = { "None", "Navi Only", "NPCs Only", "All" };
static const char* sleepingWaterfallOptions[3] = { "Always", "Once", "Never" };
static const char* bunnyHoodOptions[3] = { "Disabled", "Faster Run & Longer Jump", "Faster Run" };
static const char* mirroredWorldModes[9] = {
"Disabled", "Always", "Random", "Random (Seeded)", "Dungeons",
@ -124,7 +126,7 @@ static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large
CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"),
CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"),
};
static const char* itemCountMessageOptions[sizeof(itemCountMessageCVars) / sizeof(const char*)] = {
static const char* itemCountMessageOptions[ARRAY_COUNT(itemCountMessageCVars)] = {
"Gold Skulltula Tokens",
"Pieces of Heart",
"Heart Containers",
@ -584,9 +586,9 @@ void DrawSettingsMenu() {
ImGui::Text("Position");
UIWidgets::EnhancementCombobox(CVAR_SETTING("Notifications.Position"), notificationPosition, 0);
UIWidgets::EnhancementSliderFloat("Duration: %.0f seconds", "##NotificationDuration", CVAR_SETTING("Notifications.Duration"), 3.0f, 30.0f, "", 10.0f, false, false, false);
UIWidgets::EnhancementSliderFloat("BG Opacity: %.1f %%", "##NotificaitonBgOpacity", CVAR_SETTING("Notifications.BgOpacity"), 0.0f, 1.0f, "", 0.5f, true, false, false);
UIWidgets::EnhancementSliderFloat("Size: %.1f", "##NotificaitonSize", CVAR_SETTING("Notifications.Size"), 1.0f, 5.0f, "", 1.8f, false, false, false);
UIWidgets::EnhancementSliderFloat("Duration: %.1f seconds", "##NotificationDuration", CVAR_SETTING("Notifications.Duration"), 3.0f, 30.0f, "", 10.0f, false, true, false);
UIWidgets::EnhancementSliderFloat("BG Opacity: %.1f %%", "##NotificaitonBgOpacity", CVAR_SETTING("Notifications.BgOpacity"), 0.0f, 1.0f, "", 0.5f, true, true, false);
UIWidgets::EnhancementSliderFloat("Size: %.1f", "##NotificaitonSize", CVAR_SETTING("Notifications.Size"), 1.0f, 20.0f, "", 1.8f, false, true, false);
UIWidgets::Spacer(0);
@ -607,6 +609,7 @@ extern std::shared_ptr<AudioEditor> mAudioEditorWindow;
extern std::shared_ptr<CosmeticsEditorWindow> mCosmeticsEditorWindow;
extern std::shared_ptr<GameplayStatsWindow> mGameplayStatsWindow;
extern std::shared_ptr<TimeSplitWindow> mTimeSplitWindow;
extern std::shared_ptr<TimeDisplayWindow> mTimeDisplayWindow;
void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Enhancements"))
@ -682,8 +685,8 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedEnhancementCheckbox("Skip Owl Interactions", CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO);
UIWidgets::PaddedEnhancementCheckbox("Skip Misc Interactions", CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO);
UIWidgets::PaddedEnhancementCheckbox("Disable Title Card", CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO);
UIWidgets::PaddedEnhancementCheckbox("Skip Glitch-Aiding Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, 0);
UIWidgets::Tooltip("Skip cutscenes that are associated with useful glitches, currently this is only the Fire Temple Darunia CS and Forest Temple Poe Sisters CS");
UIWidgets::PaddedEnhancementCheckbox("Exclude Glitch-Aiding Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, 0);
UIWidgets::Tooltip("Don't skip cutscenes that are associated with useful glitches, currently this is only the Fire Temple Darunia CS, Forest Temple Poe Sisters CS and the Box Skip One Point in Jabu");
UIWidgets::PaddedEnhancementCheckbox("Skip Child Stealth", CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false);
UIWidgets::Tooltip("The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards.");
UIWidgets::PaddedEnhancementCheckbox("Skip Tower Escape", CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false);
@ -715,23 +718,15 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedEnhancementSliderInt("Crawl speed %dx", "##CRAWLSPEED", CVAR_ENHANCEMENT("CrawlSpeed"), 1, 4, "", 1, true, false, true);
UIWidgets::PaddedEnhancementCheckbox("Faster Heavy Block Lift", CVAR_ENHANCEMENT("FasterHeavyBlockLift"), false, false);
UIWidgets::Tooltip("Speeds up lifting silver rocks and obelisks");
UIWidgets::PaddedEnhancementCheckbox("Faster Rupee Accumulator", CVAR_ENHANCEMENT("TimeSavers.FasterRupeeAccumulator"), false, false);
UIWidgets::PaddedEnhancementCheckbox("Skip Pickup Messages", CVAR_ENHANCEMENT("FastDrops"), true, false);
UIWidgets::Tooltip("Skip pickup messages for new consumable items and bottle swipes");
UIWidgets::PaddedEnhancementCheckbox("Fast Ocarina Playback", CVAR_ENHANCEMENT("FastOcarinaPlayback"), true, false);
UIWidgets::Tooltip("Skip the part where the Ocarina playback is called when you play a song");
bool forceSkipScarecrow = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_SCARECROWS_SONG);
static const char* forceSkipScarecrowText = "This setting is forcefully enabled because a savefile\nwith \"Skip Scarecrow Song\" is loaded";
UIWidgets::PaddedEnhancementCheckbox("Skip Scarecrow Song", CVAR_ENHANCEMENT("InstantScarecrow"), true, false,
forceSkipScarecrow, forceSkipScarecrowText, UIWidgets::CheckboxGraphics::Checkmark);
UIWidgets::Tooltip("Pierre appears when Ocarina is pulled out. Requires learning scarecrow song.");
UIWidgets::PaddedEnhancementCheckbox("Skip Magic Arrow Equip Animation", CVAR_ENHANCEMENT("SkipArrowAnimation"), true, false);
UIWidgets::PaddedEnhancementCheckbox("Skip save confirmation", CVAR_ENHANCEMENT("SkipSaveConfirmation"), true, false);
UIWidgets::Tooltip("Skip the \"Game saved.\" confirmation screen");
UIWidgets::PaddedEnhancementCheckbox("Faster Farore's Wind", CVAR_ENHANCEMENT("FastFarores"), true, false);
UIWidgets::Tooltip("Greatly decreases cast time of Farore's Wind magic spell.");
UIWidgets::PaddedEnhancementCheckbox("Skip water take breath animation", CVAR_ENHANCEMENT("SkipSwimDeepEndAnim"), true, false);
UIWidgets::Tooltip("Skips Link's taking breath animation after coming up from water. This setting does not interfere with getting items from underwater.");
ImGui::TableNextColumn();
UIWidgets::Spacer(0);
@ -798,6 +793,30 @@ void DrawEnhancementsMenu() {
"- Not within range of Ocarina playing spots");
UIWidgets::PaddedEnhancementCheckbox("Pause Warp", CVAR_ENHANCEMENT("PauseWarp"), true, false);
UIWidgets::Tooltip("Selection of warp song in pause menu initiates warp. Disables song playback.");
UIWidgets::PaddedEnhancementCheckbox("Skip water take breath animation", CVAR_ENHANCEMENT("SkipSwimDeepEndAnim"), true, false);
UIWidgets::Tooltip("Skips Link's taking breath animation after coming up from water. This setting does not interfere with getting items from underwater.");
bool forceSkipScarecrow = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_SCARECROWS_SONG);
static const char* forceSkipScarecrowText = "This setting is forcefully enabled because a savefile\nwith \"Skip Scarecrow Song\" is loaded";
UIWidgets::PaddedEnhancementCheckbox("Skip Scarecrow Song", CVAR_ENHANCEMENT("InstantScarecrow"), true, false,
forceSkipScarecrow, forceSkipScarecrowText, UIWidgets::CheckboxGraphics::Checkmark);
UIWidgets::Tooltip("Pierre appears when Ocarina is pulled out. Requires learning scarecrow song.");
bool forceSleepingWaterfallEnhancement =
IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) == RO_WATERFALL_OPEN;
uint8_t forceSleepingWaterfallValue = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) + 1;
static const char* forceSleepingWaterfallText =
"This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded.";
UIWidgets::PaddedText("Play Zelda's Lullaby to open Sleeping Waterfall", true, false);
UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"),
sleepingWaterfallOptions, 0, forceSleepingWaterfallEnhancement,
forceSleepingWaterfallText, forceSleepingWaterfallValue);
UIWidgets::Tooltip(
"Always: Link must always play Zelda's Lullaby to open "
"the waterfall entrance to Zora's Domain.\n"
"Once: Link only needs to play Zelda's Lullaby once to "
"open the waterfall; after that, it stays open permanently.\n"
"Never: Link never needs to play Zelda's Lullaby to open the "
"waterfall; he only needs to have learned it and have an ocarina."
);
ImGui::EndTable();
ImGui::EndMenu();
@ -856,6 +875,17 @@ void DrawEnhancementsMenu() {
"Toggling while inside the shop will not change prices or restock any SOLD OUTs");
UIWidgets::PaddedEnhancementCheckbox("Aiming reticle for the bow/slingshot", CVAR_ENHANCEMENT("BowReticle"), true, false);
UIWidgets::Tooltip("Aiming with a bow or slingshot will display a reticle as with the hookshot when the projectile is ready to fire.");
if (UIWidgets::PaddedEnhancementCheckbox("Aim boomerang in first-person mode", CVAR_ENHANCEMENT("BoomerangFirstPerson"), true, false)) {
if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) {
CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0);
}
}
UIWidgets::Tooltip(
"Change aiming for the boomerang from third person to first person to see past Link's head");
if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) {
UIWidgets::PaddedEnhancementCheckbox("Aiming reticle for boomerang", CVAR_ENHANCEMENT("BoomerangReticle"), true, false);
UIWidgets::Tooltip("Aiming with the boomerang will display a reticle as with the hookshot");
}
if (UIWidgets::PaddedEnhancementCheckbox("Allow strength equipment to be toggled", CVAR_ENHANCEMENT("ToggleStrength"), true, false)) {
if (!CVarGetInteger(CVAR_ENHANCEMENT("ToggleStrength"), 0)) {
CVarSetInteger(CVAR_ENHANCEMENT("StrengthDisabled"), 0);
@ -868,7 +898,7 @@ void DrawEnhancementsMenu() {
UIWidgets::Spacer(0);
if (ImGui::BeginMenu("Item Count Messages")) {
int numOptions = sizeof(itemCountMessageCVars) / sizeof(const char*);
int numOptions = ARRAY_COUNT(itemCountMessageCVars);
bool allItemCountsChecked = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions,
[](const char* cvar) { return CVarGetInteger(cvar, 0); });
bool someItemCountsChecked = std::any_of(itemCountMessageCVars, itemCountMessageCVars + numOptions,
@ -1270,6 +1300,9 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedEnhancementCheckbox("Targetable Hookshot Reticle", CVAR_ENHANCEMENT("HookshotableReticle"), true, false);
UIWidgets::Tooltip("Use a different color when aiming at hookshotable collision");
UIWidgets::PaddedEnhancementCheckbox("Faster Rupee Accumulator", CVAR_ENHANCEMENT("TimeSavers.FasterRupeeAccumulator"), true, false);
UIWidgets::Tooltip("Causes your wallet to fill and empty faster when you gain or lose money.");
ImGui::EndMenu();
}
@ -1693,6 +1726,36 @@ void DrawEnhancementsMenu() {
mTimeSplitWindow->ToggleVisibility();
}
}
if (mTimeDisplayWindow) {
if (ImGui::Button(GetWindowButtonText("Additional Timers", CVarGetInteger(CVAR_WINDOW("TimeDisplayEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) {
mTimeDisplayWindow->ToggleVisibility();
}
}
if (mTimeDisplayWindow->IsVisible()) {
ImGui::SeparatorText("Timer Display Options");
if (!gPlayState) {
ImGui::Text("Additional Timer options\n"
"available when a file is\n"
"loaded...");
} else {
if (UIWidgets::PaddedEnhancementSliderFloat("Font Scale: %.2fx", "##FontScale", CVAR_ENHANCEMENT("TimeDisplay.FontScale"),
1.0f, 5.0f, "", 1.0f, false, true, false, true)) {
TimeDisplayInitSettings();
}
if (UIWidgets::PaddedEnhancementCheckbox("Hide Background", CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"),
false, false)) {
TimeDisplayInitSettings();
}
ImGui::Separator();
for (auto& timer : timeDisplayList) {
if (UIWidgets::PaddedEnhancementCheckbox(timer.timeLabel.c_str(), timer.timeEnable, false, false)) {
TimeDisplayUpdateDisplayOptions();
}
}
}
}
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1);

@ -593,7 +593,7 @@ namespace UIWidgets {
#if defined(__SWITCH__) || defined(__WIIU__)
srand(time(NULL));
#endif
ImVec4 color = GetRandomValue(255);
ImVec4 color = GetRandomValue();
colors->x = color.x;
colors->y = color.y;
colors->z = color.z;

@ -2,6 +2,7 @@
#include "vt.h"
#include "overlays/actors/ovl_Arms_Hook/z_arms_hook.h"
#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h"
#include "overlays/actors/ovl_En_Part/z_en_part.h"
#include "objects/gameplay_keep/gameplay_keep.h"
#include "objects/gameplay_dangeon_keep/gameplay_dangeon_keep.h"
@ -1081,14 +1082,11 @@ void TitleCard_InitPlaceName(PlayState* play, TitleCardContext* titleCtx, void*
}
void TitleCard_Update(PlayState* play, TitleCardContext* titleCtx) {
const Color_RGB8 TitleCard_Colors_ori = {255,255,255};
Color_RGB8 TitleCard_Colors = {255,255,255};
if (titleCtx->isBossCard && CVarGetInteger(CVAR_COSMETIC("HUD.TitleCard.Boss.Changed"), 1) == 2) {
TitleCard_Colors = CVarGetColor24(CVAR_COSMETIC("HUD.TitleCard.Boss.Value"), TitleCard_Colors_ori);
} else if (!titleCtx->isBossCard && CVarGetInteger(CVAR_COSMETIC("HUD.TitleCard.Map.Changed"), 1) == 2) {
TitleCard_Colors = CVarGetColor24(CVAR_COSMETIC("HUD.TitleCard.Map.Value"), TitleCard_Colors_ori);
} else {
TitleCard_Colors = TitleCard_Colors_ori;
Color_RGB8 TitleCard_Colors = { 255, 255, 255 };
if (titleCtx->isBossCard && CVarGetInteger(CVAR_COSMETIC("HUD.TitleCard.Boss.Changed"), 0) == 1) {
TitleCard_Colors = CVarGetColor24(CVAR_COSMETIC("HUD.TitleCard.Boss.Value"), TitleCard_Colors);
} else if (!titleCtx->isBossCard && CVarGetInteger(CVAR_COSMETIC("HUD.TitleCard.Map.Changed"), 0) == 1) {
TitleCard_Colors = CVarGetColor24(CVAR_COSMETIC("HUD.TitleCard.Map.Value"), TitleCard_Colors);
}
if (DECR(titleCtx->delayTimer) == 0) {
@ -2350,8 +2348,14 @@ void Actor_DrawFaroresWindPointer(PlayState* play) {
} else if (D_8015BC18 > 0.0f) {
static Vec3f effectVel = { 0.0f, -0.05f, 0.0f };
static Vec3f effectAccel = { 0.0f, -0.025f, 0.0f };
static Color_RGBA8 effectPrimCol = { 255, 255, 255, 0 };
static Color_RGBA8 effectEnvCol = { 100, 200, 0, 0 };
Color_RGBA8 effectPrimCol = { 255, 255, 255, 0 };
Color_RGBA8 effectEnvCol = { 100, 200, 0, 0 };
if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresSecondary.Changed"), 0)) {
effectEnvCol = CVarGetColor(CVAR_COSMETIC("Magic.FaroresSecondary.Value"), effectEnvCol);
}
if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresPrimary.Changed"), 0)) {
effectPrimCol = CVarGetColor(CVAR_COSMETIC("Magic.FaroresPrimary.Value"), effectPrimCol);
}
Vec3f* curPos = &gSaveContext.respawn[RESPAWN_MODE_TOP].pos;
Vec3f* nextPos = &gSaveContext.respawn[RESPAWN_MODE_DOWN].pos;
f32 prevNum = D_8015BC18;
@ -2446,8 +2450,16 @@ void Actor_DrawFaroresWindPointer(PlayState* play) {
Matrix_Push();
gDPPipeSync(POLY_XLU_DISP++);
gDPSetPrimColor(POLY_XLU_DISP++, 128, 128, 255, 255, 200, alpha);
gDPSetEnvColor(POLY_XLU_DISP++, 100, 200, 0, 255);
Color_RGB8 Spell_env = { 100, 200, 0 };
Color_RGB8 Spell_col = { 255, 255, 200 };
if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresSecondary.Changed"), 0)) {
Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.FaroresSecondary.Value"), Spell_env);
}
if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresPrimary.Changed"), 0)) {
Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.FaroresPrimary.Value"), Spell_col);
}
gDPSetPrimColor(POLY_XLU_DISP++, 128, 128, Spell_col.r, Spell_col.g, Spell_col.b, alpha);
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, 255);
Matrix_RotateZ(((play->gameplayFrames * 1500) & 0xFFFF) * M_PI / 32768.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
@ -3854,8 +3866,14 @@ Actor* Actor_GetProjectileActor(PlayState* play, Actor* refActor, f32 radius) {
// it can also be an arrow.
// Luckily, the field at the same offset in the arrow actor is the x component of a vector
// which will rarely ever be 0. So it's very unlikely for this bug to cause an issue.
//
// SoH [Port] We're making a change here, it doesn't technically fix the bug but makes it behave
// more like hardware. Because of pointer size differences in SoH this was accessing a different
// place in memory and causing issues with Dark link behavior, and probably other places too
if ((Math_Vec3f_DistXYZ(&refActor->world.pos, &actor->world.pos) > radius) ||
(((ArmsHook*)actor)->timer == 0)) {
(actor->id == ACTOR_ARMS_HOOK && ((ArmsHook*)actor)->timer == 0) ||
(actor->id == ACTOR_EN_ARROW && ((EnArrow*)actor)->unk_210.x == 0)
) {
actor = actor->next;
} else {
deltaX = Math_SinS(actor->world.rot.y) * (actor->speedXZ * 10.0f);

@ -399,6 +399,9 @@ DrawItemTableEntry sDrawItemTable[] = {
* Calls the corresponding draw function for the given draw ID
*/
void GetItem_Draw(PlayState* play, s16 drawId) {
if (drawId < 0 || drawId >= GID_MAXIMUM) {
return;
}
sDrawItemTable[drawId].drawFunc(play, drawId);
}

@ -1,5 +1,7 @@
#include "global.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
void GameOver_Init(PlayState* play) {
play->gameOverCtx.state = GAMEOVER_INACTIVE;
@ -34,7 +36,7 @@ void GameOver_Update(PlayState* play) {
gSaveContext.eventInf[1] &= ~1;
// search inventory for spoiling items and revert if necessary
if (!(IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE))) {
if (GameInteractor_Should(VB_REVERT_SPOILING_ITEMS, true)) {
for (i = 0; i < ARRAY_COUNT(gSpoilingItems); i++) {
if (INV_CONTENT(ITEM_POCKET_EGG) == gSpoilingItems[i]) {
INV_CONTENT(gSpoilingItemReverts[i]) = gSpoilingItemReverts[i];

@ -7,6 +7,7 @@
#include "textures/parameter_static/parameter_static.h"
#include "textures/message_static/message_static.h"
#include "textures/message_texture_static/message_texture_static.h"
#include "soh/Enhancements/cosmetics/CosmeticsEditor.h"
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
@ -367,6 +368,80 @@ void Message_FindCreditsMessage(PlayState* play, u16 textId) {
}
}
#pragma region [SoH] Cosmetics
#define MESSAGE_COSMETICS_HANDLE_COLOR(id) \
if (CVarGetInteger(CVAR_COSMETIC("Message." id ".Changed"), 0)) { \
Color_RGBA8 color = CVarGetColor(CVAR_COSMETIC("Message." id ".Value"), CosmeticsEditor_GetDefaultValue("Message." id)); \
msgCtx->textColorR = color.r; \
msgCtx->textColorG = color.g; \
msgCtx->textColorB = color.b; \
}
void Cosmetics_MaybeSetTextColor(MessageContext* msgCtx, u16 colorParameter) {
switch (colorParameter) {
case MSGCOL_RED:
if (msgCtx->textBoxType == TEXTBOX_TYPE_WOODEN) {
MESSAGE_COSMETICS_HANDLE_COLOR("Red.Wooden")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("Red.Normal")
}
break;
case MSGCOL_ADJUSTABLE:
if (msgCtx->textBoxType == TEXTBOX_TYPE_WOODEN) {
MESSAGE_COSMETICS_HANDLE_COLOR("Adjustable.Wooden")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("Adjustable.Normal")
}
break;
case MSGCOL_BLUE:
if (msgCtx->textBoxType == TEXTBOX_TYPE_WOODEN) {
MESSAGE_COSMETICS_HANDLE_COLOR("Blue.Wooden")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("Blue.Normal")
}
break;
case MSGCOL_LIGHTBLUE:
if (msgCtx->textBoxType == TEXTBOX_TYPE_WOODEN) {
MESSAGE_COSMETICS_HANDLE_COLOR("LightBlue.Wooden")
} else if (msgCtx->textBoxType == TEXTBOX_TYPE_NONE_NO_SHADOW) {
MESSAGE_COSMETICS_HANDLE_COLOR("LightBlue.NoneNoShadow")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("LightBlue.Normal")
}
break;
case MSGCOL_PURPLE:
if (msgCtx->textBoxType == TEXTBOX_TYPE_WOODEN) {
MESSAGE_COSMETICS_HANDLE_COLOR("Purple.Wooden")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("Purple.Normal")
}
break;
case MSGCOL_YELLOW:
if (msgCtx->textBoxType == TEXTBOX_TYPE_WOODEN) {
MESSAGE_COSMETICS_HANDLE_COLOR("Yellow.Wooden")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("Yellow.Normal")
}
break;
case MSGCOL_BLACK:
MESSAGE_COSMETICS_HANDLE_COLOR("Black")
break;
case MSGCOL_DEFAULT:
default:
if (msgCtx->textBoxType == TEXTBOX_TYPE_NONE_NO_SHADOW) {
MESSAGE_COSMETICS_HANDLE_COLOR("Default.NoneNoShadow")
} else {
MESSAGE_COSMETICS_HANDLE_COLOR("Default.Normal")
}
break;
}
}
#undef MESSAGE_COSMETICS_HANDLE_COLOR
#pragma endregion
void Message_SetTextColor(MessageContext* msgCtx, u16 colorParameter) {
switch (colorParameter) {
case MSGCOL_RED:
@ -451,6 +526,7 @@ void Message_SetTextColor(MessageContext* msgCtx, u16 colorParameter) {
}
break;
}
Cosmetics_MaybeSetTextColor(msgCtx, colorParameter);
}
void Message_DrawTextboxIcon(PlayState* play, Gfx** p, s16 x, s16 y) {
@ -853,6 +929,8 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
msgCtx->textColorR = msgCtx->textColorG = msgCtx->textColorB = 255;
}
Cosmetics_MaybeSetTextColor(msgCtx, MSGCOL_DEFAULT);
msgCtx->unk_E3D0 = 0;
charTexIdx = 0;

@ -1891,6 +1891,12 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) {
* @return u8
*/
u8 Item_Give(PlayState* play, u8 item) {
// TODO: Add ShouldItemGive
// if (!GameInteractor_ShouldItemGive(item) || item == ITEM_SHIP) {
if (item == ITEM_SHIP) {
return ITEM_NONE;
}
//prevents getting sticks without the bag in case something got missed
if (
IS_RANDO &&
@ -2486,6 +2492,11 @@ u8 Item_CheckObtainability(u8 item) {
s16 slot = SLOT(item);
s32 temp;
// SOH [Enhancements] Added to enable custom item gives
if (item == ITEM_SHIP) {
return ITEM_NONE;
}
if (item >= ITEM_STICKS_5) {
slot = SLOT(sExtraItemBases[item - ITEM_STICKS_5]);
}
@ -3388,6 +3399,11 @@ void Interface_UpdateMagicBar(PlayState* play) {
default:
gSaveContext.magicState = MAGIC_STATE_IDLE;
if (CVarGetInteger(CVAR_COSMETIC("Consumable.MagicBorder.Changed"), 0)) {
sMagicBorder = CVarGetColor24(CVAR_COSMETIC("Consumable.MagicBorder.Value"), sMagicBorder_ori);
} else {
sMagicBorder = sMagicBorder_ori;
}
break;
}
}
@ -3617,8 +3633,8 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {
s32 healthbar_offsetY = CVarGetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar.PosY"), 0);
s8 anchorType = CVarGetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), ENEMYHEALTH_ANCHOR_ACTOR);
if (CVarGetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar..Changed"), 0)) {
healthbar_red = CVarGetColor(CVAR_COSMETIC("HUD.EnemyHealthBar..Value"), healthbar_red);
if (CVarGetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar.Changed"), 0)) {
healthbar_red = CVarGetColor(CVAR_COSMETIC("HUD.EnemyHealthBar.Value"), healthbar_red);
}
if (CVarGetInteger(CVAR_COSMETIC("HUD.EnemyHealthBorder.Changed"), 0)) {
healthbar_border = CVarGetColor(CVAR_COSMETIC("HUD.EnemyHealthBorder.Value"), healthbar_border);
@ -5713,17 +5729,19 @@ void Interface_Draw(PlayState* play) {
}
// Revert any spoiling trade quest items
for (svar1 = 0; svar1 < ARRAY_COUNT(gSpoilingItems); svar1++) {
if (INV_CONTENT(ITEM_TRADE_ADULT) == gSpoilingItems[svar1]) {
gSaveContext.eventInf[0] &= 0x7F80;
osSyncPrintf("EVENT_INF=%x\n", gSaveContext.eventInf[0]);
play->nextEntranceIndex = spoilingItemEntrances[svar1];
INV_CONTENT(gSpoilingItemReverts[svar1]) = gSpoilingItemReverts[svar1];
if (GameInteractor_Should(VB_REVERT_SPOILING_ITEMS, true)) {
for (svar1 = 0; svar1 < ARRAY_COUNT(gSpoilingItems); svar1++) {
if (INV_CONTENT(ITEM_TRADE_ADULT) == gSpoilingItems[svar1]) {
gSaveContext.eventInf[0] &= 0x7F80;
osSyncPrintf("EVENT_INF=%x\n", gSaveContext.eventInf[0]);
play->nextEntranceIndex = spoilingItemEntrances[svar1];
INV_CONTENT(gSpoilingItemReverts[svar1]) = gSpoilingItemReverts[svar1];
for (svar2 = 1; svar2 < ARRAY_COUNT(gSaveContext.equips.buttonItems); svar2++) {
if (gSaveContext.equips.buttonItems[svar2] == gSpoilingItems[svar1]) {
gSaveContext.equips.buttonItems[svar2] = gSpoilingItemReverts[svar1];
Interface_LoadItemIcon1(play, svar2);
for (svar2 = 1; svar2 < ARRAY_COUNT(gSaveContext.equips.buttonItems); svar2++) {
if (gSaveContext.equips.buttonItems[svar2] == gSpoilingItems[svar1]) {
gSaveContext.equips.buttonItems[svar2] = gSpoilingItemReverts[svar1];
Interface_LoadItemIcon1(play, svar2);
}
}
}
}
@ -6094,7 +6112,7 @@ void Interface_Draw(PlayState* play) {
svar5 = CVarGetInteger(CVAR_COSMETIC("HUD.Timers.PosX"), 0)+204+X_Margins_Timer;
} else if (CVarGetInteger(CVAR_COSMETIC("HUD.Timers.PosType"), 0) == 4) {//Hidden
svar5 = -9999;
}
}
}
OVERLAY_DISP =

@ -888,6 +888,16 @@ s32 Player_HoldsSlingshot(Player* this) {
return this->heldItemAction == PLAYER_IA_SLINGSHOT;
}
// #region SOH [Enhancement]
s32 Player_HoldsBoomerang(Player* this) {
return this->heldItemAction == PLAYER_IA_BOOMERANG;
}
s32 Player_AimsBoomerang(Player* this) {
return Player_HoldsBoomerang(this) && (this->unk_834 != 0);
}
// #endregion
s32 func_8008F128(Player* this) {
return Player_HoldsHookshot(this) && (this->heldActor == NULL);
}
@ -1798,6 +1808,9 @@ Vec3f sLeftRightFootLimbModelFootPos[] = {
void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
Player* this = (Player*)thisx;
const Vec3s BoomerangViewAdult = { -31200, -9200, 17000 };
const Vec3s BoomerangViewChild = { -31200, -8700, 17000 };
if (*dList != NULL) {
Matrix_MultVec3f(&sZeroVec, D_80160000);
}
@ -2005,6 +2018,8 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve
play, this, ((this->heldItemAction == PLAYER_IA_HOOKSHOT) ? 38600.0f : 77600.0f) * CVarGetFloat(CVAR_CHEAT("HookshotReachMultiplier"), 1.0f));
}
}
// #region SOH [Enhancement]
} else if (CVarGetInteger(CVAR_ENHANCEMENT("BowReticle"), 0) && (
(this->heldItemAction == PLAYER_IA_BOW_FIRE) ||
(this->heldItemAction == PLAYER_IA_BOW_ICE) ||
@ -2023,6 +2038,21 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve
Player_DrawHookshotReticle(play, this, RETICLE_MAX);
}
}
} else if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0) && (this->heldItemAction == PLAYER_IA_BOOMERANG)) {
if (Player_HoldsBoomerang(this)) {
if (LINK_IS_ADULT) {
Matrix_RotateZYX(BoomerangViewAdult.x, BoomerangViewAdult.y, BoomerangViewAdult.z,
MTXMODE_APPLY);
} else {
Matrix_RotateZYX(BoomerangViewChild.x, BoomerangViewChild.y, BoomerangViewChild.z, MTXMODE_APPLY);
}
if (Player_AimsBoomerang(this)) {
Matrix_Translate(500.0f, 300.0f, 0.0f, MTXMODE_APPLY);
Player_DrawHookshotReticle(play, this, RETICLE_MAX);
}
}
// #endregion
}
if ((this->unk_862 != 0) || ((func_8002DD6C(this) == 0) && (heldActor != NULL))) {

@ -931,8 +931,21 @@ void EnMag_DrawInnerVanilla(Actor* thisx, PlayState* play, Gfx** gfxp) {
gDPSetAlphaCompare(gfx++, G_AC_NONE);
gDPSetCombineMode(gfx++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
gDPSetPrimColor(gfx++, 0, 0, (s16)this->copyrightAlpha, (s16)this->copyrightAlpha, (s16)this->copyrightAlpha,
(s16)this->copyrightAlpha);
if (CVarGetInteger(CVAR_COSMETIC("Title.Copyright.Changed"), 0)) {
Color_RGBA8 copyrightColor = CVarGetColor(CVAR_COSMETIC("Title.Copyright.Value"), (Color_RGBA8){ 255, 255, 255, 255 });
gDPSetPrimColor(
gfx++,
0,
0,
(s16)(((f32)copyrightColor.r / 255.0f) * this->copyrightAlpha),
(s16)(((f32)copyrightColor.g / 255.0f) * this->copyrightAlpha),
(s16)(((f32)copyrightColor.b / 255.0f) * this->copyrightAlpha),
(s16)(((f32)copyrightColor.a / 255.0f) * this->copyrightAlpha)
);
} else {
gDPSetPrimColor(gfx++, 0, 0, (s16)this->copyrightAlpha, (s16)this->copyrightAlpha, (s16)this->copyrightAlpha,
(s16)this->copyrightAlpha);
}
if ((s16)this->copyrightAlpha != 0) {
gDPLoadTextureBlock(gfx++, copy_tex, G_IM_FMT_IA, G_IM_SIZ_8b, copy_width, 16, 0, G_TX_NOMIRROR | G_TX_CLAMP,

@ -98,9 +98,8 @@ void func_80AACA40(EnMk* this, PlayState* play) {
void func_80AACA94(EnMk* this, PlayState* play) {
if (Actor_HasParent(&this->actor, play) != 0 || !GameInteractor_Should(VB_TRADE_FROG, true, this)) {
this->actor.parent = NULL;
this->actionFunc = func_80AACA40;
Flags_SetRandomizerInf(RAND_INF_ADULT_TRADES_LH_TRADE_FROG);
if (GameInteractor_Should(VB_TRADE_TIMER_EYEDROPS, true)) {
if (GameInteractor_Should(VB_TRADE_TIMER_EYEDROPS, true, this)) {
this->actionFunc = func_80AACA40;
func_80088AA0(240);
gSaveContext.eventInf[1] &= ~1;
}

@ -8,6 +8,7 @@
#include "objects/object_ru1/object_ru1.h"
#include "vt.h"
#include "soh/ResourceManagerHelpers.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_CAN_PRESS_SWITCH)
@ -763,14 +764,6 @@ void func_80AEC2C0(EnRu1* this, PlayState* play) {
func_80AEC070(this, play, something);
}
// Convenience function used so that Ruto always spawns in Jabu in rando, even after she's been kidnapped
// Equivalent to !Flags_GetInfTable(INFTABLE_145) in vanilla
bool shouldSpawnRuto() {
// Flags_GetInfTable(INFTABLE_146) check is to prevent Ruto from spawning during the short period of time when
// she's on the Zora's Sapphire pedestal but hasn't been kidnapped yet (would result in multiple Rutos otherwise)
return !Flags_GetInfTable(INFTABLE_145) || (IS_RANDO && (Flags_GetInfTable(INFTABLE_146)));
}
void func_80AEC320(EnRu1* this, PlayState* play) {
s8 actorRoom;
@ -778,7 +771,10 @@ void func_80AEC320(EnRu1* this, PlayState* play) {
func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0);
this->action = 7;
EnRu1_SetMouthIndex(this, 1);
} else if ((Flags_GetInfTable(INFTABLE_147)) && !Flags_GetInfTable(INFTABLE_140) && shouldSpawnRuto()) {
} else if (
Flags_GetInfTable(INFTABLE_147) && !Flags_GetInfTable(INFTABLE_140) &&
GameInteractor_Should(VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, !Flags_GetInfTable(INFTABLE_145), this)
) {
if (!func_80AEB020(this, play)) {
func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0);
actorRoom = this->actor.room;
@ -867,9 +863,9 @@ void func_80AEC780(EnRu1* this, PlayState* play) {
s32 pad;
Player* player = GET_PLAYER(play);
if ((func_80AEC5FC(this, play)) && (!Play_InCsMode(play)) &&
if (GameInteractor_Should(VB_PLAY_CHILD_RUTO_INTRO, (func_80AEC5FC(this, play)) && (!Play_InCsMode(play)) &&
(!(player->stateFlags1 & (PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_CLIMBING_LADDER))) &&
(player->actor.bgCheckFlags & 1)) {
(player->actor.bgCheckFlags & 1), this)) {
play->csCtx.segment = &D_80AF0880;
gSaveContext.cutsceneTrigger = 1;
@ -1183,8 +1179,11 @@ void func_80AED414(EnRu1* this, PlayState* play) {
void func_80AED44C(EnRu1* this, PlayState* play) {
s8 actorRoom;
if ((Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO)) && shouldSpawnRuto() && !Flags_GetInfTable(INFTABLE_140) &&
!Flags_GetInfTable(INFTABLE_147)) {
if (
Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO) &&
GameInteractor_Should(VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, !Flags_GetInfTable(INFTABLE_145), this) &&
!Flags_GetInfTable(INFTABLE_140) && !Flags_GetInfTable(INFTABLE_147)
) {
if (!func_80AEB020(this, play)) {
func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0);
actorRoom = this->actor.room;
@ -1550,8 +1549,8 @@ s32 func_80AEE394(EnRu1* this, PlayState* play) {
colCtx = &play->colCtx;
floorBgId = this->actor.floorBgId; // necessary match, can't move this out of this block unfortunately
dynaPolyActor = DynaPoly_GetActor(colCtx, floorBgId);
if (dynaPolyActor != NULL && dynaPolyActor->actor.id == ACTOR_BG_BDAN_OBJECTS &&
dynaPolyActor->actor.params == 0 && !Player_InCsMode(play) && play->msgCtx.msgLength == 0) {
if (GameInteractor_Should(VB_RUTO_RUN_TO_SAPPHIRE, dynaPolyActor != NULL && dynaPolyActor->actor.id == ACTOR_BG_BDAN_OBJECTS &&
dynaPolyActor->actor.params == 0 && !Player_InCsMode(play) && play->msgCtx.msgLength == 0, this, dynaPolyActor)) {
func_80AEE02C(this);
play->csCtx.segment = &D_80AF10A4;
gSaveContext.cutsceneTrigger = 1;
@ -1611,7 +1610,7 @@ s32 func_80AEE6D0(EnRu1* this, PlayState* play) {
s32 pad;
s8 curRoomNum = play->roomCtx.curRoom.num;
if (!Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE) && (func_80AEB124(play) != 0)) {
if (GameInteractor_Should(VB_RUTO_WANT_TO_BE_TOSSED_TO_SAPPHIRE, !Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE) && (func_80AEB124(play) != 0), this)) {
if (!Player_InCsMode(play)) {
Animation_Change(&this->skelAnime, &gRutoChildSeesSapphireAnim, 1.0f, 0,
Animation_GetLastFrame(&gRutoChildSquirmAnim), ANIMMODE_LOOP, -8.0f);
@ -2190,8 +2189,11 @@ void func_80AEFF40(EnRu1* this, PlayState* play) {
void func_80AEFF94(EnRu1* this, PlayState* play) {
s8 actorRoom;
if ((Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO)) && (Flags_GetInfTable(INFTABLE_140)) && shouldSpawnRuto() &&
(!(func_80AEB020(this, play)))) {
if (
Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO) && Flags_GetInfTable(INFTABLE_140) &&
GameInteractor_Should(VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, !Flags_GetInfTable(INFTABLE_145), this) &&
(!(func_80AEB020(this, play)))
) {
func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0);
actorRoom = this->actor.room;
this->action = 22;

@ -198,10 +198,14 @@ void MagicDark_DiamondDraw(Actor* thisx, PlayState* play) {
MagicDark* this = (MagicDark*)thisx;
s32 pad;
u16 gameplayFrames = play->gameplayFrames;
Color_RGB8 Spell_env_ori = {0, 100, 255};
Color_RGB8 Spell_col_ori = {170, 255, 255};
Color_RGB8 Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.NayrusSecondary.Value"), Spell_env_ori);
Color_RGB8 Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.NayrusPrimary.Value"), Spell_col_ori);
Color_RGB8 Spell_env = { 0, 100, 255 };
Color_RGB8 Spell_col = { 170, 255, 255 };
if (CVarGetInteger(CVAR_COSMETIC("Magic.NayrusSecondary.Changed"), 0)) {
Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.NayrusSecondary.Value"), Spell_env);
}
if (CVarGetInteger(CVAR_COSMETIC("Magic.NayrusPrimary.Changed"), 0)) {
Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.NayrusPrimary.Value"), Spell_col);
}
OPEN_DISPS(play->state.gfxCtx);
@ -224,13 +228,8 @@ void MagicDark_DiamondDraw(Actor* thisx, PlayState* play) {
Matrix_RotateY(this->actor.shape.rot.y * (M_PI / 0x8000), MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
if (CVarGetInteger(CVAR_COSMETIC("UseSpellsColors"),0)) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, Spell_col.r, Spell_col.g, Spell_col.b, (s32)(this->primAlpha * 0.6f) & 0xFF);
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, 128);
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 170, 255, 255, (s32)(this->primAlpha * 0.6f) & 0xFF);
gDPSetEnvColor(POLY_XLU_DISP++, 0, 100, 255, 128);
}
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, Spell_col.r, Spell_col.g, Spell_col.b, (s32)(this->primAlpha * 0.6f) & 0xFF);
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, 128);
gSPDisplayList(POLY_XLU_DISP++, sDiamondMaterialDL);
gSPDisplayList(POLY_XLU_DISP++,
Gfx_TwoTexScroll(play->state.gfxCtx, 0, gameplayFrames * 2, gameplayFrames * -4, 32, 32, 1,
@ -271,8 +270,18 @@ void MagicDark_OrbDraw(Actor* thisx, PlayState* play) {
OPEN_DISPS(play->state.gfxCtx);
Gfx_SetupDL_25Xlu(play->state.gfxCtx);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, 170, 255, 255, 255);
gDPSetEnvColor(POLY_XLU_DISP++, 0, 150, 255, 255);
Color_RGB8 Spell_env = { 0, 150, 255 };
Color_RGB8 Spell_col = { 170, 255, 255 };
if (CVarGetInteger(CVAR_COSMETIC("Magic.NayrusSecondary.Changed"), 0)) {
Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.NayrusSecondary.Value"), Spell_env);
}
if (CVarGetInteger(CVAR_COSMETIC("Magic.NayrusPrimary.Changed"), 0)) {
Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.NayrusPrimary.Value"), Spell_col);
}
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, Spell_col.r, Spell_col.g, Spell_col.b, 255);
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, 255);
Matrix_Translate(pos.x, pos.y, pos.z, MTXMODE_NEW);
Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY);
Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY);

@ -217,10 +217,14 @@ void MagicFire_Draw(Actor* thisx, PlayState* play) {
s32 pad2;
s32 i;
u8 alpha;
Color_RGB8 Spell_env_ori = {255, 0, 0};
Color_RGB8 Spell_col_ori = {255, 200, 0};
Color_RGB8 Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.DinsSecondary.Value"), Spell_env_ori);
Color_RGB8 Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.DinsPrimaryary.Value"), Spell_col_ori);
Color_RGB8 Spell_env = { 255, 0, 0 };
Color_RGB8 Spell_col = { 255, 200, 0 };
if (CVarGetInteger(CVAR_COSMETIC("Magic.DinsSecondary.Changed"), 0)) {
Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.DinsSecondary.Value"), Spell_env);
}
if (CVarGetInteger(CVAR_COSMETIC("Magic.DinsPrimaryary.Changed"), 0)) {
Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.DinsPrimary.Value"), Spell_col);
}
if (this->action > 0) {
OPEN_DISPS(play->state.gfxCtx);
@ -232,13 +236,8 @@ void MagicFire_Draw(Actor* thisx, PlayState* play) {
gDPSetColorDither(POLY_XLU_DISP++, G_CD_DISABLE);
gDPFillRectangle(POLY_XLU_DISP++, 0, 0, 319, 239);
Gfx_SetupDL_25Xlu(play->state.gfxCtx);
if (CVarGetInteger(CVAR_COSMETIC("UseSpellsColors"),0)) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, Spell_col.r, Spell_col.g, Spell_col.b, (u8)(this->alphaMultiplier * 255));
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, (u8)(this->alphaMultiplier * 255));
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, Spell_col_ori.r, Spell_col_ori.g, Spell_col_ori.b, (u8)(this->alphaMultiplier * 255));
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env_ori.r, Spell_env_ori.g, Spell_env_ori.b, (u8)(this->alphaMultiplier * 255));
}
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, Spell_col.r, Spell_col.g, Spell_col.b, (u8)(this->alphaMultiplier * 255));
gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, (u8)(this->alphaMultiplier * 255));
Matrix_Scale(0.15f, 0.15f, 0.15f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);

@ -3238,7 +3238,11 @@ s32 func_808358F0(Player* this, PlayState* play) {
AnimationContext_SetCopyAll(play, this->skelAnime.limbCount, this->upperSkelAnime.jointTable,
this->skelAnime.jointTable);
} else {
LinkAnimation_Update(play, &this->upperSkelAnime);
// #region SOH [Enhancement]
if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0)) {
// #endregion
LinkAnimation_Update(play, &this->upperSkelAnime);
}
}
func_80834EB8(this, play);
@ -5825,7 +5829,13 @@ s32 func_8083AD4C(PlayState* play, Player* this) {
camMode = shouldUseBowCamera ? CAM_MODE_BOWARROW : CAM_MODE_SLINGSHOT;
} else {
camMode = CAM_MODE_BOOMERANG;
// #region SOH [Enhancement]
if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) {
camMode = CAM_MODE_FIRSTPERSON;
// #endregion
} else {
camMode = CAM_MODE_BOOMERANG;
}
}
} else {
camMode = CAM_MODE_FIRSTPERSON;
@ -7302,6 +7312,7 @@ s32 Player_ActionHandler_2(Player* this, PlayState* play) {
interactedActor->id == ACTOR_EN_ITEM00 &&
interactedActor->params != ITEM00_HEART_PIECE &&
interactedActor->params != ITEM00_SMALL_KEY &&
interactedActor->params != ITEM00_NONE &&
interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY &&
interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY_GI
) ||
@ -11488,7 +11499,13 @@ void Player_UpdateCamAndSeqModes(PlayState* play, Player* this) {
camMode = CAM_MODE_TALK;
} else if (this->stateFlags1 & PLAYER_STATE1_FRIENDLY_ACTOR_FOCUS) {
if (this->stateFlags1 & PLAYER_STATE1_BOOMERANG_THROWN) {
camMode = CAM_MODE_FOLLOWBOOMERANG;
// #region SOH [Enhancement]
if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) {
camMode = CAM_MODE_TARGET;
// #endregion
} else {
camMode = CAM_MODE_FOLLOWBOOMERANG;
}
} else {
camMode = CAM_MODE_FOLLOWTARGET;
}
@ -11499,7 +11516,13 @@ void Player_UpdateCamAndSeqModes(PlayState* play, Player* this) {
} else if (this->stateFlags1 & PLAYER_STATE1_CHARGING_SPIN_ATTACK) {
camMode = CAM_MODE_CHARGE;
} else if (this->stateFlags1 & PLAYER_STATE1_BOOMERANG_THROWN) {
camMode = CAM_MODE_FOLLOWBOOMERANG;
// #region SOH [Enhancement]
if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) {
camMode = CAM_MODE_TARGET;
// #endregion
} else {
camMode = CAM_MODE_FOLLOWBOOMERANG;
}
Camera_SetParam(Play_GetCamera(play, 0), 8, this->boomerangActor);
} else if (this->stateFlags1 & (PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE)) {
if (Player_FriendlyLockOnOrParallel(this)) {