Merge pull request #42 from briaguya-ai/beans

beans
This commit is contained in:
briaguya 2022-06-03 14:24:30 -04:00 committed by GitHub
commit 6c2b0c90e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
106 changed files with 45409 additions and 3234 deletions

View file

@ -56,7 +56,7 @@ namespace Game {
void InitSettings() {
ModInternal::RegisterHook<ModInternal::AudioInit>(UpdateAudio);
ModInternal::RegisterHook<ModInternal::GfxInit>([] {
gfx_get_current_rendering_api()->set_texture_filter((FilteringMode) CVar_GetS32("gTextureFilter", THREE_POINT));
gfx_get_current_rendering_api()->set_texture_filter((FilteringMode) CVar_GetS32("gTextureFilter", FILTER_THREE_POINT));
SohImGui::console->opened = CVar_GetS32("gConsoleEnabled", 0);
UpdateAudio();
});

View file

@ -134,7 +134,7 @@ static struct {
//uint32_t current_width, current_height;
uint32_t render_target_height;
int current_framebuffer;
FilteringMode current_filter_mode = NONE;
FilteringMode current_filter_mode = FILTER_NONE;
int8_t depth_test;
int8_t depth_mask;
@ -410,7 +410,7 @@ static struct ShaderProgram *gfx_d3d11_create_and_load_new_shader(uint64_t shade
char buf[4096];
size_t len, num_floats;
gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, false, d3d.current_filter_mode == THREE_POINT);
gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, false, d3d.current_filter_mode == FILTER_THREE_POINT);
ComPtr<ID3DBlob> vs, ps;
ComPtr<ID3DBlob> error_blob;
@ -577,7 +577,7 @@ static void gfx_d3d11_set_sampler_parameters(int tile, bool linear_filter, uint3
D3D11_SAMPLER_DESC sampler_desc;
ZeroMemory(&sampler_desc, sizeof(D3D11_SAMPLER_DESC));
sampler_desc.Filter = linear_filter && d3d.current_filter_mode == LINEAR ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT;
sampler_desc.Filter = linear_filter && d3d.current_filter_mode == FILTER_LINEAR ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT;
sampler_desc.AddressU = gfx_cm_to_d3d11(cms);
sampler_desc.AddressV = gfx_cm_to_d3d11(cmt);
@ -682,7 +682,7 @@ static void gfx_d3d11_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t
d3d.last_resource_views[i] = d3d.textures[d3d.current_texture_ids[i]].resource_view.Get();
d3d.context->PSSetShaderResources(i, 1, d3d.textures[d3d.current_texture_ids[i]].resource_view.GetAddressOf());
if (d3d.current_filter_mode == THREE_POINT) {
if (d3d.current_filter_mode == FILTER_THREE_POINT) {
d3d.per_draw_cb_data.textures[i].width = d3d.textures[d3d.current_texture_ids[i]].width;
d3d.per_draw_cb_data.textures[i].height = d3d.textures[d3d.current_texture_ids[i]].height;
d3d.per_draw_cb_data.textures[i].linear_filtering = d3d.textures[d3d.current_texture_ids[i]].linear_filtering;

View file

@ -31,6 +31,7 @@
#define DECLARE_GFX_DXGI_FUNCTIONS
#include "gfx_dxgi.h"
#include "../../GameSettings.h"
#define WINCLASS_NAME L"N64GAME"
#define GFX_API_NAME "DirectX"
@ -271,8 +272,9 @@ static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_par
break;
case WM_DROPFILES:
DragQueryFileA((HDROP)w_param, 0, fileName, 256);
CVar_SetString("gDroppedFile", fileName);
CVar_SetString("gSpoilerLog", fileName);
CVar_SetS32("gDroppedNewSpoilerFile", 1);
Game::SaveSettings();
break;
case WM_SYSKEYDOWN:
if ((w_param == VK_RETURN) && ((l_param & 1 << 30) == 0)) {

View file

@ -74,7 +74,7 @@ static uint32_t frame_count;
static vector<Framebuffer> framebuffers;
static size_t current_framebuffer;
static float current_noise_scale;
static FilteringMode current_filter_mode = THREE_POINT;
static FilteringMode current_filter_mode = FILTER_THREE_POINT;
GLuint pixel_depth_rb, pixel_depth_fb;
size_t pixel_depth_rb_size;
@ -315,7 +315,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
append_line(fs_buf, &fs_len, "}");
}
if (current_filter_mode == THREE_POINT) {
if (current_filter_mode == FILTER_THREE_POINT) {
append_line(fs_buf, &fs_len, "#define TEX_OFFSET(off) texture2D(tex, texCoord - (off)/texSize)");
append_line(fs_buf, &fs_len, "vec4 filter3point(in sampler2D tex, in vec2 texCoord, in vec2 texSize) {");
append_line(fs_buf, &fs_len, " vec2 offset = fract(texCoord*texSize - vec2(0.5));");
@ -577,7 +577,7 @@ static uint32_t gfx_cm_to_opengl(uint32_t val) {
}
static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) {
const GLint filter = linear_filter && current_filter_mode == LINEAR ? GL_LINEAR : GL_NEAREST;
const GLint filter = linear_filter && current_filter_mode == FILTER_LINEAR ? GL_LINEAR : GL_NEAREST;
glActiveTexture(GL_TEXTURE0 + tile);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);

View file

@ -16,9 +16,9 @@ struct GfxClipParameters {
};
enum FilteringMode {
THREE_POINT,
LINEAR,
NONE
FILTER_THREE_POINT,
FILTER_LINEAR,
FILTER_NONE
};
struct GfxRenderingAPI {

View file

@ -28,6 +28,7 @@
#include <WTypesbase.h>
#endif
#include <time.h>
#include "../../GameSettings.h"
#define GFX_API_NAME "SDL2 - OpenGL"
@ -252,8 +253,9 @@ static void gfx_sdl_handle_events(void) {
}
break;
case SDL_DROPFILE:
CVar_SetString("gDroppedFile", event.drop.file);
CVar_SetString("gSpoilerLog", event.drop.file);
CVar_SetS32("gDroppedNewSpoilerFile", 1);
Game::SaveSettings();
break;
case SDL_QUIT:
SDL_Quit(); // bandaid fix for linux window closing issue

View file

@ -25,6 +25,7 @@
#include "Lib/Fast3D/gfx_rendering_api.h"
#include "Lib/spdlog/include/spdlog/common.h"
#include "Utils/StringHelper.h"
#include <thread>
#ifdef ENABLE_OPENGL
#include "Lib/ImGui/backends/imgui_impl_opengl3.h"
@ -40,6 +41,11 @@
IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
#endif
// #include "../../soh/include/randomizer/main.cpp"
// #include "../../soh/include/randomizer/main.hpp"
// #include "../../soh/include/randomizer/rando_main.hpp"
// #include "../../soh/include/randomizer/spoiler_log.hpp"
// #include "../../soh/soh/OTRGlobals.h"
using namespace Ship;
bool oldCursorState = true;
@ -55,6 +61,8 @@ OSContPad* pads;
std::map<std::string, GameAsset*> DefaultAssets;
// SpoilerData gSpoilerData;
namespace SohImGui {
WindowImpl impl;
@ -412,6 +420,10 @@ namespace SohImGui {
pads = cont_pad;
});
Game::InitSettings();
CVar_SetS32("gRandoGenerating", 0);
CVar_SetS32("gDroppedNewSpoilerFile", 0);
Game::SaveSettings();
}
void Update(EventImpl event) {
@ -419,6 +431,7 @@ namespace SohImGui {
Game::SaveSettings();
needs_save = false;
}
ImGuiProcessEvent(event);
}
@ -810,7 +823,7 @@ namespace SohImGui {
ImGui::Text("Texture Filter (Needs reload)");
GfxRenderingAPI* gapi = gfx_get_current_rendering_api();
if (ImGui::BeginCombo("##filters", filters[gapi->get_texture_filter()])) {
for (int fId = 0; fId <= FilteringMode::NONE; fId++) {
for (int fId = 0; fId <= FilteringMode::FILTER_NONE; fId++) {
if (ImGui::Selectable(filters[fId], fId == gapi->get_texture_filter())) {
INFO("New Filter: %s", filters[fId]);
gapi->set_texture_filter((FilteringMode)fId);
@ -1024,12 +1037,6 @@ namespace SohImGui {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Randomizer"))
{
EnhancementCheckbox("Enable Randomizer", "gRandomizer");
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Developer Tools"))
{
EnhancementCheckbox("OoT Debug Mode", "gDebugEnabled");

View file

@ -87,5 +87,4 @@ namespace SohImGui {
void ResetColor(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha);
ImTextureID GetTextureByID(int id);
ImTextureID GetTextureByName(const std::string& name);
// void LoadItemLocations(const char* spoilerFileName);
}

View file

@ -87,14 +87,14 @@ TEXTURE_FILES_OUT := $(foreach f,$(TEXTURE_FILES_PNG:.png=.inc.c),build/$f) \
$(foreach f,$(TEXTURE_FILES_JPG:.jpg=.jpg.inc.c),build/$f) \
CXX_FILES := \
$(shell find soh -type f -name *.cpp)
$(shell find soh -type f -name '*.cpp')
C_FILES := \
$(shell find soh -type f -name *.c) \
$(shell find src/boot -type f -name *.c) \
$(shell find src/buffers -type f -name *.c) \
$(shell find src/code -type f -name *.c) \
$(shell find src/overlays -type f -name *.c) \
$(shell find soh -type f -name '*.c') \
$(shell find src/boot -type f -name '*.c') \
$(shell find src/buffers -type f -name '*.c') \
$(shell find src/code -type f -name '*.c') \
$(shell find src/overlays -type f -name '*.c') \
src/libultra/gu/coss.c \
src/libultra/gu/guLookAt.c \
src/libultra/gu/guLookAtHilite.c \

View file

@ -446,6 +446,7 @@ u32 Actor_TextboxIsClosing(Actor* actor, GlobalContext* globalCtx);
s8 func_8002F368(GlobalContext* globalCtx);
void Actor_GetScreenPos(GlobalContext* globalCtx, Actor* actor, s16* x, s16* y);
u32 Actor_HasParent(Actor* actor, GlobalContext* globalCtx);
s32 GiveItemWithoutActor(GlobalContext* globalCtx, s32 getItemId);
s32 func_8002F434(Actor* actor, GlobalContext* globalCtx, s32 getItemId, f32 xzRange, f32 yRange);
void func_8002F554(Actor* actor, GlobalContext* globalCtx, s32 getItemId);
void func_8002F580(Actor* actor, GlobalContext* globalCtx);

View file

@ -64,7 +64,7 @@ typedef struct {
typedef struct {
RandomizerCheck check;
RandomizerGet get;
} ItemLocation;
} ItemLocationRando;
typedef struct {
/* 0x0000 */ s32 entranceIndex; // start of `save` substruct, originally called "memory"
@ -179,7 +179,7 @@ typedef struct {
/* 0x1420 */ s16 worldMapArea;
/* 0x1422 */ s16 sunsSongState; // controls the effects of suns song
/* 0x1424 */ s16 healthAccumulator;
ItemLocation itemLocations[700];
ItemLocationRando itemLocations[500];
Sprite seedIcons[5];
} SaveContext; // size = 0x1428

511
soh/randomizer/beans.json Normal file
View file

@ -0,0 +1,511 @@
{
"file_hash": [
2,
2,
2,
2,
2
],
"locations": {
"Barinade": "Magic Bean",
"Bongo Bongo": "Magic Bean",
"Bottom of the Well Back Left Bombable Chest": "Magic Bean",
"Bottom of the Well Center Skulltula Chest": "Magic Bean",
"Bottom of the Well Compass Chest": "Magic Bean",
"Bottom of the Well Fire Keese Chest": "Magic Bean",
"Bottom of the Well Freestanding Key": "Magic Bean",
"Bottom of the Well Front Center Bombable Chest": "Magic Bean",
"Bottom of the Well Front Left Fake Wall Chest": "Magic Bean",
"Bottom of the Well GS East Inner Room": "Magic Bean",
"Bottom of the Well GS Like Like Cage": "Magic Bean",
"Bottom of the Well GS West Inner Room": "Magic Bean",
"Bottom of the Well Invisible Chest": "Magic Bean",
"Bottom of the Well Lens of Truth Chest": "Magic Bean",
"Bottom of the Well Like Like Chest": "Magic Bean",
"Bottom of the Well Map Chest": "Magic Bean",
"Bottom of the Well Right Bottom Fake Wall Chest": "Magic Bean",
"Bottom of the Well Underwater Front Chest": "Magic Bean",
"Bottom of the Well Underwater Left Chest": "Magic Bean",
"Colossus Deku Scrub Grotto Front": "Magic Bean",
"Colossus Deku Scrub Grotto Rear": "Magic Bean",
"Colossus Freestanding PoH": "Magic Bean",
"Colossus GS Bean Patch": "Magic Bean",
"Colossus GS Hill": "Magic Bean",
"Colossus GS Tree": "Magic Bean",
"Colossus Great Fairy Reward": "Magic Bean",
"DMC Deku Scrub": "Magic Bean",
"DMC Deku Scrub Grotto Center": "Magic Bean",
"DMC Deku Scrub Grotto Left": "Magic Bean",
"DMC Deku Scrub Grotto Right": "Magic Bean",
"DMC GS Bean Patch": "Magic Bean",
"DMC GS Crate": "Magic Bean",
"DMC Great Fairy Reward": "Magic Bean",
"DMC Upper Grotto Chest": "Magic Bean",
"DMC Volcano Freestanding PoH": "Magic Bean",
"DMC Wall Freestanding PoH": "Magic Bean",
"DMT Chest": "Magic Bean",
"DMT Cow Grotto Cow": "Magic Bean",
"DMT Freestanding PoH": "Magic Bean",
"DMT GS Above Dodongos Cavern": "Magic Bean",
"DMT GS Bean Patch": "Magic Bean",
"DMT GS Falling Rocks Path": "Magic Bean",
"DMT GS Near Kak": "Magic Bean",
"DMT Great Fairy Reward": "Magic Bean",
"DMT Storms Grotto Chest": "Magic Bean",
"DMT Trade Broken Sword": "Magic Bean",
"DMT Trade Claim Check": "Magic Bean",
"DMT Trade Eyedrops": "Magic Bean",
"Deku Theater Mask of Truth": "Magic Bean",
"Deku Theater Skull Mask": "Magic Bean",
"Deku Tree Basement Chest": "Magic Bean",
"Deku Tree Compass Chest": "Magic Bean",
"Deku Tree Compass Room Side Chest": "Magic Bean",
"Deku Tree GS Basement Back Room": "Magic Bean",
"Deku Tree GS Basement Gate": "Magic Bean",
"Deku Tree GS Basement Vines": "Magic Bean",
"Deku Tree GS Compass Room": "Magic Bean",
"Deku Tree Map Chest": "Magic Bean",
"Deku Tree Queen Gohma Heart Container": "Magic Bean",
"Deku Tree Slingshot Chest": "Magic Bean",
"Deku Tree Slingshot Room Side Chest": "Magic Bean",
"Dodongos Cavern Bomb Bag Chest": "Magic Bean",
"Dodongos Cavern Bomb Flower Platform Chest": "Magic Bean",
"Dodongos Cavern Boss Room Chest": "Magic Bean",
"Dodongos Cavern Compass Chest": "Magic Bean",
"Dodongos Cavern Deku Scrub Lobby": "Magic Bean",
"Dodongos Cavern Deku Scrub Near Bomb Bag Left": "Magic Bean",
"Dodongos Cavern Deku Scrub Near Bomb Bag Right": "Magic Bean",
"Dodongos Cavern Deku Scrub Side Room Near Dodongos": "Magic Bean",
"Dodongos Cavern End Of Bridge Chest": "Magic Bean",
"Dodongos Cavern GS Alcove Above Stairs": "Magic Bean",
"Dodongos Cavern GS Back Room": "Magic Bean",
"Dodongos Cavern GS Scarecrow": "Magic Bean",
"Dodongos Cavern GS Side Room Near Lower Lizalfos": "Magic Bean",
"Dodongos Cavern GS Vines Above Stairs": "Magic Bean",
"Dodongos Cavern King Dodongo Heart Container": "Magic Bean",
"Dodongos Cavern Map Chest": "Magic Bean",
"Fire Temple Big Lava Room Blocked Door Chest": "Magic Bean",
"Fire Temple Big Lava Room Lower Open Door Chest": "Magic Bean",
"Fire Temple Boss Key Chest": "Magic Bean",
"Fire Temple Boulder Maze Lower Chest": "Magic Bean",
"Fire Temple Boulder Maze Shortcut Chest": "Magic Bean",
"Fire Temple Boulder Maze Side Room Chest": "Magic Bean",
"Fire Temple Boulder Maze Upper Chest": "Magic Bean",
"Fire Temple Compass Chest": "Magic Bean",
"Fire Temple Flare Dancer Chest": "Magic Bean",
"Fire Temple GS Boss Key Loop": "Magic Bean",
"Fire Temple GS Boulder Maze": "Magic Bean",
"Fire Temple GS Scarecrow Climb": "Magic Bean",
"Fire Temple GS Scarecrow Top": "Magic Bean",
"Fire Temple GS Song of Time Room": "Magic Bean",
"Fire Temple Highest Goron Chest": "Magic Bean",
"Fire Temple Map Chest": "Magic Bean",
"Fire Temple Megaton Hammer Chest": "Magic Bean",
"Fire Temple Near Boss Chest": "Magic Bean",
"Fire Temple Scarecrow Chest": "Magic Bean",
"Fire Temple Volvagia Heart Container": "Magic Bean",
"Forest Temple Basement Chest": "Magic Bean",
"Forest Temple Blue Poe Chest": "Magic Bean",
"Forest Temple Boss Key Chest": "Magic Bean",
"Forest Temple Bow Chest": "Magic Bean",
"Forest Temple Eye Switch Chest": "Magic Bean",
"Forest Temple Falling Ceiling Room Chest": "Magic Bean",
"Forest Temple First Room Chest": "Magic Bean",
"Forest Temple First Stalfos Chest": "Magic Bean",
"Forest Temple Floormaster Chest": "Magic Bean",
"Forest Temple GS Basement": "Magic Bean",
"Forest Temple GS First Room": "Magic Bean",
"Forest Temple GS Level Island Courtyard": "Magic Bean",
"Forest Temple GS Lobby": "Magic Bean",
"Forest Temple GS Raised Island Courtyard": "Magic Bean",
"Forest Temple Map Chest": "Magic Bean",
"Forest Temple Phantom Ganon Heart Container": "Magic Bean",
"Forest Temple Raised Island Courtyard Chest": "Magic Bean",
"Forest Temple Red Poe Chest": "Magic Bean",
"Forest Temple Well Chest": "Magic Bean",
"GC Darunias Joy": "Magic Bean",
"GC Deku Scrub Grotto Center": "Magic Bean",
"GC Deku Scrub Grotto Left": "Magic Bean",
"GC Deku Scrub Grotto Right": "Magic Bean",
"GC GS Boulder Maze": "Magic Bean",
"GC GS Center Platform": "Magic Bean",
"GC Maze Center Chest": "Magic Bean",
"GC Maze Left Chest": "Magic Bean",
"GC Maze Right Chest": "Magic Bean",
"GC Medigoron": "Magic Bean",
"GC Pot Freestanding PoH": "Magic Bean",
"GC Rolling Goron as Adult": "Magic Bean",
"GC Rolling Goron as Child": "Magic Bean",
"GC Shop Item 1": "Magic Bean",
"GC Shop Item 2": "Magic Bean",
"GC Shop Item 3": "Magic Bean",
"GC Shop Item 4": "Magic Bean",
"GC Shop Item 5": "Magic Bean",
"GC Shop Item 6": "Magic Bean",
"GC Shop Item 7": "Magic Bean",
"GC Shop Item 8": "Magic Bean",
"GF Chest": "Magic Bean",
"GF GS Archery Range": "Magic Bean",
"GF GS Top Floor": "Magic Bean",
"GF Gerudo Token": "Magic Bean",
"GF HBA 1000 Points": "Magic Bean",
"GF HBA 1500 Points": "Magic Bean",
"GF North F1 Carpenter": "Magic Bean",
"GF North F2 Carpenter": "Magic Bean",
"GF South F1 Carpenter": "Magic Bean",
"GF South F2 Carpenter": "Magic Bean",
"GV Chest": "Magic Bean",
"GV Cow": "Magic Bean",
"GV Crate Freestanding PoH": "Magic Bean",
"GV Deku Scrub Grotto Front": "Magic Bean",
"GV Deku Scrub Grotto Rear": "Magic Bean",
"GV GS Bean Patch": "Magic Bean",
"GV GS Behind Tent": "Magic Bean",
"GV GS Pillar": "Magic Bean",
"GV GS Small Bridge": "Magic Bean",
"GV Trade Saw": "Magic Bean",
"GV Waterfall Freestanding PoH": "Magic Bean",
"GY Composers Grave Chest": "Magic Bean",
"GY Dampe Gravedigging Tour": "Magic Bean",
"GY Dampe Race Freestanding PoH": "Magic Bean",
"GY Freestanding PoH": "Magic Bean",
"GY Heart Piece Grave Chest": "Magic Bean",
"GY Hookshot Chest": "Magic Bean",
"GY Shield Grave Chest": "Magic Bean",
"Ganon": "Magic Bean",
"Ganon's Castle Deku Scrub Center-Left": "Magic Bean",
"Ganon's Castle Deku Scrub Center-Right": "Magic Bean",
"Ganon's Castle Deku Scrub Left": "Magic Bean",
"Ganon's Castle Deku Scrub Right": "Magic Bean",
"Ganon's Castle Forest Trial Chest": "Magic Bean",
"Ganon's Castle Light Trial First Left Chest": "Magic Bean",
"Ganon's Castle Light Trial First Right Chest": "Magic Bean",
"Ganon's Castle Light Trial Invisible Enemies Chest": "Magic Bean",
"Ganon's Castle Light Trial Lullaby Chest": "Magic Bean",
"Ganon's Castle Light Trial Second Left Chest": "Magic Bean",
"Ganon's Castle Light Trial Second Right Chest": "Magic Bean",
"Ganon's Castle Light Trial Third Left Chest": "Magic Bean",
"Ganon's Castle Light Trial Third Right Chest": "Magic Bean",
"Ganon's Castle Shadow Trial Front Chest": "Magic Bean",
"Ganon's Castle Shadow Trial Golden Gauntlets Chest": "Magic Bean",
"Ganon's Castle Spirit Trial Crystal Switch Chest": "Magic Bean",
"Ganon's Castle Spirit Trial Invisible Chest": "Magic Bean",
"Ganon's Castle Water Trial Left Chest": "Magic Bean",
"Ganon's Castle Water Trial Right Chest": "Magic Bean",
"Ganon's Tower Boss Key Chest": "Magic Bean",
"Gerudo Training Grounds Beamos Chest": "Magic Bean",
"Gerudo Training Grounds Before Heavy Block Chest": "Magic Bean",
"Gerudo Training Grounds Eye Statue Chest": "Magic Bean",
"Gerudo Training Grounds Freestanding Key": "Magic Bean",
"Gerudo Training Grounds Hammer Room Clear Chest": "Magic Bean",
"Gerudo Training Grounds Hammer Room Switch Chest": "Magic Bean",
"Gerudo Training Grounds Heavy Block First Chest": "Magic Bean",
"Gerudo Training Grounds Heavy Block Fourth Chest": "Magic Bean",
"Gerudo Training Grounds Heavy Block Second Chest": "Magic Bean",
"Gerudo Training Grounds Heavy Block Third Chest": "Magic Bean",
"Gerudo Training Grounds Hidden Ceiling Chest": "Magic Bean",
"Gerudo Training Grounds Lobby Left Chest": "Magic Bean",
"Gerudo Training Grounds Lobby Right Chest": "Magic Bean",
"Gerudo Training Grounds Maze Path Final Chest": "Magic Bean",
"Gerudo Training Grounds Maze Path First Chest": "Magic Bean",
"Gerudo Training Grounds Maze Path Second Chest": "Magic Bean",
"Gerudo Training Grounds Maze Path Third Chest": "Magic Bean",
"Gerudo Training Grounds Maze Right Central Chest": "Magic Bean",
"Gerudo Training Grounds Maze Right Side Chest": "Magic Bean",
"Gerudo Training Grounds Near Scarecrow Chest": "Magic Bean",
"Gerudo Training Grounds Stalfos Chest": "Magic Bean",
"Gerudo Training Grounds Underwater Silver Rupee Chest": "Magic Bean",
"Graveyard GS Bean Patch": "Magic Bean",
"Graveyard GS Wall": "Magic Bean",
"HC GS Storms Grotto": "Magic Bean",
"HC GS Tree": "Magic Bean",
"HC Great Fairy Reward": "Magic Bean",
"HC Malon Egg": "Magic Bean",
"HC Zeldas Letter": "Magic Bean",
"HF Cow Grotto Cow": "Magic Bean",
"HF Deku Scrub Grotto": "Magic Bean",
"HF GS Cow Grotto": "Magic Bean",
"HF GS Near Kak Grotto": "Magic Bean",
"HF Near Market Grotto Chest": "Magic Bean",
"HF Ocarina of Time Item": "Magic Bean",
"HF Open Grotto Chest": "Magic Bean",
"HF Southeast Grotto Chest": "Magic Bean",
"HF Tektite Grotto Freestanding PoH": "Magic Bean",
"Ice Cavern Compass Chest": "Magic Bean",
"Ice Cavern Freestanding PoH": "Magic Bean",
"Ice Cavern GS Heart Piece Room": "Magic Bean",
"Ice Cavern GS Push Block Room": "Magic Bean",
"Ice Cavern GS Spinning Scythe Room": "Magic Bean",
"Ice Cavern Iron Boots Chest": "Magic Bean",
"Ice Cavern Map Chest": "Magic Bean",
"Jabu Jabus Belly Barinade Heart Container": "Magic Bean",
"Jabu Jabus Belly Boomerang Chest": "Magic Bean",
"Jabu Jabus Belly Compass Chest": "Magic Bean",
"Jabu Jabus Belly Deku Scrub": "Magic Bean",
"Jabu Jabus Belly GS Lobby Basement Lower": "Magic Bean",
"Jabu Jabus Belly GS Lobby Basement Upper": "Magic Bean",
"Jabu Jabus Belly GS Near Boss": "Magic Bean",
"Jabu Jabus Belly GS Water Switch Room": "Magic Bean",
"Jabu Jabus Belly Map Chest": "Magic Bean",
"KF GS Bean Patch": "Magic Bean",
"KF GS House of Twins": "Magic Bean",
"KF GS Know It All House": "Magic Bean",
"KF Kokiri Sword Chest": "Magic Bean",
"KF Links House Cow": "Magic Bean",
"KF Mido Bottom Left Chest": "Magic Bean",
"KF Mido Bottom Right Chest": "Magic Bean",
"KF Mido Top Left Chest": "Magic Bean",
"KF Mido Top Right Chest": "Magic Bean",
"KF Shop Item 1": "Magic Bean",
"KF Shop Item 2": "Magic Bean",
"KF Shop Item 3": "Magic Bean",
"KF Shop Item 4": "Magic Bean",
"KF Shop Item 5": "Magic Bean",
"KF Shop Item 6": "Magic Bean",
"KF Shop Item 7": "Magic Bean",
"KF Shop Item 8": "Magic Bean",
"KF Storms Grotto Chest": "Magic Bean",
"Kak 10 Gold Skulltula Reward": "Magic Bean",
"Kak 20 Gold Skulltula Reward": "Magic Bean",
"Kak 30 Gold Skulltula Reward": "Magic Bean",
"Kak 40 Gold Skulltula Reward": "Magic Bean",
"Kak 50 Gold Skulltula Reward": "Magic Bean",
"Kak Anju as Adult": "Magic Bean",
"Kak Anju as Child": "Magic Bean",
"Kak Bazaar Item 1": "Magic Bean",
"Kak Bazaar Item 2": "Magic Bean",
"Kak Bazaar Item 3": "Magic Bean",
"Kak Bazaar Item 4": "Magic Bean",
"Kak Bazaar Item 5": "Magic Bean",
"Kak Bazaar Item 6": "Magic Bean",
"Kak Bazaar Item 7": "Magic Bean",
"Kak Bazaar Item 8": "Magic Bean",
"Kak GS Above Impas House": "Magic Bean",
"Kak GS Guards House": "Magic Bean",
"Kak GS House Under Construction": "Magic Bean",
"Kak GS Skulltula House": "Magic Bean",
"Kak GS Tree": "Magic Bean",
"Kak GS Watchtower": "Magic Bean",
"Kak Impas House Cow": "Magic Bean",
"Kak Impas House Freestanding PoH": "Magic Bean",
"Kak Man on Roof": "Magic Bean",
"Kak Open Grotto Chest": "Magic Bean",
"Kak Potion Shop Item 1": "Magic Bean",
"Kak Potion Shop Item 2": "Magic Bean",
"Kak Potion Shop Item 3": "Magic Bean",
"Kak Potion Shop Item 4": "Magic Bean",
"Kak Potion Shop Item 5": "Magic Bean",
"Kak Potion Shop Item 6": "Magic Bean",
"Kak Potion Shop Item 7": "Magic Bean",
"Kak Potion Shop Item 8": "Magic Bean",
"Kak Redead Grotto Chest": "Magic Bean",
"Kak Shooting Gallery Reward": "Magic Bean",
"Kak Trade Odd Mushroom": "Magic Bean",
"Kak Trade Pocket Cucco": "Magic Bean",
"Kak Windmill Freestanding PoH": "Magic Bean",
"King Dodongo": "Magic Bean",
"LH Adult Fishing": "Magic Bean",
"LH Child Fishing": "Magic Bean",
"LH Deku Scrub Grotto Center": "Magic Bean",
"LH Deku Scrub Grotto Left": "Magic Bean",
"LH Deku Scrub Grotto Right": "Magic Bean",
"LH Freestanding PoH": "Magic Bean",
"LH GS Bean Patch": "Magic Bean",
"LH GS Lab Crate": "Magic Bean",
"LH GS Lab Wall": "Magic Bean",
"LH GS Small Island": "Magic Bean",
"LH GS Tree": "Magic Bean",
"LH Lab Dive": "Magic Bean",
"LH Lab Trade Eyeball Frog": "Magic Bean",
"LH Sun": "Magic Bean",
"LH Underwater Item": "Magic Bean",
"LLR Deku Scrub Grotto Center": "Magic Bean",
"LLR Deku Scrub Grotto Left": "Magic Bean",
"LLR Deku Scrub Grotto Right": "Magic Bean",
"LLR Freestanding PoH": "Magic Bean",
"LLR GS Back Wall": "Magic Bean",
"LLR GS House Window": "Magic Bean",
"LLR GS Rain Shed": "Magic Bean",
"LLR GS Tree": "Magic Bean",
"LLR Stables Left Cow": "Magic Bean",
"LLR Stables Right Cow": "Magic Bean",
"LLR Talons Chickens": "Magic Bean",
"LLR Tower Left Cow": "Magic Bean",
"LLR Tower Right Cow": "Magic Bean",
"LW Deku Scrub Grotto Front": "Magic Bean",
"LW Deku Scrub Grotto Rear": "Magic Bean",
"LW Deku Scrub Near Bridge": "Magic Bean",
"LW Deku Scrub Near Deku Theater Left": "Magic Bean",
"LW Deku Scrub Near Deku Theater Right": "Magic Bean",
"LW GS Above Theater": "Magic Bean",
"LW GS Bean Patch Near Bridge": "Magic Bean",
"LW GS Bean Patch Near Theater": "Magic Bean",
"LW Gift From Saria": "Magic Bean",
"LW Near Shortcuts Grotto Chest": "Magic Bean",
"LW Ocarina Memory Game": "Magic Bean",
"LW Skull Kid": "Magic Bean",
"LW Target in Woods": "Magic Bean",
"LW Trade Cojiro": "Magic Bean",
"LW Trade Odd Poultice": "Magic Bean",
"Link's Pocket": "Light Medallion",
"MK 10 Big Poes": "Magic Bean",
"MK Bazaar Item 1": "Magic Bean",
"MK Bazaar Item 2": "Magic Bean",
"MK Bazaar Item 3": "Magic Bean",
"MK Bazaar Item 4": "Magic Bean",
"MK Bazaar Item 5": "Magic Bean",
"MK Bazaar Item 6": "Magic Bean",
"MK Bazaar Item 7": "Magic Bean",
"MK Bazaar Item 8": "Magic Bean",
"MK Bombchu Bowling Bombchus": "Magic Bean",
"MK Bombchu Bowling First Prize": "Magic Bean",
"MK Bombchu Bowling Second Prize": "Magic Bean",
"MK Bombchu Shop Item 1": "Magic Bean",
"MK Bombchu Shop Item 2": "Magic Bean",
"MK Bombchu Shop Item 3": "Magic Bean",
"MK Bombchu Shop Item 4": "Magic Bean",
"MK Bombchu Shop Item 5": "Magic Bean",
"MK Bombchu Shop Item 6": "Magic Bean",
"MK Bombchu Shop Item 7": "Magic Bean",
"MK Bombchu Shop Item 8": "Magic Bean",
"MK Chest Game Fifth Room Chest": "Magic Bean",
"MK Chest Game First Room Chest": "Magic Bean",
"MK Chest Game Fourth Room Chest": "Magic Bean",
"MK Chest Game Second Room Chest": "Magic Bean",
"MK Chest Game Third Room Chest": "Magic Bean",
"MK Lost Dog": "Magic Bean",
"MK Potion Shop Item 1": "Magic Bean",
"MK Potion Shop Item 2": "Magic Bean",
"MK Potion Shop Item 3": "Magic Bean",
"MK Potion Shop Item 4": "Magic Bean",
"MK Potion Shop Item 5": "Magic Bean",
"MK Potion Shop Item 6": "Magic Bean",
"MK Potion Shop Item 7": "Magic Bean",
"MK Potion Shop Item 8": "Magic Bean",
"MK Shooting Gallery": "Magic Bean",
"MK Treasure Chest Game Reward": "Magic Bean",
"Market GS Guard House": "Magic Bean",
"Morpha": "Magic Bean",
"OGC GS": "Magic Bean",
"OGC Great Fairy Reward": "Magic Bean",
"Phantom Ganon": "Magic Bean",
"Queen Gohma": "Magic Bean",
"SFM Deku Scrub Grotto Front": "Magic Bean",
"SFM Deku Scrub Grotto Rear": "Magic Bean",
"SFM GS": "Magic Bean",
"SFM Wolfos Grotto Chest": "Magic Bean",
"Shadow Temple After Wind Enemy Chest": "Magic Bean",
"Shadow Temple After Wind Hidden Chest": "Magic Bean",
"Shadow Temple Bongo Bongo Heart Container": "Magic Bean",
"Shadow Temple Boss Key Chest": "Magic Bean",
"Shadow Temple Compass Chest": "Magic Bean",
"Shadow Temple Early Silver Rupee Chest": "Magic Bean",
"Shadow Temple Falling Spikes Lower Chest": "Magic Bean",
"Shadow Temple Falling Spikes Switch Chest": "Magic Bean",
"Shadow Temple Falling Spikes Upper Chest": "Magic Bean",
"Shadow Temple Freestanding Key": "Magic Bean",
"Shadow Temple GS Falling Spikes Room": "Magic Bean",
"Shadow Temple GS Like Like Room": "Magic Bean",
"Shadow Temple GS Near Ship": "Magic Bean",
"Shadow Temple GS Single Giant Pot": "Magic Bean",
"Shadow Temple GS Triple Giant Pot": "Magic Bean",
"Shadow Temple Hover Boots Chest": "Magic Bean",
"Shadow Temple Invisible Blades Invisible Chest": "Magic Bean",
"Shadow Temple Invisible Blades Visible Chest": "Magic Bean",
"Shadow Temple Invisible Floormaster Chest": "Magic Bean",
"Shadow Temple Invisible Spikes Chest": "Magic Bean",
"Shadow Temple Map Chest": "Magic Bean",
"Shadow Temple Spike Walls Left Chest": "Magic Bean",
"Shadow Temple Wind Hint Chest": "Magic Bean",
"Sheik at Colossus": "Magic Bean",
"Sheik at Temple": "Magic Bean",
"Sheik in Crater": "Magic Bean",
"Sheik in Forest": "Magic Bean",
"Sheik in Ice Cavern": "Magic Bean",
"Sheik in Kakariko": "Magic Bean",
"Song from Composers Grave": "Magic Bean",
"Song from Impa": "Magic Bean",
"Song from Malon": "Magic Bean",
"Song from Ocarina of Time": "Magic Bean",
"Song from Saria": "Magic Bean",
"Song from Windmill": "Magic Bean",
"Spirit Temple Boss Key Chest": "Magic Bean",
"Spirit Temple Child Bridge Chest": "Magic Bean",
"Spirit Temple Child Climb East Chest": "Magic Bean",
"Spirit Temple Child Climb North Chest": "Magic Bean",
"Spirit Temple Child Early Torches Chest": "Magic Bean",
"Spirit Temple Compass Chest": "Magic Bean",
"Spirit Temple Early Adult Right Chest": "Magic Bean",
"Spirit Temple First Mirror Left Chest": "Magic Bean",
"Spirit Temple First Mirror Right Chest": "Magic Bean",
"Spirit Temple GS Boulder Room": "Magic Bean",
"Spirit Temple GS Hall After Sun Block Room": "Magic Bean",
"Spirit Temple GS Lobby": "Magic Bean",
"Spirit Temple GS Metal Fence": "Magic Bean",
"Spirit Temple GS Sun on Floor Room": "Magic Bean",
"Spirit Temple Hallway Left Invisible Chest": "Magic Bean",
"Spirit Temple Hallway Right Invisible Chest": "Magic Bean",
"Spirit Temple Map Chest": "Magic Bean",
"Spirit Temple Mirror Shield Chest": "Magic Bean",
"Spirit Temple Near Four Armos Chest": "Magic Bean",
"Spirit Temple Silver Gauntlets Chest": "Magic Bean",
"Spirit Temple Statue Room Hand Chest": "Magic Bean",
"Spirit Temple Statue Room Northeast Chest": "Magic Bean",
"Spirit Temple Sun Block Room Chest": "Magic Bean",
"Spirit Temple Topmost Chest": "Magic Bean",
"Spirit Temple Twinrova Heart Container": "Magic Bean",
"ToT Light Arrow Cutscene": "Magic Bean",
"Twinrova": "Magic Bean",
"Volvagia": "Magic Bean",
"Wasteland Carpet Salesman": "Magic Bean",
"Wasteland Chest": "Magic Bean",
"Wasteland GS": "Magic Bean",
"Water Temple Boss Key Chest": "Magic Bean",
"Water Temple Central Bow Target Chest": "Magic Bean",
"Water Temple Central Pillar Chest": "Magic Bean",
"Water Temple Compass Chest": "Magic Bean",
"Water Temple Cracked Wall Chest": "Magic Bean",
"Water Temple Dragon Chest": "Magic Bean",
"Water Temple GS Behind Gate": "Magic Bean",
"Water Temple GS Central Pillar": "Magic Bean",
"Water Temple GS Falling Platform Room": "Magic Bean",
"Water Temple GS Near Boss Key Chest": "Magic Bean",
"Water Temple GS River": "Magic Bean",
"Water Temple Longshot Chest": "Magic Bean",
"Water Temple Map Chest": "Magic Bean",
"Water Temple Morpha Heart Container": "Magic Bean",
"Water Temple River Chest": "Magic Bean",
"Water Temple Torches Chest": "Magic Bean",
"ZD Chest": "Magic Bean",
"ZD Diving Minigame": "Magic Bean",
"ZD GS Frozen Waterfall": "Magic Bean",
"ZD King Zora Thawed": "Magic Bean",
"ZD Shop Item 1": "Magic Bean",
"ZD Shop Item 2": "Magic Bean",
"ZD Shop Item 3": "Magic Bean",
"ZD Shop Item 4": "Magic Bean",
"ZD Shop Item 5": "Magic Bean",
"ZD Shop Item 6": "Magic Bean",
"ZD Shop Item 7": "Magic Bean",
"ZD Shop Item 8": "Magic Bean",
"ZD Trade Prescription": "Magic Bean",
"ZF Bottom Freestanding PoH": "Magic Bean",
"ZF GS Above The Log": "Magic Bean",
"ZF GS Hidden Cave": "Magic Bean",
"ZF GS Tree": "Magic Bean",
"ZF Great Fairy Reward": "Magic Bean",
"ZF Iceberg Freestanding PoH": "Magic Bean",
"ZR Deku Scrub Grotto Front": "Magic Bean",
"ZR Deku Scrub Grotto Rear": "Magic Bean",
"ZR Frogs Ocarina Game": "Magic Bean",
"ZR Frogs in the Rain": "Magic Bean",
"ZR GS Above Bridge": "Magic Bean",
"ZR GS Ladder": "Magic Bean",
"ZR GS Near Raised Grottos": "Magic Bean",
"ZR GS Tree": "Magic Bean",
"ZR Magic Bean Salesman": "Magic Bean",
"ZR Near Domain Freestanding PoH": "Magic Bean",
"ZR Near Open Grotto Freestanding PoH": "Magic Bean",
"ZR Open Grotto Chest": "Magic Bean"
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
#include <Lib/spdlog/include/spdlog/spdlog.h>
void GenerateRandomizer() {
int ret = Playthrough::Playthrough_Init(std::hash<std::string>{}(Settings::seed));
if (ret < 0) {
if (ret == -1) { // Failed to generate after 5 tries
SPDLOG_ERROR(
"\n\nFailed to generate after 5 tries.\nPress B to go back to the menu.\nA different seed might be "
"successful.");
return;
} else {
SPDLOG_ERROR("\n\nError %d with fill.\nPress Select to exit or B to go back to the menu.\n", ret);
return;
}
}
const auto& randomizerHash = GetRandomizerHash();
}

View file

@ -173,7 +173,37 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="soh\Enhancements\randomizer.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\cosmetics.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\custom_messages.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\debug.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\dungeon.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\entrance.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\fill.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hints.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_list.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_location.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_pool.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\logic.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\menu.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\music.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\patch.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\playthrough.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\preset.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\random.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\rando_main.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\settings.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\setting_descriptions.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\shops.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\sound_effects.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\spoiler_log.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\starting_inventory.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\tinyxml2.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\trial.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\utils.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\randomizer.cpp" />
<ClCompile Include="soh\frame_interpolation.cpp" />
<ClCompile Include="soh\Enhancements\bootcommands.c" />
<ClCompile Include="soh\Enhancements\debugconsole.cpp" />
@ -880,7 +910,42 @@
<ClCompile Include="src\overlays\misc\ovl_map_mark_data\z_map_mark_data.c" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="soh\Enhancements\randomizer.h" />
<ClCompile Include="soh\Enhancements\randomizer\randomizer.h" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\category.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\cosmetics.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\custom_messages.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\debug.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\dungeon.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\entrance.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\fill.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\hints.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\hint_list.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_list.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_location.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_pool.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\keys.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\location_access.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\logic.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\menu.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\music.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\patch.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\playthrough.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\pool_functions.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\preset.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\random.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\randomizer.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\rando_main.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\settings.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\setting_descriptions.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\shops.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\sound_effects.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\spoiler_log.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\starting_inventory.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\text.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\tinyxml2.h" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\trial.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\utils.hpp" />
<ClInclude Include="randomizerTypes.h" />
<ClInclude Include="soh\frame_interpolation.h" />
<ClInclude Include="include\alloca.h" />

View file

@ -707,6 +707,36 @@
<ClCompile Include="src\overlays\misc\ovl_kaleido_scope\z_lmap_mark_data.c" />
<ClCompile Include="src\overlays\misc\ovl_map_mark_data\z_map_mark_data.c" />
<ClCompile Include="soh\Enhancements\randomizer.h" />
<ClCompile Include="include\randomizer\debug.cpp" />
<ClCompile Include="include\randomizer\dungeon.cpp" />
<ClCompile Include="include\randomizer\entrance.cpp" />
<ClCompile Include="include\randomizer\fill.cpp" />
<ClCompile Include="include\randomizer\hint_list.cpp" />
<ClCompile Include="include\randomizer\hints.cpp" />
<ClCompile Include="include\randomizer\item.cpp" />
<ClCompile Include="include\randomizer\item_list.cpp" />
<ClCompile Include="include\randomizer\item_location.cpp" />
<ClCompile Include="include\randomizer\item_pool.cpp" />
<ClCompile Include="include\randomizer\location_access.cpp" />
<ClCompile Include="include\randomizer\logic.cpp" />
<ClCompile Include="include\randomizer\menu.cpp" />
<ClCompile Include="include\randomizer\music.cpp" />
<ClCompile Include="include\randomizer\patch.cpp" />
<ClCompile Include="include\randomizer\playthrough.cpp" />
<ClCompile Include="include\randomizer\preset.cpp" />
<ClCompile Include="include\randomizer\random.cpp" />
<ClCompile Include="include\randomizer\setting_descriptions.cpp" />
<ClCompile Include="include\randomizer\settings.cpp" />
<ClCompile Include="include\randomizer\shops.cpp" />
<ClCompile Include="include\randomizer\sound_effects.cpp" />
<ClCompile Include="include\randomizer\spoiler_log.cpp" />
<ClCompile Include="include\randomizer\starting_inventory.cpp" />
<ClCompile Include="include\randomizer\tinyxml2.cpp" />
<ClCompile Include="include\randomizer\trial.cpp" />
<ClCompile Include="include\randomizer\utils.cpp" />
<ClCompile Include="include\randomizer\cosmetics.cpp" />
<ClCompile Include="include\randomizer\custom_messages.cpp" />
<ClCompile Include="include\randomizer\rando_main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="soh\frame_interpolation.h" />
@ -1231,6 +1261,41 @@
<ClInclude Include="src\overlays\effects\ovl_Effect_Ss_Stone1\z_eff_ss_stone1.h" />
<ClInclude Include="src\overlays\misc\ovl_kaleido_scope\z_kaleido_scope.h" />
<ClInclude Include="randomizerTypes.h" />
<ClInclude Include="include\randomizer\debug.hpp" />
<ClInclude Include="include\randomizer\dungeon.hpp" />
<ClInclude Include="include\randomizer\entrance.hpp" />
<ClInclude Include="include\randomizer\fill.hpp" />
<ClInclude Include="include\randomizer\hint_list.hpp" />
<ClInclude Include="include\randomizer\hints.hpp" />
<ClInclude Include="include\randomizer\item.hpp" />
<ClInclude Include="include\randomizer\item_list.hpp" />
<ClInclude Include="include\randomizer\item_location.hpp" />
<ClInclude Include="include\randomizer\item_pool.hpp" />
<ClInclude Include="include\randomizer\keys.hpp" />
<ClInclude Include="include\randomizer\location_access.hpp" />
<ClInclude Include="include\randomizer\logic.hpp" />
<ClInclude Include="include\randomizer\menu.hpp" />
<ClInclude Include="include\randomizer\music.hpp" />
<ClInclude Include="include\randomizer\patch.hpp" />
<ClInclude Include="include\randomizer\playthrough.hpp" />
<ClInclude Include="include\randomizer\pool_functions.hpp" />
<ClInclude Include="include\randomizer\preset.hpp" />
<ClInclude Include="include\randomizer\random.hpp" />
<ClInclude Include="include\randomizer\randomizer.hpp" />
<ClInclude Include="include\randomizer\setting_descriptions.hpp" />
<ClInclude Include="include\randomizer\settings.hpp" />
<ClInclude Include="include\randomizer\shops.hpp" />
<ClInclude Include="include\randomizer\sound_effects.hpp" />
<ClInclude Include="include\randomizer\spoiler_log.hpp" />
<ClInclude Include="include\randomizer\starting_inventory.hpp" />
<ClInclude Include="include\randomizer\text.hpp" />
<ClInclude Include="include\randomizer\tinyxml2.h" />
<ClInclude Include="include\randomizer\trial.hpp" />
<ClInclude Include="include\randomizer\utils.hpp" />
<ClInclude Include="include\randomizer\category.hpp" />
<ClInclude Include="include\randomizer\cosmetics.hpp" />
<ClInclude Include="include\randomizer\custom_messages.hpp" />
<ClInclude Include="include\randomizer\rando_main.hpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Resource.rc" />

View file

@ -4,6 +4,7 @@
#include "debugconsole.h"
#include "../libultraship/SohImGuiImpl.h"
// #include "include/randomizer/rando_main.hpp"
#include "savestates.h"
#include <vector>
@ -441,6 +442,13 @@ static bool GetCVarHandler(const std::vector<std::string>& args) {
return CMD_SUCCESS;
}
// static bool GenerateRandoHandler(const std::vector<std::string>& args) {
// RandoMain::GenerateRando();
// INFO("[SOH] Randomized?");
// return CMD_SUCCESS;
// }
void DebugConsole_Init(void) {
CMD_REGISTER("kill", { KillPlayerHandler, "Commit suicide." });
CMD_REGISTER("map", { LoadSceneHandler, "Load up kak?" });
@ -490,6 +498,7 @@ void DebugConsole_Init(void) {
CMD_REGISTER("set_slot", { StateSlotSelectHandler, "Selects a SaveState slot", {
{ "Slot number", ArgumentType::NUMBER, }
} });
// CMD_REGISTER("generate_rando", { GenerateRandoHandler, "Generate Rando." });
DebugConsole_LoadCVars();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
#pragma once
enum class Category {
cNull,
cKokiriForest,
cForest,
cGrotto,
cMinigame,
cChestMinigame,
cLostWoods,
cDekuScrub,
cDekuScrubUpgrades,
cNeedSpiritualStones,
cSacredForestMeadow,
cHyruleField,
cLakeHylia,
cGerudo,
cGerudoValley,
cGerudoFortress,
cHauntedWasteland,
cDesertColossus,
cInnerMarket,
cMarket,
cHyruleCastle,
cKakarikoVillage,
cKakariko,
cSkulltulaHouse,
cGraveyard,
cDeathMountainTrail,
cDeathMountain,
cGoronCity,
cDeathMountainCrater,
cZorasRiver,
cZorasDomain,
cZorasFountain,
cLonLonRanch,
cDekuTree,
cDodongosCavern,
cJabuJabusBelly,
cForestTemple,
cFireTemple,
cWaterTemple,
cSpiritTemple,
cShadowTemple,
cBottomOfTheWell,
cIceCavern,
cGerudoTrainingGrounds,
cGanonsCastle,
cSkulltula,
cBossHeart,
cTempleOfTime,
cFairies,
cOutsideGanonsCastle,
cSong,
cSongDungeonReward,
cCow,
cShop,
cMerchant,
cVanillaSmallKey,
cVanillaGFSmallKey,
cVanillaBossKey,
cVanillaMap,
cVanillaCompass,
cAdultTrade,
};
enum class OptionCategory {
Setting,
Cosmetic,
Toggle,
};

View file

@ -0,0 +1,152 @@
#include "cosmetics.hpp"
#include "random.hpp"
#include <sstream>
namespace Cosmetics {
bool ValidHexString(std::string_view hexStr) {
return hexStr.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && hexStr.length() == 6;
}
Color_RGBAf HexStrToColorRGBAf(const std::string& hexStr) {
uint32_t hex = std::stoi(hexStr, nullptr, 16);
return Color_RGBAf{
.r = ((hex & 0xFF0000) >> 16) / 255.0f,
.g = ((hex & 0x00FF00) >> 8) / 255.0f,
.b = (hex & 0x0000FF) / 255.0f,
.a = 1.0f,
};
}
Color_RGBA8 HexStrToColorRGBA8(const std::string& hexStr) {
uint32_t hex = std::stoi(hexStr, nullptr, 16);
return Color_RGBA8{
.r = (uint8_t)((hex & 0xFF0000) >> 16),
.g = (uint8_t)((hex & 0x00FF00) >> 8),
.b = (uint8_t)(hex & 0x0000FF),
.a = 0xFF,
};
}
std::string CustomColorOptionText(std::string_view color) {
return std::string(CUSTOM_COLOR_STR.substr(0, CUSTOM_COLOR_STR.length() - 6)).append(color);
}
std::string GetCustomColor(const std::string& str) {
return str.substr(str.length() - 6);
}
const std::array<std::string_view, 13> gauntletColors = {
"FFFFFF", //Silver
"FECF0F", //Gold
"000006", //Black
"025918", //Green
"06025A", //Blue
"600602", //Bronze
"FF0000", //Red
"025DB0", //Sky Blue
"FA6A90", //Pink
"FF00FF", //Magenta
"D83A00", //Orange
"5B8A06", //Lime
"800080", //Purple
};
const std::array<std::string_view, 28> tunicColors = {
"168A0E", //Kokiri Green
"96000E", //Goron Red
"002A8E", //Zora Blue
"202020", //Black
"FFFFFF", //White
"FF4A0A", //Orange
"86FF23", //Yellow
"0094B0", //Cyan
"362076", //Indigo
"7B0080", //Purple
"F04F7D", //Pink
"323232", //Dark Gray
"E44B4B", //Salmon
"5A1A20", //Wine Red
"7E705B", //Beige
"583923", //Brown
"72602B", //Sand
"6b7E5D", //Tea Green
"3C5800", //Dark Green
"6CFFF8", //Powder Blue
"005A4B", //Teal
"0259FF", //Sky Blue
"33405E", //Faded Blue
"635AD2", //Lavender
"9C1B4B", //Magenta
"52314F", //Mauve
"505A59", //Silver
"F16F16", //Gold
};
const std::array<std::string_view, 20> naviInnerColors = {
"FFFFFF", //White
"00FF00", //Green
"9696FF", //Light Blue
"FFFF00", //Yellow
"FF0000", //Red
"FF00FF", //Magenta
"FECC3C", //Gold
"000000", //Black
"FFFFFF", //Tatl
"49146C", //Tael
"2C9EC4", //Fi
"E6DE83", //Ciela
"D14902", //Epona
"629C5F", //Ezlo
"A83317", //KoRL
"032660", //Linebeck
"D62E31", //Loftwing
"192426", //Midna
"977A6C", //Phantom Zelda
"FF0000", //Rainbow (starts at red)
};
const std::array<std::string_view, 20> naviOuterColors = {
"0000FF", //White
"00FF00", //Green
"9696FF", //Light Blue
"C89B00", //Yellow
"FF0000", //Red
"C8009B", //Magenta
"FEC007", //Gold
"000000", //Black
"C89800", //Tatl
"FF0000", //Tael
"2C1983", //Fi
"C6BE5B", //Ciela
"551F08", //Epona
"3F5D37", //Ezlo
"DED7C5", //KoRL
"EFFFFF", //Linebeck
"FDE6CC", //Loftwing
"D28330", //Midna
"6F4667", //Phantom Zelda
"FF0000", //Rainbow (starts at red)
};
const std::array<std::string_view, 13> weaponTrailColors = {
"FFFFFF", //White
"000000", //Black
"FF0000", //Red
"00FF00", //Green
"0000FF", //Blue
"FFFF00", //Yellow
"00FFFF", //Cyan
"FF00FF", //Magenta
"FFA500", //Orange
"FFD700", //Gold
"800080", //Purple
"FF69B4", //Pink
"FF0000", //Rainbow (starts at red)
};
//Generate random hex color
std::string RandomColor() {
std::ostringstream color;
color << std::hex << (rand() % 0x1000000); //use default rand to not interfere with main settings
return color.str();
}
} //namespace Cosmetics

View file

@ -0,0 +1,50 @@
#pragma once
#include <array>
#include <string>
#include <vector>
namespace Cosmetics {
constexpr std::string_view RANDOM_CHOICE_STR = "Random Choice";
constexpr std::string_view RANDOM_COLOR_STR = "Random Color";
constexpr std::string_view CUSTOM_COLOR_STR = "Custom #FFFFFF";
constexpr std::string_view CUSTOM_COLOR_PREFIX = "Custom #";
constexpr std::string_view RANDOM_CHOICE_DESC = "A random color from the list of colors will be\nchosen.";
constexpr std::string_view RANDOM_COLOR_DESC = "A completely random color will be chosen.";
constexpr std::string_view CUSTOM_COLOR_DESC = "Press A and type in a custom 6 digit hex color.";
enum CosmeticSettings {
RANDOM_CHOICE,
RANDOM_COLOR,
CUSTOM_COLOR,
NON_COLOR_COUNT,
};
struct Color_RGBAf {
float r;
float g;
float b;
float a;
};
struct Color_RGBA8 {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
extern const std::array<std::string_view, 13> gauntletColors;
extern const std::array<std::string_view, 28> tunicColors;
extern const std::array<std::string_view, 20> naviInnerColors;
extern const std::array<std::string_view, 20> naviOuterColors;
extern const std::array<std::string_view, 13> weaponTrailColors;
bool ValidHexString(std::string_view hexStr);
Color_RGBAf HexStrToColorRGBAf(const std::string& hexStr);
Color_RGBA8 HexStrToColorRGBA8(const std::string& hexStr);
std::string CustomColorOptionText(std::string_view color);
std::string GetCustomColor(const std::string& str);
std::string RandomColor();
} //namespace Cosmetics

View file

@ -0,0 +1,495 @@
#include "custom_messages.hpp"
#include "debug.hpp"
#include "shops.hpp"
#include "z64item.h"
#include <array>
#include <set>
#include <sstream>
namespace CustomMessages {
using namespace std::literals::string_literals;
class MessageEntryComp {
public:
bool operator()(const MessageEntry& lhs, const MessageEntry& rhs) const {
return lhs.id < rhs.id;
}
};
constexpr std::array EnglishDungeonNames = {
"Deku Tree",
"Dodongo's Cavern",
"Jabu Jabu's Belly",
"Forest Temple",
"Fire Temple",
"Water Temple",
"Spirit Temple",
"Shadow Temple",
"Bottom of the Well",
"Ice Cavern",
"Ganon's Tower",
"Gerudo Training Grounds",
"Gerudo Fortress",
"Ganon's Castle",
};
constexpr std::array FrenchDungeonNames = {
"vénérable arbre Mojo",
"caverne Dodongo",
"ventre de Jabu-Jabu",
"temple de la forêt",
"temple du feu",
"temple de l'eau",
"temple de l'esprit",
"temple de l'ombre",
"puits",
"caverne de glace",
"",
"gymnase Gerudo",
"forteresse Gerudo",
"château de Ganon",
};
constexpr std::array FrenchDungeonArticles = {
"du ",
"de la ",
"du ",
"du ",
"du ",
"du ",
"du ",
"du ",
"du ",
"de la ",
"",
"du ",
"de la ",
"du ",
};
constexpr std::array SpanishDungeonNames = {
"Gran Árbol Deku",
"Cueva de los Dodongos",
"Tripa de Jabu-Jabu",
"Templo del Bosque",
"Templo del Fuego",
"Templo del Agua",
"Templo del Espíritu",
"Templo de las Sombras",
"Fondo del pozo",
"Caverna de hielo",
"Torre de Ganon",
"Centro de Instrucción Gerudo",
"Fortaleza Gerudo",
"Castillo de Ganon",
};
constexpr std::array SpanishDungeonArticles = {
"del",
"de la",
"de la",
"del",
"del",
"del",
"del",
"del",
"del",
"de la",
"de la",
"del",
"de la",
"del",
};
constexpr std::array DungeonColors = {
QM_GREEN,
QM_RED,
QM_BLUE,
QM_GREEN,
QM_RED,
QM_BLUE,
QM_YELLOW,
QM_PINK,
QM_PINK,
QM_LBLUE,
QM_BLACK,
QM_YELLOW,
QM_YELLOW,
QM_RED,
};
std::set<MessageEntry, MessageEntryComp> messageEntries;
std::vector<MessageEntry> arrangedMessageEntries;
std::stringstream messageData;
std::string arrangedMessageData;
//textBoxType and textBoxPosition are defined here: https://wiki.cloudmodding.com/oot/Text_Format#Message_Id
void CreateMessage(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition,
std::string englishText, std::string frenchText, std::string spanishText) {
MessageEntry newEntry = { textId, unk_04, textBoxType, textBoxPosition, { 0 } };
while ((englishText.size() % 4) != 0) {
englishText += "\0"s;
}
messageData.seekg(0, messageData.end);
newEntry.info[ENGLISH_U].offset = (char*)((int)messageData.tellg());
newEntry.info[ENGLISH_U].length = englishText.size();
messageData << englishText;
while ((frenchText.size() % 4) != 0) {
frenchText += "\0"s;
}
messageData.seekg(0, messageData.end);
newEntry.info[FRENCH_U].offset = (char*)((int)messageData.tellg());
newEntry.info[FRENCH_U].length = frenchText.size();
messageData << frenchText;
while ((spanishText.size() % 4) != 0) {
spanishText += "\0"s;
}
messageData.seekg(0, messageData.end);
newEntry.info[SPANISH_U].offset = (char*)((int)messageData.tellg());
newEntry.info[SPANISH_U].length = spanishText.size();
messageData << spanishText;
messageEntries.insert(newEntry);
}
void CreateMessageFromTextObject(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, const Text& text) {
CreateMessage(textId, unk_04, textBoxType, textBoxPosition, text.GetEnglish(), text.GetFrench(), text.GetSpanish());
}
uint32_t NumMessages() {
return messageEntries.size();
}
std::pair<const char*, uint32_t> RawMessageEntryData() {
arrangedMessageEntries.assign(messageEntries.begin(), messageEntries.end());
const char* data = (const char*)arrangedMessageEntries.data();
uint32_t size = arrangedMessageEntries.size() * sizeof(MessageEntry);
return { data, size };
}
std::pair<const char*, uint32_t> RawMessageData() {
arrangedMessageData = messageData.str();
const char* data = arrangedMessageData.data();
uint32_t size = arrangedMessageData.size();
return { data, size };
}
void CreateAlwaysIncludedMessages() {
// Bombchu (10) Purchase Prompt
CreateMessage(0x8C, 0, 2, 3,
INSTANT_TEXT_ON()+"Bombchu (10): 99 Rupees"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Buy"+NEWLINE()+"Don't buy"+COLOR(QM_WHITE)+MESSAGE_END(),
INSTANT_TEXT_ON()+"Bombchus (10): 99 rubis"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Acheter"+NEWLINE()+"Ne pas acheter"+COLOR(QM_WHITE)+MESSAGE_END(),
INSTANT_TEXT_ON()+"Bombchus (10): 99 rupias"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Comprar"+NEWLINE()+"No comprar"+COLOR(QM_WHITE)+MESSAGE_END());
//Gold Skulltula Tokens (there are two text IDs the game uses)
for (const uint32_t textId : {0xB4, 0xB5}) {
CreateMessage(textId, 0, 2, 3,
INSTANT_TEXT_ON()+"You destroyed a "+COLOR(QM_RED)+"Gold Skulltula"+COLOR(QM_WHITE)+". You got a"+NEWLINE()+"token proving you destroyed it!"+NEWLINE()+NEWLINE()+"You have "+COLOR(QM_RED)+SKULLTULAS_DESTROYED()+COLOR(QM_WHITE)+" tokens!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"Vous venez de détruire une "+COLOR(QM_RED)+"Skulltula d'or"+COLOR(QM_WHITE)+"!"+NEWLINE()+"Ce symbole prouve votre prouesse!"+NEWLINE()+NEWLINE()+"Vous avez "+COLOR(QM_RED)+SKULLTULAS_DESTROYED()+COLOR(QM_WHITE)+" jetons!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"¡Has eliminado una "+COLOR(QM_RED)+"skulltula dorada"+COLOR(QM_WHITE)+" y has"+NEWLINE()+"conseguido un símbolo para probarlo!"+NEWLINE()+NEWLINE()+"¡Tienes "+COLOR(QM_RED)+SKULLTULAS_DESTROYED()+COLOR(QM_WHITE)+" símbolos!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Bombchu (10) Description
CreateMessage(0xBC, 0, 2, 3,
INSTANT_TEXT_ON()+COLOR(QM_RED)+"Bombchu (10): 99 Rupees"+NEWLINE()+COLOR(QM_WHITE)+"These look like toy mice, but they're"+NEWLINE()+"actually self-propelled time bombs!"+INSTANT_TEXT_OFF()+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+"Bombchus (10): 99 rubis"+NEWLINE()+COLOR(QM_WHITE)+"Profilée comme une souris mécanique, il"+NEWLINE()+"s'agit en fait d'une bombe à retardement"+NEWLINE()+"autopropulsée!"+INSTANT_TEXT_OFF()+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+"Bombchus (10): 99 rupias"+NEWLINE()+COLOR(QM_WHITE)+"Aunque parezcan ratoncitos de juguete,"+NEWLINE()+"¡son bombas de relojería autopropulsadas!"+INSTANT_TEXT_OFF()+SHOP_MESSAGE_BOX()+MESSAGE_END());
//Boss Keys
for (uint32_t bossKey = 0; bossKey <= (DUNGEON_SHADOW_TEMPLE - DUNGEON_FOREST_TEMPLE); bossKey++) {
uint32_t dungeon = DUNGEON_FOREST_TEMPLE + bossKey;
CreateMessage(0x9D4 + bossKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Boss Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[dungeon])+"clé d'or "+NEWLINE()+FrenchDungeonArticles[dungeon]+" "+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"¡Tienes la "+COLOR(DungeonColors[dungeon])+"gran llave "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
CreateMessage(0x9D9, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[DUNGEON_GANONS_CASTLE_FIRST_PART])+EnglishDungeonNames[DUNGEON_GANONS_CASTLE_FIRST_PART]+NEWLINE()+"Boss Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[DUNGEON_GANONS_CASTLE_FIRST_PART])+"clé d'or "+NEWLINE()+FrenchDungeonArticles[DUNGEON_GANONS_CASTLE_FIRST_PART]+" "+FrenchDungeonNames[DUNGEON_GANONS_CASTLE_FIRST_PART]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"¡Tienes la "+COLOR(DungeonColors[DUNGEON_GANONS_CASTLE_FIRST_PART])+"gran llave "+SpanishDungeonArticles[DUNGEON_GANONS_CASTLE_FIRST_PART]+NEWLINE()+SpanishDungeonNames[DUNGEON_GANONS_CASTLE_FIRST_PART]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
//Compasses
for (uint32_t dungeon = DUNGEON_DEKU_TREE; dungeon <= DUNGEON_ICE_CAVERN; dungeon++) {
CreateMessage(0x9DA + dungeon, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_COMPASS)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Compass"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_COMPASS)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[dungeon])+"boussole "+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_COMPASS)+INSTANT_TEXT_ON()+"¡Tienes la "+COLOR(DungeonColors[dungeon])+"brújula "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Maps
for (uint32_t dungeon = DUNGEON_DEKU_TREE; dungeon <= DUNGEON_ICE_CAVERN; dungeon++) {
CreateMessage(0x9E4 + dungeon, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_DUNGEON_MAP)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Map"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_DUNGEON_MAP)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[dungeon])+"carte "+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_DUNGEON_MAP)+INSTANT_TEXT_ON()+"¡Has obtenido el "+COLOR(DungeonColors[dungeon])+"mapa "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Small Keys
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_BOTTOM_OF_THE_WELL - DUNGEON_FOREST_TEMPLE); smallKey++) {
uint32_t dungeon = DUNGEON_FOREST_TEMPLE + smallKey;
CreateMessage(0x9EE + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Small Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez une "+COLOR(DungeonColors[dungeon])+"petite clé"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido una "+COLOR(DungeonColors[dungeon])+"llave pequeña "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_GANONS_CASTLE_FIRST_PART - DUNGEON_GERUDO_TRAINING_GROUNDS); smallKey++) {
uint32_t dungeon = DUNGEON_GERUDO_TRAINING_GROUNDS + smallKey;
CreateMessage(0x9F4 + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Small Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez une "+COLOR(DungeonColors[dungeon])+"petite clé"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido una "+COLOR(DungeonColors[dungeon])+"llave pequeña "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Key Rings
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_BOTTOM_OF_THE_WELL - DUNGEON_FOREST_TEMPLE); smallKey++) {
uint32_t dungeon = DUNGEON_FOREST_TEMPLE + smallKey;
CreateMessage(0x9300 + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Key Ring"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez un "+COLOR(DungeonColors[dungeon])+"trousseau"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido un "+COLOR(DungeonColors[dungeon])+"llavero "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_GANONS_CASTLE_FIRST_PART - DUNGEON_GERUDO_TRAINING_GROUNDS); smallKey++) {
uint32_t dungeon = DUNGEON_GERUDO_TRAINING_GROUNDS + smallKey;
CreateMessage(0x9306 + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Key Ring"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez un "+COLOR(DungeonColors[dungeon])+"trousseau"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido un "+COLOR(DungeonColors[dungeon])+"llavero "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Tycoon's Wallet
CreateMessage(0x09F7, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_WALLET_GIANT)+INSTANT_TEXT_ON()+"You got a "+COLOR(QM_RED)+"Tycoon's Wallet"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+NEWLINE()+"It's gigantic! Now you can carry"+NEWLINE()+"up to "+COLOR(QM_YELLOW)+"999 "+COLOR(QM_WHITE)+COLOR(QM_YELLOW)+"Rupees"+COLOR(QM_WHITE)+"!"+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_WALLET_GIANT)+INSTANT_TEXT_ON()+"Vous obtenez la "+COLOR(QM_RED)+"bourse de star"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+NEWLINE()+"Elle peut contenir jusqu'à "+COLOR(QM_YELLOW)+"999 "+COLOR(QM_WHITE)+COLOR(QM_YELLOW)+"rubis"+COLOR(QM_WHITE)+"!"+NEWLINE()+"C'est gigantesque!"+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_WALLET_GIANT)+INSTANT_TEXT_ON()+"¡Has conseguido una "+COLOR(QM_RED)+"bolsa para ricachones"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+NEWLINE()+"¡Qué descomunal! Ya puedes llevar"+NEWLINE()+"hasta "+COLOR(QM_YELLOW)+"999 "+COLOR(QM_WHITE)+COLOR(QM_YELLOW)+"rupias"+COLOR(QM_WHITE)+"!"+MESSAGE_END());
//Saria's Song Default Hint
CreateMessage(0x0A00, 0, 2, 3,
UNSKIPPABLE()+"Have you tried talking to the gossip"+NEWLINE()+ "stones around Hyrule? They might have"+NEWLINE()+"some good advice... Hee hee!"+WAIT_FOR_INPUT()+"If you learn something from the gossip stones,"+NEWLINE()+"I will remember it!"+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"As-tu parlé aux pierres à potins"+NEWLINE()+ "dans Hyrule? Elles sont de bons conseils..."+NEWLINE()+"Hi hi!"+WAIT_FOR_INPUT()+"Si elles te révèlent quelque chose,"+NEWLINE()+"je m'en souviendrai!"+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"¿Has probado a consultarle a las"+NEWLINE()+ "piedras chismosas esparcidas por Hyrule? Puede"+NEWLINE()+"que sean de ayuda a tu empresa... ¡Ji, ji!"+WAIT_FOR_INPUT()+"¡Puedo recordarte todo lo que aprendas de ellas,"+NEWLINE()+"si así lo deseas!"+EVENT_TRIGGER()+MESSAGE_END());
//Poe Collector (when enough has been sold)
CreateMessage(0x70F8, 0, 0, 0,
UNSKIPPABLE()+"Wait a minute! WOW!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"You have earned enough points!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Young man, you are a genuine "+COLOR(QM_RED)+"ghost hunter"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Is that what you expected me to say?"+NEWLINE()+"Heh heh heh!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Because of you, I have extra inventory of"+NEWLINE()+COLOR(QM_RED)+"Big Poes"+COLOR(QM_WHITE)+", so this will be the last time I can"+NEWLINE()
+"buy one of these ghosts."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"You're thinking about what I promised would"+NEWLINE()+"happen when you earned enough points."+NEWLINE()+"Heh heh."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Don't worry. I didn't forget. Just take this."+MESSAGE_END(),
UNSKIPPABLE()+"Ooooh! WHOA!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Tu as obtenu suffisamment de points!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Tu es un véritable "+COLOR(QM_RED)+"chasseur de fantômes"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Il est content, hein?"+NEWLINE()+"Il est content le monsieur?"+NEWLINE()+"Hé hé hé!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Grâce à toi, mon stock d'"+COLOR(QM_RED)+"Âmes"+COLOR(QM_WHITE)+" est plein!"+NEWLINE()+"C'est donc la dernière fois que nous"+NEWLINE()+"faisons affaire."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Je sais, je sais..."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Nous avions passé un pacte..."+NEWLINE()+"Tu as eu tes points et je t'en félicite..."+NEWLINE()+"Hé hé hé!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Alors prends donc ceci, mon bon ami!"+MESSAGE_END(),
UNSKIPPABLE()+"¡Un momento! ¡OYE!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Has conseguido los puntos suficientes!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Jovencito, eres un auténtico "+COLOR(QM_RED)+"cazador de"+NEWLINE()+"fantasmas"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"¿Era eso lo que esperabas que dijera?"+NEWLINE()+"¡Je, je je!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Gracias a ti, ya tengo la cantidad necesaria"+NEWLINE()+"de "+COLOR(QM_RED)+"grandes poes"+COLOR(QM_WHITE)+", así que esta será la"+NEWLINE()
+"última vez que te compre unos de ese tipo."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¿Recuerdas lo que te dije que ocurriría"+NEWLINE()+"cuando tuvieses suficientes puntos?"+NEWLINE()+"Je, je, je."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Tranquilo, que no se me ha olvidado."+NEWLINE()+"Toma esto."+MESSAGE_END());
//Ice Trap
CreateMessage(0x9002, 0, 2, 3,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+COLOR(QM_RED)+"FOOL!"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+COLOR(QM_RED)+"IDIOT!"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+COLOR(QM_RED)+"¡TONTO!"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END());
//Business Scrubs
//The less significant byte represents the price of the item
for (uint32_t price = 0; price <= 95; price += 5) {
CreateMessage(0x9000 + price, 0, 0, 0,
INSTANT_TEXT_ON()+"I'll sell you something good for "+COLOR(QM_RED)+std::to_string(price)+" Rupees"+COLOR(QM_WHITE)+"!"+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"OK"+NEWLINE()+"No way"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"Je te vends un truc super pour "+COLOR(QM_RED)+std::to_string(price)+" Rubis"+COLOR(QM_WHITE)+"!"+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"D'accord"+NEWLINE()+"Hors de question"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"¡Te puedo vender algo bueno por "+COLOR(QM_RED)+std::to_string(price)+" rupias"+COLOR(QM_WHITE)+"!"+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Vale"+NEWLINE()+"Ni hablar"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Poe Collector
//The last digit represent the number of poes needed to collect
for (uint32_t poes = 1; poes <= 10; poes++) {
CreateMessage(0x9080 + poes, 0, 0, 0,
UNSKIPPABLE()+"Oh, you brought a Poe today!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Hmmmm!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Very interesting! This is a "+COLOR(QM_RED)+"Big Poe"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"I'll buy it for "+COLOR(QM_RED)+"50 Rupees"+COLOR(QM_WHITE)+"."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"On top of that, I'll put "+COLOR(QM_RED)+"100 points "+COLOR(QM_WHITE)+"on"+NEWLINE()+"your card."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"If you earn "+COLOR(QM_RED)+std::to_string(poes * 100)+" points"+COLOR(QM_WHITE)+", you'll be a"+NEWLINE()+"happy man! Heh heh."+MESSAGE_END(),
UNSKIPPABLE()+"Oh! Tu as apporté un fantôme!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Hmmmm!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Magnifique!"+NEWLINE()+"C'est une "+COLOR(QM_RED)+"Âme"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Je t'en donne "+COLOR(QM_RED)+"50 Rubis"+COLOR(QM_WHITE)+"."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Et en plus, j'inscris "+COLOR(QM_RED)+"100 points "+COLOR(QM_WHITE)+NEWLINE()+"sur ta carte."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Obtiens "+COLOR(QM_RED)+std::to_string(poes * 100)+" points"+COLOR(QM_WHITE)+" et tu ne"+NEWLINE()+"seras pas déçu..."+NEWLINE()+"Hé hé hé."+MESSAGE_END(),
UNSKIPPABLE()+"¡Vaya! ¡Traes un poe!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Mmm! ¿A ver?"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Qué interesante! ¡Es un "+COLOR(QM_RED)+"gran poe"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Te daré "+COLOR(QM_RED)+"50 rupias "+COLOR(QM_WHITE)+"por él."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Y además agregaré "+COLOR(QM_RED)+"100 puntos "+COLOR(QM_WHITE)+"a tu"+NEWLINE()+"tarjeta."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"¡Si llegas a "+COLOR(QM_RED)+std::to_string(poes * 100)+" puntos"+COLOR(QM_WHITE)+", serás muy feliz!"+NEWLINE()+"Je, je, je..."+MESSAGE_END());
}
//Talon (this is to prevent accidentally skipping Malon in HC)
CreateMessage(0x9100, 0, 2, 0,
UNSKIPPABLE()+"You should go talk to my daughter Malon,"+NEWLINE()+"she has an item for you."+NEWLINE()+SET_SPEED(3)+"........."+SET_SPEED(0)+WAIT_FOR_INPUT()+"I have to think about some stuff now,"+NEWLINE()+"please don't distract me."+MESSAGE_END(),
UNSKIPPABLE()+"Zzzz... Muh... Malon..."+NEWLINE()+"Parler avec... Malon..."+NEWLINE()+SET_SPEED(3)+"........."+SET_SPEED(0)+WAIT_FOR_INPUT()+"Si fatigué..."+NEWLINE()+"Quelle vie..."+MESSAGE_END(),
UNSKIPPABLE()+"Habla con Malon, tiene algo que darte..."+SET_SPEED(3)+"........."+SET_SPEED(0)+MESSAGE_END());
//Bow Shooting Gallery reminder
CreateMessage(0x9140, 0, 0, 0,
UNSKIPPABLE()+"Wonderful! Bravo! Perfect!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Here's a fantastic present! But I have"+COLOR(QM_RED)+NEWLINE()+"something else "+COLOR(QM_WHITE)+"for you once you have a bow."+SET_SPEED(30)+" "+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"Merveilleux! Bravo! C'est parfait!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Voici un prix fantastique! J'aurai "+COLOR(QM_RED)+"autre chose"+COLOR(QM_WHITE)+NEWLINE()+"pour toi quand tu auras un arc."+SET_SPEED(30)+" "+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"¡Espectacular! ¡Bravo! ¡Perfecto!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Toma este sensacional regalo! Pero te tengo"+NEWLINE()+"guardado "+COLOR(QM_RED)+"algo más "+COLOR(QM_WHITE)+"para cuando traigas tu"+NEWLINE()+"propio arco."+SET_SPEED(30)+" "+EVENT_TRIGGER()+MESSAGE_END());
//Shopsanity items
//64 textboxes, 2 for each of 32 potential shopsanity items
for(uint32_t shopitems = 0; shopitems < NonShopItems.size(); shopitems++) {
Text name = NonShopItems[shopitems].Name;
std::string price = std::to_string(NonShopItems[shopitems].Price);
//Prevent names from being too long and overflowing textbox
if (name.GetEnglish() == "Piece of Heart (Treasure Chest Minigame)") {
name = Text{"Piece of Heart", "Quart de coeur", "Pieza de corazón"};
} else if (name.GetEnglish() == "Green Rupee (Treasure Chest Minigame)") {
name = Text{"Green Rupee", "Rubis vert", "Rupia verde"};
}
//Message to display when hovering over the item
if (NonShopItems[shopitems].Repurchaseable) { //Different checkbox for repurchaseable items
CreateMessage(0x9200+shopitems*2, 0, 0, 0,
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetEnglish()+": "+price+" Rupees"+NEWLINE()+COLOR(QM_WHITE)+"Special deal!"+NEWLINE()+"Buy as many as you want!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetFrench()+": "+price+" rubis"+NEWLINE()+COLOR(QM_WHITE)+"Offre spéciale!"+NEWLINE()+"Achetez-en à volonté!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetSpanish()+": "+price+" rupias"+NEWLINE()+COLOR(QM_WHITE)+"¡Oferta especial!"+NEWLINE()+"¡Compra todo lo que quieras!"+SHOP_MESSAGE_BOX()+MESSAGE_END());
}
else { //Normal textbox
CreateMessage(0x9200+shopitems*2, 0, 0, 0,
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetEnglish()+": "+price+" Rupees"+NEWLINE()+COLOR(QM_WHITE)+"Special deal! ONE LEFT!"+NEWLINE()+"Get it while it lasts!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetFrench()+": "+price+" rubis"+NEWLINE()+COLOR(QM_WHITE)+"Offre spéciale! DERNIER EN STOCK!"+NEWLINE()+"Faites vite!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetSpanish()+": "+price+" rupias"+NEWLINE()+COLOR(QM_WHITE)+"¡Oferta especial! ¡SOLO QUEDA UNA UNIDAD!"+NEWLINE()+"¡Hazte con ella antes de que se agote!"+SHOP_MESSAGE_BOX()+MESSAGE_END());
}
//Message to display when going to buy the item
CreateMessage(0x9200+shopitems*2+1, 0, 0, 0,
INSTANT_TEXT_ON()+name.GetEnglish()+": "+price+" Rupees"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Buy"+NEWLINE()+"Don't buy"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+name.GetFrench()+": "+price+" rubis"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Acheter"+NEWLINE()+"Ne pas acheter"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+name.GetSpanish()+": "+price+" rupias"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Comprar"+NEWLINE()+"No comprar"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//easter egg
CreateMessage(0x96F, 0, 2, 2,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Oh hey, you watched all the credits!"+NEWLINE()+CENTER_TEXT()+"Here's a prize for your patience."+NEWLINE()+CENTER_TEXT()+"Unlocking MQ and saving..."+NEWLINE()+NEWLINE()+CENTER_TEXT()+COLOR(QM_RED)+"Do not remove the Game Card"+NEWLINE()+CENTER_TEXT()+"or turn the power off."+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"The Legend of Zelda Ocarina of Time 3D"+NEWLINE()+CENTER_TEXT()+"Master Quest va être déverrouillé."+NEWLINE()+CENTER_TEXT()+"Sauvegarde... Veuillez patienter."+NEWLINE()+NEWLINE()+CENTER_TEXT()+COLOR(QM_RED)+"N'éteignez pas la console et"+NEWLINE()+CENTER_TEXT()+"ne retirez pas la carte de jeu"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Desbloqueando The Legend of Zelda"+NEWLINE()+CENTER_TEXT()+"Ocarina of Time 3D Master Quest."+NEWLINE()+CENTER_TEXT()+"Guardando. Espera un momento..."+NEWLINE()+NEWLINE()+CENTER_TEXT()+COLOR(QM_RED)+"No saques la tarjeta de juego"+NEWLINE()+CENTER_TEXT()+"ni apagues la consola."+INSTANT_TEXT_OFF()+MESSAGE_END());
CreateMessage(0x970, 0, 2, 3,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Master Quest doesn't affect the Randomizer,"+NEWLINE()+CENTER_TEXT()+"so you can use 3 more save slots now."+NEWLINE()+NEWLINE()+CENTER_TEXT()+"Thanks for playing!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Vous pouvez désormais jouer à"+NEWLINE()+CENTER_TEXT()+"The Legend of Zelda Ocarina of Time 3D"+NEWLINE()+CENTER_TEXT()+"Master Quest!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"¡Ya puedes jugar The Legend of Zelda"+NEWLINE()+CENTER_TEXT()+"Ocarina of Time 3D Master Quest!"+INSTANT_TEXT_OFF()+MESSAGE_END());
//Messages for the new Lake Hylia switch
CreateMessage(0x346, 0, 1, 3,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Water level control system."+NEWLINE()+CENTER_TEXT()+"Keep away!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Contrôle du niveau d'eau."+NEWLINE()+CENTER_TEXT()+"Ne pas toucher!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Control del nivel del agua."+NEWLINE()+CENTER_TEXT()+"¡No te acerques!"+INSTANT_TEXT_OFF()+MESSAGE_END());
CreateMessage(0x1B3, 0, 0, 3,
UNSKIPPABLE()+"This switch is rustier than you think."+WAIT_FOR_INPUT()+"Something must be wrong with the pipe"+NEWLINE()+"system in the Water Temple."+MESSAGE_END(),
UNSKIPPABLE()+"Cet interrupteur est très rouillé."+WAIT_FOR_INPUT()+"Quelque chose ne va pas avec"+NEWLINE()+"la tuyauterie du Temple de l'Eau."+MESSAGE_END(),
UNSKIPPABLE()+"El interruptor está más oxidado de lo que"+NEWLINE()+"aparenta."+WAIT_FOR_INPUT()+"Algo debe andar mal en el sistema de"+NEWLINE()+"cañerías del Templo del Agua."+MESSAGE_END());
//Treasure chest shop keys. If they're not randomized leave the base game text
if (Settings::ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_SINGLE_KEYS)) {
CreateMessage(0x0F3, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(QM_RED)+"Treasure Chest Shop"+NEWLINE()+"Small Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez une "+COLOR(QM_RED)+"petite clé"+NEWLINE()+"de la chasse aux trésors"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido una "+COLOR(QM_RED)+"llave pequeña del"+NEWLINE()+"Cofre del Tesoro"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
} else if (Settings::ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_PACK)) {
CreateMessage(0x0F3, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got all 6 "+COLOR(QM_RED)+"Treasure Chest Shop"+NEWLINE()+"Small Keys"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez les "+COLOR(QM_RED)+"petites clés"+NEWLINE()+"de la chasse aux trésors"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido todas las 6 "+COLOR(QM_RED)+"llaves"+NEWLINE()+"pequeñas del Cofre del Tesoro"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
}
Text AddColorsAndFormat(Text text, const std::vector<uint8_t>& colors /*= {}*/) {
//for each language
for (std::string* textStr : {&text.english, &text.french, &text.spanish}) {
//insert playername
size_t atSymbol = textStr->find('@');
while (atSymbol != std::string::npos) {
textStr->replace(atSymbol, 1, PLAYER_NAME());
atSymbol = textStr->find('@');
}
//insert newlines either manually or when encountering a '&'
constexpr size_t lineLength = 44;
size_t lastNewline = 0;
while (lastNewline + lineLength < textStr->length()) {
size_t carrot = textStr->find('^', lastNewline);
size_t ampersand = textStr->find('&', lastNewline);
size_t lastSpace = textStr->rfind(' ', lastNewline + lineLength);
size_t lastPeriod = textStr->rfind('.', lastNewline + lineLength);
//replace '&' first if it's within the newline range
if (ampersand < lastNewline + lineLength) {
textStr->replace(ampersand, 1, NEWLINE());
lastNewline = ampersand + NEWLINE().length();
//or move the lastNewline cursor to the next line if a '^' is encountered
} else if (carrot < lastNewline + lineLength) {
lastNewline = carrot + 1;
//some lines need to be split but don't have spaces, look for periods instead
} else if (lastSpace == std::string::npos) {
textStr->replace(lastPeriod, 1, "."+NEWLINE());
lastNewline = lastPeriod + NEWLINE().length() + 1;
} else {
textStr->replace(lastSpace, 1, NEWLINE());
lastNewline = lastSpace + NEWLINE().length();
}
}
//clean up any remaining '&' characters
size_t ampersand = textStr->find('&');
while (ampersand != std::string::npos) {
textStr->replace(ampersand, 1, NEWLINE());
ampersand = textStr->find('&');
}
//insert box break
size_t carrotSymbol = textStr->find('^');
while (carrotSymbol != std::string::npos) {
textStr->replace(carrotSymbol, 1, INSTANT_TEXT_OFF()+WAIT_FOR_INPUT()+INSTANT_TEXT_ON());
carrotSymbol = textStr->find('^');
}
//add colors
for (auto color : colors) {
size_t firstHashtag = textStr->find('#');
if (firstHashtag != std::string::npos) {
textStr->replace(firstHashtag, 1, COLOR(color));
size_t secondHashtag = textStr->find('#');
if (secondHashtag == std::string::npos) {
CitraPrint("ERROR: Couldn't find second '#' in " + (*textStr));
} else {
textStr->replace(secondHashtag, 1, COLOR(QM_WHITE));
}
}
}
}
return Text{"","",""}+UNSKIPPABLE()+INSTANT_TEXT_ON()+text+INSTANT_TEXT_OFF()+MESSAGE_END();
}
void ClearMessages() {
messageEntries.clear();
arrangedMessageEntries.clear();
messageData.str("");
arrangedMessageData = "";
}
std::string MESSAGE_END() { return "\x7F\x00"s; }
std::string WAIT_FOR_INPUT() { return "\x7F\x01"s; }
std::string HORIZONTAL_SPACE(uint8_t x) {
return "\x7F\x02"s + char(x);
}
std::string GO_TO(uint16_t x) {
return "\x7F\x03"s + char(x >> 8) + char(x & 0x00FF);
}
std::string INSTANT_TEXT_ON() { return "\x7F\x04"s; }
std::string INSTANT_TEXT_OFF() { return "\x7F\x05"s; }
std::string SHOP_MESSAGE_BOX() { return "\x7F\x06\x00"s; }
std::string EVENT_TRIGGER() { return "\x7F\x07"s; }
std::string DELAY_FRAMES(uint8_t x) {
return "\x7F\x08"s + char(x);
}
std::string CLOSE_AFTER(uint8_t x) {
return "\x7F\x0A"s + char(x);
}
std::string PLAYER_NAME() { return "\x7F\x0B"s; }
std::string PLAY_OCARINA() { return "\x7F\x0C"s; }
std::string ITEM_OBTAINED(uint8_t x) {
return "\x7F\x0F"s + char(x);
}
std::string SET_SPEED(uint8_t x) {
return "\x7F\x10"s + char(x);
}
std::string SKULLTULAS_DESTROYED() { return "\x7F\x15"s; }
std::string CURRENT_TIME() { return "\x7F\x17"s; }
std::string UNSKIPPABLE() { return "\x7F\x19"s; }
std::string TWO_WAY_CHOICE() { return "\x7F\x1A\xFF\xFF\xFF\xFF"s; }
std::string NEWLINE() { return "\x7F\x1C"s; }
std::string COLOR(uint8_t x) { return "\x7F\x1D"s + char(x); }
std::string CENTER_TEXT() { return "\x7F\x1E"s; }
std::string IF_NOT_MQ() { return "\x7F\x29"s; }
std::string MQ_ELSE() { return "\x7F\x2A"s; }
std::string MQ_END() { return "\x7F\x2B"s; }
}

View file

@ -0,0 +1,93 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "text.hpp"
#define QM_WHITE 0x00
#define QM_RED 0x41
#define QM_GREEN 0x42
#define QM_BLUE 0x43
#define QM_LBLUE 0x44
#define QM_PINK 0x45
#define QM_YELLOW 0x46
#define QM_BLACK 0x47
namespace CustomMessages {
typedef struct {
// In the true file format, offset is the offset into the QM file.
// In randomizer, offset will be a pointer to the text in the game's address space.
// Since these pointers will be much larger as u32 than the original script's offsets,
// We will be able to distinguish between original and custom text using their numerical value.
const char* offset;
uint32_t length;
} MessageLanguageInfo;
typedef enum {
/* 0x00 */ JAPANESE_J,
/* 0x01 */ ENGLISH_U,
/* 0x02 */ ENGLISH_E,
/* 0x03 */ GERMAN_E,
/* 0x04 */ FRENCH_E,
/* 0x05 */ FRENCH_U,
/* 0x06 */ SPANISH_E,
/* 0x07 */ SPANISH_U,
/* 0x08 */ ITALIAN_E,
/* 0x09 */ DUTCH_E,
} MessageLanguage;
typedef struct {
uint32_t id;
uint32_t unk_04;
uint32_t unk_08;
uint32_t unk_0C;
MessageLanguageInfo info[10];
} MessageEntry; // size = 0x60
typedef struct {
char magic[4]; //"QM\0\0"
uint32_t unk_04;
uint32_t numEntries;
uint32_t unk_0C;
} MessageFileHeader;
void CreateMessage(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition,
std::string englishText, std::string frenchText, std::string spanishText);
void CreateMessageFromTextObject(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, const Text& text);
uint32_t NumMessages();
std::pair<const char*, uint32_t> RawMessageEntryData();
std::pair<const char*, uint32_t> RawMessageData();
void CreateAlwaysIncludedMessages();
Text AddColorsAndFormat(Text text, const std::vector<uint8_t>& colors = {});
void ClearMessages();
std::string MESSAGE_END();
std::string WAIT_FOR_INPUT();
std::string HORIZONTAL_SPACE(uint8_t x);
std::string GO_TO(uint16_t x);
std::string INSTANT_TEXT_ON();
std::string INSTANT_TEXT_OFF();
std::string SHOP_MESSAGE_BOX();
std::string EVENT_TRIGGER();
std::string DELAY_FRAMES(uint8_t x);
std::string CLOSE_AFTER(uint8_t x);
std::string PLAYER_NAME();
std::string PLAY_OCARINA();
std::string ITEM_OBTAINED(uint8_t x);
std::string SET_SPEED(uint8_t x);
std::string SKULLTULAS_DESTROYED();
std::string CURRENT_TIME();
std::string UNSKIPPABLE();
std::string TWO_WAY_CHOICE();
std::string NEWLINE();
std::string COLOR(uint8_t x);
std::string CENTER_TEXT();
std::string IF_NOT_MQ();
std::string MQ_ELSE();
std::string MQ_END();
}

View file

@ -0,0 +1,5 @@
#include "debug.hpp"
void CitraPrint (std::string_view str) {
}

View file

@ -0,0 +1,5 @@
#pragma once
#include <string_view>
void CitraPrint(std::string_view str);

View file

@ -0,0 +1,616 @@
#include "dungeon.hpp"
#include "category.hpp"
#include "item_location.hpp"
#include "pool_functions.hpp"
#include "keys.hpp"
namespace Dungeon {
DungeonInfo::DungeonInfo(std::string name_, uint32_t map_, uint32_t compass_, uint32_t smallKey_, uint32_t keyRing_,
uint32_t bossKey_, uint8_t vanillaKeyCount_, uint8_t mqKeyCount_,
std::vector<uint32_t> vanillaLocations_,
std::vector<uint32_t> mqLocations_,
std::vector<uint32_t> sharedLocations_)
: name(std::move(name_)),
map(map_),
compass(compass_),
smallKey(smallKey_),
keyRing(keyRing_),
bossKey(bossKey_),
vanillaKeyCount(vanillaKeyCount_),
mqKeyCount(mqKeyCount_),
vanillaLocations(std::move(vanillaLocations_)),
mqLocations(std::move(mqLocations_)),
sharedLocations(std::move(sharedLocations_)) {}
DungeonInfo::~DungeonInfo() = default;
uint32_t DungeonInfo::GetSmallKey() const {
return smallKey;
}
uint32_t DungeonInfo::GetKeyRing() const {
return keyRing;
}
uint32_t DungeonInfo::GetMap() const {
return map;
}
uint32_t DungeonInfo::GetCompass() const {
return compass;
}
uint32_t DungeonInfo::GetBossKey() const {
return bossKey;
}
void DungeonInfo::PlaceVanillaMap() {
if (map == NONE) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto mapLocation = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaMap); })[0];
PlaceItemInLocation(mapLocation, map);
}
void DungeonInfo::PlaceVanillaCompass() {
if (compass == NONE) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto compassLocation = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaCompass); })[0];
PlaceItemInLocation(compassLocation, compass);
}
void DungeonInfo::PlaceVanillaBossKey() {
if (bossKey == NONE || bossKey == GANONS_CASTLE_BOSS_KEY) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto bossKeyLocation = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaBossKey); })[0];
PlaceItemInLocation(bossKeyLocation, bossKey);
}
void DungeonInfo::PlaceVanillaSmallKeys() {
if (smallKey == NONE) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto smallKeyLocations = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaSmallKey); });
for (auto location : smallKeyLocations) {
PlaceItemInLocation(location, smallKey);
}
}
//Gets the chosen dungeon locations for a playthrough (so either MQ or Vanilla)
std::vector<uint32_t> DungeonInfo::GetDungeonLocations() const {
auto locations = masterQuest ? mqLocations : vanillaLocations;
AddElementsToPool(locations, sharedLocations);
return locations;
}
//Gets all dungeon locations (MQ + Vanilla)
std::vector<uint32_t> DungeonInfo::GetEveryLocation() const {
auto locations = vanillaLocations;
AddElementsToPool(locations, mqLocations);
AddElementsToPool(locations, sharedLocations);
return locations;
}
DungeonInfo DekuTree = DungeonInfo("Deku Tree", DEKU_TREE_MAP, DEKU_TREE_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
DEKU_TREE_MAP_CHEST,
DEKU_TREE_COMPASS_CHEST,
DEKU_TREE_COMPASS_ROOM_SIDE_CHEST,
DEKU_TREE_BASEMENT_CHEST,
DEKU_TREE_SLINGSHOT_CHEST,
DEKU_TREE_SLINGSHOT_ROOM_SIDE_CHEST,
DEKU_TREE_GS_BASEMENT_BACK_ROOM,
DEKU_TREE_GS_BASEMENT_GATE,
DEKU_TREE_GS_BASEMENT_VINES,
DEKU_TREE_GS_COMPASS_ROOM,
}, {
//MQ Locations
DEKU_TREE_MQ_MAP_CHEST,
DEKU_TREE_MQ_COMPASS_CHEST,
DEKU_TREE_MQ_SLINGSHOT_CHEST,
DEKU_TREE_MQ_SLINGSHOT_ROOM_BACK_CHEST,
DEKU_TREE_MQ_BASEMENT_CHEST,
DEKU_TREE_MQ_BEFORE_SPINNING_LOG_CHEST,
DEKU_TREE_MQ_AFTER_SPINNING_LOG_CHEST,
DEKU_TREE_MQ_DEKU_SCRUB,
DEKU_TREE_MQ_GS_LOBBY,
DEKU_TREE_MQ_GS_COMPASS_ROOM,
DEKU_TREE_MQ_GS_BASEMENT_GRAVES_ROOM,
DEKU_TREE_MQ_GS_BASEMENT_BACK_ROOM,
}, {
//Shared Locations
DEKU_TREE_QUEEN_GOHMA_HEART,
QUEEN_GOHMA,
});
DungeonInfo DodongosCavern = DungeonInfo("Dodongo's Cavern", DODONGOS_CAVERN_MAP, DODONGOS_CAVERN_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
DODONGOS_CAVERN_MAP_CHEST,
DODONGOS_CAVERN_COMPASS_CHEST,
DODONGOS_CAVERN_BOMB_FLOWER_PLATFORM_CHEST,
DODONGOS_CAVERN_BOMB_BAG_CHEST,
DODONGOS_CAVERN_END_OF_BRIDGE_CHEST,
DODONGOS_CAVERN_DEKU_SCRUB_LOBBY,
DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS,
DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT,
DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT,
DODONGOS_CAVERN_GS_VINES_ABOVE_STAIRS,
DODONGOS_CAVERN_GS_SCARECROW,
DODONGOS_CAVERN_GS_ALCOVE_ABOVE_STAIRS,
DODONGOS_CAVERN_GS_BACK_ROOM,
DODONGOS_CAVERN_GS_SIDE_ROOM_NEAR_LOWER_LIZALFOS,
}, {
//MQ Locations
DODONGOS_CAVERN_MQ_MAP_CHEST,
DODONGOS_CAVERN_MQ_BOMB_BAG_CHEST,
DODONGOS_CAVERN_MQ_COMPASS_CHEST,
DODONGOS_CAVERN_MQ_LARVAE_ROOM_CHEST,
DODONGOS_CAVERN_MQ_TORCH_PUZZLE_ROOM_CHEST,
DODONGOS_CAVERN_MQ_UNDER_GRAVE_CHEST,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_REAR,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_FRONT,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_STAIRCASE,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_SIDE_ROOM_NEAR_LOWER_LIZALFOS,
DODONGOS_CAVERN_MQ_GS_SCRUB_ROOM,
DODONGOS_CAVERN_MQ_GS_SONG_OF_TIME_BLOCK_ROOM,
DODONGOS_CAVERN_MQ_GS_LIZALFOS_ROOM,
DODONGOS_CAVERN_MQ_GS_LARVAE_ROOM,
DODONGOS_CAVERN_MQ_GS_BACK_AREA,
}, {
//Shared Locations
DODONGOS_CAVERN_BOSS_ROOM_CHEST,
DODONGOS_CAVERN_KING_DODONGO_HEART,
KING_DODONGO,
});
DungeonInfo JabuJabusBelly = DungeonInfo("Jabu Jabu's Belly", JABU_JABUS_BELLY_MAP, JABU_JABUS_BELLY_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
JABU_JABUS_BELLY_MAP_CHEST,
JABU_JABUS_BELLY_COMPASS_CHEST,
JABU_JABUS_BELLY_BOOMERANG_CHEST,
JABU_JABUS_BELLY_DEKU_SCRUB,
JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_LOWER,
JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_UPPER,
JABU_JABUS_BELLY_GS_NEAR_BOSS,
JABU_JABUS_BELLY_GS_WATER_SWITCH_ROOM,
}, {
//MQ Locations
JABU_JABUS_BELLY_MQ_FIRST_ROOM_SIDE_CHEST,
JABU_JABUS_BELLY_MQ_MAP_CHEST,
JABU_JABUS_BELLY_MQ_SECOND_ROOM_LOWER_CHEST,
JABU_JABUS_BELLY_MQ_COMPASS_CHEST,
JABU_JABUS_BELLY_MQ_SECOND_ROOM_UPPER_CHEST,
JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_SWITCHES_CHEST,
JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_VINES_CHEST,
JABU_JABUS_BELLY_MQ_NEAR_BOSS_CHEST,
JABU_JABUS_BELLY_MQ_FALLING_LIKE_LIKE_ROOM_CHEST,
JABU_JABUS_BELLY_MQ_BOOMERANG_ROOM_SMALL_CHEST,
JABU_JABUS_BELLY_MQ_BOOMERANG_CHEST,
JABU_JABUS_BELLY_MQ_COW,
JABU_JABUS_BELLY_MQ_GS_TAILPASARAN_ROOM,
JABU_JABUS_BELLY_MQ_GS_INVISIBLE_ENEMIES_ROOM,
JABU_JABUS_BELLY_MQ_GS_BOOMERANG_CHEST_ROOM,
JABU_JABUS_BELLY_MQ_GS_NEAR_BOSS,
}, {
//Shared Locations
JABU_JABUS_BELLY_BARINADE_HEART,
BARINADE,
});
DungeonInfo ForestTemple = DungeonInfo("Forest Temple", FOREST_TEMPLE_MAP, FOREST_TEMPLE_COMPASS, FOREST_TEMPLE_SMALL_KEY, FOREST_TEMPLE_KEY_RING, FOREST_TEMPLE_BOSS_KEY, 5, 6, {
//Vanilla Locations
FOREST_TEMPLE_FIRST_ROOM_CHEST,
FOREST_TEMPLE_FIRST_STALFOS_CHEST,
FOREST_TEMPLE_RAISED_ISLAND_COURTYARD_CHEST,
FOREST_TEMPLE_MAP_CHEST,
FOREST_TEMPLE_WELL_CHEST,
FOREST_TEMPLE_FALLING_CEILING_ROOM_CHEST,
FOREST_TEMPLE_EYE_SWITCH_CHEST,
FOREST_TEMPLE_BOSS_KEY_CHEST,
FOREST_TEMPLE_FLOORMASTER_CHEST,
FOREST_TEMPLE_BOW_CHEST,
FOREST_TEMPLE_RED_POE_CHEST,
FOREST_TEMPLE_BLUE_POE_CHEST,
FOREST_TEMPLE_BASEMENT_CHEST,
FOREST_TEMPLE_GS_RAISED_ISLAND_COURTYARD,
FOREST_TEMPLE_GS_FIRST_ROOM,
FOREST_TEMPLE_GS_LEVEL_ISLAND_COURTYARD,
FOREST_TEMPLE_GS_LOBBY,
FOREST_TEMPLE_GS_BASEMENT,
}, {
//MQ Locations
FOREST_TEMPLE_MQ_FIRST_ROOM_CHEST,
FOREST_TEMPLE_MQ_WOLFOS_CHEST,
FOREST_TEMPLE_MQ_BOW_CHEST,
FOREST_TEMPLE_MQ_RAISED_ISLAND_COURTYARD_LOWER_CHEST,
FOREST_TEMPLE_MQ_RAISED_ISLAND_COURTYARD_UPPER_CHEST,
FOREST_TEMPLE_MQ_WELL_CHEST,
FOREST_TEMPLE_MQ_MAP_CHEST,
FOREST_TEMPLE_MQ_COMPASS_CHEST,
FOREST_TEMPLE_MQ_FALLING_CEILING_ROOM_CHEST,
FOREST_TEMPLE_MQ_BASEMENT_CHEST,
FOREST_TEMPLE_MQ_REDEAD_CHEST,
FOREST_TEMPLE_MQ_BOSS_KEY_CHEST,
FOREST_TEMPLE_MQ_GS_FIRST_HALLWAY,
FOREST_TEMPLE_MQ_GS_BLOCK_PUSH_ROOM,
FOREST_TEMPLE_MQ_GS_RAISED_ISLAND_COURTYARD,
FOREST_TEMPLE_MQ_GS_LEVEL_ISLAND_COURTYARD,
FOREST_TEMPLE_MQ_GS_WELL,
}, {
//Shared Locations
FOREST_TEMPLE_PHANTOM_GANON_HEART,
PHANTOM_GANON,
});
DungeonInfo FireTemple = DungeonInfo("Fire Temple", FIRE_TEMPLE_MAP, FIRE_TEMPLE_COMPASS, FIRE_TEMPLE_SMALL_KEY, FIRE_TEMPLE_KEY_RING, FIRE_TEMPLE_BOSS_KEY, 8, 5, {
//Vanilla Locations
FIRE_TEMPLE_NEAR_BOSS_CHEST,
FIRE_TEMPLE_FLARE_DANCER_CHEST,
FIRE_TEMPLE_BOSS_KEY_CHEST,
FIRE_TEMPLE_BIG_LAVA_ROOM_BLOCKED_DOOR_CHEST,
FIRE_TEMPLE_BIG_LAVA_ROOM_LOWER_OPEN_DOOR_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_LOWER_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_UPPER_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_SIDE_ROOM_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_SHORTCUT_CHEST,
FIRE_TEMPLE_SCARECROW_CHEST,
FIRE_TEMPLE_MAP_CHEST,
FIRE_TEMPLE_COMPASS_CHEST,
FIRE_TEMPLE_HIGHEST_GORON_CHEST,
FIRE_TEMPLE_MEGATON_HAMMER_CHEST,
FIRE_TEMPLE_GS_SONG_OF_TIME_ROOM,
FIRE_TEMPLE_GS_BOSS_KEY_LOOP,
FIRE_TEMPLE_GS_BOULDER_MAZE,
FIRE_TEMPLE_GS_SCARECROW_TOP,
FIRE_TEMPLE_GS_SCARECROW_CLIMB,
}, {
//MQ Locations
FIRE_TEMPLE_MQ_NEAR_BOSS_CHEST,
FIRE_TEMPLE_MQ_MEGATON_HAMMER_CHEST,
FIRE_TEMPLE_MQ_COMPASS_CHEST,
FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CHEST,
FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CHEST,
FIRE_TEMPLE_MQ_CHEST_ON_FIRE,
FIRE_TEMPLE_MQ_MAP_ROOM_SIDE_CHEST,
FIRE_TEMPLE_MQ_MAP_CHEST,
FIRE_TEMPLE_MQ_BOSS_KEY_CHEST,
FIRE_TEMPLE_MQ_BIG_LAVA_ROOM_BLOCKED_DOOR_CHEST,
FIRE_TEMPLE_MQ_LIZALFOS_MAZE_SIDE_ROOM_CHEST,
FIRE_TEMPLE_MQ_FREESTANDING_KEY,
FIRE_TEMPLE_MQ_GS_ABOVE_FIRE_WALL_MAZE,
FIRE_TEMPLE_MQ_GS_FIRE_WALL_MAZE_CENTER,
FIRE_TEMPLE_MQ_GS_BIG_LAVA_ROOM_OPEN_DOOR,
FIRE_TEMPLE_MQ_GS_FIRE_WALL_MAZE_SIDE_ROOM,
FIRE_TEMPLE_MQ_GS_SKULL_ON_FIRE,
}, {
//Shared Locations
FIRE_TEMPLE_VOLVAGIA_HEART,
VOLVAGIA,
});
DungeonInfo WaterTemple = DungeonInfo("Water Temple", WATER_TEMPLE_MAP, WATER_TEMPLE_COMPASS, WATER_TEMPLE_SMALL_KEY, WATER_TEMPLE_KEY_RING, WATER_TEMPLE_BOSS_KEY, 6, 2, {
//Vanilla Locations
WATER_TEMPLE_MAP_CHEST,
WATER_TEMPLE_COMPASS_CHEST,
WATER_TEMPLE_TORCHES_CHEST,
WATER_TEMPLE_DRAGON_CHEST,
WATER_TEMPLE_CENTRAL_BOW_TARGET_CHEST,
WATER_TEMPLE_CENTRAL_PILLAR_CHEST,
WATER_TEMPLE_CRACKED_WALL_CHEST,
WATER_TEMPLE_BOSS_KEY_CHEST,
WATER_TEMPLE_LONGSHOT_CHEST,
WATER_TEMPLE_RIVER_CHEST,
WATER_TEMPLE_GS_BEHIND_GATE,
WATER_TEMPLE_GS_FALLING_PLATFORM_ROOM,
WATER_TEMPLE_GS_CENTRAL_PILLAR,
WATER_TEMPLE_GS_NEAR_BOSS_KEY_CHEST,
WATER_TEMPLE_GS_RIVER,
}, {
//MQ Locations
WATER_TEMPLE_MQ_CENTRAL_PILLAR_CHEST,
WATER_TEMPLE_MQ_BOSS_KEY_CHEST,
WATER_TEMPLE_MQ_LONGSHOT_CHEST,
WATER_TEMPLE_MQ_COMPASS_CHEST,
WATER_TEMPLE_MQ_MAP_CHEST,
WATER_TEMPLE_MQ_FREESTANDING_KEY,
WATER_TEMPLE_MQ_GS_BEFORE_UPPER_WATER_SWITCH,
WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA,
WATER_TEMPLE_MQ_GS_LIZALFOS_HALLWAY,
WATER_TEMPLE_MQ_GS_RIVER,
WATER_TEMPLE_MQ_GS_TRIPLE_WALL_TORCH,
}, {
//Shared Locations
WATER_TEMPLE_MORPHA_HEART,
MORPHA,
});
DungeonInfo SpiritTemple = DungeonInfo("Spirit Temple", SPIRIT_TEMPLE_MAP, SPIRIT_TEMPLE_COMPASS, SPIRIT_TEMPLE_SMALL_KEY, SPIRIT_TEMPLE_KEY_RING, SPIRIT_TEMPLE_BOSS_KEY, 5, 7, {
//Vanilla Locations
SPIRIT_TEMPLE_CHILD_BRIDGE_CHEST,
SPIRIT_TEMPLE_CHILD_EARLY_TORCHES_CHEST,
SPIRIT_TEMPLE_COMPASS_CHEST,
SPIRIT_TEMPLE_EARLY_ADULT_RIGHT_CHEST,
SPIRIT_TEMPLE_FIRST_MIRROR_LEFT_CHEST,
SPIRIT_TEMPLE_FIRST_MIRROR_RIGHT_CHEST,
SPIRIT_TEMPLE_MAP_CHEST,
SPIRIT_TEMPLE_CHILD_CLIMB_NORTH_CHEST,
SPIRIT_TEMPLE_CHILD_CLIMB_EAST_CHEST,
SPIRIT_TEMPLE_SUN_BLOCK_ROOM_CHEST,
SPIRIT_TEMPLE_STATUE_ROOM_HAND_CHEST,
SPIRIT_TEMPLE_STATUE_ROOM_NORTHEAST_CHEST,
SPIRIT_TEMPLE_NEAR_FOUR_ARMOS_CHEST,
SPIRIT_TEMPLE_HALLWAY_LEFT_INVISIBLE_CHEST,
SPIRIT_TEMPLE_HALLWAY_RIGHT_INVISIBLE_CHEST,
SPIRIT_TEMPLE_BOSS_KEY_CHEST,
SPIRIT_TEMPLE_TOPMOST_CHEST,
SPIRIT_TEMPLE_GS_HALL_AFTER_SUN_BLOCK_ROOM,
SPIRIT_TEMPLE_GS_BOULDER_ROOM,
SPIRIT_TEMPLE_GS_LOBBY,
SPIRIT_TEMPLE_GS_SUN_ON_FLOOR_ROOM,
SPIRIT_TEMPLE_GS_METAL_FENCE,
}, {
//MQ Locations
SPIRIT_TEMPLE_MQ_ENTRANCE_FRONT_LEFT_CHEST,
SPIRIT_TEMPLE_MQ_ENTRANCE_BACK_RIGHT_CHEST,
SPIRIT_TEMPLE_MQ_ENTRANCE_FRONT_RIGHT_CHEST,
SPIRIT_TEMPLE_MQ_ENTRANCE_BACK_LEFT_CHEST,
SPIRIT_TEMPLE_MQ_CHILD_HAMMER_SWITCH_CHEST,
SPIRIT_TEMPLE_MQ_MAP_CHEST,
SPIRIT_TEMPLE_MQ_MAP_ROOM_ENEMY_CHEST,
SPIRIT_TEMPLE_MQ_CHILD_CLIMB_NORTH_CHEST,
SPIRIT_TEMPLE_MQ_CHILD_CLIMB_SOUTH_CHEST,
SPIRIT_TEMPLE_MQ_COMPASS_CHEST,
SPIRIT_TEMPLE_MQ_STATUE_ROOM_LULLABY_CHEST,
SPIRIT_TEMPLE_MQ_STATUE_ROOM_INVISIBLE_CHEST,
SPIRIT_TEMPLE_MQ_SILVER_BLOCK_HALLWAY_CHEST,
SPIRIT_TEMPLE_MQ_SUN_BLOCK_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_SYMPHONY_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_LEEVER_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_BEAMOS_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_CHEST_SWITCH_CHEST,
SPIRIT_TEMPLE_MQ_BOSS_KEY_CHEST,
SPIRIT_TEMPLE_MQ_MIRROR_PUZZLE_INVISIBLE_CHEST,
SPIRIT_TEMPLE_MQ_GS_SYMPHONY_ROOM,
SPIRIT_TEMPLE_MQ_GS_LEEVER_ROOM,
SPIRIT_TEMPLE_MQ_GS_NINE_THRONES_ROOM_WEST,
SPIRIT_TEMPLE_MQ_GS_NINE_THRONES_ROOM_NORTH,
SPIRIT_TEMPLE_MQ_GS_SUN_BLOCK_ROOM,
}, {
//Shared Locations
SPIRIT_TEMPLE_SILVER_GAUNTLETS_CHEST,
SPIRIT_TEMPLE_MIRROR_SHIELD_CHEST,
SPIRIT_TEMPLE_TWINROVA_HEART,
TWINROVA,
});
DungeonInfo ShadowTemple = DungeonInfo("Shadow Temple", SHADOW_TEMPLE_MAP, SHADOW_TEMPLE_COMPASS, SHADOW_TEMPLE_SMALL_KEY, SHADOW_TEMPLE_KEY_RING, SHADOW_TEMPLE_BOSS_KEY, 5, 6, {
//Vanilla Locations
SHADOW_TEMPLE_MAP_CHEST,
SHADOW_TEMPLE_HOVER_BOOTS_CHEST,
SHADOW_TEMPLE_COMPASS_CHEST,
SHADOW_TEMPLE_EARLY_SILVER_RUPEE_CHEST,
SHADOW_TEMPLE_INVISIBLE_BLADES_VISIBLE_CHEST,
SHADOW_TEMPLE_INVISIBLE_BLADES_INVISIBLE_CHEST,
SHADOW_TEMPLE_FALLING_SPIKES_LOWER_CHEST,
SHADOW_TEMPLE_FALLING_SPIKES_UPPER_CHEST,
SHADOW_TEMPLE_FALLING_SPIKES_SWITCH_CHEST,
SHADOW_TEMPLE_INVISIBLE_SPIKES_CHEST,
SHADOW_TEMPLE_WIND_HINT_CHEST,
SHADOW_TEMPLE_AFTER_WIND_ENEMY_CHEST,
SHADOW_TEMPLE_AFTER_WIND_HIDDEN_CHEST,
SHADOW_TEMPLE_SPIKE_WALLS_LEFT_CHEST,
SHADOW_TEMPLE_BOSS_KEY_CHEST,
SHADOW_TEMPLE_INVISIBLE_FLOORMASTER_CHEST,
SHADOW_TEMPLE_FREESTANDING_KEY,
SHADOW_TEMPLE_GS_SINGLE_GIANT_POT,
SHADOW_TEMPLE_GS_FALLING_SPIKES_ROOM,
SHADOW_TEMPLE_GS_TRIPLE_GIANT_POT,
SHADOW_TEMPLE_GS_LIKE_LIKE_ROOM,
SHADOW_TEMPLE_GS_NEAR_SHIP,
}, {
//MQ Locations
SHADOW_TEMPLE_MQ_COMPASS_CHEST,
SHADOW_TEMPLE_MQ_HOVER_BOOTS_CHEST,
SHADOW_TEMPLE_MQ_EARLY_GIBDOS_CHEST,
SHADOW_TEMPLE_MQ_MAP_CHEST,
SHADOW_TEMPLE_MQ_BEAMOS_SILVER_RUPEES_CHEST,
SHADOW_TEMPLE_MQ_FALLING_SPIKES_SWITCH_CHEST,
SHADOW_TEMPLE_MQ_FALLING_SPIKES_LOWER_CHEST,
SHADOW_TEMPLE_MQ_FALLING_SPIKES_UPPER_CHEST,
SHADOW_TEMPLE_MQ_INVISIBLE_SPIKES_CHEST,
SHADOW_TEMPLE_MQ_BOSS_KEY_CHEST,
SHADOW_TEMPLE_MQ_SPIKE_WALLS_LEFT_CHEST,
SHADOW_TEMPLE_MQ_STALFOS_ROOM_CHEST,
SHADOW_TEMPLE_MQ_INVISIBLE_BLADES_INVISIBLE_CHEST,
SHADOW_TEMPLE_MQ_INVISIBLE_BLADES_VISIBLE_CHEST,
SHADOW_TEMPLE_MQ_BOMB_FLOWER_CHEST,
SHADOW_TEMPLE_MQ_WIND_HINT_CHEST,
SHADOW_TEMPLE_MQ_AFTER_WIND_HIDDEN_CHEST,
SHADOW_TEMPLE_MQ_AFTER_WIND_ENEMY_CHEST,
SHADOW_TEMPLE_MQ_NEAR_SHIP_INVISIBLE_CHEST,
SHADOW_TEMPLE_MQ_FREESTANDING_KEY,
SHADOW_TEMPLE_MQ_GS_FALLING_SPIKES_ROOM,
SHADOW_TEMPLE_MQ_GS_WIND_HINT_ROOM,
SHADOW_TEMPLE_MQ_GS_AFTER_WIND,
SHADOW_TEMPLE_MQ_GS_AFTER_SHIP,
SHADOW_TEMPLE_MQ_GS_NEAR_BOSS,
}, {
//Shared Locations
SHADOW_TEMPLE_BONGO_BONGO_HEART,
BONGO_BONGO,
});
DungeonInfo BottomOfTheWell = DungeonInfo("Bottom of the Well", BOTTOM_OF_THE_WELL_MAP, BOTTOM_OF_THE_WELL_COMPASS, BOTTOM_OF_THE_WELL_SMALL_KEY, BOTTOM_OF_THE_WELL_KEY_RING, NONE, 3, 2, {
//Vanilla Locations
BOTTOM_OF_THE_WELL_FRONT_LEFT_FAKE_WALL_CHEST,
BOTTOM_OF_THE_WELL_FRONT_CENTER_BOMBABLE_CHEST,
BOTTOM_OF_THE_WELL_RIGHT_BOTTOM_FAKE_WALL_CHEST,
BOTTOM_OF_THE_WELL_COMPASS_CHEST,
BOTTOM_OF_THE_WELL_CENTER_SKULLTULA_CHEST,
BOTTOM_OF_THE_WELL_BACK_LEFT_BOMBABLE_CHEST,
BOTTOM_OF_THE_WELL_LENS_OF_TRUTH_CHEST,
BOTTOM_OF_THE_WELL_INVISIBLE_CHEST,
BOTTOM_OF_THE_WELL_UNDERWATER_FRONT_CHEST,
BOTTOM_OF_THE_WELL_UNDERWATER_LEFT_CHEST,
BOTTOM_OF_THE_WELL_MAP_CHEST,
BOTTOM_OF_THE_WELL_FIRE_KEESE_CHEST,
BOTTOM_OF_THE_WELL_LIKE_LIKE_CHEST,
BOTTOM_OF_THE_WELL_FREESTANDING_KEY,
BOTTOM_OF_THE_WELL_GS_LIKE_LIKE_CAGE,
BOTTOM_OF_THE_WELL_GS_EAST_INNER_ROOM,
BOTTOM_OF_THE_WELL_GS_WEST_INNER_ROOM,
}, {
//MQ Locations
BOTTOM_OF_THE_WELL_MQ_MAP_CHEST,
BOTTOM_OF_THE_WELL_MQ_LENS_OF_TRUTH_CHEST,
BOTTOM_OF_THE_WELL_MQ_COMPASS_CHEST,
BOTTOM_OF_THE_WELL_MQ_DEAD_HAND_FREESTANDING_KEY,
BOTTOM_OF_THE_WELL_MQ_EAST_INNER_ROOM_FREESTANDING_KEY,
BOTTOM_OF_THE_WELL_MQ_GS_BASEMENT,
BOTTOM_OF_THE_WELL_MQ_GS_COFFIN_ROOM,
BOTTOM_OF_THE_WELL_MQ_GS_WEST_INNER_ROOM,
}, {});
DungeonInfo IceCavern = DungeonInfo("Ice Cavern", ICE_CAVERN_MAP, ICE_CAVERN_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
ICE_CAVERN_MAP_CHEST,
ICE_CAVERN_COMPASS_CHEST,
ICE_CAVERN_IRON_BOOTS_CHEST,
ICE_CAVERN_FREESTANDING_POH,
ICE_CAVERN_GS_PUSH_BLOCK_ROOM,
ICE_CAVERN_GS_SPINNING_SCYTHE_ROOM,
ICE_CAVERN_GS_HEART_PIECE_ROOM,
}, {
//MQ Locations
ICE_CAVERN_MQ_IRON_BOOTS_CHEST,
ICE_CAVERN_MQ_COMPASS_CHEST,
ICE_CAVERN_MQ_MAP_CHEST,
ICE_CAVERN_MQ_FREESTANDING_POH,
ICE_CAVERN_MQ_GS_SCARECROW,
ICE_CAVERN_MQ_GS_ICE_BLOCK,
ICE_CAVERN_MQ_GS_RED_ICE,
}, {
//Shared Locations
SHEIK_IN_ICE_CAVERN,
});
DungeonInfo GerudoTrainingGrounds = DungeonInfo("Gerudo Training Grounds", NONE, NONE, GERUDO_TRAINING_GROUNDS_SMALL_KEY, GERUDO_TRAINING_GROUNDS_KEY_RING, NONE, 9, 3, {
//Vanilla Locations
GERUDO_TRAINING_GROUNDS_LOBBY_LEFT_CHEST,
GERUDO_TRAINING_GROUNDS_LOBBY_RIGHT_CHEST,
GERUDO_TRAINING_GROUNDS_STALFOS_CHEST,
GERUDO_TRAINING_GROUNDS_BEAMOS_CHEST,
GERUDO_TRAINING_GROUNDS_HIDDEN_CEILING_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_FIRST_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_SECOND_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_THIRD_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_FINAL_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_RIGHT_CENTRAL_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_RIGHT_SIDE_CHEST,
GERUDO_TRAINING_GROUNDS_UNDERWATER_SILVER_RUPEE_CHEST,
GERUDO_TRAINING_GROUNDS_HAMMER_ROOM_CLEAR_CHEST,
GERUDO_TRAINING_GROUNDS_HAMMER_ROOM_SWITCH_CHEST,
GERUDO_TRAINING_GROUNDS_EYE_STATUE_CHEST,
GERUDO_TRAINING_GROUNDS_NEAR_SCARECROW_CHEST,
GERUDO_TRAINING_GROUNDS_BEFORE_HEAVY_BLOCK_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_FIRST_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_SECOND_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_THIRD_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_FOURTH_CHEST,
GERUDO_TRAINING_GROUNDS_FREESTANDING_KEY,
}, {
//MQ Locations
GERUDO_TRAINING_GROUNDS_MQ_LOBBY_RIGHT_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_LOBBY_LEFT_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_FIRST_IRON_KNUCKLE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_BEFORE_HEAVY_BLOCK_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_EYE_STATUE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_FLAME_CIRCLE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_SECOND_IRON_KNUCKLE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_DINOLFOS_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_ICE_ARROWS_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_RIGHT_CENTRAL_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_FIRST_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_RIGHT_SIDE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_THIRD_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_SECOND_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_HIDDEN_CEILING_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_UNDERWATER_SILVER_RUPEE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_HEAVY_BLOCK_CHEST,
}, {});
DungeonInfo GanonsCastle = DungeonInfo("Ganon's Castle", NONE, NONE, GANONS_CASTLE_SMALL_KEY, GANONS_CASTLE_KEY_RING, GANONS_CASTLE_BOSS_KEY, 2, 3, {
//Vanilla Locations
GANONS_CASTLE_FOREST_TRIAL_CHEST,
GANONS_CASTLE_WATER_TRIAL_LEFT_CHEST,
GANONS_CASTLE_WATER_TRIAL_RIGHT_CHEST,
GANONS_CASTLE_SHADOW_TRIAL_FRONT_CHEST,
GANONS_CASTLE_SHADOW_TRIAL_GOLDEN_GAUNTLETS_CHEST,
GANONS_CASTLE_SPIRIT_TRIAL_CRYSTAL_SWITCH_CHEST,
GANONS_CASTLE_SPIRIT_TRIAL_INVISIBLE_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_FIRST_LEFT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_SECOND_LEFT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_THIRD_LEFT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_FIRST_RIGHT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_SECOND_RIGHT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_THIRD_RIGHT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_INVISIBLE_ENEMIES_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_LULLABY_CHEST,
GANONS_CASTLE_DEKU_SCRUB_LEFT,
GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT,
GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT,
GANONS_CASTLE_DEKU_SCRUB_RIGHT,
}, {
//MQ Locations
GANONS_CASTLE_MQ_WATER_TRIAL_CHEST,
GANONS_CASTLE_MQ_FOREST_TRIAL_EYE_SWITCH_CHEST,
GANONS_CASTLE_MQ_FOREST_TRIAL_FROZEN_EYE_SWITCH_CHEST,
GANONS_CASTLE_MQ_LIGHT_TRIAL_LULLABY_CHEST,
GANONS_CASTLE_MQ_SHADOW_TRIAL_BOMB_FLOWER_CHEST,
GANONS_CASTLE_MQ_SHADOW_TRIAL_EYE_SWITCH_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_GOLDEN_GAUNTLETS_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_SUN_BACK_RIGHT_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_SUN_BACK_LEFT_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_SUN_FRONT_LEFT_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_FIRST_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_INVISIBLE_CHEST,
GANONS_CASTLE_MQ_FOREST_TRIAL_FREESTANDING_KEY,
GANONS_CASTLE_MQ_DEKU_SCRUB_RIGHT,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_LEFT,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_RIGHT,
GANONS_CASTLE_MQ_DEKU_SCRUB_LEFT,
}, {
//Shared Locations
GANONS_TOWER_BOSS_KEY_CHEST,
GANON,
});
const DungeonArray dungeonList = {
&DekuTree,
&DodongosCavern,
&JabuJabusBelly,
&ForestTemple,
&FireTemple,
&WaterTemple,
&SpiritTemple,
&ShadowTemple,
&BottomOfTheWell,
&IceCavern,
&GerudoTrainingGrounds,
&GanonsCastle,
};
} //namespace Dungeon

View file

@ -0,0 +1,104 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "keys.hpp"
namespace Dungeon {
class DungeonInfo {
public:
DungeonInfo(std::string name_, uint32_t map_, uint32_t compass_, uint32_t smallKey_, uint32_t keyRing_, uint32_t bossKey_,
uint8_t vanillaKeyCount_, uint8_t mqKeyCount_,
std::vector<uint32_t> vanillaLocations_,
std::vector<uint32_t> mqLocations_,
std::vector<uint32_t> sharedLocations_);
~DungeonInfo();
const std::string& GetName() const {
return name;
}
void SetMQ() {
masterQuest = true;
}
void ClearMQ() {
masterQuest = false;
}
bool IsMQ() const {
return masterQuest;
}
void SetKeyRing() {
hasKeyRing = true;
}
void ClearKeyRing() {
hasKeyRing = false;
}
bool HasKeyRing() const {
return hasKeyRing;
}
bool IsVanilla() const {
return !masterQuest;
}
uint8_t GetSmallKeyCount() const {
return (masterQuest) ? mqKeyCount : vanillaKeyCount;
}
uint32_t GetSmallKey() const;
uint32_t GetKeyRing() const;
uint32_t GetMap() const;
uint32_t GetCompass() const;
uint32_t GetBossKey() const;
void PlaceVanillaMap();
void PlaceVanillaCompass();
void PlaceVanillaBossKey();
void PlaceVanillaSmallKeys();
// Gets the chosen dungeon locations for a playthrough (so either MQ or Vanilla)
std::vector<uint32_t> GetDungeonLocations() const;
// Gets all dungeon locations (MQ + Vanilla)
std::vector<uint32_t> GetEveryLocation() const;
private:
std::string name;
uint32_t map;
uint32_t compass;
uint32_t smallKey;
uint32_t keyRing;
uint32_t bossKey;
uint8_t vanillaKeyCount;
uint8_t mqKeyCount;
bool masterQuest = false;
bool hasKeyRing = false;
std::vector<uint32_t> vanillaLocations;
std::vector<uint32_t> mqLocations;
std::vector<uint32_t> sharedLocations;
};
extern DungeonInfo DekuTree;
extern DungeonInfo DodongosCavern;
extern DungeonInfo JabuJabusBelly;
extern DungeonInfo ForestTemple;
extern DungeonInfo FireTemple;
extern DungeonInfo WaterTemple;
extern DungeonInfo SpiritTemple;
extern DungeonInfo ShadowTemple;
extern DungeonInfo BottomOfTheWell;
extern DungeonInfo IceCavern;
extern DungeonInfo GerudoTrainingGrounds;
extern DungeonInfo GanonsCastle;
using DungeonArray = std::array<DungeonInfo*, 12>;
extern const DungeonArray dungeonList;
} // namespace Dungeon

View file

@ -0,0 +1,877 @@
#include "entrance.hpp"
#include "fill.hpp"
#include "settings.hpp"
#include "item_list.hpp"
#include "item_pool.hpp"
#include "item_location.hpp"
#include "debug.hpp"
#include "spoiler_log.hpp"
#include "hints.hpp"
#include "location_access.hpp"
#include <vector>
#include <utility>
#include <set>
#include <map>
#include <Lib/spdlog/include/spdlog/spdlog.h>
std::list<EntranceOverride> entranceOverrides = {};
bool noRandomEntrances = false;
static bool entranceShuffleFailure = false;
static int totalRandomizableEntrances = 0;
static int curNumRandomizedEntrances = 0;
typedef struct {
EntranceType type;
uint32_t parentRegion;
uint32_t connectedRegion;
int16_t index;
int16_t blueWarp;
} EntranceLinkInfo;
//primary, secondary
using EntranceInfoPair = std::pair<EntranceLinkInfo, EntranceLinkInfo>;
using EntrancePair = std::pair<Entrance*, Entrance*>;
//The entrance randomization algorithm used here is a direct copy of
//the algorithm used in the original N64 randomizer (except now in C++ instead
//of python). It may be easier to understand the algorithm by looking at the
//base randomizer's code instead:
// https://github.com/TestRunnerSRL/OoT-Randomizer/blob/Dev/EntranceShuffle.py
// Updates the user on how many entrances are currently shuffled
static void DisplayEntranceProgress() {
std::string dots = ".";
float progress = (float)curNumRandomizedEntrances / (float)totalRandomizableEntrances;
if (progress > 0.33) {
dots += ".";
} else {
dots += " ";
}
if (progress > 0.66) {
dots += ".";
} else {
dots += " ";
}
printf("\x1b[7;29H%s", dots.c_str());
#ifdef ENABLE_DEBUG
if (curNumRandomizedEntrances == totalRandomizableEntrances) {
Areas::DumpWorldGraph("Finish Validation");
}
#endif
}
void SetAllEntrancesData(std::vector<EntranceInfoPair>& entranceShuffleTable) {
for (auto& entrancePair: entranceShuffleTable) {
auto& forwardEntry = entrancePair.first;
auto& returnEntry = entrancePair.second;
//set data
Entrance* forwardEntrance = AreaTable(forwardEntry.parentRegion)->GetExit(forwardEntry.connectedRegion);
forwardEntrance->SetIndex(forwardEntry.index);
forwardEntrance->SetBlueWarp(forwardEntry.blueWarp);
forwardEntrance->SetType(forwardEntry.type);
forwardEntrance->SetAsPrimary();
// if type == 'Grotto':
// forward_entrance.data['index'] = 0x0700 + forward_entrance.data['grotto_id']
if (returnEntry.parentRegion != NONE) {
Entrance* returnEntrance = AreaTable(returnEntry.parentRegion)->GetExit(returnEntry.connectedRegion);
returnEntrance->SetIndex(returnEntry.index);
returnEntrance->SetBlueWarp(returnEntry.blueWarp);
returnEntrance->SetType(returnEntry.type);
forwardEntrance->BindTwoWay(returnEntrance);
// if type == 'Grotto':
// return_entrance.data['index'] = 0x0800 + return_entrance.data['grotto_id']
}
}
}
static std::vector<Entrance*> AssumeEntrancePool(std::vector<Entrance*>& entrancePool) {
std::vector<Entrance*> assumedPool = {};
for (Entrance* entrance : entrancePool) {
Entrance* assumedForward = entrance->AssumeReachable();
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
Entrance* assumedReturn = entrance->GetReverse()->AssumeReachable();
//mixed pool assumption stuff
assumedForward->BindTwoWay(assumedReturn);
}
assumedPool.push_back(assumedForward);
}
return assumedPool;
}
//returns restrictive entrances and soft entrances in an array of size 2 (restrictive vector is index 0, soft is index 1)
static std::array<std::vector<Entrance*>, 2> SplitEntrancesByRequirements(std::vector<Entrance*>& entrancesToSplit, std::vector<Entrance*>& assumedEntrances) {
//First, disconnect all root assumed entrances and save which regions they were originally connected to, so we can reconnect them later
std::map<Entrance*, uint32_t> originalConnectedRegions = {};
std::set<Entrance*> entrancesToDisconnect = {};
for (Entrance* entrance : assumedEntrances) {
entrancesToDisconnect.insert(entrance);
if (entrance->GetReverse() != nullptr) {
entrancesToDisconnect.insert(entrance->GetReverse());
}
}
//disconnect each entrance temporarily to find restrictive vs soft entrances
//soft entrances are ones that can be accessed by both ages (child/adult) at both times of day (day/night)
//restrictive entrances are ones that do not meet this criteria
for (Entrance* entrance : entrancesToDisconnect) {
if (entrance->GetConnectedRegionKey() != NONE) {
originalConnectedRegions[entrance] = entrance->Disconnect();
}
}
std::vector<Entrance*> restrictiveEntrances = {};
std::vector<Entrance*> softEntrances = {};
Logic::LogicReset();
// //Apply the effects of all advancement items to search for entrance accessibility
std::vector<uint32_t> items = FilterFromPool(ItemPool, [](const auto i){ return ItemTable(i).IsAdvancement();});
for (uint32_t unplacedItem : items) {
ItemTable(unplacedItem).ApplyEffect();
}
// run a search to see what's accessible
GetAccessibleLocations({});
for (Entrance* entrance : entrancesToSplit) {
// if an entrance is accessible at all times of day by both ages, it's a soft entrance with no restrictions
if (entrance->ConditionsMet(true)) {
softEntrances.push_back(entrance);
} else {
restrictiveEntrances.push_back(entrance);
}
}
//Reconnect all disconnected entrances
for (Entrance* entrance : entrancesToDisconnect) {
entrance->Connect(originalConnectedRegions[entrance]);
}
return {restrictiveEntrances, softEntrances};
}
static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
//Entrances shouldn't connect to their own scene, fail in this situation
if (entrance->GetParentRegion()->scene != "" && entrance->GetParentRegion()->scene == target->GetConnectedRegion()->scene) {
auto message = "Entrance " + entrance->GetName() + " attempted to connect with own scene target " + target->to_string() + ". Connection failed.\n";
SPDLOG_INFO(message);
return false;
}
//one-way entrance stuff
return true;
}
//Change connections between an entrance and a target assumed entrance, in order to test the connections afterwards if necessary
static void ChangeConnections(Entrance* entrance, Entrance* targetEntrance) {
auto message = "Attempting to connect " + entrance->GetName() + " to " + targetEntrance->to_string() + "\n";
SPDLOG_INFO(message);
entrance->Connect(targetEntrance->Disconnect());
entrance->SetReplacement(targetEntrance->GetReplacement());
if (entrance->GetReverse() != nullptr /*&& entrances aren't decoupled*/) {
targetEntrance->GetReplacement()->GetReverse()->Connect(entrance->GetReverse()->GetAssumed()->Disconnect());
targetEntrance->GetReplacement()->GetReverse()->SetReplacement(entrance->GetReverse());
}
}
static void RestoreConnections(Entrance* entrance, Entrance* targetEntrance) {
targetEntrance->Connect(entrance->Disconnect());
entrance->SetReplacement(nullptr);
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
entrance->GetReverse()->GetAssumed()->Connect(targetEntrance->GetReplacement()->GetReverse()->Disconnect());
targetEntrance->GetReplacement()->GetReverse()->SetReplacement(nullptr);
}
}
static void DeleteTargetEntrance(Entrance* targetEntrance) {
if (targetEntrance->GetConnectedRegionKey() != NONE) {
targetEntrance->Disconnect();
}
if (targetEntrance->GetParentRegionKey() != NONE) {
targetEntrance->GetParentRegion()->RemoveExit(targetEntrance);
targetEntrance->SetParentRegion(NONE);
}
}
static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) {
DeleteTargetEntrance(targetEntrance);
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
auto replacedReverse = targetEntrance->GetReplacement()->GetReverse();
DeleteTargetEntrance(replacedReverse->GetReverse()->GetAssumed());
}
}
// Returns whether or not we can affirm the entrance can never be accessed as the given age
static bool EntranceUnreachableAs(Entrance* entrance, uint8_t age, std::vector<Entrance*>& alreadyChecked) {
if (entrance == nullptr) {
SPDLOG_INFO("Entrance is nullptr in EntranceUnreachableAs()");
return true;
}
alreadyChecked.push_back(entrance);
auto type = entrance->GetType();
// The following cases determine when we say an entrance is not safe to affirm unreachable as the given age
if (type == EntranceType::WarpSong || type == EntranceType::Overworld) {
// Note that we consider all overworld entrances as potentially accessible as both ages, to be completely safe
return false;
} else if (type == EntranceType::OwlDrop) {
return age == AGE_ADULT;
} else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == KF_LINKS_HOUSE) {
return age == AGE_ADULT;
} else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == TEMPLE_OF_TIME) {
return age == AGE_CHILD;
}
// Other entrances such as Interior, Dungeon or Grotto are fine unless they have a parent which is one of the above
// cases Recursively check parent entrances to verify that they are also not reachable as the wrong age
auto& parentEntrances = entrance->GetParentRegion()->entrances;
for (Entrance* parentEntrance : parentEntrances) {
// if parentEntrance is in alreadyChecked, then continue
if (ElementInContainer(parentEntrance, alreadyChecked)) {
continue;
}
bool unreachable = EntranceUnreachableAs(parentEntrance, age, alreadyChecked);
if (!unreachable) {
return false;
}
}
return true;
}
static bool ValidateWorld(Entrance* entrancePlaced) {
SPDLOG_INFO("Validating world\n");
//check certain conditions when certain types of ER are enabled
EntranceType type = EntranceType::None;
if (entrancePlaced != nullptr) {
type = entrancePlaced->GetType();
}
bool checkPoeCollectorAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ ||
type == EntranceType::Interior || type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
bool checkOtherEntranceAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL) /*|| Settings::ShuffleOverworldSpawns*/) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ ||
type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
// Check to make sure all locations are still reachable
Logic::LogicReset();
GetAccessibleLocations({}, SearchMode::ValidateWorld, "", checkPoeCollectorAccess, checkOtherEntranceAccess);
// if not world.decouple_entrances:
// Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age
// This means we need to hard check that none of the relevant entrances are ever reachable as that age
// This is mostly relevant when mixing entrance pools or shuffling special interiors (such as windmill or kak potion shop)
// Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
std::array<std::string, 2> childForbidden = {"OGC Great Fairy Fountain -> Castle Grounds", "GV Carpenter Tent -> GV Fortress Side"};
std::array<std::string, 2> adultForbidden = {"HC Great Fairy Fountain -> Castle Grounds", "HC Storms Grotto -> Castle Grounds"};
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
for (auto& entrance: allShuffleableEntrances) {
std::vector<Entrance*> alreadyChecked = {};
if (entrance->IsShuffled()) {
if (entrance->GetReplacement() != nullptr) {
auto replacementName = entrance->GetReplacement()->GetName();
alreadyChecked.push_back(entrance->GetReplacement()->GetReverse());
if (ElementInContainer(replacementName, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential child access\n";
SPDLOG_INFO(message);
return false;
} else if (ElementInContainer(replacementName, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential adult access\n";
SPDLOG_INFO(message);
return false;
}
}
} else {
auto name = entrance->GetName();
alreadyChecked.push_back(entrance->GetReverse());
if (ElementInContainer(name, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
auto message = name + " is potentially accessible as child\n";
SPDLOG_INFO(message);
return false;
} else if (ElementInContainer(name, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
auto message = name + " is potentially accessible as adult\n";
SPDLOG_INFO(message);
return false;
}
}
}
if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF) && Settings::GossipStoneHints.IsNot(HINTS_NO_HINTS) &&
(entrancePlaced == nullptr || type == EntranceType::Interior || type == EntranceType::SpecialInterior)) {
//When cows are shuffled, ensure both Impa's House entrances are in the same hint area because the cow is reachable from both sides
if (Settings::ShuffleCows) {
auto impasHouseFrontHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE);
auto impasHouseBackHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE_BACK);
if (impasHouseFrontHintRegion != NONE && impasHouseBackHintRegion != NONE && impasHouseBackHintRegion != LINKS_POCKET && impasHouseFrontHintRegion != LINKS_POCKET && impasHouseBackHintRegion != impasHouseFrontHintRegion) {
auto message = "Kak Impas House entrances are not in the same hint area\n";
SPDLOG_INFO(message);
return false;
}
}
}
// If all locations aren't reachable, that means that one of the conditions failed when searching
if (!allLocationsReachable) {
if (checkOtherEntranceAccess) {
// At least one valid starting region with all basic refills should be reachable without using any items at the beginning of the seed
if (!AreaTable(KOKIRI_FOREST)->HasAccess() && !AreaTable(KAKARIKO_VILLAGE)->HasAccess()) {
SPDLOG_INFO("Invalid starting area\n");
return false;
}
// Check that a region where time passes is always reachable as both ages without having collected any items
if (!Areas::HasTimePassAccess(AGE_CHILD) || !Areas::HasTimePassAccess(AGE_ADULT)) {
SPDLOG_INFO("Time passing is not guaranteed as both ages\n");
return false;
}
// The player should be able to get back to ToT after going through time, without having collected any items
// This is important to ensure that the player never loses access to the pedestal after going through time
if (Settings::ResolvedStartingAge == AGE_CHILD && !AreaTable(TEMPLE_OF_TIME)->Adult()) {
SPDLOG_INFO("Path to Temple of Time as adult is not guaranteed\n");
return false;
} else if (Settings::ResolvedStartingAge == AGE_ADULT && !AreaTable(TEMPLE_OF_TIME)->Child()) {
SPDLOG_INFO("Path to Temple of Time as child is not guaranteed\n");
return false;
}
}
// The Big Poe shop should always be accessible as adult without the need to use any bottles
// This is important to ensure that players can never lock their only bottles by filling them with Big Poes they can't sell
if (checkPoeCollectorAccess) {
if (!AreaTable(MARKET_GUARD_HOUSE)->Adult()) {
SPDLOG_INFO("Big Poe Shop access is not guarenteed as adult\n");
return false;
}
}
SPDLOG_INFO("All Locations NOT REACHABLE\n");
return false;
}
return true;
}
static bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
if (!AreEntrancesCompatible(entrance, target, rollbacks)) {
return false;
}
ChangeConnections(entrance, target);
if (ValidateWorld(entrance)) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
//PlacementLog_Msg(message);
//Areas::DumpWorldGraph(ticks);
#endif
rollbacks.push_back(EntrancePair{entrance, target});
curNumRandomizedEntrances++;
DisplayEntranceProgress();
return true;
} else {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
//PlacementLog_Msg(message);
//Areas::DumpWorldGraph(ticks);
#endif
if (entrance->GetConnectedRegionKey() != NONE) {
RestoreConnections(entrance, target);
}
}
DisplayEntranceProgress();
return false;
}
// Shuffle entrances by placing them instead of entrances in the provided target entrances list
static bool ShuffleEntrances(std::vector<Entrance*>& entrances, std::vector<Entrance*>& targetEntrances, std::vector<EntrancePair>& rollbacks) {
Shuffle(entrances);
//place all entrances in the pool, validating after every placement
for (Entrance* entrance : entrances) {
if (entrance->GetConnectedRegionKey() != NONE) {
continue;
}
Shuffle(targetEntrances);
for (Entrance* target : targetEntrances) {
if (target->GetConnectedRegionKey() == NONE) {
continue;
}
if (ReplaceEntrance(entrance, target, rollbacks)) {
break;
}
}
if (entrance->GetConnectedRegionKey() == NONE) {
return false;
}
}
//all entrances were validly connected
return true;
}
static void ShuffleEntrancePool(std::vector<Entrance*>& entrancePool, std::vector<Entrance*>& targetEntrances) {
noRandomEntrances = false;
auto splitEntrances = SplitEntrancesByRequirements(entrancePool, targetEntrances);
auto& restrictiveEntrances = splitEntrances[0];
auto& softEntrances = splitEntrances[1];
int retries = 20;
while (retries > 0) {
if (retries != 20) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Failed to connect entrances. Retrying " + std::to_string(retries) + " more times.\nDumping World Graph at " + ticks + "\n";
PlacementLog_Msg(message);
Areas::DumpWorldGraph(ticks);
#endif
}
retries--;
std::vector<EntrancePair> rollbacks = {};
//Shuffle Restrictive Entrances first while more regions are available in
//order to heavily reduce the chances of the placement failing
bool success = ShuffleEntrances(restrictiveEntrances, targetEntrances, rollbacks);
if (success) {
success = ShuffleEntrances(softEntrances, targetEntrances, rollbacks);
if(!success) {
for (auto& pair : rollbacks) {
RestoreConnections(pair.first, pair.second);
curNumRandomizedEntrances--;
}
continue;
}
} else {
for (auto& pair : rollbacks) {
RestoreConnections(pair.first, pair.second);
curNumRandomizedEntrances--;
}
continue;
}
//If there are no issues, log the connections and continue
for (auto& pair : rollbacks) {
ConfirmReplacement(pair.first, pair.second);
}
break;
}
if (retries <= 0) {
SPDLOG_INFO("Entrance placement attempt count exceeded. Restarting randomization completely");
entranceShuffleFailure = true;
}
}
//Process for setting up the shuffling of all entrances to be shuffled
int ShuffleAllEntrances() {
totalRandomizableEntrances = 0;
curNumRandomizedEntrances = 0;
std::vector<EntranceInfoPair> entranceShuffleTable = {
//Parent Region Connected Region index blue warp
{{EntranceType::Dungeon, KF_OUTSIDE_DEKU_TREE, DEKU_TREE_ENTRYWAY, 0x0000},
{EntranceType::Dungeon, DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE, 0x0209, 0x0457}},
{{EntranceType::Dungeon, DEATH_MOUNTAIN_TRAIL, DODONGOS_CAVERN_ENTRYWAY, 0x0004},
{EntranceType::Dungeon, DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL, 0x0242, 0x047A}},
{{EntranceType::Dungeon, ZORAS_FOUNTAIN, JABU_JABUS_BELLY_ENTRYWAY, 0x0028},
{EntranceType::Dungeon, JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN, 0x0221, 0x010E}},
{{EntranceType::Dungeon, SACRED_FOREST_MEADOW, FOREST_TEMPLE_ENTRYWAY, 0x0169},
{EntranceType::Dungeon, FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW, 0x0215, 0x0608}},
{{EntranceType::Dungeon, DMC_CENTRAL_LOCAL, FIRE_TEMPLE_ENTRYWAY, 0x0165},
{EntranceType::Dungeon, FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL, 0x024A, 0x0564}},
{{EntranceType::Dungeon, LAKE_HYLIA, WATER_TEMPLE_ENTRYWAY, 0x0010},
{EntranceType::Dungeon, WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA, 0x021D, 0x060C}},
{{EntranceType::Dungeon, DESERT_COLOSSUS, SPIRIT_TEMPLE_ENTRYWAY, 0x0082},
{EntranceType::Dungeon, SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS, 0x01E1, 0x0610}},
{{EntranceType::Dungeon, GRAVEYARD_WARP_PAD_REGION, SHADOW_TEMPLE_ENTRYWAY, 0x0037},
{EntranceType::Dungeon, SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION, 0x0205, 0x0580}},
{{EntranceType::Dungeon, KAKARIKO_VILLAGE, BOTTOM_OF_THE_WELL_ENTRYWAY, 0x0098},
{EntranceType::Dungeon, BOTTOM_OF_THE_WELL_ENTRYWAY, KAKARIKO_VILLAGE, 0x02A6}},
{{EntranceType::Dungeon, ZORAS_FOUNTAIN, ICE_CAVERN_ENTRYWAY, 0x0088},
{EntranceType::Dungeon, ICE_CAVERN_ENTRYWAY, ZORAS_FOUNTAIN, 0x03D4}},
{{EntranceType::Dungeon, GERUDO_FORTRESS, GERUDO_TRAINING_GROUNDS_ENTRYWAY, 0x0008},
{EntranceType::Dungeon, GERUDO_TRAINING_GROUNDS_ENTRYWAY, GERUDO_FORTRESS, 0x03A8}},
{{EntranceType::GanonDungeon, GANONS_CASTLE_GROUNDS, GANONS_CASTLE_ENTRYWAY, 0x0467},
{EntranceType::GanonDungeon, GANONS_CASTLE_ENTRYWAY, GANONS_CASTLE_GROUNDS, 0x023D}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_MIDOS_HOUSE, 0x0433},
{EntranceType::Interior, KF_MIDOS_HOUSE, KOKIRI_FOREST, 0x0443}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_SARIAS_HOUSE, 0x0437},
{EntranceType::Interior, KF_SARIAS_HOUSE, KOKIRI_FOREST, 0x0447}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_HOUSE_OF_TWINS, 0x009C},
{EntranceType::Interior, KF_HOUSE_OF_TWINS, KOKIRI_FOREST, 0x033C}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_KNOW_IT_ALL_HOUSE, 0x00C9},
{EntranceType::Interior, KF_KNOW_IT_ALL_HOUSE, KOKIRI_FOREST, 0x026A}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_KOKIRI_SHOP, 0x00C1},
{EntranceType::Interior, KF_KOKIRI_SHOP, KOKIRI_FOREST, 0x0266}},
{{EntranceType::Interior, LAKE_HYLIA, LH_LAB, 0x0043},
{EntranceType::Interior, LH_LAB, LAKE_HYLIA, 0x03CC}},
{{EntranceType::Interior, LH_FISHING_ISLAND, LH_FISHING_HOLE, 0x045F},
{EntranceType::Interior, LH_FISHING_HOLE, LH_FISHING_ISLAND, 0x0309}},
{{EntranceType::Interior, GV_FORTRESS_SIDE, GV_CARPENTER_TENT, 0x03A0},
{EntranceType::Interior, GV_CARPENTER_TENT, GV_FORTRESS_SIDE, 0x03D0}},
{{EntranceType::Interior, MARKET_ENTRANCE, MARKET_GUARD_HOUSE, 0x007E},
{EntranceType::Interior, MARKET_GUARD_HOUSE, MARKET_ENTRANCE, 0x026E}},
{{EntranceType::Interior, THE_MARKET, MARKET_MASK_SHOP, 0x0530},
{EntranceType::Interior, MARKET_MASK_SHOP, THE_MARKET, 0x01D1}},
{{EntranceType::Interior, THE_MARKET, MARKET_BOMBCHU_BOWLING, 0x0507},
{EntranceType::Interior, MARKET_BOMBCHU_BOWLING, THE_MARKET, 0x03BC}},
{{EntranceType::Interior, THE_MARKET, MARKET_POTION_SHOP, 0x0388},
{EntranceType::Interior, MARKET_POTION_SHOP, THE_MARKET, 0x02A2}},
{{EntranceType::Interior, THE_MARKET, MARKET_TREASURE_CHEST_GAME, 0x0063},
{EntranceType::Interior, MARKET_TREASURE_CHEST_GAME, THE_MARKET, 0x01D5}},
{{EntranceType::Interior, MARKET_BACK_ALLEY, MARKET_BOMBCHU_SHOP, 0x0528},
{EntranceType::Interior, MARKET_BOMBCHU_SHOP, MARKET_BACK_ALLEY, 0x03C0}},
{{EntranceType::Interior, MARKET_BACK_ALLEY, MARKET_MAN_IN_GREEN_HOUSE, 0x043B},
{EntranceType::Interior, MARKET_MAN_IN_GREEN_HOUSE, MARKET_BACK_ALLEY, 0x0067}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_CARPENTER_BOSS_HOUSE, 0x02FD},
{EntranceType::Interior, KAK_CARPENTER_BOSS_HOUSE, KAKARIKO_VILLAGE, 0x0349}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_HOUSE_OF_SKULLTULA, 0x0550},
{EntranceType::Interior, KAK_HOUSE_OF_SKULLTULA, KAKARIKO_VILLAGE, 0x04EE}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_IMPAS_HOUSE, 0x039C},
{EntranceType::Interior, KAK_IMPAS_HOUSE, KAKARIKO_VILLAGE, 0x0345}},
{{EntranceType::Interior, KAK_IMPAS_LEDGE, KAK_IMPAS_HOUSE_BACK, 0x05C8},
{EntranceType::Interior, KAK_IMPAS_HOUSE_BACK, KAK_IMPAS_LEDGE, 0x05DC}},
{{EntranceType::Interior, KAK_BACKYARD, KAK_ODD_POULTICE_BUILDING, 0x0072},
{EntranceType::Interior, KAK_ODD_POULTICE_BUILDING, KAK_BACKYARD, 0x034D}},
{{EntranceType::Interior, THE_GRAVEYARD, GRAVEYARD_DAMPES_HOUSE, 0x030D},
{EntranceType::Interior, GRAVEYARD_DAMPES_HOUSE, THE_GRAVEYARD, 0x0355}},
{{EntranceType::Interior, GORON_CITY, GC_SHOP, 0x037C},
{EntranceType::Interior, GC_SHOP, GORON_CITY, 0x03FC}},
{{EntranceType::Interior, ZORAS_DOMAIN, ZD_SHOP, 0x0380},
{EntranceType::Interior, ZD_SHOP, ZORAS_DOMAIN, 0x03C4}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_TALONS_HOUSE, 0x004F},
{EntranceType::Interior, LLR_TALONS_HOUSE, LON_LON_RANCH, 0x0378}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_STABLES, 0x02F9},
{EntranceType::Interior, LLR_STABLES, LON_LON_RANCH, 0x042F}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_TOWER, 0x05D0},
{EntranceType::Interior, LLR_TOWER, LON_LON_RANCH, 0x05D4}},
{{EntranceType::Interior, THE_MARKET, MARKET_BAZAAR, 0x052C},
{EntranceType::Interior, MARKET_BAZAAR, THE_MARKET, 0x03B8}},
{{EntranceType::Interior, THE_MARKET, MARKET_SHOOTING_GALLERY, 0x016D},
{EntranceType::Interior, MARKET_SHOOTING_GALLERY, THE_MARKET, 0x01CD}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_BAZAAR, 0x00B7},
{EntranceType::Interior, KAK_BAZAAR, KAKARIKO_VILLAGE, 0x0201}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_SHOOTING_GALLERY, 0x003B},
{EntranceType::Interior, KAK_SHOOTING_GALLERY, KAKARIKO_VILLAGE, 0x0463}},
{{EntranceType::Interior, DESERT_COLOSSUS, COLOSSUS_GREAT_FAIRY_FOUNTAIN, 0x0588},
{EntranceType::Interior, COLOSSUS_GREAT_FAIRY_FOUNTAIN, DESERT_COLOSSUS, 0x057C}},
{{EntranceType::Interior, HYRULE_CASTLE_GROUNDS, HC_GREAT_FAIRY_FOUNTAIN, 0x0578},
{EntranceType::Interior, HC_GREAT_FAIRY_FOUNTAIN, CASTLE_GROUNDS, 0x0340}},
{{EntranceType::Interior, GANONS_CASTLE_GROUNDS, OGC_GREAT_FAIRY_FOUNTAIN, 0x04C2},
{EntranceType::Interior, OGC_GREAT_FAIRY_FOUNTAIN, CASTLE_GROUNDS, 0x03E8}}, //0x3E8 is an unused entrance index repruposed to differentiate between the HC and OGC fairy fountain exits (normally they both use 0x340)
{{EntranceType::Interior, DMC_LOWER_NEARBY, DMC_GREAT_FAIRY_FOUNTAIN, 0x04BE},
{EntranceType::Interior, DMC_GREAT_FAIRY_FOUNTAIN, DMC_LOWER_LOCAL, 0x0482}},
{{EntranceType::Interior, DEATH_MOUNTAIN_SUMMIT, DMT_GREAT_FAIRY_FOUNTAIN, 0x0315},
{EntranceType::Interior, DMT_GREAT_FAIRY_FOUNTAIN, DEATH_MOUNTAIN_SUMMIT, 0x045B}},
{{EntranceType::Interior, ZORAS_FOUNTAIN, ZF_GREAT_FAIRY_FOUNTAIN, 0x0371},
{EntranceType::Interior, ZF_GREAT_FAIRY_FOUNTAIN, ZORAS_FOUNTAIN, 0x0394}},
{{EntranceType::SpecialInterior, KOKIRI_FOREST, KF_LINKS_HOUSE, 0x0272},
{EntranceType::SpecialInterior, KF_LINKS_HOUSE, KOKIRI_FOREST, 0x0211}},
{{EntranceType::SpecialInterior, TOT_ENTRANCE, TEMPLE_OF_TIME, 0x0053},
{EntranceType::SpecialInterior, TEMPLE_OF_TIME, TOT_ENTRANCE, 0x0472}},
{{EntranceType::SpecialInterior, KAKARIKO_VILLAGE, KAK_WINDMILL, 0x0453},
{EntranceType::SpecialInterior, KAK_WINDMILL, KAKARIKO_VILLAGE, 0x0351}},
{{EntranceType::SpecialInterior, KAKARIKO_VILLAGE, KAK_POTION_SHOP_FRONT, 0x0384},
{EntranceType::SpecialInterior, KAK_POTION_SHOP_FRONT, KAKARIKO_VILLAGE, 0x044B}},
{{EntranceType::SpecialInterior, KAK_BACKYARD, KAK_POTION_SHOP_BACK, 0x03EC},
{EntranceType::SpecialInterior, KAK_POTION_SHOP_BACK, KAK_BACKYARD, 0x04FF}},
// Grotto Loads use an entrance index of 0x0700 + their grotto id. The id is used as index for the
// grottoLoadTable in src/grotto.c
// Grotto Returns use an entrance index of 0x0800 + their grotto id. The id is used as index for the
// grottoReturnTable in src/grotto.c
{{EntranceType::GrottoGrave, DESERT_COLOSSUS, COLOSSUS_GROTTO, 0x0700},
{EntranceType::GrottoGrave, COLOSSUS_GROTTO, DESERT_COLOSSUS, 0x0800}},
{{EntranceType::GrottoGrave, LAKE_HYLIA, LH_GROTTO, 0x0701},
{EntranceType::GrottoGrave, LH_GROTTO, LAKE_HYLIA, 0x0801}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_STORMS_GROTTO, 0x0702},
{EntranceType::GrottoGrave, ZR_STORMS_GROTTO, ZORAS_RIVER, 0x0802}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_FAIRY_GROTTO, 0x0703},
{EntranceType::GrottoGrave, ZR_FAIRY_GROTTO, ZORAS_RIVER, 0x0803}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_OPEN_GROTTO, 0x0704},
{EntranceType::GrottoGrave, ZR_OPEN_GROTTO, ZORAS_RIVER, 0x0804}},
{{EntranceType::GrottoGrave, DMC_LOWER_NEARBY, DMC_HAMMER_GROTTO, 0x0705},
{EntranceType::GrottoGrave, DMC_HAMMER_GROTTO, DMC_LOWER_LOCAL, 0x0805}},
{{EntranceType::GrottoGrave, DMC_UPPER_NEARBY, DMC_UPPER_GROTTO, 0x0706},
{EntranceType::GrottoGrave, DMC_UPPER_GROTTO, DMC_UPPER_LOCAL, 0x0806}},
{{EntranceType::GrottoGrave, GC_GROTTO_PLATFORM, GC_GROTTO, 0x0707},
{EntranceType::GrottoGrave, GC_GROTTO, GC_GROTTO_PLATFORM, 0x0807}},
{{EntranceType::GrottoGrave, DEATH_MOUNTAIN_TRAIL, DMT_STORMS_GROTTO, 0x0708},
{EntranceType::GrottoGrave, DMT_STORMS_GROTTO, DEATH_MOUNTAIN_TRAIL, 0x0808}},
{{EntranceType::GrottoGrave, DEATH_MOUNTAIN_SUMMIT, DMT_COW_GROTTO, 0x0709},
{EntranceType::GrottoGrave, DMT_COW_GROTTO, DEATH_MOUNTAIN_SUMMIT, 0x0809}},
{{EntranceType::GrottoGrave, KAK_BACKYARD, KAK_OPEN_GROTTO, 0x070A},
{EntranceType::GrottoGrave, KAK_OPEN_GROTTO, KAK_BACKYARD, 0x080A}},
{{EntranceType::GrottoGrave, KAKARIKO_VILLAGE, KAK_REDEAD_GROTTO, 0x070B},
{EntranceType::GrottoGrave, KAK_REDEAD_GROTTO, KAKARIKO_VILLAGE, 0x080B}},
{{EntranceType::GrottoGrave, HYRULE_CASTLE_GROUNDS, HC_STORMS_GROTTO, 0x070C},
{EntranceType::GrottoGrave, HC_STORMS_GROTTO, CASTLE_GROUNDS, 0x080C}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_TEKTITE_GROTTO, 0x070D},
{EntranceType::GrottoGrave, HF_TEKTITE_GROTTO, HYRULE_FIELD, 0x080D}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_NEAR_KAK_GROTTO, 0x070E},
{EntranceType::GrottoGrave, HF_NEAR_KAK_GROTTO, HYRULE_FIELD, 0x080E}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_FAIRY_GROTTO, 0x070F},
{EntranceType::GrottoGrave, HF_FAIRY_GROTTO, HYRULE_FIELD, 0x080F}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_NEAR_MARKET_GROTTO, 0x0710},
{EntranceType::GrottoGrave, HF_NEAR_MARKET_GROTTO, HYRULE_FIELD, 0x0810}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_COW_GROTTO, 0x0711},
{EntranceType::GrottoGrave, HF_COW_GROTTO, HYRULE_FIELD, 0x0811}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_INSIDE_FENCE_GROTTO, 0x0712},
{EntranceType::GrottoGrave, HF_INSIDE_FENCE_GROTTO, HYRULE_FIELD, 0x0812}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_OPEN_GROTTO, 0x0713},
{EntranceType::GrottoGrave, HF_OPEN_GROTTO, HYRULE_FIELD, 0x0813}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_SOUTHEAST_GROTTO, 0x0714},
{EntranceType::GrottoGrave, HF_SOUTHEAST_GROTTO, HYRULE_FIELD, 0x0814}},
{{EntranceType::GrottoGrave, LON_LON_RANCH, LLR_GROTTO, 0x0715},
{EntranceType::GrottoGrave, LLR_GROTTO, LON_LON_RANCH, 0x0815}},
{{EntranceType::GrottoGrave, SFM_ENTRYWAY, SFM_WOLFOS_GROTTO, 0x0716},
{EntranceType::GrottoGrave, SFM_WOLFOS_GROTTO, SFM_ENTRYWAY, 0x0816}},
{{EntranceType::GrottoGrave, SACRED_FOREST_MEADOW, SFM_STORMS_GROTTO, 0x0717},
{EntranceType::GrottoGrave, SFM_STORMS_GROTTO, SACRED_FOREST_MEADOW, 0x0817}},
{{EntranceType::GrottoGrave, SACRED_FOREST_MEADOW, SFM_FAIRY_GROTTO, 0x0718},
{EntranceType::GrottoGrave, SFM_FAIRY_GROTTO, SACRED_FOREST_MEADOW, 0x0818}},
{{EntranceType::GrottoGrave, LW_BEYOND_MIDO, LW_SCRUBS_GROTTO, 0x0719},
{EntranceType::GrottoGrave, LW_SCRUBS_GROTTO, LW_BEYOND_MIDO, 0x0819}},
{{EntranceType::GrottoGrave, THE_LOST_WOODS, LW_NEAR_SHORTCUTS_GROTTO, 0x071A},
{EntranceType::GrottoGrave, LW_NEAR_SHORTCUTS_GROTTO, THE_LOST_WOODS, 0x081A}},
{{EntranceType::GrottoGrave, KOKIRI_FOREST, KF_STORMS_GROTTO, 0x071B},
{EntranceType::GrottoGrave, KF_STORMS_GROTTO, KOKIRI_FOREST, 0x081B}},
{{EntranceType::GrottoGrave, ZORAS_DOMAIN, ZD_STORMS_GROTTO, 0x071C},
{EntranceType::GrottoGrave, ZD_STORMS_GROTTO, ZORAS_DOMAIN, 0x081C}},
{{EntranceType::GrottoGrave, GERUDO_FORTRESS, GF_STORMS_GROTTO, 0x071D},
{EntranceType::GrottoGrave, GF_STORMS_GROTTO, GERUDO_FORTRESS, 0x081D}},
{{EntranceType::GrottoGrave, GV_FORTRESS_SIDE, GV_STORMS_GROTTO, 0x071E},
{EntranceType::GrottoGrave, GV_STORMS_GROTTO, GV_FORTRESS_SIDE, 0x081E}},
{{EntranceType::GrottoGrave, GV_GROTTO_LEDGE, GV_OCTOROK_GROTTO, 0x071F},
{EntranceType::GrottoGrave, GV_OCTOROK_GROTTO, GV_GROTTO_LEDGE, 0x081F}},
{{EntranceType::GrottoGrave, LW_BEYOND_MIDO, DEKU_THEATER, 0x0720},
{EntranceType::GrottoGrave, DEKU_THEATER, LW_BEYOND_MIDO, 0x0820}},
// Graves have their own specified entrance indices
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_SHIELD_GRAVE, 0x004B},
{EntranceType::GrottoGrave, GRAVEYARD_SHIELD_GRAVE, THE_GRAVEYARD, 0x035D}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_HEART_PIECE_GRAVE, 0x031C},
{EntranceType::GrottoGrave, GRAVEYARD_HEART_PIECE_GRAVE, THE_GRAVEYARD, 0x0361}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_COMPOSERS_GRAVE, 0x002D},
{EntranceType::GrottoGrave, GRAVEYARD_COMPOSERS_GRAVE, THE_GRAVEYARD, 0x050B}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_DAMPES_GRAVE, 0x044F},
{EntranceType::GrottoGrave, GRAVEYARD_DAMPES_GRAVE, THE_GRAVEYARD, 0x0359}},
{{EntranceType::Overworld, KOKIRI_FOREST, LW_BRIDGE_FROM_FOREST, 0x05E0},
{EntranceType::Overworld, LW_BRIDGE, KOKIRI_FOREST, 0x020D}},
{{EntranceType::Overworld, KOKIRI_FOREST, THE_LOST_WOODS, 0x011E},
{EntranceType::Overworld, LW_FOREST_EXIT, KOKIRI_FOREST, 0x0286}},
{{EntranceType::Overworld, THE_LOST_WOODS, GC_WOODS_WARP, 0x04E2},
{EntranceType::Overworld, GC_WOODS_WARP, THE_LOST_WOODS, 0x04D6}},
{{EntranceType::Overworld, THE_LOST_WOODS, ZORAS_RIVER, 0x01DD},
{EntranceType::Overworld, ZORAS_RIVER, THE_LOST_WOODS, 0x04DA}},
{{EntranceType::Overworld, LW_BEYOND_MIDO, SFM_ENTRYWAY, 0x00FC},
{EntranceType::Overworld, SFM_ENTRYWAY, LW_BEYOND_MIDO, 0x01A9}},
{{EntranceType::Overworld, LW_BRIDGE, HYRULE_FIELD, 0x0185},
{EntranceType::Overworld, HYRULE_FIELD, LW_BRIDGE, 0x04DE}},
{{EntranceType::Overworld, HYRULE_FIELD, LAKE_HYLIA, 0x0102},
{EntranceType::Overworld, LAKE_HYLIA, HYRULE_FIELD, 0x0189}},
{{EntranceType::Overworld, HYRULE_FIELD, GERUDO_VALLEY, 0x0117},
{EntranceType::Overworld, GERUDO_VALLEY, HYRULE_FIELD, 0x018D}},
{{EntranceType::Overworld, HYRULE_FIELD, MARKET_ENTRANCE, 0x0276},
{EntranceType::Overworld, MARKET_ENTRANCE, HYRULE_FIELD, 0x01FD}},
{{EntranceType::Overworld, HYRULE_FIELD, KAKARIKO_VILLAGE, 0x00DB},
{EntranceType::Overworld, KAKARIKO_VILLAGE, HYRULE_FIELD, 0x017D}},
{{EntranceType::Overworld, HYRULE_FIELD, ZR_FRONT, 0x00EA},
{EntranceType::Overworld, ZR_FRONT, HYRULE_FIELD, 0x0181}},
{{EntranceType::Overworld, HYRULE_FIELD, LON_LON_RANCH, 0x0157},
{EntranceType::Overworld, LON_LON_RANCH, HYRULE_FIELD, 0x01F9}},
{{EntranceType::Overworld, LAKE_HYLIA, ZORAS_DOMAIN, 0x0328},
{EntranceType::Overworld, ZORAS_DOMAIN, LAKE_HYLIA, 0x0560}},
{{EntranceType::Overworld, GV_FORTRESS_SIDE, GERUDO_FORTRESS, 0x0129},
{EntranceType::Overworld, GERUDO_FORTRESS, GV_FORTRESS_SIDE, 0x022D}},
{{EntranceType::Overworld, GF_OUTSIDE_GATE, WASTELAND_NEAR_FORTRESS, 0x0130},
{EntranceType::Overworld, WASTELAND_NEAR_FORTRESS, GF_OUTSIDE_GATE, 0x03AC}},
{{EntranceType::Overworld, WASTELAND_NEAR_COLOSSUS, DESERT_COLOSSUS, 0x0123},
{EntranceType::Overworld, DESERT_COLOSSUS, WASTELAND_NEAR_COLOSSUS, 0x0365}},
{{EntranceType::Overworld, MARKET_ENTRANCE, THE_MARKET, 0x00B1},
{EntranceType::Overworld, THE_MARKET, MARKET_ENTRANCE, 0x0033}},
{{EntranceType::Overworld, THE_MARKET, CASTLE_GROUNDS, 0x0138},
{EntranceType::Overworld, CASTLE_GROUNDS, THE_MARKET, 0x025A}},
{{EntranceType::Overworld, THE_MARKET, TOT_ENTRANCE, 0x0171},
{EntranceType::Overworld, TOT_ENTRANCE, THE_MARKET, 0x025E}},
{{EntranceType::Overworld, KAKARIKO_VILLAGE, THE_GRAVEYARD, 0x00E4},
{EntranceType::Overworld, THE_GRAVEYARD, KAKARIKO_VILLAGE, 0x0195}},
{{EntranceType::Overworld, KAK_BEHIND_GATE, DEATH_MOUNTAIN_TRAIL, 0x013D},
{EntranceType::Overworld, DEATH_MOUNTAIN_TRAIL, KAK_BEHIND_GATE, 0x0191}},
{{EntranceType::Overworld, DEATH_MOUNTAIN_TRAIL, GORON_CITY, 0x014D},
{EntranceType::Overworld, GORON_CITY, DEATH_MOUNTAIN_TRAIL, 0x01B9}},
{{EntranceType::Overworld, GC_DARUNIAS_CHAMBER, DMC_LOWER_LOCAL, 0x0246},
{EntranceType::Overworld, DMC_LOWER_NEARBY, GC_DARUNIAS_CHAMBER, 0x01C1}},
{{EntranceType::Overworld, DEATH_MOUNTAIN_SUMMIT, DMC_UPPER_LOCAL, 0x0147},
{EntranceType::Overworld, DMC_UPPER_NEARBY, DEATH_MOUNTAIN_SUMMIT, 0x01BD}},
{{EntranceType::Overworld, ZR_BEHIND_WATERFALL, ZORAS_DOMAIN, 0x0108},
{EntranceType::Overworld, ZORAS_DOMAIN, ZR_BEHIND_WATERFALL, 0x019D}},
{{EntranceType::Overworld, ZD_BEHIND_KING_ZORA, ZORAS_FOUNTAIN, 0x0225},
{EntranceType::Overworld, ZORAS_FOUNTAIN, ZD_BEHIND_KING_ZORA, 0x01A1}},
};
entranceShuffleFailure = false;
SetAllEntrancesData(entranceShuffleTable);
// one_way_entrance_pools = OrderedDict()
std::map<EntranceType, std::vector<Entrance*>> entrancePools = {};
// one_way_priorities = {}
//owl drops
//spawns
//warpsongs
//Shuffle Dungeon Entrances
if (Settings::ShuffleDungeonEntrances.IsNot(SHUFFLEDUNGEONS_OFF)) {
entrancePools[EntranceType::Dungeon] = GetShuffleableEntrances(EntranceType::Dungeon);
//Add Ganon's Castle, if set to On + Ganon
if (Settings::ShuffleDungeonEntrances.Is(SHUFFLEDUNGEONS_GANON)) {
AddElementsToPool(entrancePools[EntranceType::Dungeon], GetShuffleableEntrances(EntranceType::GanonDungeon));
}
//If forest is closed don't allow a forest escape via spirit temple hands
if (Settings::OpenForest.Is(OPENFOREST_CLOSED) && !(Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances)) {
FilterAndEraseFromPool(entrancePools[EntranceType::Dungeon], [](const Entrance* entrance){return entrance->GetParentRegionKey() == KF_OUTSIDE_DEKU_TREE &&
entrance->GetConnectedRegionKey() == DEKU_TREE_ENTRYWAY;});
}
//decoupled entrances stuff
}
//interior entrances
if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF)) {
entrancePools[EntranceType::Interior] = GetShuffleableEntrances(EntranceType::Interior);
//special interiors
if (Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) {
AddElementsToPool(entrancePools[EntranceType::Interior], GetShuffleableEntrances(EntranceType::SpecialInterior));
}
//decoupled entrance stuff
}
//grotto entrances
if (Settings::ShuffleGrottoEntrances) {
entrancePools[EntranceType::GrottoGrave] = GetShuffleableEntrances(EntranceType::GrottoGrave);
//decoupled entrance stuff
}
//overworld entrances
if (Settings::ShuffleOverworldEntrances) {
bool excludeOverworldReverse = false; //mix_entrance_pools == all && !decouple_entrances
entrancePools[EntranceType::Overworld] = GetShuffleableEntrances(EntranceType::Overworld, excludeOverworldReverse);
// if not worlds[0].decouple_entrances:
// entrance_pools['Overworld'].remove(world.get_entrance('GV Lower Stream -> Lake Hylia'))
if (!excludeOverworldReverse) {
totalRandomizableEntrances -= 26; //Only count each overworld entrance once
}
}
//Set shuffled entrances as such
for (auto& pool : entrancePools) {
for (Entrance* entrance : pool.second) {
entrance->SetAsShuffled();
totalRandomizableEntrances++;
if (entrance->GetReverse() != nullptr) {
entrance->GetReverse()->SetAsShuffled();
}
}
}
//combine entrance pools if mixing pools
//Build target entrance pools and set the assumption for entrances being reachable
//one way entrance stuff
//assume entrance pools for each type
std::map<EntranceType, std::vector<Entrance*>> targetEntrancePools = {};
for (auto& pool : entrancePools) {
targetEntrancePools[pool.first] = AssumeEntrancePool(pool.second);
}
//distribution stuff
//check placed on-way entrances
//remove replaced entrances so we don't place two in one target
//remvoe priority targets if any placed entrances point at their regions
//place priority entrances
//delete all targets that we just placed from one way target pools so multiple one way entrances don't use the same target
//shuffle all entrances among pools to shuffle
for (auto& pool : entrancePools) {
ShuffleEntrancePool(pool.second, targetEntrancePools[pool.first]);
if (entranceShuffleFailure) {
return ENTRANCE_SHUFFLE_FAILURE;
}
}
return ENTRANCE_SHUFFLE_SUCCESS;
}
//Create the set of entrances that will be overridden
void CreateEntranceOverrides() {
entranceOverrides.clear();
if (noRandomEntrances) {
return;
}
SPDLOG_INFO("\nCREATING ENTRANCE OVERRIDES\n");
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
for (Entrance* entrance : allShuffleableEntrances) {
//Double-check to make sure the entrance is actually shuffled
if (!entrance->IsShuffled()) {
continue;
}
auto message = "Setting " + entrance->to_string() + "\n";
SPDLOG_INFO(message);
int16_t originalIndex = entrance->GetIndex();
int16_t destinationIndex = entrance->GetReverse()->GetIndex();
int16_t originalBlueWarp = entrance->GetBlueWarp();
int16_t replacementIndex = entrance->GetReplacement()->GetIndex();
int16_t replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex();
entranceOverrides.push_back({
.index = originalIndex,
.destination = destinationIndex,
.blueWarp = originalBlueWarp,
.override = replacementIndex,
.overrideDestination = replacementDestinationIndex,
});
message = "\tOriginal: " + std::to_string(originalIndex) + "\n";
SPDLOG_INFO(message);
message = "\tReplacement " + std::to_string(replacementIndex) + "\n";
SPDLOG_INFO(message);
}
}
std::vector<std::list<Entrance*>> playthroughEntrances;

View file

@ -0,0 +1,320 @@
#pragma once
#include "keys.hpp"
#include "location_access.hpp"
#include "debug.hpp"
#include <string>
#include <list>
#define ENTRANCE_SHUFFLE_SUCCESS 0
#define ENTRANCE_SHUFFLE_FAILURE 1
typedef struct {
int16_t index;
int16_t destination;
int16_t blueWarp;
int16_t override;
int16_t overrideDestination;
} EntranceOverride;
typedef enum {
ENTRANCE_GROUP_NO_GROUP,
ENTRANCE_GROUP_KOKIRI_FOREST,
ENTRANCE_GROUP_LOST_WOODS,
ENTRANCE_GROUP_KAKARIKO,
ENTRANCE_GROUP_GRAVEYARD,
ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL,
ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER,
ENTRANCE_GROUP_GORON_CITY,
ENTRANCE_GROUP_ZORAS_DOMAIN,
ENTRANCE_GROUP_HYRULE_FIELD,
ENTRANCE_GROUP_LON_LON_RANCH,
ENTRANCE_GROUP_LAKE_HYLIA,
ENTRANCE_GROUP_GERUDO_VALLEY,
ENTRANCE_GROUP_HAUNTED_WASTELAND,
ENTRANCE_GROUP_MARKET,
ENTRANCE_GROUP_HYRULE_CASTLE,
SPOILER_ENTRANCE_GROUP_COUNT,
} SpoilerEntranceGroup;
typedef enum {
ENTRANCE_TYPE_OVERWORLD,
ENTRANCE_TYPE_INTERIOR,
ENTRANCE_TYPE_GROTTO,
ENTRANCE_TYPE_DUNGEON,
ENTRANCE_TYPE_COUNT,
} TrackerEntranceType;
typedef struct {
int16_t index;
char* name;
SpoilerEntranceGroup group;
TrackerEntranceType type;
uint8_t oneExit;
} EntranceData;
typedef struct {
uint8_t EntranceCount;
uint16_t GroupEntranceCounts[SPOILER_ENTRANCE_GROUP_COUNT];
uint16_t GroupOffsets[SPOILER_ENTRANCE_GROUP_COUNT];
} EntranceTrackingData;
extern std::list<EntranceOverride> entranceOverrides;
enum class EntranceType {
None,
OwlDrop,
Spawn,
WarpSong,
Dungeon,
GanonDungeon,
Interior,
SpecialInterior,
GrottoGrave,
Overworld,
Extra,
All,
};
class Entrance {
public:
Entrance(uint32_t connectedRegion_, std::vector<ConditionFn> conditions_met_)
: connectedRegion(connectedRegion_) {
conditions_met.resize(2);
for (size_t i = 0; i < conditions_met_.size(); i++) {
conditions_met[i] = conditions_met_[i];
}
}
bool GetConditionsMet() const {
if (Settings::Logic.Is(LOGIC_NONE) || Settings::Logic.Is(LOGIC_VANILLA)) {
return true;
} else if (Settings::Logic.Is(LOGIC_GLITCHLESS)) {
return conditions_met[0]();
} else if (Settings::Logic.Is(LOGIC_GLITCHED)) {
if (conditions_met[0]()) {
return true;
} else if (conditions_met[1] != NULL) {
return conditions_met[1]();
}
}
return false;
}
std::string to_string() const {
return AreaTable(parentRegion)->regionName + " -> " + AreaTable(connectedRegion)->regionName;
}
void SetName(std::string name_ = "") {
if (name_ == "") {
name = AreaTable(parentRegion)->regionName + " -> " + AreaTable(connectedRegion)->regionName;
} else {
name = std::move(name_);
}
}
std::string GetName() const {
return name;
}
void printAgeTimeAccess() {
CitraPrint("Name: ");
CitraPrint(name);
auto message = "Child Day: " + std::to_string(CheckConditionAtAgeTime(Logic::IsChild, Logic::AtDay)) + "\t"
"Child Night: " + std::to_string(CheckConditionAtAgeTime(Logic::IsChild, Logic::AtNight)) + "\t"
"Adult Day: " + std::to_string(CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtDay)) + "\t"
"Adult Night: " + std::to_string(CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtNight));
CitraPrint(message);
}
bool ConditionsMet(bool allAgeTimes = false) const {
Area* parent = AreaTable(parentRegion);
int conditionsMet = 0;
if (allAgeTimes && !parent->AllAccess()) {
return false;
}
//check all possible day/night condition combinations
conditionsMet = (parent->childDay && CheckConditionAtAgeTime(Logic::IsChild, Logic::AtDay, allAgeTimes)) +
(parent->childNight && CheckConditionAtAgeTime(Logic::IsChild, Logic::AtNight, allAgeTimes)) +
(parent->adultDay && CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtDay, allAgeTimes)) +
(parent->adultNight && CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtNight, allAgeTimes));
return conditionsMet && (!allAgeTimes || conditionsMet == 4);
}
uint32_t Getuint32_t() const {
return connectedRegion;
}
//set the logic to be a specific age and time of day and see if the condition still holds
bool CheckConditionAtAgeTime(bool& age, bool& time, bool passAnyway = false) const {
Logic::IsChild = false;
Logic::IsAdult = false;
Logic::AtDay = false;
Logic::AtNight = false;
time = true;
age = true;
Logic::UpdateHelpers();
return GetConditionsMet() && (connectedRegion != NONE || passAnyway);
}
uint32_t GetConnectedRegionKey() const {
return connectedRegion;
}
Area* GetConnectedRegion() const {
return AreaTable(connectedRegion);
}
void SetParentRegion(uint32_t newParent) {
parentRegion = newParent;
}
uint32_t GetParentRegionKey() const {
return parentRegion;
}
Area* GetParentRegion() const {
return AreaTable(parentRegion);
}
void SetNewEntrance(uint32_t newRegion) {
connectedRegion = newRegion;
}
void SetAsShuffled() {
shuffled = true;
}
bool IsShuffled() const {
return shuffled;
}
bool IsAddedToPool() const {
return addedToPool;
}
void AddToPool() {
addedToPool = true;
}
void RemoveFromPool() {
addedToPool = false;
}
void SetAsPrimary() {
primary = true;
}
bool IsPrimary() const {
return primary;
}
int16_t GetIndex() const {
return index;
}
void SetIndex(int16_t newIndex) {
index = newIndex;
}
int16_t GetBlueWarp() const {
return blueWarp;
}
void SetBlueWarp(int16_t newBlueWarp) {
blueWarp = newBlueWarp;
}
Entrance* GetAssumed() const {
return assumed;
}
void SetReplacement(Entrance* newReplacement) {
replacement = newReplacement;
}
Entrance* GetReplacement() const {
return replacement;
}
EntranceType GetType() const {
return type;
}
void SetType(EntranceType newType) {
type = newType;
}
Entrance* GetReverse() const {
return reverse;
}
void Connect(uint32_t newConnectedRegion) {
connectedRegion = newConnectedRegion;
AreaTable(newConnectedRegion)->entrances.push_front(this);
}
uint32_t Disconnect() {
AreaTable(connectedRegion)->entrances.remove_if([this](const auto entrance){return this == entrance;});
uint32_t previouslyConnected = connectedRegion;
connectedRegion = NONE;
return previouslyConnected;
}
void BindTwoWay(Entrance* otherEntrance) {
reverse = otherEntrance;
otherEntrance->reverse = this;
}
Entrance* GetNewTarget() {
AreaTable(ROOT)->AddExit(ROOT, connectedRegion, []{return true;});
Entrance* targetEntrance = AreaTable(ROOT)->GetExit(connectedRegion);
targetEntrance->SetReplacement(this);
targetEntrance->SetName(GetParentRegion()->regionName + " -> " + GetConnectedRegion()->regionName);
return targetEntrance;
}
Entrance* AssumeReachable() {
if (assumed == nullptr) {
assumed = GetNewTarget();
Disconnect();
}
return assumed;
}
private:
uint32_t parentRegion;
uint32_t connectedRegion;
std::vector<ConditionFn> conditions_met;
//Entrance Randomizer stuff
EntranceType type = EntranceType::None;
Entrance* target = nullptr;
Entrance* reverse = nullptr;
Entrance* assumed = nullptr;
Entrance* replacement = nullptr;
int16_t index = 0xFFFF;
int16_t blueWarp = 0;
bool shuffled = false;
bool primary = false;
bool addedToPool = false;
std::string name = "";
};
int ShuffleAllEntrances();
void CreateEntranceOverrides();
EntranceTrackingData* GetEntranceTrackingData();
extern std::vector<std::list<Entrance*>> playthroughEntrances;
extern bool noRandomEntrances;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
#pragma once
#include "keys.hpp"
#include <vector>
#include <string>
enum class SearchMode {
ReachabilitySearch,
GeneratePlaythrough,
CheckBeatable,
AllLocationsReachable,
ValidateWorld,
TimePassAccess,
TempleOfTimeAccess,
ValidStartingRegion,
PoeCollectorAccess,
};
void ClearProgress();
void VanillaFill();
int Fill();
std::vector<uint32_t> GetAccessibleLocations(const std::vector<uint32_t>& allowedLocations,
SearchMode mode = SearchMode::ReachabilitySearch, std::string ignore = "",
bool checkPoeCollectorAccess = false,
bool checkOtherEntranceAccess = false);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
#pragma once
#include "hints.hpp"
#include "keys.hpp"
#include <vector>
void HintTable_Init();
const HintText& Hint(uint32_t hintKey);
std::vector<HintText> GetHintCategory(HintCategory category);

View file

@ -0,0 +1,855 @@
#include "hints.hpp"
#include "custom_messages.hpp"
#include "dungeon.hpp"
#include "item_location.hpp"
#include "item_pool.hpp"
#include "logic.hpp"
#include "random.hpp"
#include "spoiler_log.hpp"
#include "fill.hpp"
#include "hint_list.hpp"
#include "trial.hpp"
#include "entrance.hpp"
#include "z64item.h"
#include <Lib/spdlog/include/spdlog/spdlog.h>
using namespace CustomMessages;
using namespace Logic;
using namespace Settings;
using namespace Trial;
constexpr std::array<HintSetting, 4> hintSettingTable{{
// Useless hints
{
.dungeonsWothLimit = 2,
.dungeonsBarrenLimit = 1,
.namedItemsRequired = false,
.distTable = {{
{.type = HintType::Trial, .order = 1, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Always, .order = 2, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Woth, .order = 3, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Barren, .order = 4, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Entrance, .order = 5, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Sometimes, .order = 6, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Random, .order = 7, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Item, .order = 8, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Song, .order = 9, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Overworld, .order = 10, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Dungeon, .order = 11, .weight = 0, .fixed = 0, .copies = 0},
{.type = HintType::Junk, .order = 12, .weight = 99, .fixed = 0, .copies = 0},
{.type = HintType::NamedItem, .order = 13, .weight = 0, .fixed = 0, .copies = 0},
}},
},
// Balanced hints
{
.dungeonsWothLimit = 2,
.dungeonsBarrenLimit = 1,
.namedItemsRequired = true,
.distTable = {{
{.type = HintType::Trial, .order = 1, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Always, .order = 2, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Woth, .order = 3, .weight = 7, .fixed = 0, .copies = 1},
{.type = HintType::Barren, .order = 4, .weight = 4, .fixed = 0, .copies = 1},
{.type = HintType::Entrance, .order = 5, .weight = 6, .fixed = 0, .copies = 1},
{.type = HintType::Sometimes, .order = 6, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Random, .order = 7, .weight = 12, .fixed = 0, .copies = 1},
{.type = HintType::Item, .order = 8, .weight = 10, .fixed = 0, .copies = 1},
{.type = HintType::Song, .order = 9, .weight = 2, .fixed = 0, .copies = 1},
{.type = HintType::Overworld, .order = 10, .weight = 4, .fixed = 0, .copies = 1},
{.type = HintType::Dungeon, .order = 11, .weight = 3, .fixed = 0, .copies = 1},
{.type = HintType::Junk, .order = 12, .weight = 6, .fixed = 0, .copies = 1},
{.type = HintType::NamedItem, .order = 13, .weight = 0, .fixed = 0, .copies = 1},
}},
},
// Strong hints
{
.dungeonsWothLimit = 2,
.dungeonsBarrenLimit = 1,
.namedItemsRequired = true,
.distTable = {{
{.type = HintType::Trial, .order = 1, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Always, .order = 2, .weight = 0, .fixed = 0, .copies = 2},
{.type = HintType::Woth, .order = 3, .weight = 12, .fixed = 0, .copies = 2},
{.type = HintType::Barren, .order = 4, .weight = 12, .fixed = 0, .copies = 1},
{.type = HintType::Entrance, .order = 5, .weight = 4, .fixed = 0, .copies = 1},
{.type = HintType::Sometimes, .order = 6, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Random, .order = 7, .weight = 8, .fixed = 0, .copies = 1},
{.type = HintType::Item, .order = 8, .weight = 8, .fixed = 0, .copies = 1},
{.type = HintType::Song, .order = 9, .weight = 4, .fixed = 0, .copies = 1},
{.type = HintType::Overworld, .order = 10, .weight = 6, .fixed = 0, .copies = 1},
{.type = HintType::Dungeon, .order = 11, .weight = 6, .fixed = 0, .copies = 1},
{.type = HintType::Junk, .order = 12, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::NamedItem, .order = 13, .weight = 0, .fixed = 0, .copies = 1},
}},
},
// Very strong hints
{
.dungeonsWothLimit = 40,
.dungeonsBarrenLimit = 40,
.namedItemsRequired = true,
.distTable = {{
{.type = HintType::Trial, .order = 1, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Always, .order = 2, .weight = 0, .fixed = 0, .copies = 2},
{.type = HintType::Woth, .order = 3, .weight = 15, .fixed = 0, .copies = 2},
{.type = HintType::Barren, .order = 4, .weight = 15, .fixed = 0, .copies = 1},
{.type = HintType::Entrance, .order = 5, .weight = 10, .fixed = 0, .copies = 1},
{.type = HintType::Sometimes, .order = 6, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Random, .order = 7, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::Item, .order = 8, .weight = 5, .fixed = 0, .copies = 1},
{.type = HintType::Song, .order = 9, .weight = 2, .fixed = 0, .copies = 1},
{.type = HintType::Overworld, .order = 10, .weight = 7, .fixed = 0, .copies = 1},
{.type = HintType::Dungeon, .order = 11, .weight = 7, .fixed = 0, .copies = 1},
{.type = HintType::Junk, .order = 12, .weight = 0, .fixed = 0, .copies = 1},
{.type = HintType::NamedItem, .order = 13, .weight = 0, .fixed = 0, .copies = 1},
}},
},
}};
std::array<DungeonInfo, 10> dungeonInfoData;
static Area* GetHintRegion(const uint32_t area) {
std::vector<uint32_t> alreadyChecked = {};
std::vector<uint32_t> spotQueue = {area};
while (!spotQueue.empty()) {
uint32_t region = spotQueue.back();
alreadyChecked.push_back(region);
spotQueue.pop_back();
if (AreaTable(region)->hintKey != NONE) {
return AreaTable(region);
}
//add unchecked entrances to spot queue
bool checked = false;
for (auto& entrance : AreaTable(region)->entrances) {
for (uint32_t checkedEntrance : alreadyChecked) {
if (entrance->GetParentRegionKey() == checkedEntrance) {
checked = true;
break;
}
}
if (!checked) {
spotQueue.insert(spotQueue.begin(), entrance->GetParentRegionKey());
}
}
}
return AreaTable(NONE);
}
uint32_t GetHintRegionHintKey(const uint32_t area) {
return GetHintRegion(area)->hintKey;
}
uint32_t GetHintRegionuint32_t(const uint32_t area) {
return GetHintRegion(area)->hintKey;
}
uint32_t GetLocationRegionuint32_t(const uint32_t location) {
return GetHintRegion(Location(location)->GetParentRegionKey())->hintKey;
}
static std::vector<uint32_t> GetAccessibleGossipStones(const uint32_t hintedLocation = GANON) {
//temporarily remove the hinted location's item, and then perform a
//reachability search for gossip stone locations.
uint32_t originalItem = Location(hintedLocation)->GetPlaceduint32_t();
Location(hintedLocation)->SetPlacedItem(NONE);
LogicReset();
auto accessibleGossipStones = GetAccessibleLocations(gossipStoneLocations);
//Give the item back to the location
Location(hintedLocation)->SetPlacedItem(originalItem);
return accessibleGossipStones;
}
static void AddHint(Text hint, const uint32_t gossipStone, const std::vector<uint8_t>& colors = {}) {
//save hints as dummy items for writing to the spoiler log
NewItem(gossipStone, Item{hint, ITEMTYPE_EVENT, GI_RUPEE_BLUE_LOSE, false, &noVariable, NONE});
Location(gossipStone)->SetPlacedItem(gossipStone);
//create the in game message
uint32_t messageId = 0x400 + Location(gossipStone)->GetFlag();
uint32_t sariaMessageId = 0xA00 + Location(gossipStone)->GetFlag();
CreateMessageFromTextObject(messageId, 0, 2, 3, AddColorsAndFormat(hint, colors));
CreateMessageFromTextObject(sariaMessageId, 0, 2, 3, AddColorsAndFormat(hint + EVENT_TRIGGER(), colors));
}
static void CreateLocationHint(const std::vector<uint32_t>& possibleHintLocations) {
//return if there aren't any hintable locations or gossip stones available
if (possibleHintLocations.empty()) {
SPDLOG_INFO("\tNO LOCATIONS TO HINT\n\n");
return;
}
uint32_t hintedLocation = RandomElement(possibleHintLocations);
const std::vector<uint32_t> accessibleGossipStones = GetAccessibleGossipStones(hintedLocation);
SPDLOG_INFO("\tLocation: ");
SPDLOG_INFO(Location(hintedLocation)->GetName());
SPDLOG_INFO("\n");
SPDLOG_INFO("\tItem: ");
SPDLOG_INFO(Location(hintedLocation)->GetPlacedItemName().GetEnglish());
SPDLOG_INFO("\n");
if (accessibleGossipStones.empty()) {
SPDLOG_INFO("\tNO GOSSIP STONES TO PLACE HINT\n\n");
return;
}
uint32_t gossipStone = RandomElement(accessibleGossipStones);
Location(hintedLocation)->SetAsHinted();
//make hint text
Text locationHintText = Location(hintedLocation)->GetHint().GetText();
Text itemHintText = Location(hintedLocation)->GetPlacedItem().GetHint().GetText();
Text prefix = Hint(PREFIX).GetText();
Text finalHint = prefix + locationHintText + " #"+itemHintText+"#.";
SPDLOG_INFO("\tMessage: ");
SPDLOG_INFO(finalHint.english);
SPDLOG_INFO("\n\n");
AddHint(finalHint, gossipStone, {QM_GREEN, QM_RED});
}
static void CreateWothHint(uint8_t* remainingDungeonWothHints) {
// get locations that are in the current playthrough
std::vector<uint32_t> possibleHintLocations = {};
// iterate through playthrough locations by sphere
std::vector<uint32_t> wothHintLocations =
FilterFromPool(wothLocations, [remainingDungeonWothHints](uint32_t loc) {
return Location(loc)->IsHintable() && // only filter hintable locations
!(Location(loc)->IsHintedAt()) && // only filter locations that haven't been hinted at
(Location(loc)->IsOverworld() ||
(Location(loc)->IsDungeon() &&
(*remainingDungeonWothHints) > 0)); // make sure we haven't surpassed the woth dungeon limit
});
AddElementsToPool(possibleHintLocations, wothHintLocations);
// If no more locations can be hinted at for woth, then just try to get another hint
if (possibleHintLocations.empty()) {
SPDLOG_INFO("\tNO LOCATIONS TO HINT\n\n");
return;
}
uint32_t hintedLocation = RandomElement(possibleHintLocations);
SPDLOG_INFO("\tLocation: ");
SPDLOG_INFO(Location(hintedLocation)->GetName());
SPDLOG_INFO("\n");
SPDLOG_INFO("\tItem: ");
SPDLOG_INFO(Location(hintedLocation)->GetPlacedItemName().GetEnglish());
SPDLOG_INFO("\n");
// get an accessible gossip stone
const std::vector<uint32_t> gossipStoneLocations = GetAccessibleGossipStones(hintedLocation);
if (gossipStoneLocations.empty()) {
SPDLOG_INFO("\tNO GOSSIP STONES TO PLACE HINT\n\n");
return;
}
Location(hintedLocation)->SetAsHinted();
uint32_t gossipStone = RandomElement(gossipStoneLocations);
// form hint text
Text locationText;
if (Location(hintedLocation)->IsDungeon()) {
*remainingDungeonWothHints -= 1;
uint32_t parentRegion = Location(hintedLocation)->GetParentRegionKey();
locationText = AreaTable(parentRegion)->GetHint().GetText();
} else {
uint32_t parentRegion = Location(hintedLocation)->GetParentRegionKey();
locationText = GetHintRegion(parentRegion)->GetHint().GetText();
}
Text finalWothHint = Hint(PREFIX).GetText() + "#" + locationText + "#" + Hint(WAY_OF_THE_HERO).GetText();
SPDLOG_INFO("\tMessage: ");
SPDLOG_INFO(finalWothHint.english);
SPDLOG_INFO("\n\n");
AddHint(finalWothHint, gossipStone, { QM_LBLUE });
}
static void CreateBarrenHint(uint8_t* remainingDungeonBarrenHints, std::vector<uint32_t>& barrenLocations) {
// remove dungeon locations if necessary
if (*remainingDungeonBarrenHints < 1) {
barrenLocations =
FilterFromPool(barrenLocations, [](const uint32_t loc) { return !(Location(loc)->IsDungeon()); });
}
if (barrenLocations.empty()) {
return;
}
uint32_t hintedLocation = RandomElement(barrenLocations, true);
SPDLOG_INFO("\tLocation: ");
SPDLOG_INFO(Location(hintedLocation)->GetName());
SPDLOG_INFO("\n");
SPDLOG_INFO("\tItem: ");
SPDLOG_INFO(Location(hintedLocation)->GetPlacedItemName().GetEnglish());
SPDLOG_INFO("\n");
// get an accessible gossip stone
const std::vector<uint32_t> gossipStoneLocations = GetAccessibleGossipStones(hintedLocation);
if (gossipStoneLocations.empty()) {
SPDLOG_INFO("\tNO GOSSIP STONES TO PLACE HINT\n\n");
return;
}
Location(hintedLocation)->SetAsHinted();
uint32_t gossipStone = RandomElement(gossipStoneLocations);
// form hint text
Text locationText;
if (Location(hintedLocation)->IsDungeon()) {
*remainingDungeonBarrenHints -= 1;
uint32_t parentRegion = Location(hintedLocation)->GetParentRegionKey();
locationText = Hint(AreaTable(parentRegion)->hintKey).GetText();
} else {
uint32_t parentRegion = Location(hintedLocation)->GetParentRegionKey();
locationText = Hint(GetHintRegion(parentRegion)->hintKey).GetText();
}
Text finalBarrenHint =
Hint(PREFIX).GetText() + Hint(PLUNDERING).GetText() + "#" + locationText + "#" + Hint(FOOLISH).GetText();
SPDLOG_INFO("\tMessage: ");
SPDLOG_INFO(finalBarrenHint.english);
SPDLOG_INFO("\n\n");
AddHint(finalBarrenHint, gossipStone, { QM_PINK });
// get rid of all other locations in this same barren region
barrenLocations = FilterFromPool(barrenLocations, [hintedLocation](uint32_t loc) {
return GetHintRegion(Location(loc)->GetParentRegionKey())->hintKey !=
GetHintRegion(Location(hintedLocation)->GetParentRegionKey())->hintKey;
});
}
static void CreateRandomLocationHint(const bool goodItem = false) {
const std::vector<uint32_t> possibleHintLocations = FilterFromPool(allLocations, [goodItem](const uint32_t loc) {
return Location(loc)->IsHintable() && !(Location(loc)->IsHintedAt()) && (!goodItem || Location(loc)->GetPlacedItem().IsMajorItem());
});
//If no more locations can be hinted at, then just try to get another hint
if (possibleHintLocations.empty()) {
SPDLOG_INFO("\tNO LOCATIONS TO HINT\n\n");
return;
}
uint32_t hintedLocation = RandomElement(possibleHintLocations);
SPDLOG_INFO("\tLocation: ");
SPDLOG_INFO(Location(hintedLocation)->GetName());
SPDLOG_INFO("\n");
SPDLOG_INFO("\tItem: ");
SPDLOG_INFO(Location(hintedLocation)->GetPlacedItemName().GetEnglish());
SPDLOG_INFO("\n");
//get an acessible gossip stone
const std::vector<uint32_t> gossipStoneLocations = GetAccessibleGossipStones(hintedLocation);
if (gossipStoneLocations.empty()) {
SPDLOG_INFO("\tNO GOSSIP STONES TO PLACE HINT\n\n");
return;
}
Location(hintedLocation)->SetAsHinted();
uint32_t gossipStone = RandomElement(gossipStoneLocations);
//form hint text
Text itemText = Location(hintedLocation)->GetPlacedItem().GetHint().GetText();
if (Location(hintedLocation)->IsDungeon()) {
uint32_t parentRegion = Location(hintedLocation)->GetParentRegionKey();
Text locationText = AreaTable(parentRegion)->GetHint().GetText();
Text finalHint = Hint(PREFIX).GetText()+"#"+locationText+"# "+Hint(HOARDS).GetText()+" #"+itemText+"#.";
SPDLOG_INFO("\tMessage: ");
SPDLOG_INFO(finalHint.english);
SPDLOG_INFO("\n\n");
AddHint(finalHint, gossipStone, {QM_GREEN, QM_RED});
} else {
Text locationText = GetHintRegion(Location(hintedLocation)->GetParentRegionKey())->GetHint().GetText();
Text finalHint = Hint(PREFIX).GetText()+"#"+itemText+"# "+Hint(CAN_BE_FOUND_AT).GetText()+" #"+locationText+"#.";
SPDLOG_INFO("\tMessage: ");
SPDLOG_INFO(finalHint.english);
SPDLOG_INFO("\n\n");
AddHint(finalHint, gossipStone, {QM_RED, QM_GREEN});
}
}
static void CreateGoodItemHint() {
CreateRandomLocationHint(true);
}
static void CreateJunkHint() {
//duplicate junk hints are possible for now
const HintText junkHint = RandomElement(GetHintCategory(HintCategory::Junk));
LogicReset();
const std::vector<uint32_t> gossipStones = GetAccessibleLocations(gossipStoneLocations);
if (gossipStones.empty()) {
SPDLOG_INFO("\tNO GOSSIP STONES TO PLACE HINT\n\n");
return;
}
uint32_t gossipStone = RandomElement(gossipStones);
Text hint = junkHint.GetText();
SPDLOG_INFO("\tMessage: ");
SPDLOG_INFO(hint.english);
SPDLOG_INFO("\n\n");
AddHint(hint, gossipStone, {QM_PINK});
}
static std::vector<uint32_t> CalculateBarrenRegions() {
std::vector<uint32_t> barrenLocations = {};
std::vector<uint32_t> potentiallyUsefulLocations = {};
for (uint32_t loc : allLocations) {
// If a location has a major item or is a way of the hero location, it is not barren
if (Location(loc)->GetPlacedItem().IsMajorItem() || ElementInContainer(loc, wothLocations)) {
AddElementsToPool(potentiallyUsefulLocations, std::vector{loc});
} else {
if (loc != LINKS_POCKET) { //Nobody cares to know if Link's Pocket is barren
AddElementsToPool(barrenLocations, std::vector{loc});
}
}
}
// Leave only locations at barren regions in the list
auto finalBarrenLocations = FilterFromPool(barrenLocations, [&potentiallyUsefulLocations](uint32_t loc){
for (uint32_t usefulLoc : potentiallyUsefulLocations) {
uint32_t barrenKey = GetLocationRegionuint32_t(loc);
uint32_t usefulKey = GetLocationRegionuint32_t(usefulLoc);
if (barrenKey == usefulKey) {
return false;
}
}
return true;
});
return finalBarrenLocations;
}
static void CreateTrialHints() {
//six trials
if (RandomGanonsTrials && GanonsTrialsCount.Is(6)) {
//get a random gossip stone
auto gossipStones = GetAccessibleGossipStones();
auto gossipStone = RandomElement(gossipStones, false);
//make hint
auto hint = Hint(PREFIX).GetText() + Hint(SIX_TRIALS).GetText();
AddHint(hint, gossipStone, {QM_PINK});
//zero trials
} else if (RandomGanonsTrials && GanonsTrialsCount.Is(0)) {
//get a random gossip stone
auto gossipStones = GetAccessibleGossipStones();
auto gossipStone = RandomElement(gossipStones, false);
//make hint
auto hint = Hint(PREFIX).GetText() + Hint(ZERO_TRIALS).GetText();
AddHint(hint, gossipStone, {QM_YELLOW});
//4 or 5 required trials
} else if (GanonsTrialsCount.Is(5) || GanonsTrialsCount.Is(4)) {
//get skipped trials
std::vector<TrialInfo*> trials = {};
trials.assign(trialList.begin(), trialList.end());
auto skippedTrials = FilterFromPool(trials, [](TrialInfo* trial){return trial->IsSkipped();});
//create a hint for each skipped trial
for (auto& trial : skippedTrials) {
//get a random gossip stone
auto gossipStones = GetAccessibleGossipStones();
auto gossipStone = RandomElement(gossipStones, false);
//make hint
auto hint = Hint(PREFIX).GetText()+"#"+trial->GetName()+"#"+Hint(FOUR_TO_FIVE_TRIALS).GetText();
AddHint(hint, gossipStone, {QM_YELLOW});
}
//1 to 3 trials
} else if (GanonsTrialsCount.Value<uint8_t>() >= 1 && GanonsTrialsCount.Value<uint8_t>() <= 3) {
//get requried trials
std::vector<TrialInfo*> trials = {};
trials.assign(trialList.begin(), trialList.end());
auto requiredTrials = FilterFromPool(trials, [](TrialInfo* trial){return trial->IsRequired();});
//create a hint for each required trial
for (auto& trial : requiredTrials) {
//get a random gossip stone
auto gossipStones = GetAccessibleGossipStones();
auto gossipStone = RandomElement(gossipStones, false);
//make hint
auto hint = Hint(PREFIX).GetText()+"#"+trial->GetName()+"#"+Hint(ONE_TO_THREE_TRIALS).GetText();
AddHint(hint, gossipStone, {QM_PINK});
}
}
}
static void CreateGanonText() {
//funny ganon line
auto ganonText = RandomElement(GetHintCategory(HintCategory::GanonLine)).GetText();
CreateMessageFromTextObject(0x70CB, 0, 2, 3, AddColorsAndFormat(ganonText));
//Get the location of the light arrows
auto lightArrowLocation = FilterFromPool(allLocations, [](const uint32_t loc){return Location(loc)->GetPlaceduint32_t() == LIGHT_ARROWS;});
Text text;
//If there is no light arrow location, it was in the player's inventory at the start
if (lightArrowLocation.empty()) {
text = Hint(LIGHT_ARROW_LOCATION_HINT).GetText()+Hint(YOUR_POCKET).GetText();
} else {
text = Hint(LIGHT_ARROW_LOCATION_HINT).GetText()+GetHintRegion(Location(lightArrowLocation[0])->GetParentRegionKey())->GetHint().GetText();
}
text = text + "!";
CreateMessageFromTextObject(0x70CC, 0, 2, 3, AddColorsAndFormat(text));
}
//Find the location which has the given itemKey and create the generic altar text for the reward
static Text BuildDungeonRewardText(ItemID itemID, const uint32_t itemKey) {
uint32_t location = FilterFromPool(allLocations, [itemKey](const uint32_t loc){return Location(loc)->GetPlaceduint32_t() == itemKey;})[0];
Location(location)->SetAsHinted();
//Calling ITEM_OBTAINED draws the passed in itemID to the left side of the textbox
return Text()+ITEM_OBTAINED(itemID)+"#"+GetHintRegion(Location(location)->GetParentRegionKey())->GetHint().GetText()+"#...^";
}
static Text BuildDoorOfTimeText() {
std::string itemObtained;
Text doorOfTimeText;
if (OpenDoorOfTime.Is(OPENDOOROFTIME_OPEN)) {
itemObtained = ITEM_OBTAINED(ITEM_SWORD_MASTER);
doorOfTimeText = Hint(CHILD_ALTAR_TEXT_END_DOTOPEN).GetText();
} else if (OpenDoorOfTime.Is(OPENDOOROFTIME_CLOSED)) {
itemObtained = ITEM_OBTAINED(ITEM_OCARINA_FAIRY);
doorOfTimeText = Hint(CHILD_ALTAR_TEXT_END_DOTCLOSED).GetText();
} else if (OpenDoorOfTime.Is(OPENDOOROFTIME_INTENDED)) {
itemObtained = ITEM_OBTAINED(ITEM_OCARINA_TIME);
doorOfTimeText = Hint(CHILD_ALTAR_TEXT_END_DOTINTENDED).GetText();
}
return Text()+itemObtained+doorOfTimeText;
}
//insert the required number into the hint and set the singular/plural form
static Text BuildCountReq(const uint32_t req, const Option& count) {
Text requirement = Hint(req).GetTextCopy();
if (count.Value<uint8_t>() == 1) {
requirement.SetForm(SINGULAR);
} else {
requirement.SetForm(PLURAL);
}
requirement.Replace("%d", std::to_string(count.Value<uint8_t>()));
return requirement;
}
static Text BuildBridgeReqsText() {
Text bridgeText;
if (Bridge.Is(RAINBOWBRIDGE_OPEN)) {
bridgeText = Hint(BRIDGE_OPEN_HINT).GetText();
} else if (Bridge.Is(RAINBOWBRIDGE_VANILLA)) {
bridgeText = Hint(BRIDGE_VANILLA_HINT).GetText();
} else if (Bridge.Is(RAINBOWBRIDGE_STONES)) {
bridgeText = BuildCountReq(BRIDGE_STONES_HINT, BridgeStoneCount);
} else if (Bridge.Is(RAINBOWBRIDGE_MEDALLIONS)) {
bridgeText = BuildCountReq(BRIDGE_MEDALLIONS_HINT, BridgeMedallionCount);
} else if (Bridge.Is(RAINBOWBRIDGE_REWARDS)) {
bridgeText = BuildCountReq(BRIDGE_REWARDS_HINT, BridgeRewardCount);
} else if (Bridge.Is(RAINBOWBRIDGE_DUNGEONS)) {
bridgeText = BuildCountReq(BRIDGE_DUNGEONS_HINT, BridgeDungeonCount);
} else if (Bridge.Is(RAINBOWBRIDGE_TOKENS)) {
bridgeText = BuildCountReq(BRIDGE_TOKENS_HINT, BridgeTokenCount);
}
return Text()+ITEM_OBTAINED(ITEM_ARROW_LIGHT)+bridgeText+"^";
}
static Text BuildGanonBossKeyText() {
Text ganonBossKeyText;
if (GanonsBossKey.Is(GANONSBOSSKEY_START_WITH)) {
ganonBossKeyText = Hint(GANON_BK_START_WITH_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_VANILLA)) {
ganonBossKeyText = Hint(GANON_BK_VANILLA_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_OWN_DUNGEON)) {
ganonBossKeyText = Hint(GANON_BK_OWN_DUNGEON_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_ANY_DUNGEON)) {
ganonBossKeyText = Hint(GANON_BK_ANY_DUNGEON_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_OVERWORLD)) {
ganonBossKeyText = Hint(GANON_BK_OVERWORLD_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_ANYWHERE)) {
ganonBossKeyText = Hint(GANON_BK_ANYWHERE_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_VANILLA)) {
ganonBossKeyText = Hint(LACS_VANILLA_HINT).GetText();
} else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_STONES)) {
ganonBossKeyText = BuildCountReq(LACS_STONES_HINT, LACSStoneCount);
} else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_MEDALLIONS)) {
ganonBossKeyText = BuildCountReq(LACS_MEDALLIONS_HINT, LACSMedallionCount);
} else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_REWARDS)) {
ganonBossKeyText = BuildCountReq(LACS_REWARDS_HINT, LACSRewardCount);
} else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_DUNGEONS)) {
ganonBossKeyText = BuildCountReq(LACS_DUNGEONS_HINT, LACSDungeonCount);
} else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_TOKENS)) {
ganonBossKeyText = BuildCountReq(LACS_TOKENS_HINT, LACSTokenCount);
}
return Text()+ITEM_OBTAINED(ITEM_KEY_BOSS)+ganonBossKeyText+"^";
}
static void CreateAltarText() {
//Child Altar Text
Text childText = Hint(SPIRITUAL_STONE_TEXT_START).GetText()+"^"+
//Spiritual Stones
(StartingKokiriEmerald.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_KOKIRI_EMERALD, KOKIRI_EMERALD)) +
(StartingGoronRuby.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_GORON_RUBY, GORON_RUBY)) +
(StartingZoraSapphire.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_ZORA_SAPPHIRE, ZORA_SAPPHIRE)) +
//How to open Door of Time, the event trigger is necessary to read the altar multiple times
BuildDoorOfTimeText()+EVENT_TRIGGER();
CreateMessageFromTextObject(0x7040, 0, 2, 3, AddColorsAndFormat(childText, {QM_GREEN, QM_RED, QM_BLUE}));
//Adult Altar Text
Text adultText = Hint(ADULT_ALTAR_TEXT_START).GetText()+"^"+
//Medallion Areas
(StartingLightMedallion.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_MEDALLION_LIGHT, LIGHT_MEDALLION)) +
(StartingForestMedallion.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_MEDALLION_FOREST, FOREST_MEDALLION)) +
(StartingFireMedallion.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_MEDALLION_FIRE, FIRE_MEDALLION)) +
(StartingWaterMedallion.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_MEDALLION_WATER, WATER_MEDALLION)) +
(StartingSpiritMedallion.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_MEDALLION_SPIRIT, SPIRIT_MEDALLION)) +
(StartingShadowMedallion.Value<uint8_t>() ? Text{ "##", "##", "##" }
: BuildDungeonRewardText(ITEM_MEDALLION_SHADOW, SHADOW_MEDALLION)) +
//Bridge requirement
BuildBridgeReqsText()+
//Ganons Boss Key requirement
BuildGanonBossKeyText()+
//End
Hint(ADULT_ALTAR_TEXT_END).GetText()+EVENT_TRIGGER();
CreateMessageFromTextObject(0x7088, 0, 2, 3, AddColorsAndFormat(adultText, {QM_RED, QM_YELLOW, QM_GREEN, QM_RED, QM_BLUE, QM_YELLOW, QM_PINK, QM_RED, QM_RED, QM_RED, QM_RED}));
}
void CreateMerchantsHints() {
Text medigoronItemText = Location(GC_MEDIGORON)->GetPlacedItem().GetHint().GetText();
Text carpetSalesmanItemText = Location(WASTELAND_BOMBCHU_SALESMAN)->GetPlacedItem().GetHint().GetText();
Text carpetSalesmanItemClearText = Location(WASTELAND_BOMBCHU_SALESMAN)->GetPlacedItem().GetHint().GetClear();
Text medigoronText = Hint(MEDIGORON_DIALOG_FIRST).GetText()+medigoronItemText+Hint(MEDIGORON_DIALOG_SECOND).GetText();
Text carpetSalesmanTextOne = Hint(CARPET_SALESMAN_DIALOG_FIRST).GetText()+carpetSalesmanItemText+Hint(CARPET_SALESMAN_DIALOG_SECOND).GetText();
Text carpetSalesmanTextTwo = Hint(CARPET_SALESMAN_DIALOG_THIRD).GetText()+carpetSalesmanItemClearText+Hint(CARPET_SALESMAN_DIALOG_FOURTH).GetText();
CreateMessageFromTextObject(0x9120, 0, 2, 3, AddColorsAndFormat(medigoronText, {QM_RED, QM_GREEN}));
CreateMessageFromTextObject(0x6077, 0, 2, 3, AddColorsAndFormat(carpetSalesmanTextOne, {QM_RED, QM_GREEN}));
CreateMessageFromTextObject(0x6078, 0, 2, 3, AddColorsAndFormat(carpetSalesmanTextTwo, {QM_RED, QM_YELLOW, QM_RED}));
}
void CreateAllHints() {
CreateGanonText();
CreateAltarText();
SPDLOG_INFO("\nNOW CREATING HINTS\n");
const HintSetting& hintSetting = hintSettingTable[Settings::HintDistribution.Value<uint8_t>()];
uint8_t remainingDungeonWothHints = hintSetting.dungeonsWothLimit;
uint8_t remainingDungeonBarrenHints = hintSetting.dungeonsBarrenLimit;
// Add 'always' location hints
if (hintSetting.distTable[static_cast<int>(HintType::Always)].copies > 0) {
// Only filter locations that had a random item placed at them (e.g. don't get cow locations if shuffle cows is off)
auto alwaysHintLocations = FilterFromPool(allLocations, [](const uint32_t loc){
return Location(loc)->GetHint().GetType() == HintCategory::Always &&
Location(loc)->IsHintable() && !(Location(loc)->IsHintedAt());
});
for (auto& hint : conditionalAlwaysHints) {
uint32_t loc = hint.first;
if (hint.second() && Location(loc)->IsHintable() && !Location(loc)->IsHintedAt()) {
alwaysHintLocations.push_back(loc);
}
}
for (uint32_t location : alwaysHintLocations) {
CreateLocationHint({location});
}
}
//Add 'trial' location hints
if (hintSetting.distTable[static_cast<int>(HintType::Trial)].copies > 0) {
CreateTrialHints();
}
//create a vector with each hint type proportional to it's weight in the distribution setting.
//ootr uses a weighted probability function to decide hint types, but selecting randomly from
//this vector will do for now
std::vector<HintType> remainingHintTypes = {};
for (HintDistributionSetting hds : hintSetting.distTable) {
remainingHintTypes.insert(remainingHintTypes.end(), hds.weight, hds.type);
}
Shuffle(remainingHintTypes);
//get barren regions
auto barrenLocations = CalculateBarrenRegions();
//Calculate dungeon woth/barren info
std::vector<std::string> dungeonNames = {"Deku Tree", "Dodongos Cavern", "Jabu Jabus Belly", "Forest Temple", "Fire Temple",
"Water Temple", "Spirit Temple", "Shadow Temple", "Bottom of the Well", "Ice Cavern"};
//Get list of all barren dungeons
std::vector<std::string> barrenDungeons;
for (uint32_t barrenLocation : barrenLocations) {
std::string barrenRegion = GetHintRegion(Location(barrenLocation)->GetParentRegionKey())->scene;
bool isDungeon = std::find(dungeonNames.begin(), dungeonNames.end(), barrenRegion) != dungeonNames.end();
//If it hasn't already been added to the list and is a dungeon, add to list
if (isDungeon && std::find(barrenDungeons.begin(), barrenDungeons.end(), barrenRegion) == barrenDungeons.end()) {
barrenDungeons.push_back(barrenRegion);
}
}
SPDLOG_INFO("\nBarren Dungeons:\n");
for (std::string barrenDungeon : barrenDungeons) {
SPDLOG_INFO(barrenDungeon + "\n");
}
//Get list of all woth dungeons
std::vector<std::string> wothDungeons;
for (uint32_t wothLocation : wothLocations) {
std::string wothRegion = GetHintRegion(Location(wothLocation)->GetParentRegionKey())->scene;
bool isDungeon = std::find(dungeonNames.begin(), dungeonNames.end(), wothRegion) != dungeonNames.end();
//If it hasn't already been added to the list and is a dungeon, add to list
if (isDungeon && std::find(wothDungeons.begin(), wothDungeons.end(), wothRegion) == wothDungeons.end()) {
wothDungeons.push_back(wothRegion);
}
}
SPDLOG_INFO("\nWoth Dungeons:\n");
for (std::string wothDungeon : wothDungeons) {
SPDLOG_INFO(wothDungeon + "\n");
}
//Set DungeonInfo array for each dungeon
for (uint32_t i = 0; i < dungeonInfoData.size(); i++) {
std::string dungeonName = dungeonNames[i];
if (std::find(barrenDungeons.begin(), barrenDungeons.end(), dungeonName) != barrenDungeons.end()) {
dungeonInfoData[i] = DungeonInfo::DUNGEON_BARREN;
} else if (std::find(wothDungeons.begin(), wothDungeons.end(), dungeonName) != wothDungeons.end()) {
dungeonInfoData[i] = DungeonInfo::DUNGEON_WOTH;
} else {
dungeonInfoData[i] = DungeonInfo::DUNGEON_NEITHER;
}
}
std::array<std::string, 13> hintTypeNames = {
"Trial",
"Always",
"WotH",
"Barren",
"Entrance",
"Sometimes",
"Random",
"Item",
"Song",
"Overworld",
"Dungeon",
"Junk",
"NamedItem",
};
//while there are still gossip stones remaining
while (FilterFromPool(gossipStoneLocations, [](const uint32_t loc){return Location(loc)->GetPlaceduint32_t() == NONE;}).size() != 0) {
//TODO: fixed hint types
if (remainingHintTypes.empty()) {
break;
}
//get a random hint type from the remaining hints
HintType type = RandomElement(remainingHintTypes, true);
SPDLOG_INFO("Attempting to make hint of type: ");
SPDLOG_INFO(hintTypeNames[static_cast<int>(type)]);
SPDLOG_INFO("\n");
//create the appropriate hint for the type
if (type == HintType::Woth) {
CreateWothHint(&remainingDungeonWothHints);
} else if (type == HintType::Barren) {
CreateBarrenHint(&remainingDungeonBarrenHints, barrenLocations);
} else if (type == HintType::Sometimes){
std::vector<uint32_t> sometimesHintLocations = FilterFromPool(allLocations, [](const uint32_t loc){return Location(loc)->GetHint().GetType() == HintCategory::Sometimes && Location(loc)->IsHintable() && !(Location(loc)->IsHintedAt());});
CreateLocationHint(sometimesHintLocations);
} else if (type == HintType::Random) {
CreateRandomLocationHint();
} else if (type == HintType::Item) {
CreateGoodItemHint();
} else if (type == HintType::Song){
std::vector<uint32_t> songHintLocations = FilterFromPool(allLocations, [](const uint32_t loc){return Location(loc)->IsCategory(Category::cSong) && Location(loc)->IsHintable() && !(Location(loc)->IsHintedAt());});
CreateLocationHint(songHintLocations);
} else if (type == HintType::Overworld){
std::vector<uint32_t> overworldHintLocations = FilterFromPool(allLocations, [](const uint32_t loc){return Location(loc)->IsOverworld() && Location(loc)->IsHintable() && !(Location(loc)->IsHintedAt());});
CreateLocationHint(overworldHintLocations);
} else if (type == HintType::Dungeon){
std::vector<uint32_t> dungeonHintLocations = FilterFromPool(allLocations, [](const uint32_t loc){return Location(loc)->IsDungeon() && Location(loc)->IsHintable() && !(Location(loc)->IsHintedAt());});
CreateLocationHint(dungeonHintLocations);
} else if (type == HintType::Junk) {
CreateJunkHint();
}
}
//If any gossip stones failed to have a hint placed on them for some reason, place a junk hint as a failsafe.
for (uint32_t gossipStone : FilterFromPool(gossipStoneLocations, [](const uint32_t loc){return Location(loc)->GetPlaceduint32_t() == NONE;})) {
const HintText junkHint = RandomElement(GetHintCategory(HintCategory::Junk));
AddHint(junkHint.GetText(), gossipStone, {QM_PINK});
}
//Getting gossip stone locations temporarily sets one location to not be reachable.
//Call the function one last time to get rid of false positives on locations not
//being reachable.
GetAccessibleLocations({});
}

View file

@ -0,0 +1,222 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "keys.hpp"
#include "text.hpp"
#include "random.hpp"
#include "settings.hpp"
#include <functional>
enum class HintType {
Trial,
Always,
Woth, //Way of the Hero
Barren,
Entrance,
Sometimes,
Random,
Item,
Song,
Overworld,
Dungeon,
Junk,
NamedItem,
MaxCount,
};
struct HintDistributionSetting {
HintType type;
uint8_t order;
size_t weight;
uint8_t fixed;
uint8_t copies;
};
struct HintSetting {
using DistributionTable = std::array<HintDistributionSetting, static_cast<int>(HintType::MaxCount)>;
uint8_t dungeonsWothLimit;
uint8_t dungeonsBarrenLimit;
bool namedItemsRequired;
DistributionTable distTable;
};
enum class HintCategory {
Item,
Always,
Sometimes,
Exclude,
Entrance,
Region,
Junk,
DungeonName,
Boss,
Bridge,
GanonsBossKey,
LACS,
Altar,
Validation,
LightArrow,
GanonLine,
MerchantsDialogs,
};
class HintText {
public:
HintText() = default;
HintText(std::vector<Text> obscureText_, std::vector<Text> ambiguousText_, Text clearText_, HintCategory type_)
: obscureText(std::move(obscureText_)),
ambiguousText(std::move(ambiguousText_)),
clearText(std::move(clearText_)),
type(type_) {}
static auto Item(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Item};
}
static auto Always(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Always};
}
static auto Sometimes(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Sometimes};
}
static auto Exclude(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Exclude};
}
static auto Entrance(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Entrance};
}
static auto Region(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Region};
}
static auto Junk(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Junk};
}
static auto DungeonName(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::DungeonName};
}
static auto Boss(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Boss};
}
static auto Bridge(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Bridge};
}
static auto GanonsBossKey(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::GanonsBossKey};
}
static auto LACS(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::LACS};
}
static auto Altar(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Altar};
}
static auto Validation(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::Validation};
}
static auto LightArrow(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::LightArrow};
}
static auto GanonLine(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::GanonLine};
}
static auto MerchantsDialogs(std::vector<Text>&& obscureText, std::vector<Text>&& ambiguousText = {}, Text&& clearText = {}) {
return HintText{std::move(obscureText), std::move(ambiguousText), std::move(clearText), HintCategory::MerchantsDialogs};
}
Text& GetObscure() {
return RandomElement(obscureText);
}
const Text& GetObscure() const {
return RandomElement(obscureText);
}
Text& GetAmbiguous() {
if (ambiguousText.size() > 0) {
return RandomElement(ambiguousText);
}
return RandomElement(obscureText);
}
const Text& GetAmbiguous() const {
if (ambiguousText.size() > 0) {
return RandomElement(ambiguousText);
}
return RandomElement(obscureText);
}
const Text& GetClear() const {
if (clearText.GetEnglish().empty()) {
return GetObscure();
}
return clearText;
}
const Text& GetText() const {
if (Settings::ClearerHints.Is(HINTMODE_OBSCURE)) {
return GetObscure();
} else if (Settings::ClearerHints.Is(HINTMODE_AMBIGUOUS)){
return GetAmbiguous();
} else {
return GetClear();
}
}
const Text GetTextCopy() const {
if (Settings::ClearerHints.Is(HINTMODE_OBSCURE)) {
return GetObscure();
} else if (Settings::ClearerHints.Is(HINTMODE_AMBIGUOUS)){
return GetAmbiguous();
} else {
return GetClear();
}
}
HintCategory GetType() const {
return type;
}
bool operator==(const HintText& right) const {
return obscureText == right.obscureText &&
ambiguousText == right.ambiguousText &&
clearText == right.clearText;
}
bool operator!=(const HintText& right) const {
return !operator==(right);
}
private:
std::vector<Text> obscureText = {};
std::vector<Text> ambiguousText = {};
Text clearText;
HintCategory type;
};
using ConditionalAlwaysHint = std::pair<uint32_t, std::function<bool()>>;
//10 dungeons as GTG and GC are excluded
extern std::array<DungeonInfo, 10> dungeonInfoData;
extern std::array<ConditionalAlwaysHint, 9> conditionalAlwaysHints;
extern uint32_t GetHintRegionHintKey(const uint32_t area);
extern void CreateAllHints();
extern void CreateMerchantsHints();

View file

@ -0,0 +1,84 @@
#include "item.hpp"
#include <array>
#include "item_location.hpp"
#include "logic.hpp"
#include "random.hpp"
#include "item_pool.hpp"
#include "settings.hpp"
#include "z64item.h"
Item::Item(Text name_, ItemType type_, int getItemId_, bool advancement_, bool* logicVar_, uint32_t hintKey_,
uint16_t price_)
: name(std::move(name_)),
type(type_),
getItemId(getItemId_),
advancement(advancement_),
logicVar(logicVar_),
hintKey(hintKey_),
price(price_) {}
Item::Item(Text name_, ItemType type_, int getItemId_, bool advancement_, uint8_t* logicVar_, uint32_t hintKey_,
uint16_t price_)
: name(std::move(name_)),
type(type_),
getItemId(getItemId_),
advancement(advancement_),
logicVar(logicVar_),
hintKey(hintKey_),
price(price_) {}
Item::~Item() = default;
void Item::ApplyEffect() {
//If this is a key ring, logically add as many keys as we could need
if (FOREST_TEMPLE_KEY_RING <= hintKey && hintKey <= GANONS_CASTLE_KEY_RING) {
*std::get<uint8_t*>(logicVar) += 10;
}
else {
if (std::holds_alternative<bool*>(logicVar)) {
*std::get<bool*>(logicVar) = true;
} else {
*std::get<uint8_t*>(logicVar) += 1;
}
}
Logic::UpdateHelpers();
}
void Item::UndoEffect() {
if (FOREST_TEMPLE_KEY_RING <= hintKey && hintKey <= GANONS_CASTLE_KEY_RING) {
*std::get<uint8_t*>(logicVar) -= 10;
}
else {
if (std::holds_alternative<bool*>(logicVar)) {
*std::get<bool*>(logicVar) = false;
} else {
*std::get<uint8_t*>(logicVar) -= 1;
}
}
Logic::UpdateHelpers();
}
ItemOverride_Value Item::Value() const {
ItemOverride_Value val;
val.all = 0;
val.itemId = getItemId;
if (getItemId == GI_ICE_TRAP) {
val.looksLikeItemId = RandomElement(IceTrapModels);
}
if (!Settings::ColoredBossKeys && (getItemId >= 0x95 && getItemId <= 0x9A)) { //Boss keys
val.looksLikeItemId = GI_KEY_BOSS;
}
if (!Settings::ColoredKeys && (getItemId >= 0xAF && getItemId <= 0xB7)) { //Small keys
val.looksLikeItemId = GI_KEY_SMALL;
}
if (type == ITEMTYPE_SHOP) {
// With the current shopsanity implementation, we need a way to detect
// regular shop items. This method should have no unintended side effects
// unless there was a multiworld with 256 players... so, it should be fine.
val.player = 0xFF;
}
return val;
}

View file

@ -0,0 +1,142 @@
#pragma once
#include <string>
#include <variant>
#include "keys.hpp"
#include "hint_list.hpp"
#include "settings.hpp"
union ItemOverride_Value;
enum ItemType {
ITEMTYPE_ITEM,
ITEMTYPE_MAP,
ITEMTYPE_COMPASS,
ITEMTYPE_BOSSKEY,
ITEMTYPE_SMALLKEY,
ITEMTYPE_TOKEN,
ITEMTYPE_FORTRESS_SMALLKEY,
ITEMTYPE_EVENT,
ITEMTYPE_DROP,
ITEMTYPE_REFILL,
ITEMTYPE_SONG,
ITEMTYPE_SHOP,
ITEMTYPE_DUNGEONREWARD
};
class Item {
public:
Item() = default;
Item(Text name_, ItemType type_, int getItemId_, bool advancement_, bool* logicVar_, uint32_t hintKey_,
uint16_t price_ = 0);
Item(Text name_, ItemType type_, int getItemId_, bool advancement_, uint8_t* logicVar_, uint32_t hintKey_,
uint16_t price_ = 0);
~Item();
void ApplyEffect();
void UndoEffect();
ItemOverride_Value Value() const;
const Text& GetName() const {
return name;
}
bool IsAdvancement() const {
return advancement;
}
int GetItemID() const {
return getItemId;
}
ItemType GetItemType() const {
return type;
}
uint16_t GetPrice() const {
return price;
}
void SetPrice(uint16_t price_) {
price = price_;
}
void SetAsPlaythrough() {
playthrough = true;
}
bool IsPlaythrough() const {
return playthrough;
}
bool IsBottleItem() const {
return getItemId == 0x0F || //Empty Bottle
getItemId == 0X14 || //Bottle with Milk
(getItemId >= 0x8C && getItemId <= 0x94); //Rest of bottled contents
}
bool IsMajorItem() const {
using namespace Settings;
if (type == ITEMTYPE_TOKEN) {
return Bridge.Is(RAINBOWBRIDGE_TOKENS) || LACSCondition == LACSCONDITION_TOKENS;
}
if (type == ITEMTYPE_DROP || type == ITEMTYPE_EVENT || type == ITEMTYPE_SHOP || type == ITEMTYPE_MAP || type == ITEMTYPE_COMPASS) {
return false;
}
if (type == ITEMTYPE_DUNGEONREWARD && (ShuffleRewards.Is(REWARDSHUFFLE_END_OF_DUNGEON))) {
return false;
}
if (name.GetEnglish().find("Bombchus") != std::string::npos && !BombchusInLogic) {
return false;
}
if (type == ITEMTYPE_SMALLKEY && (Keysanity.Is(KEYSANITY_VANILLA) || Keysanity.Is(KEYSANITY_OWN_DUNGEON))) {
return false;
}
if (type == ITEMTYPE_FORTRESS_SMALLKEY && GerudoKeys.Is(GERUDOKEYS_VANILLA)) {
return false;
}
if ((type == ITEMTYPE_BOSSKEY && getItemId != 0x9A) && (BossKeysanity.Is(BOSSKEYSANITY_VANILLA) || BossKeysanity.Is(BOSSKEYSANITY_OWN_DUNGEON))) {
return false;
}
//Ganons Castle Boss Key
if (getItemId == 0x9A && (GanonsBossKey.Is(GANONSBOSSKEY_VANILLA) || GanonsBossKey.Is(GANONSBOSSKEY_OWN_DUNGEON))) {
return false;
}
return IsAdvancement();
}
const uint32_t GetHintKey() const {
return hintKey;
}
const HintText& GetHint() const {
return Hint(hintKey);
}
bool operator== (const Item& right) const {
return type == right.GetItemType() && getItemId == right.GetItemID();
}
bool operator!= (const Item& right) const {
return !operator==(right);
}
private:
Text name;
ItemType type;
int getItemId;
bool advancement;
std::variant<bool*, uint8_t*> logicVar;
uint32_t hintKey;
uint16_t price;
bool playthrough = false;
};

View file

@ -0,0 +1,290 @@
#include "item_list.hpp"
#include "logic.hpp"
#include <array>
#include "z64item.h"
using namespace Logic;
static std::array<Item, KEY_ENUM_MAX> itemTable;
void ItemTable_Init() { //English name French Spanish Item Type getItemID advancement logic hint key
itemTable[NONE] = Item(Text{"No Item", "Rien", "Sin Objeto"}, ITEMTYPE_EVENT, GI_RUPEE_GREEN, false, &noVariable, NONE);
itemTable[KOKIRI_SWORD] = Item(Text{"Kokiri Sword", "Épée Kokiri", "Espada Kokiri"}, ITEMTYPE_ITEM, GI_SWORD_KOKIRI, true, &KokiriSword, KOKIRI_SWORD);
//[MASTER_SWORD]
itemTable[GIANTS_KNIFE] = Item(Text{"Giant's Knife", "Lame de géant", "Espada de Biggoron"}, ITEMTYPE_ITEM, GI_SWORD_KNIFE, false, &noVariable, GIANTS_KNIFE);
itemTable[BIGGORON_SWORD] = Item(Text{"Biggoron's Sword", "Épée de Grogoron", "Espada de Biggoron"}, ITEMTYPE_ITEM, GI_SWORD_BGS, true, &BiggoronSword, BIGGORON_SWORD);
itemTable[DEKU_SHIELD] = Item(Text{"Deku Shield", "Bouclier Mojo", "Escudo deku"}, ITEMTYPE_ITEM, GI_SHIELD_DEKU, false, &noVariable, DEKU_SHIELD);
itemTable[HYLIAN_SHIELD] = Item(Text{"Hylian Shield", "Bouclier Hylien", "Escudo hyliano"}, ITEMTYPE_ITEM, GI_SHIELD_HYLIAN, false, &noVariable, HYLIAN_SHIELD);
itemTable[MIRROR_SHIELD] = Item(Text{"Mirror Shield", "Bouclier miroir", "Escudo espejo"}, ITEMTYPE_ITEM, GI_SHIELD_MIRROR, true, &MirrorShield, MIRROR_SHIELD);
itemTable[GORON_TUNIC] = Item(Text{"Goron Tunic", "Tunique Goron", "Sayo goron"}, ITEMTYPE_ITEM, GI_TUNIC_GORON, true, &GoronTunic, GORON_TUNIC);
itemTable[ZORA_TUNIC] = Item(Text{"Zora Tunic", "Tunique Zora", "Sayo zora"}, ITEMTYPE_ITEM, GI_TUNIC_ZORA, true, &ZoraTunic, ZORA_TUNIC);
itemTable[IRON_BOOTS] = Item(Text{"Iron Boots", "Bottes de plomb", "Botas de hierro"}, ITEMTYPE_ITEM, GI_BOOTS_IRON, true, &IronBoots, IRON_BOOTS);
itemTable[HOVER_BOOTS] = Item(Text{"Hover Boots", "Bottes des airs", "Botas voladoras"}, ITEMTYPE_ITEM, GI_BOOTS_HOVER, true, &HoverBoots, HOVER_BOOTS);
itemTable[BOOMERANG] = Item(Text{"Boomerang", "Boomerang", "Búmeran"}, ITEMTYPE_ITEM, GI_BOOMERANG, true, &Boomerang, BOOMERANG);
itemTable[LENS_OF_TRUTH] = Item(Text{"Lens of Truth", "Loupe de vérité", "Lupa de la Verdad"}, ITEMTYPE_ITEM, GI_LENS, true, &LensOfTruth, LENS_OF_TRUTH);
itemTable[MEGATON_HAMMER] = Item(Text{"Megaton Hammer", "Masse des titans", "Martillo Megatón"}, ITEMTYPE_ITEM, GI_HAMMER, true, &Hammer, MEGATON_HAMMER);
itemTable[SHARD_OF_AGONY] = Item(Text{"Shard of Agony", "Fragment de souffrance", "Piedra de la Agonía"}, ITEMTYPE_ITEM, GI_STONE_OF_AGONY, true, &ShardOfAgony, SHARD_OF_AGONY);
itemTable[DINS_FIRE] = Item(Text{"Din's Fire", "Feu de Din", "Fuego de Din"}, ITEMTYPE_ITEM, GI_DINS_FIRE, true, &DinsFire, DINS_FIRE);
itemTable[FARORES_WIND] = Item(Text{"Farore's Wind", "Vent de Farore", "Viento de Farore"}, ITEMTYPE_ITEM, GI_FARORES_WIND, true, &FaroresWind, FARORES_WIND);
itemTable[NAYRUS_LOVE] = Item(Text{"Nayru's Love", "Amour de Nayru", "Amor de Nayru"}, ITEMTYPE_ITEM, GI_NAYRUS_LOVE, true, &NayrusLove, NAYRUS_LOVE);
itemTable[FIRE_ARROWS] = Item(Text{"Fire Arrow", "Flèche de feu", "Flecha de fuego"}, ITEMTYPE_ITEM, GI_ARROW_FIRE, true, &FireArrows, FIRE_ARROWS);
itemTable[ICE_ARROWS] = Item(Text{"Ice Arrow", "Flèche de glace", "Flecha de hielo"}, ITEMTYPE_ITEM, GI_ARROW_ICE, true, &IceArrows, ICE_ARROWS);
itemTable[LIGHT_ARROWS] = Item(Text{"Light Arrow", "Flèche de lumière", "Flecha de luz"}, ITEMTYPE_ITEM, GI_ARROW_LIGHT, true, &LightArrows, LIGHT_ARROWS);
itemTable[GERUDO_TOKEN] = Item(Text{"Gerudo Token", "Carte Gerudo", "Pase de socio gerudo"}, ITEMTYPE_ITEM, GI_GERUDO_CARD, true, &GerudoToken, GERUDO_TOKEN);
itemTable[MAGIC_BEAN] = Item(Text{"Magic Bean", "Haricot magique", "Habichuelas mágicas"}, ITEMTYPE_ITEM, GI_BEAN, true, &MagicBean, MAGIC_BEAN);
itemTable[MAGIC_BEAN_PACK] = Item(Text{"Magic Bean Pack", "Paquet de haricots magiques", "Lote de habichuelas mágicas"}, ITEMTYPE_ITEM, 0xC9, true, &MagicBeanPack, MAGIC_BEAN_PACK);
itemTable[DOUBLE_DEFENSE] = Item(Text{"Double Defense", "Double défence", "Doble poder defensivo"}, ITEMTYPE_ITEM, 0xB8, true, &DoubleDefense, DOUBLE_DEFENSE);
itemTable[WEIRD_EGG] = Item(Text{"Weird Egg", "Œuf curieux", "Huevo extraño"}, ITEMTYPE_ITEM, GI_WEIRD_EGG, true, &WeirdEgg, WEIRD_EGG);
// itemTable[CUCCO] = Item(Text{"Cucco", "Cocotte", "Cuco"}, ITEMTYPE_ITEM, GI_CUCCO, true, &Cucco, CUCCO);
itemTable[ZELDAS_LETTER] = Item(Text{"Zelda's Letter", "Lettre de Zelda", "Carta de Zelda"}, ITEMTYPE_ITEM, GI_LETTER_ZELDA, true, &ZeldasLetter, ZELDAS_LETTER);
// itemTable[KEATON_MASK] = Item(Text{"Keaton Mask", "Masque de Keaton", "Careta de Keaton"}, ITEMTYPE_ITEM, GI_MASK_KEATON, true, &KeatonMask, KEATON_MASK);
// itemTable[SKULL_MASK] = Item(Text{"Skull Mask", "Masque de mort", "Máscara de calavera"}, ITEMTYPE_ITEM, GI_MASK_SKULL, true, &SkullMask, SKULL_MASK);
// itemTable[SPOOKY_MASK] = Item(Text{"Spooky Mask", "Masque d'Effroi", "Máscara tenebrosa"}, ITEMTYPE_ITEM, GI_MASK_SPOOKY, true, &SpookyMask, SPOOKY_MASK);
// itemTable[BUNNY_HOOD] = Item(Text{"Bunny Hood", "Masque du lapin", "Capucha de conejo"}, ITEMTYPE_ITEM, GI_MASK_BUNNY, true, &BunnyHood, BUNNY_HOOD);
// itemTable[GORON_MASK] = Item(Text{"Goron Mask", "Masque Goron", "Máscara Goron"}, ITEMTYPE_ITEM, GI_MASK_GORON, true, &GoronMask, GORON_MASK);
// itemTable[ZORA_MASK] = Item(Text{"Zora Mask", "Masque Zora", "Máscara Zora"}, ITEMTYPE_ITEM, GI_MASK_ZORA, true, &ZoraMask, ZORA_MASK);
// itemTable[GERUDO_MASK] = Item(Text{"Gerudo Mask", "Masque Gerudo", "Máscara Gerudo"}, ITEMTYPE_ITEM, GI_MASK_GERUDO, true, &GerudoMask, GERUDO_MASK);
// itemTable[MASK_OF_TRUTH] = Item(Text{"Mask of Truth", "Masque de vérité", "Máscara de la Verdad"}, ITEMTYPE_ITEM, GI_MASK_MASK, true, &MaskofTruth, MASK_OF_TRUTH);
itemTable[POCKET_EGG] = Item(Text{"Pocket Egg", "Œuf de poche", "Huevo de bolsillo"}, ITEMTYPE_ITEM, GI_POCKET_EGG, true, &PocketEgg, POCKET_EGG);
// itemTable[POCKET_CUCCO] = Item(Text{"Pocket Cucco", "Cocotte de poche", "Cuco de bolsillo"}, ITEMTYPE_ITEM, GI_POCKET_CUCCO, true, &PocketCucco, POCKET_CUCCO);
itemTable[COJIRO] = Item(Text{"Cojiro", "P'tit Poulet", "Cojiro"}, ITEMTYPE_ITEM, GI_COJIRO, true, &Cojiro, COJIRO);
itemTable[ODD_MUSHROOM] = Item(Text{"Odd Mushroom", "Champignon suspect", "Champiñón extraño"}, ITEMTYPE_ITEM, GI_ODD_MUSHROOM, true, &OddMushroom, ODD_MUSHROOM);
itemTable[ODD_POULTICE] = Item(Text{"Odd Poultice", "Mixture suspecte", "Medicina rara"}, ITEMTYPE_ITEM, GI_ODD_POTION, true, &OddPoultice, ODD_POULTICE);
itemTable[POACHERS_SAW] = Item(Text{"Poacher's Saw", "Scie du chasseur", "Sierra del furtivo"}, ITEMTYPE_ITEM, GI_SAW, true, &PoachersSaw, POACHERS_SAW);
itemTable[BROKEN_SWORD] = Item(Text{"Broken Goron's Sword", "Épée brisée de Goron", "Espada goron rota"}, ITEMTYPE_ITEM, GI_SWORD_BROKEN, true, &BrokenSword, BROKEN_SWORD);
itemTable[PRESCRIPTION] = Item(Text{"Prescription", "Ordonnance", "Receta"}, ITEMTYPE_ITEM, GI_PRESCRIPTION, true, &Prescription, PRESCRIPTION);
itemTable[EYEBALL_FROG] = Item(Text{"Eyeball Frog", "Crapaud-qui-louche", "Rana de ojos saltones"}, ITEMTYPE_ITEM, GI_FROG, true, &EyeballFrog, EYEBALL_FROG);
itemTable[EYEDROPS] = Item(Text{"World's Finest Eyedrops", "Super gouttes", "Supergotas oculares"}, ITEMTYPE_ITEM, GI_EYEDROPS, true, &Eyedrops, EYEDROPS);
itemTable[CLAIM_CHECK] = Item(Text{"Claim Check", "Certificat", "Recibo"}, ITEMTYPE_ITEM, GI_CLAIM_CHECK, true, &ClaimCheck, CLAIM_CHECK);
itemTable[GOLD_SKULLTULA_TOKEN] = Item(Text{"Gold Skulltula Token", "Jeton de Skulltula dorée", "Símbolo de skulltula dorada"}, ITEMTYPE_TOKEN, GI_SKULL_TOKEN, true, &GoldSkulltulaTokens, GOLD_SKULLTULA_TOKEN);
//Progression Items
itemTable[PROGRESSIVE_HOOKSHOT] = Item(Text{"Progressive Hookshot", "Grappin (prog.)", "Gancho progresivo"}, ITEMTYPE_ITEM, 0x80, true, &ProgressiveHookshot, PROGRESSIVE_HOOKSHOT);
itemTable[PROGRESSIVE_STRENGTH] = Item(Text{"Progressive Strength Upgrade", "Amélioration de force (prog.)", "Fuerza progresiva"}, ITEMTYPE_ITEM, 0x81, true, &ProgressiveStrength, PROGRESSIVE_STRENGTH);
itemTable[PROGRESSIVE_BOMB_BAG] = Item(Text{"Progressive Bomb Bag", "Sac de bombes (prog.)", "Saco de bombas progresivo"}, ITEMTYPE_ITEM, 0x82, true, &ProgressiveBombBag, PROGRESSIVE_BOMB_BAG);
itemTable[PROGRESSIVE_BOW] = Item(Text{"Progressive Bow", "Arc (prog.)", "Arco progresivo"}, ITEMTYPE_ITEM, 0x83, true, &ProgressiveBow, PROGRESSIVE_BOW);
itemTable[PROGRESSIVE_SLINGSHOT] = Item(Text{"Progressive Slingshot", "Lance-pierre (prog.)", "Resortera progresiva"}, ITEMTYPE_ITEM, 0x84, true, &ProgressiveBulletBag, PROGRESSIVE_SLINGSHOT);
itemTable[PROGRESSIVE_WALLET] = Item(Text{"Progressive Wallet", "Bourse (prog.)", "Bolsa de rupias progresiva"}, ITEMTYPE_ITEM, 0x85, true, &ProgressiveWallet, PROGRESSIVE_WALLET);
itemTable[PROGRESSIVE_SCALE] = Item(Text{"Progressive Scale", "Écaille (prog.)", "Escama progresiva"}, ITEMTYPE_ITEM, 0x86, true, &ProgressiveScale, PROGRESSIVE_SCALE);
itemTable[PROGRESSIVE_NUT_UPGRADE] = Item(Text{"Progressive Nut Capacity", "Capacité de noix (prog.)", "Capacidad de nueces deku progresiva"}, ITEMTYPE_ITEM, 0x87, false, &noVariable, PROGRESSIVE_NUT_UPGRADE);
itemTable[PROGRESSIVE_STICK_UPGRADE] = Item(Text{"Progressive Stick Capacity", "Capacité de bâtons (prog.)", "Capacidad de palos deku progresiva"}, ITEMTYPE_ITEM, 0x88, false, &noVariable, PROGRESSIVE_STICK_UPGRADE);
itemTable[PROGRESSIVE_BOMBCHUS] = Item(Text{"Progressive Bombchu", "Bombchus (prog.)", "Bombchus progresivos"}, ITEMTYPE_ITEM, 0x89, true, &Bombchus, PROGRESSIVE_BOMBCHUS);
itemTable[PROGRESSIVE_MAGIC_METER] = Item(Text{"Progressive Magic Meter", "Jauge de magie (prog.)", "Poder mágico progresivo"}, ITEMTYPE_ITEM, 0x8A, true, &ProgressiveMagic, PROGRESSIVE_MAGIC_METER);
itemTable[PROGRESSIVE_OCARINA] = Item(Text{"Progressive Ocarina", "Ocarina (prog.)", "Ocarina progresiva"}, ITEMTYPE_ITEM, 0x8B, true, &ProgressiveOcarina, PROGRESSIVE_OCARINA);
itemTable[PROGRESSIVE_GORONSWORD] = Item(Text{"Progressive Goron Sword", "Épée Goron (prog.)", "Espada Goron progresiva"}, ITEMTYPE_ITEM, 0xD4, true, &ProgressiveGiantKnife, PROGRESSIVE_GORONSWORD);
//Bottles
itemTable[EMPTY_BOTTLE] = Item(Text{"Empty Bottle", "Flacon vide", "Botella vacía"}, ITEMTYPE_ITEM, 0x0F, true, &Bottles, EMPTY_BOTTLE);
itemTable[BOTTLE_WITH_MILK] = Item(Text{"Bottle with Milk", "Flacon de lait", "Botella de leche Lon Lon"}, ITEMTYPE_ITEM, 0x14, true, &Bottles, BOTTLE_WITH_MILK);
itemTable[BOTTLE_WITH_RED_POTION] = Item(Text{"Bottle with Red Potion", "Flacon de potion rouge", "Botella de poción roja"}, ITEMTYPE_ITEM, 0x8C, true, &Bottles, BOTTLE_WITH_RED_POTION);
itemTable[BOTTLE_WITH_GREEN_POTION] = Item(Text{"Bottle with Green Potion", "Flacon de potion verte", "Botella de poción verde"}, ITEMTYPE_ITEM, 0x8D, true, &Bottles, BOTTLE_WITH_GREEN_POTION);
itemTable[BOTTLE_WITH_BLUE_POTION] = Item(Text{"Bottle with Blue Potion", "Flacon de potion bleue", "Botella de poción azul"}, ITEMTYPE_ITEM, 0x8E, true, &Bottles, BOTTLE_WITH_BLUE_POTION);
itemTable[BOTTLE_WITH_FAIRY] = Item(Text{"Bottle with Fairy", "Flacon avec une fée", "Hada en una botella"}, ITEMTYPE_ITEM, 0x8F, true, &Bottles, BOTTLE_WITH_FAIRY);
itemTable[BOTTLE_WITH_FISH] = Item(Text{"Bottle with Fish", "Flacon avec un poisson", "Pez en una botella"}, ITEMTYPE_ITEM, 0x90, true, &Bottles, BOTTLE_WITH_FISH);
itemTable[BOTTLE_WITH_BLUE_FIRE] = Item(Text{"Bottle with Blue Fire", "Flacon de flamme bleue", "Botella de fuego azul"}, ITEMTYPE_ITEM, 0x91, true, &Bottles, BOTTLE_WITH_BLUE_FIRE);
itemTable[BOTTLE_WITH_BUGS] = Item(Text{"Bottle with Bugs", "Flacon d'insectes", "Insecto en una botella"}, ITEMTYPE_ITEM, 0x92, true, &Bottles, BOTTLE_WITH_BUGS);
itemTable[BOTTLE_WITH_POE] = Item(Text{"Bottle with Poe", "Flacon avec un Esprit", "Poe en una botella"}, ITEMTYPE_ITEM, 0x94, true, &Bottles, BOTTLE_WITH_POE);
//Special bottles that can't immediately dump contents
itemTable[RUTOS_LETTER] = Item(Text{"Bottle with Ruto's Letter", "Flacon avec une lettre", "Carta de Ruto"}, ITEMTYPE_ITEM, 0x15, true, &RutosLetter, RUTOS_LETTER);
itemTable[BOTTLE_WITH_BIG_POE] = Item(Text{"Bottle with Big Poe", "Flacon avec une Âme", "Gran Poe en una botella"}, ITEMTYPE_ITEM, 0x93, true, &BottleWithBigPoe, BOTTLE_WITH_BIG_POE);
//Songs
itemTable[ZELDAS_LULLABY] = Item(Text{"Zelda's Lullaby", "Berceuse de Zelda", "Nana de Zelda"}, ITEMTYPE_SONG, 0xC1, true, &ZeldasLullaby, ZELDAS_LULLABY);
itemTable[EPONAS_SONG] = Item(Text{"Epona's Song", "Chant d'Épona", "Canción de Epona"}, ITEMTYPE_SONG, 0xC2, true, &EponasSong, EPONAS_SONG);
itemTable[SARIAS_SONG] = Item(Text{"Saria's Song", "Chant de Saria", "Canción de Saria"}, ITEMTYPE_SONG, 0xC3, true, &SariasSong, SARIAS_SONG);
itemTable[SUNS_SONG] = Item(Text{"Sun's Song", "Chant du soleil", "Canción del Sol"}, ITEMTYPE_SONG, 0xC4, true, &SunsSong, SUNS_SONG);
itemTable[SONG_OF_TIME] = Item(Text{"Song of Time", "Chant du temps", "Canción del tiempo"}, ITEMTYPE_SONG, 0xC5, true, &SongOfTime, SONG_OF_TIME);
itemTable[SONG_OF_STORMS] = Item(Text{"Song of Storms", "Chant des tempêtes", "Canción de la tormenta"}, ITEMTYPE_SONG, 0xC6, true, &SongOfStorms, SONG_OF_STORMS);
itemTable[MINUET_OF_FOREST] = Item(Text{"Minuet of Forest", "Menuet de la forêt", "Minueto del bosque"}, ITEMTYPE_SONG, 0xBB, true, &MinuetOfForest, MINUET_OF_FOREST);
itemTable[BOLERO_OF_FIRE] = Item(Text{"Bolero of Fire", "Boléro du feu", "Bolero del fuego"}, ITEMTYPE_SONG, 0xBC, true, &BoleroOfFire, BOLERO_OF_FIRE);
itemTable[SERENADE_OF_WATER] = Item(Text{"Serenade of Water", "Sérénade de l'eau", "Serenata del agua"}, ITEMTYPE_SONG, 0xBD, true, &SerenadeOfWater, SERENADE_OF_WATER);
itemTable[REQUIEM_OF_SPIRIT] = Item(Text{"Requiem of Spirit", "Requiem des esprits", "Réquiem del espíritu"}, ITEMTYPE_SONG, 0xBE, true, &RequiemOfSpirit, REQUIEM_OF_SPIRIT);
itemTable[NOCTURNE_OF_SHADOW] = Item(Text{"Nocturne of Shadow", "Nocturne de l'ombre", "Nocturno de la sombra"}, ITEMTYPE_SONG, 0xBF, true, &NocturneOfShadow, NOCTURNE_OF_SHADOW);
itemTable[PRELUDE_OF_LIGHT] = Item(Text{"Prelude of Light", "Prélude de la lumière", "Preludio de la luz"}, ITEMTYPE_SONG, 0xC0, true, &PreludeOfLight, PRELUDE_OF_LIGHT);
//Maps and Compasses
itemTable[DEKU_TREE_MAP] = Item(Text{"Great Deku Tree Map", "Carte de l'arbre Mojo", "Mapa del Gran Árbol Deku"}, ITEMTYPE_MAP, 0xA5, false, &noVariable, DEKU_TREE_MAP);
itemTable[DODONGOS_CAVERN_MAP] = Item(Text{"Dodongo's Cavern Map", "Carte de la grotte Dodongo", "Mapa de la Cueva de los Dodongos"}, ITEMTYPE_MAP, 0xA6, false, &noVariable, DODONGOS_CAVERN_MAP);
itemTable[JABU_JABUS_BELLY_MAP] = Item(Text{"Jabu-Jabu's Belly Map", "Carte de Jabu-Jabu", "Mapa de la tripa de Jabu-Jabu"}, ITEMTYPE_MAP, 0xA7, false, &noVariable, JABU_JABUS_BELLY_MAP);
itemTable[FOREST_TEMPLE_MAP] = Item(Text{"Forest Temple Map", "Carte du temple de la forêt", "Mapa del Templo del Bosque"}, ITEMTYPE_MAP, 0xA8, false, &noVariable, FOREST_TEMPLE_MAP);
itemTable[FIRE_TEMPLE_MAP] = Item(Text{"Fire Temple Map", "Carte du temple du feu", "Mapa del Templo del Fuego"}, ITEMTYPE_MAP, 0xA9, false, &noVariable, FIRE_TEMPLE_MAP);
itemTable[WATER_TEMPLE_MAP] = Item(Text{"Water Temple Map", "Carte du temple de l'eau", "Mapa del Templo del Agua"}, ITEMTYPE_MAP, 0xAA, false, &noVariable, WATER_TEMPLE_MAP);
itemTable[SPIRIT_TEMPLE_MAP] = Item(Text{"Spirit Temple Map", "Carte du temple de l'esprit", "Mapa del Templo del Espíritu"}, ITEMTYPE_MAP, 0xAB, false, &noVariable, SPIRIT_TEMPLE_MAP);
itemTable[SHADOW_TEMPLE_MAP] = Item(Text{"Shadow Temple Map", "Carte du temple de l'ombre", "Mapa del Templo de la Sombra"}, ITEMTYPE_MAP, 0xAC, false, &noVariable, SHADOW_TEMPLE_MAP);
itemTable[BOTTOM_OF_THE_WELL_MAP] = Item(Text{"Bottom of the Well Map", "Carte du fond du puits", "Mapa del fondo del pozo"}, ITEMTYPE_MAP, 0xAD, false, &noVariable, BOTTOM_OF_THE_WELL_MAP);
itemTable[ICE_CAVERN_MAP] = Item(Text{"Ice Cavern Map", "Carte de la caverne polaire", "Mapa de la caverna de hielo"}, ITEMTYPE_MAP, 0xAE, false, &noVariable, ICE_CAVERN_MAP);
itemTable[DEKU_TREE_COMPASS] = Item(Text{"Great Deku Tree Compass", "Boussole de l'arbre Mojo", "Brújula del Gran Árbol Deku"}, ITEMTYPE_COMPASS, 0x9B, false, &noVariable, DEKU_TREE_COMPASS);
itemTable[DODONGOS_CAVERN_COMPASS] = Item(Text{"Dodongo's Cavern Compass", "Boussole de la grotte Dodongo", "Brújula de la Cueva de los Dodongos"}, ITEMTYPE_COMPASS, 0x9C, false, &noVariable, DODONGOS_CAVERN_COMPASS);
itemTable[JABU_JABUS_BELLY_COMPASS] = Item(Text{"Jabu-Jabu's Belly Compass", "Boussole de Jabu-Jabu", "Brújula de la tripa de Jabu-Jabu"}, ITEMTYPE_COMPASS, 0x9D, false, &noVariable, JABU_JABUS_BELLY_COMPASS);
itemTable[FOREST_TEMPLE_COMPASS] = Item(Text{"Forest Temple Compass", "Boussole du temple de la forêt", "Brújula del Templo del Bosque"}, ITEMTYPE_COMPASS, 0x9E, false, &noVariable, FOREST_TEMPLE_COMPASS);
itemTable[FIRE_TEMPLE_COMPASS] = Item(Text{"Fire Temple Compass", "Boussole du temple du feu", "Brújula del Templo del Fuego"}, ITEMTYPE_COMPASS, 0x9F, false, &noVariable, FIRE_TEMPLE_COMPASS);
itemTable[WATER_TEMPLE_COMPASS] = Item(Text{"Water Temple Compass", "Boussole du temple de l'eau", "Brújula del Templo del Agua"}, ITEMTYPE_COMPASS, 0xA0, false, &noVariable, WATER_TEMPLE_COMPASS);
itemTable[SPIRIT_TEMPLE_COMPASS] = Item(Text{"Spirit Temple Compass", "Boussole du temple de l'esprit", "Brújula del Templo del Espíritu"}, ITEMTYPE_COMPASS, 0xA1, false, &noVariable, SPIRIT_TEMPLE_COMPASS);
itemTable[SHADOW_TEMPLE_COMPASS] = Item(Text{"Shadow Temple Compass", "Boussole du temple de l'ombre", "Brújula del Templo de las Sombras"}, ITEMTYPE_COMPASS, 0xA2, false, &noVariable, SHADOW_TEMPLE_COMPASS);
itemTable[BOTTOM_OF_THE_WELL_COMPASS] = Item(Text{"Bottom of the Well Compass", "Boussole du fond du puits", "Brújula del fondo del pozo"}, ITEMTYPE_COMPASS, 0xA3, false, &noVariable, BOTTOM_OF_THE_WELL_COMPASS);
itemTable[ICE_CAVERN_COMPASS] = Item(Text{"Ice Cavern Compass", "Boussole de la caverne polaire", "Brújula de la caverna de hielo"}, ITEMTYPE_COMPASS, 0xA4, false, &noVariable, ICE_CAVERN_COMPASS);
//Boss Keys
itemTable[FOREST_TEMPLE_BOSS_KEY] = Item(Text{"Forest Temple Big Key", "Clé d'or du temple de la forêt", "Gran llave del Templo del Bosque"}, ITEMTYPE_BOSSKEY, 0x95, true, &BossKeyForestTemple, FOREST_TEMPLE_BOSS_KEY);
itemTable[FIRE_TEMPLE_BOSS_KEY] = Item(Text{"Fire Temple Big Key", "Clé d'or du temple du feu", "Gran llave del Templo del Fuego"}, ITEMTYPE_BOSSKEY, 0x96, true, &BossKeyFireTemple, FIRE_TEMPLE_BOSS_KEY);
itemTable[WATER_TEMPLE_BOSS_KEY] = Item(Text{"Water Temple Big Key", "Clé d'or du temple de l'eau", "Gran llave del Templo del Agua"}, ITEMTYPE_BOSSKEY, 0x97, true, &BossKeyWaterTemple, WATER_TEMPLE_BOSS_KEY);
itemTable[SPIRIT_TEMPLE_BOSS_KEY] = Item(Text{"Spirit Temple Big Key", "Clé d'or du temple de l'esprit", "Gran llave del Templo del Espíritu"}, ITEMTYPE_BOSSKEY, 0x98, true, &BossKeySpiritTemple, SPIRIT_TEMPLE_BOSS_KEY);
itemTable[SHADOW_TEMPLE_BOSS_KEY] = Item(Text{"Shadow Temple Big Key", "Clé d'or du temple de l'ombre", "Gran llave del Templo de las Sombras"}, ITEMTYPE_BOSSKEY, 0x99, true, &BossKeyShadowTemple, SHADOW_TEMPLE_BOSS_KEY);
itemTable[GANONS_CASTLE_BOSS_KEY] = Item(Text{"Ganon's Castle Big Key", "Clé d'or du château de Ganon", "Gran llave del Castillo de Ganon"}, ITEMTYPE_BOSSKEY, 0x9A, true, &BossKeyGanonsCastle, GANONS_CASTLE_BOSS_KEY);
//Small Keys
itemTable[FOREST_TEMPLE_SMALL_KEY] = Item(Text{"Forest Temple Small Key", "Petite clé du temple de la forêt", "Llave del Templo del Bosque"}, ITEMTYPE_SMALLKEY, 0xAF, true, &ForestTempleKeys, FOREST_TEMPLE_SMALL_KEY);
itemTable[FIRE_TEMPLE_SMALL_KEY] = Item(Text{"Fire Temple Small Key", "Petite clé du temple du feu", "Llave del Templo del Fuego"}, ITEMTYPE_SMALLKEY, 0xB0, true, &FireTempleKeys, FIRE_TEMPLE_SMALL_KEY);
itemTable[WATER_TEMPLE_SMALL_KEY] = Item(Text{"Water Temple Small Key", "Petite clé du temple de l'eau", "Llave del Templo del Agua"}, ITEMTYPE_SMALLKEY, 0xB1, true, &WaterTempleKeys, WATER_TEMPLE_SMALL_KEY);
itemTable[SPIRIT_TEMPLE_SMALL_KEY] = Item(Text{"Spirit Temple Small Key", "Petite clé du temple de l'esprit", "Llave del Templo del Espíritu"}, ITEMTYPE_SMALLKEY, 0xB2, true, &SpiritTempleKeys, SPIRIT_TEMPLE_SMALL_KEY);
itemTable[SHADOW_TEMPLE_SMALL_KEY] = Item(Text{"Shadow Temple Small Key", "Petite clé du temple de l'ombre", "Llave del Templo de las Sombras"}, ITEMTYPE_SMALLKEY, 0xB3, true, &ShadowTempleKeys, SHADOW_TEMPLE_SMALL_KEY);
itemTable[BOTTOM_OF_THE_WELL_SMALL_KEY] = Item(Text{"Bottom of the Well Small Key", "Petite clé du fond du puits", "Llave del fondo del pozo"}, ITEMTYPE_SMALLKEY, 0xB4, true, &BottomOfTheWellKeys, BOTTOM_OF_THE_WELL_SMALL_KEY);
itemTable[GERUDO_TRAINING_GROUNDS_SMALL_KEY] = Item(Text{"Training Grounds Small Key", "Petite clé du gymnase Gerudo", "Llave del Centro de Instrucción"}, ITEMTYPE_SMALLKEY, 0xB5, true, &GerudoTrainingGroundsKeys, GERUDO_TRAINING_GROUNDS_SMALL_KEY);
itemTable[GERUDO_FORTRESS_SMALL_KEY] = Item(Text{"Gerudo Fortress Small Key", "Petite clé du repaire Gerudo", "Llave de la Fortaleza Gerudo"}, ITEMTYPE_FORTRESS_SMALLKEY, 0xB6, true, &GerudoFortressKeys, GERUDO_FORTRESS_SMALL_KEY);
itemTable[GANONS_CASTLE_SMALL_KEY] = Item(Text{"Ganon's Castle Small Key", "Petite clé du château de Ganon", "Llave del Castillo de Ganon"}, ITEMTYPE_SMALLKEY, 0xB7, true, &GanonsCastleKeys, GANONS_CASTLE_SMALL_KEY);
itemTable[TREASURE_GAME_SMALL_KEY] = Item(Text{"Chest Game Small Key", "Clé de la chasse-aux-trésors", "Llave del Cofre del Tesoro"}, ITEMTYPE_ITEM, 0xDE, true, &TreasureGameKeys, TREASURE_GAME_SMALL_KEY);
// Key Rings
itemTable[FOREST_TEMPLE_KEY_RING] = Item(Text{"Forest Temple Key Ring", "Trousseau du temple de la forêt", "Llavero del Templo del Bosque"}, ITEMTYPE_SMALLKEY, 0xD5, true, &ForestTempleKeys, FOREST_TEMPLE_KEY_RING);
itemTable[FIRE_TEMPLE_KEY_RING] = Item(Text{"Fire Temple Key Ring", "Trousseau du temple du feu", "Llavero del Templo del Fuego"}, ITEMTYPE_SMALLKEY, 0xD6, true, &FireTempleKeys, FIRE_TEMPLE_KEY_RING);
itemTable[WATER_TEMPLE_KEY_RING] = Item(Text{"Water Temple Key Ring", "Trousseau du temple de l'eau", "Llavero del Templo del Agua"}, ITEMTYPE_SMALLKEY, 0xD7, true, &WaterTempleKeys, WATER_TEMPLE_KEY_RING);
itemTable[SPIRIT_TEMPLE_KEY_RING] = Item(Text{"Spirit Temple Key Ring", "Trousseau du temple de l'esprit", "Llavero del Templo del Espíritu"}, ITEMTYPE_SMALLKEY, 0xD8, true, &SpiritTempleKeys, SPIRIT_TEMPLE_KEY_RING);
itemTable[SHADOW_TEMPLE_KEY_RING] = Item(Text{"Shadow Temple Key Ring", "Trousseau du temple de l'ombre", "Llavero del Templo de las Sombras"}, ITEMTYPE_SMALLKEY, 0xD9, true, &ShadowTempleKeys, SHADOW_TEMPLE_KEY_RING);
itemTable[BOTTOM_OF_THE_WELL_KEY_RING] = Item(Text{"Bottom of the Well Key Ring", "Trousseau du fond du puits", "Llavero del fondo del pozo"}, ITEMTYPE_SMALLKEY, 0xDA, true, &BottomOfTheWellKeys, BOTTOM_OF_THE_WELL_KEY_RING);
itemTable[GERUDO_TRAINING_GROUNDS_KEY_RING] = Item(Text{"Training Grounds Key Ring", "Trousseau du gymnase Gerudo", "Llavero del Centro de Instrucción"}, ITEMTYPE_SMALLKEY, 0xDB, true, &GerudoTrainingGroundsKeys, GERUDO_TRAINING_GROUNDS_KEY_RING);
itemTable[GERUDO_FORTRESS_KEY_RING] = Item(Text{"Gerudo Fortress Key Ring", "Trousseau du repaire Gerudo", "Llavero de la Fortaleza Gerudo"}, ITEMTYPE_FORTRESS_SMALLKEY, 0xDC, true, &GerudoFortressKeys, GERUDO_FORTRESS_KEY_RING);
itemTable[GANONS_CASTLE_KEY_RING] = Item(Text{"Ganon's Castle Key Ring", "Trousseau du château de Ganon", "Llavero del Castillo de Ganon"}, ITEMTYPE_SMALLKEY, 0xDD, true, &GanonsCastleKeys, GANONS_CASTLE_KEY_RING);
//Stones and Medallions
itemTable[KOKIRI_EMERALD] = Item(Text{"Kokiri's Emerald", "Émeraude Kokiri", "Esmeralda de los Kokiri"}, ITEMTYPE_DUNGEONREWARD, 0xCB, true, &KokiriEmerald, KOKIRI_EMERALD);
itemTable[GORON_RUBY] = Item(Text{"Goron's Ruby", "Rubis Goron", "Rubí de los Goron"}, ITEMTYPE_DUNGEONREWARD, 0xCC, true, &GoronRuby, GORON_RUBY);
itemTable[ZORA_SAPPHIRE] = Item(Text{"Zora's Sapphire", "Saphir Zora", "Zafiro de los Zora"}, ITEMTYPE_DUNGEONREWARD, 0xCD, true, &ZoraSapphire, ZORA_SAPPHIRE);
itemTable[FOREST_MEDALLION] = Item(Text{"Forest Medallion", "Médaillon de la forêt", "Medallón del Bosque"}, ITEMTYPE_DUNGEONREWARD, 0xCE, true, &ForestMedallion, FOREST_MEDALLION);
itemTable[FIRE_MEDALLION] = Item(Text{"Fire Medallion", "Médaillon du feu", "Medallón del Fuego"}, ITEMTYPE_DUNGEONREWARD, 0xCF, true, &FireMedallion, FIRE_MEDALLION);
itemTable[WATER_MEDALLION] = Item(Text{"Water Medallion", "Médaillon de l'eau", "Medallón del Agua"}, ITEMTYPE_DUNGEONREWARD, 0xD0, true, &WaterMedallion, WATER_MEDALLION);
itemTable[SPIRIT_MEDALLION] = Item(Text{"Spirit Medallion", "Médaillon de l'esprit", "Medallón del Espíritu"}, ITEMTYPE_DUNGEONREWARD, 0xD1, true, &SpiritMedallion, SPIRIT_MEDALLION);
itemTable[SHADOW_MEDALLION] = Item(Text{"Shadow Medallion", "Médaillon de l'ombre", "Medallón de la Sombra"}, ITEMTYPE_DUNGEONREWARD, 0xD2, true, &ShadowMedallion, SHADOW_MEDALLION);
itemTable[LIGHT_MEDALLION] = Item(Text{"Light Medallion", "Médaillon de la lumière", "Medallón de la Luz"}, ITEMTYPE_DUNGEONREWARD, 0xD3, true, &LightMedallion, LIGHT_MEDALLION);
//Generic Items
itemTable[RECOVERY_HEART] = Item(Text{"Recovery Heart", "Cœur d'énergie", "Corazón"}, ITEMTYPE_ITEM, GI_HEART, false, &noVariable, RECOVERY_HEART);
itemTable[GREEN_RUPEE] = Item(Text{"Green Rupee", "Rubis vert", "Rupia verde"}, ITEMTYPE_ITEM, GI_RUPEE_GREEN, false, &noVariable, GREEN_RUPEE);
itemTable[BLUE_RUPEE] = Item(Text{"Blue Rupee", "Rubis bleu", "Rupia azul"}, ITEMTYPE_ITEM, GI_RUPEE_BLUE, false, &noVariable, BLUE_RUPEE);
itemTable[RED_RUPEE] = Item(Text{"Red Rupee", "Rubis rouge", "Rupia roja"}, ITEMTYPE_ITEM, GI_RUPEE_RED, false, &noVariable, RED_RUPEE);
itemTable[PURPLE_RUPEE] = Item(Text{"Purple Rupee", "Rubis pourpre", "Rupia morada"}, ITEMTYPE_ITEM, GI_RUPEE_PURPLE, false, &noVariable, PURPLE_RUPEE);
itemTable[HUGE_RUPEE] = Item(Text{"Huge Rupee", "Énorme rubis", "Rupia gigante"}, ITEMTYPE_ITEM, GI_RUPEE_GOLD, false, &noVariable, HUGE_RUPEE);
itemTable[PIECE_OF_HEART] = Item(Text{"Piece of Heart", "Quart de cœur", "Pieza de corazón"}, ITEMTYPE_ITEM, GI_HEART_PIECE, true, &PieceOfHeart, PIECE_OF_HEART);
itemTable[HEART_CONTAINER] = Item(Text{"Heart Container", "Réceptacle de cœur", "Contenedor de corazón"}, ITEMTYPE_ITEM, GI_HEART_CONTAINER_2, true, &HeartContainer, HEART_CONTAINER);
itemTable[ICE_TRAP] = Item(Text{"Ice Trap", "Piège de glace", "Trampa de hielo"}, ITEMTYPE_ITEM, GI_ICE_TRAP, false, &noVariable, ICE_TRAP);
itemTable[MILK] = Item(Text{"Milk", "Lait", "Leche Lon Lon"}, ITEMTYPE_ITEM, GI_MILK, false, &noVariable, NONE);
//Refills
itemTable[BOMBS_5] = Item(Text{"Bombs (5)", "Bombes (5)", "Bombas (5)"}, ITEMTYPE_REFILL, GI_BOMBS_5, false, &noVariable, BOMBS_5);
itemTable[BOMBS_10] = Item(Text{"Bombs (10)", "Bombes (10)", "Bombas (10)"}, ITEMTYPE_REFILL, GI_BOMBS_10, false, &noVariable, BOMBS_10);
itemTable[BOMBS_20] = Item(Text{"Bombs (20)", "Bombes (20)", "Bombas (20)"}, ITEMTYPE_REFILL, GI_BOMBS_20, false, &noVariable, BOMBS_20);
itemTable[BOMBCHU_5] = Item(Text{"Bombchu (5)", "Bombchus (5)", "Bombchus (5)"}, ITEMTYPE_REFILL, GI_BOMBCHUS_5, true, &Bombchus5, BOMBCHU_5);
itemTable[BOMBCHU_10] = Item(Text{"Bombchu (10)", "Bombchus (10)", "Bombchus (10)"}, ITEMTYPE_REFILL, GI_BOMBCHUS_10, true, &Bombchus10, BOMBCHU_10);
itemTable[BOMBCHU_20] = Item(Text{"Bombchu (20)", "Bombchus (20)", "Bombchus (20)"}, ITEMTYPE_REFILL, GI_BOMBCHUS_20, true, &Bombchus20, BOMBCHU_20);
itemTable[BOMBCHU_DROP] = Item(Text{"Bombchu Drop", "Bombchus", "Bombchus"}, ITEMTYPE_DROP, GI_BOMBCHUS_10, true, &BombchuDrop, NONE);
itemTable[ARROWS_5] = Item(Text{"Arrows (5)", "Flèches (5)", "Flechas (5)"}, ITEMTYPE_REFILL, GI_ARROWS_SMALL, false, &noVariable, ARROWS_5);
itemTable[ARROWS_10] = Item(Text{"Arrows (10)", "Flèches (10)", "Flechas (10)"}, ITEMTYPE_REFILL, GI_ARROWS_MEDIUM, false, &noVariable, ARROWS_10);
itemTable[ARROWS_30] = Item(Text{"Arrows (30)", "Flèches (30)", "Flechas (30)"}, ITEMTYPE_REFILL, GI_ARROWS_LARGE, false, &noVariable, ARROWS_30);
itemTable[DEKU_NUTS_5] = Item(Text{"Deku Nuts (5)", "Noix Mojo (5)", "Nueces deku (5)"}, ITEMTYPE_REFILL, GI_NUTS_5, false, &noVariable, DEKU_NUTS_5);
itemTable[DEKU_NUTS_10] = Item(Text{"Deku Nuts (10)", "Noix Mojo (10)", "Nueces deku (10)"}, ITEMTYPE_REFILL, GI_NUTS_10, false, &noVariable, DEKU_NUTS_10);
itemTable[DEKU_SEEDS_30] = Item(Text{"Deku Seeds (30)", "Graines Mojo (30)", "Semillas deku (30)"}, ITEMTYPE_REFILL, GI_SEEDS_30, false, &noVariable, DEKU_SEEDS_30);
itemTable[DEKU_STICK_1] = Item(Text{"Deku Stick (1)", "Bâton Mojo (1)", "Palo deku (1)"}, ITEMTYPE_REFILL, GI_STICKS_1, false, &noVariable, DEKU_STICK_1);
itemTable[RED_POTION_REFILL] = Item(Text{"Red Potion Refill", "Potion rouge", "Recarga de poción roja"}, ITEMTYPE_REFILL, GI_POTION_RED, false, &noVariable, NONE);
itemTable[GREEN_POTION_REFILL] = Item(Text{"Green Potion Refill", "Potion verte", "Recarga de poción verde"}, ITEMTYPE_REFILL, GI_POTION_GREEN, false, &noVariable, NONE);
itemTable[BLUE_POTION_REFILL] = Item(Text{"Blue Potion Refill", "Potion bleue", "Recarga de poción azul"}, ITEMTYPE_REFILL, GI_POTION_BLUE, false, &noVariable, NONE);
//Treasure Game
itemTable[TREASURE_GAME_HEART] = Item(Text{"Piece of Heart (Treasure Chest Minigame)", "Quart de cœur (Chasse-aux-trésors)", "Pieza de corazón (Cofre del Tesoro)"}, ITEMTYPE_ITEM, GI_HEART_PIECE_WIN, true, &PieceOfHeart, TREASURE_GAME_HEART);
itemTable[TREASURE_GAME_GREEN_RUPEE] = Item(Text{"Green Rupee (Treasure Chest Minigame)", "Rubis vert (Chasse-aux-trésors)", "Rupia Verde (Cofre del Tesoro)"}, ITEMTYPE_ITEM, GI_RUPEE_GREEN_LOSE, false, &noVariable, TREASURE_GAME_GREEN_RUPEE);
//Shop Items price
itemTable[BUY_DEKU_NUT_5] = Item(Text{"Buy Deku Nut (5)", "Acheter: Noix Mojo (5)", "Comprar nueces deku (5)"}, ITEMTYPE_SHOP, 0x00, true, &Nuts, DEKU_NUTS_5, 15);
itemTable[BUY_ARROWS_30] = Item(Text{"Buy Arrows (30)", "Acheter: Flèches (30)", "Comprar flechas (30)"}, ITEMTYPE_SHOP, 0x01, true, &BuyArrow, ARROWS_30, 60);
itemTable[BUY_ARROWS_50] = Item(Text{"Buy Arrows (50)", "Acheter: Flèches (50)", "Comprar flechas (50)"}, ITEMTYPE_SHOP, 0x02, true, &BuyArrow, ARROWS_30, 90);
itemTable[BUY_BOMBS_525] = Item(Text{"Buy Bombs (5) [25]", "Acheter: Bombes (5) [25]", "Comprar bombas (5) [25]"}, ITEMTYPE_SHOP, 0x03, true, &BuyBomb, BOMBS_5, 25);
itemTable[BUY_DEKU_NUT_10] = Item(Text{"Buy Deku Nut (10)", "Acheter: Noix Mojo (10)", "Comprar Nueces deku (10)"}, ITEMTYPE_SHOP, 0x04, true, &Nuts, DEKU_NUTS_10, 30);
itemTable[BUY_DEKU_STICK_1] = Item(Text{"Buy Deku Stick (1)", "Acheter: Bâton Mojo (1)", "Comprar palos deku (1)"}, ITEMTYPE_SHOP, 0x05, true, &Sticks, DEKU_STICK_1, 10);
itemTable[BUY_BOMBS_10] = Item(Text{"Buy Bombs (10)", "Acheter: Bombes (10)", "Comprar Bombas (10)"}, ITEMTYPE_SHOP, 0x06, true, &BuyBomb, BOMBS_10, 50);
itemTable[BUY_FISH] = Item(Text{"Buy Fish", "Acheter: Poisson", "Comprar pez"}, ITEMTYPE_SHOP, 0x07, true, &FishAccess, BOTTLE_WITH_FISH, 200);
itemTable[BUY_RED_POTION_30] = Item(Text{"Buy Red Potion [30]", "Acheter: Potion rouge [30]", "Comprar poción roja [30]"}, ITEMTYPE_SHOP, 0x08, false, &noVariable, BOTTLE_WITH_RED_POTION, 30);
itemTable[BUY_GREEN_POTION] = Item(Text{"Buy Green Potion", "Acheter: Potion verte", "Comprar poción verde"}, ITEMTYPE_SHOP, 0x09, true, &BuyGPotion, BOTTLE_WITH_GREEN_POTION, 30);
itemTable[BUY_BLUE_POTION] = Item(Text{"Buy Blue Potion", "Acheter: Potion bleue", "Comprar poción azul"}, ITEMTYPE_SHOP, 0x0A, true, &BuyBPotion, BOTTLE_WITH_BLUE_POTION, 100);
itemTable[BUY_HYLIAN_SHIELD] = Item(Text{"Buy Hylian Shield", "Acheter: Bouclier Hylien", "Comprar escudo hyliano"}, ITEMTYPE_SHOP, 0x0C, true, &HylianShield, HYLIAN_SHIELD, 80);
itemTable[BUY_DEKU_SHIELD] = Item(Text{"Buy Deku Shield", "Acheter: Bouclier Mojo", "Comprar escudo deku"}, ITEMTYPE_SHOP, 0x0D, true, &DekuShield, DEKU_SHIELD, 40);
itemTable[BUY_GORON_TUNIC] = Item(Text{"Buy Goron Tunic", "Acheter: Tunique Goron", "Comprar sayo goron"}, ITEMTYPE_SHOP, 0x0E, true, &GoronTunic, GORON_TUNIC, 200);
itemTable[BUY_ZORA_TUNIC] = Item(Text{"Buy Zora Tunic", "Acheter: Tunique Zora", "Comprar sayo zora"}, ITEMTYPE_SHOP, 0x0F, true, &ZoraTunic, ZORA_TUNIC, 300);
itemTable[BUY_HEART] = Item(Text{"Buy Heart", "Acheter: Coeur d'énergie", "Comprar corazón"}, ITEMTYPE_SHOP, 0x10, false, &noVariable, RECOVERY_HEART, 10);
itemTable[BUY_BOMBCHU_10] = Item(Text{"Buy Bombchu (10)", "Acheter: Bombchus (10)", "Comprar bombchus (10)"}, ITEMTYPE_SHOP, 0x15, true, &BuyBombchus10, BOMBCHU_10, 99);
itemTable[BUY_BOMBCHU_20] = Item(Text{"Buy Bombchu (20)", "Acheter: Bombchus (20)", "Comprar bombchus (20)"}, ITEMTYPE_SHOP, 0x16, true, &BuyBombchus20, BOMBCHU_20, 180);
itemTable[BUY_BOMBCHU_5] = Item(Text{"Buy Bombchu (5)", "Acheter: Bombchus (5)", "Comprar bombchus (5)"}, ITEMTYPE_SHOP, 0x18, true, &BuyBombchus5, BOMBCHU_5, 60);
itemTable[BUY_DEKU_SEEDS_30] = Item(Text{"Buy Deku Seeds (30)", "Acheter: Graines Mojo (30)", "Comprar semillas deku (30)"}, ITEMTYPE_SHOP, 0x1D, true, &BuySeed, DEKU_SEEDS_30, 30);
itemTable[SOLD_OUT] = Item(Text{"Sold Out", "Vendu", "Vendido"}, ITEMTYPE_SHOP, 0x26, false, &noVariable, NONE, 0);
itemTable[BUY_BLUE_FIRE] = Item(Text{"Buy Blue Fire", "Acheter: Flamme bleue", "Comprar fuego azul"}, ITEMTYPE_SHOP, 0x27, true, &BlueFireAccess, BOTTLE_WITH_BLUE_FIRE, 300);
itemTable[BUY_BOTTLE_BUG] = Item(Text{"Buy Bottle Bug", "Acheter: Insectes en flacon", "Comprar bichos"}, ITEMTYPE_SHOP, 0x28, true, &BugsAccess, BOTTLE_WITH_BUGS, 50);
itemTable[BUY_POE] = Item(Text{"Buy Poe", "Acheter: Esprit", "Comprar Poe"}, ITEMTYPE_SHOP, 0x2A, false, &noVariable, BOTTLE_WITH_BIG_POE, 30);
itemTable[BUY_FAIRYS_SPIRIT] = Item(Text{"Buy Fairy's Spirit", "Acheter: Fée", "Comprar hada"}, ITEMTYPE_SHOP, 0x2B, true, &FairyAccess, BOTTLE_WITH_FAIRY, 50);
itemTable[BUY_ARROWS_10] = Item(Text{"Buy Arrows (10)", "Acheter: Flèches (10)", "Comprar flechas (10)"}, ITEMTYPE_SHOP, 0x2C, true, &BuyArrow, ARROWS_10, 20);
itemTable[BUY_BOMBS_20] = Item(Text{"Buy Bombs (20)", "Acheter: Bombes (20)", "Comprar bombas (20)"}, ITEMTYPE_SHOP, 0x2D, true, &BuyBomb, BOMBS_20, 80);
itemTable[BUY_BOMBS_30] = Item(Text{"Buy Bombs (30)", "Acheter: Bombes (30)", "Comprar bombas (30)"}, ITEMTYPE_SHOP, 0x2E, true, &BuyBomb, BOMBS_20, 120);
itemTable[BUY_BOMBS_535] = Item(Text{"Buy Bombs (5) [35]", "Acheter: Bombes (5) [35]", "Comprar bombas (5) [35]"}, ITEMTYPE_SHOP, 0x2F, true, &BuyBomb, BOMBS_5, 35);
itemTable[BUY_RED_POTION_40] = Item(Text{"Buy Red Potion [40]", "Acheter: Potion rouge [40]", "Comprar poción roja [40]"}, ITEMTYPE_SHOP, 0x30, false, &noVariable, BOTTLE_WITH_RED_POTION, 40);
itemTable[BUY_RED_POTION_50] = Item(Text{"Buy Red Potion [50]", "Acheter: Potion rouge [50]", "Comprar poción roja [50]"}, ITEMTYPE_SHOP, 0x31, false, &noVariable, BOTTLE_WITH_RED_POTION, 50);
itemTable[TRIFORCE] = Item(Text{"Triforce", "Triforce", "Trifuerza"}, ITEMTYPE_EVENT, GI_RUPEE_RED_LOSE, false, &noVariable, NONE);
itemTable[HINT] = Item(Text{"Hint", "Indice", "Pista"}, ITEMTYPE_EVENT, GI_RUPEE_BLUE_LOSE, false, &noVariable, NONE);
// itemTable[HOOKSHOT] = Item(Text{"Hookshot", "Grappin", "Gancho"}, ITEMTYPE_ITEM, 0x80, true, &ProgressiveHookshot, HOOKSHOT);
// itemTable[LONGSHOT] = Item(Text{"Longshot", "Super-Grappin", "Supergancho"}, ITEMTYPE_ITEM, 0x80, true, &ProgressiveHookshot, LONGSHOT);
// itemTable[FAIRY_OCARINA] = Item(Text{"Fairy Ocarina", "Ocarina des fées", "Ocarina de las Hadas"}, ITEMTYPE_ITEM, 0x8B, true, &ProgressiveOcarina, FAIRY_OCARINA);
// itemTable[OCARINA_OF_TIME] = Item(Text{"Ocarina of Time", "Ocarina du temps", "Ocarina del Tiempo"}, ITEMTYPE_ITEM, 0x8B, true, &ProgressiveOcarina, OCARINA_OF_TIME);
// itemTable[BOMB_BAG] = Item(Text{"Bomb Bag", "Sac de bombes", "Saco de bombas"}, ITEMTYPE_ITEM, 0x82, true, &ProgressiveBombBag, BOMB_BAG);
// itemTable[BIG_BOMB_BAG] = Item(Text{"Big Bomb Bag", "Grand sac de bombes", "Saco de bombas grande"}, ITEMTYPE_ITEM, 0x82, true, &ProgressiveBombBag, BIG_BOMB_BAG);
// itemTable[BIGGEST_BOMB_BAG] = Item(Text{"Biggest Bomb Bag", "Énorme sac de bombes", "Saco de bombas gigante"}, ITEMTYPE_ITEM, 0x82, true, &ProgressiveBombBag, BIGGEST_BOMB_BAG);
// itemTable[FAIRY_BOW] = Item(Text{"Fairy Bow", "Arc des fées", "Arco de las Hadas"}, ITEMTYPE_ITEM, 0x83, true, &ProgressiveBow, FAIRY_BOW);
// itemTable[BIG_QUIVER] = Item(Text{"Big Quiver", "Grand carquois", "Carcaj grande"}, ITEMTYPE_ITEM, 0x83, true, &ProgressiveBow, BIG_QUIVER);
// itemTable[BIGGEST_QUIVER] = Item(Text{"Biggest Quiver", "Énorme carquois", "Carcaj gigante"}, ITEMTYPE_ITEM, 0x83, true, &ProgressiveBow, BIGGEST_QUIVER);
// itemTable[FAIRY_SLINGSHOT] = Item(Text{"Fairy Slingshot", "Lance-pierre des fées", "Resortera de las hadas"}, ITEMTYPE_ITEM, 0x84, true, &ProgressiveBulletBag, FAIRY_SLINGSHOT);
// itemTable[BIG_BULLET_BAG] = Item(Text{"Big Deku Seed Bullet Bag", "Grand sac de graines mojo", "Bolsa de semillas deku grande"}, ITEMTYPE_ITEM, 0x84, true, &ProgressiveBulletBag, BIG_BULLET_BAG);
// itemTable[BIGGEST_BULLET_BAD] = Item(Text{"Biggest Deku Seed Bullet Bag", "Énorme sac de graines mojo", "Bolsa de semillas deku gigante"}, ITEMTYPE_ITEM, 0x84, true, &ProgressiveBulletBag, BIGGEST_BULLET_BAD);
// itemTable[GORONS_BRACELET] = Item(Text{"Goron's Bracelet", "Bracelet Goron", "Brazalete de los Goron"}, ITEMTYPE_ITEM, 0x81, true, &ProgressiveStrength, GORONS_BRACELET);
// itemTable[SILVER_GAUNTLETS] = Item(Text{"Silver Gauntlets", "Gantelets d'argent", "Guantes de plata"}, ITEMTYPE_ITEM, 0x81, true, &ProgressiveStrength, SILVER_GAUNTLETS);
// itemTable[GOLDEN_GAUNTLETS] = Item(Text{"Golden Gauntlets", "Gantelets d'or", "Guantes de oro"}, ITEMTYPE_ITEM, 0x81, true, &ProgressiveStrength, GOLDEN_GAUNTLETS);
// itemTable[SILVER_SCALE] = Item(Text{"Silver Scale", "Écaille d'argent", "Escama de Plata"}, ITEMTYPE_ITEM, 0x86, true, &ProgressiveScale, SILVER_SCALE);
// itemTable[GOLDEN_SCALE] = Item(Text{"Golden Scale", "Écaille d'or", "Escama de Oro"}, ITEMTYPE_ITEM, 0x86, true, &ProgressiveScale, GOLDEN_SCALE);
// itemTable[ADULT_WALLET] = Item(Text{"Adult Wallet", "Bourse d'adulte", "Bolsa de adulto"}, ITEMTYPE_ITEM, 0x85, true, &ProgressiveWallet, ADULT_WALLET);
// itemTable[GIANT_WALLET] = Item(Text{"Giant Wallet", "Bourse de géant", "Bolsa gigante"}, ITEMTYPE_ITEM, 0x85, true, &ProgressiveWallet, GIANT_WALLET);
// itemTable[TYCOON_WALLET] = Item(Text{"Tycoon Wallet", "Bourse de star", "Bolsa de ricachón"}, ITEMTYPE_ITEM, 0x85, true, &ProgressiveWallet, TYCOON_WALLET);
// itemTable[DEKU_NUT_CAPACITY_30] = Item(Text{"Deku Nut Capacity (30)", "Capacité de noix Mojo (20)", "Capacidad de nueces deku (40)"}, ITEMTYPE_ITEM, 0x87, false, &noVariable, DEKU_NUT_CAPACITY_30);
// itemTable[DEKU_NUT_CAPACITY_40] = Item(Text{"Deku Nut Capacity (40)", "Capacité de noix Mojo (30)", "Capacidad de nueces deku (50)"}, ITEMTYPE_ITEM, 0x87, false, &noVariable, DEKU_NUT_CAPACITY_40);
// itemTable[DEKU_NUT_CAPACITY_20] = Item(Text{"Deku Stick Capacity (20)", "Capacité de bâtons Mojo (20)", "Capacidad de palos deku (20)"}, ITEMTYPE_ITEM, 0x88, false, &noVariable, DEKU_NUT_CAPACITY_20);
// itemTable[DEKU_NUT_CAPACITY_30] = Item(Text{"Deku Nut Capacity (30)", "Capacité de noix Mojo (20)", "Capacidad de nueces deku (40)"}, ITEMTYPE_ITEM, 0x88, false, &noVariable, DEKU_NUT_CAPACITY_30);
// itemTable[MAGIC_METER] = Item(Text{"Magic Meter", "Jauge de magie", "Poder mágico"}, ITEMTYPE_ITEM, 0x8A, true, &ProgressiveMagic, MAGIC_METER);
// itemTable[ENHANCED_MAGIC_METER] = Item(Text{"Enhanced Magic Meter", "Jauge de magie améliorée", "Poder mágico mejorado"}, ITEMTYPE_ITEM, 0x8A, true, &ProgressiveMagic, ENHANCED_MAGIC_METER);
}
Item& ItemTable(const uint32_t itemKey) {
return itemTable[itemKey];
}
//This function should only be used to place items containing hint text
//at gossip stone locations.
void NewItem(const uint32_t itemKey, const Item item) {
if (itemKey <= BUY_RED_POTION_50) {
printf("\x1b[25;0HWARNING: ATTEMPTED TO OVERWRITE ITEM %lu\n", itemKey);
return;
}
itemTable[itemKey] = item;
}

View file

@ -0,0 +1,8 @@
#pragma once
#include "item.hpp"
#include "keys.hpp"
void ItemTable_Init();
Item& ItemTable(uint32_t itemKey);
void NewItem(uint32_t itemKey, Item item);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,481 @@
#pragma once
#include <algorithm>
#include <array>
#include <cstdio>
#include <functional>
#include <set>
#include <string>
#include <vector>
#include "spoiler_log.hpp"
#include "item_list.hpp"
enum ItemOverride_Type {
OVR_BASE_ITEM = 0,
OVR_CHEST = 1,
OVR_COLLECTABLE = 2,
OVR_SKULL = 3,
OVR_GROTTO_SCRUB = 4,
OVR_DELAYED = 5,
OVR_TEMPLE = 6,
};
typedef enum {
DUNGEON_DEKU_TREE = 0,
DUNGEON_DODONGOS_CAVERN,
DUNGEON_JABUJABUS_BELLY,
DUNGEON_FOREST_TEMPLE,
DUNGEON_FIRE_TEMPLE,
DUNGEON_WATER_TEMPLE,
DUNGEON_SPIRIT_TEMPLE,
DUNGEON_SHADOW_TEMPLE,
DUNGEON_BOTTOM_OF_THE_WELL,
DUNGEON_ICE_CAVERN,
DUNGEON_GANONS_CASTLE_SECOND_PART,
DUNGEON_GERUDO_TRAINING_GROUNDS,
DUNGEON_GERUDO_FORTRESS,
DUNGEON_GANONS_CASTLE_FIRST_PART,
DUNGEON_GANONS_CASTLE_FLOOR_BENEATH_BOSS_CHAMBER,
DUNGEON_GANONS_CASTLE_CRUMBLING,
DUNGEON_TREASURE_CHEST_SHOP,
DUNGEON_DEKU_TREE_BOSS_ROOM,
DUNGEON_DODONGOS_CAVERN_BOSS_ROOM,
DUNGEON_JABUJABUS_BELLY_BOSS_ROOM,
} DungeonId;
typedef union ItemOverride_Key {
uint32_t all;
struct {
char pad_;
uint8_t scene;
uint8_t type;
uint8_t flag;
};
} ItemOverride_Key;
typedef union ItemOverride_Value {
uint32_t all;
struct {
uint16_t itemId;
uint8_t player;
uint8_t looksLikeItemId;
};
} ItemOverride_Value;
typedef struct ItemOverride {
ItemOverride_Key key;
ItemOverride_Value value;
} ItemOverride;
class Entrance;
enum class ItemLocationType {
Base,
Chest,
Collectable,
GSToken,
GrottoScrub,
Delayed,
TempleReward,
HintStone,
OtherHint,
};
class SpoilerCollectionCheck {
public:
SpoilerCollectionCheckType type = SpoilerCollectionCheckType::SPOILER_CHK_NONE;
uint8_t scene = 0;
uint8_t flag = 0;
SpoilerCollectionCheck() {}
SpoilerCollectionCheck(SpoilerCollectionCheckType type_, uint8_t scene_, uint8_t flag_) : type(type_), scene(scene_), flag(flag_) {}
static auto None() {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_NONE, 0x00, 0x00);
}
static auto AlwaysCollected() {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_ALWAYS_COLLECTED, 0x00, 0x00);
}
static auto ItemGetInf(uint8_t slot) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_ITEM_GET_INF, 0x00, slot);
}
static auto EventChkInf(uint8_t flag) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_EVENT_CHK_INF, 0xFF, flag);
}
static auto InfTable(uint8_t offset, uint8_t bit) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_INF_TABLE, offset, bit);
}
static auto Collectable(uint8_t scene, uint8_t flag) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE, scene, flag);
}
static auto Chest(uint8_t scene, uint8_t flag) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_CHEST, scene, flag);
}
static auto Cow(uint8_t scene, uint8_t flag) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_COW, scene, flag);
}
static auto Fishing(uint8_t bit) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_MINIGAME, 0x00, bit);
}
static auto Scrub(uint8_t scene, uint8_t bit) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_SCRUB, scene, bit);
}
static auto Biggoron(uint8_t mask) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_BIGGORON, 0x00, mask);
}
static auto GerudoToken() {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_GERUDO_TOKEN, 0x00, 0x00);
}
static auto BigPoePoints() {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_POE_POINTS, 0x00, 0x00);
}
static auto ShopItem(uint8_t scene, uint8_t itemSlot) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_SHOP_ITEM, scene, itemSlot);
}
static auto MagicBeans(uint8_t scene, uint8_t flag) {
return SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_MAGIC_BEANS, scene, flag);
}
};
class ItemLocation {
public:
ItemLocation() = default;
ItemLocation(uint8_t scene_, ItemLocationType type_, uint8_t flag_, std::string name_, uint32_t hintKey_, uint32_t vanillaItem_, std::vector<Category> categories_, uint16_t price_ = 0, SpoilerCollectionCheck collectionCheck_ = SpoilerCollectionCheck(), SpoilerCollectionCheckGroup collectionCheckGroup_ = SpoilerCollectionCheckGroup::GROUP_NO_GROUP)
: scene(scene_), type(type_), flag(flag_), name(std::move(name_)), hintKey(hintKey_), vanillaItem(vanillaItem_), categories(std::move(categories_)), price(price_), collectionCheck(collectionCheck_), collectionCheckGroup(collectionCheckGroup_) {}
ItemOverride_Key Key() const {
ItemOverride_Key key;
key.all = 0;
key.scene = scene;
key.type = static_cast<uint8_t>(type); //TODO make sure these match up
key.flag = flag;
return key;
}
SpoilerCollectionCheck GetCollectionCheck() const {
return collectionCheck;
}
SpoilerCollectionCheckGroup GetCollectionCheckGroup() const {
return collectionCheckGroup;
}
uint8_t GetScene() const {
return scene;
}
uint8_t GetFlag() const {
return flag;
}
bool IsAddedToPool() const {
return addedToPool;
}
void AddToPool() {
addedToPool = true;
}
const uint32_t GetHintKey() const {
return hintKey;
}
void RemoveFromPool() {
addedToPool = false;
}
const std::string& GetName() const {
return name;
}
const Text& GetPlacedItemName() const {
return ItemTable(placedItem).GetName();
}
const Item& GetPlacedItem() const {
return ItemTable(placedItem);
}
uint32_t GetPlacedItemKey() const {
return placedItem;
}
uint32_t GetPlaceduint32_t() const {
return placedItem;
}
void SetPlacedItem(const uint32_t item) {
placedItem = item;
SetPrice(ItemTable(placedItem).GetPrice());
}
//Saves an item to be set as placedItem later
void SetDelayedItem(const uint32_t item) {
delayedItem = item;
}
//Place the vanilla item in this location
void PlaceVanillaItem() {
placedItem = vanillaItem;
}
void ApplyPlacedItemEffect() {
ItemTable(placedItem).ApplyEffect();
}
//Set placedItem as item saved in SetDelayedItem
void SaveDelayedItem() {
placedItem = delayedItem;
delayedItem = NONE;
}
uint16_t GetPrice() const {
if (ItemTable(placedItem).GetItemType() == ITEMTYPE_SHOP) {
return ItemTable(placedItem).GetPrice();
}
return price;
}
void SetPrice(uint16_t price_) {
//don't override price if the price was set for shopsanity
if (hasShopsanityPrice) {
return;
}
price = price_;
}
void SetShopsanityPrice(uint16_t price_) {
price = price_;
hasShopsanityPrice = true;
}
bool HasShopsanityPrice() const {
return hasShopsanityPrice;
}
bool IsExcluded() const {
return excludedOption.Value<bool>();
}
bool IsCategory(Category category) const {
return std::any_of(categories.begin(), categories.end(),
[category](auto entry) { return entry == category; });
}
bool IsDungeon() const {
return (type != ItemLocationType::GSToken && (scene < 0x0E || (scene > 0x10 && scene < 0x1A))) || (type == ItemLocationType::GSToken && scene < 0x0A);
}
bool IsOverworld() const {
return !IsDungeon();
}
bool IsShop() const {
return (scene >= 0x2C && scene <= 0x32);
}
Option * GetExcludedOption() {
return &excludedOption;
}
const uint32_t Getuint32_t() const {
return hintKey;
}
const HintText& GetHint() const {
return Hint(hintKey);
}
bool IsHintedAt() const {
return hintedAt;
}
void SetAsHinted() {
hintedAt = true;
}
bool IsHidden() const {
return hidden;
}
void SetHidden(const bool hidden_) {
hidden = hidden_;
}
bool IsHintable() const {
return isHintable;
}
void SetAsHintable() {
isHintable = true;
}
void SetParentRegion(uint32_t region) {
parentRegion = region;
}
uint32_t GetParentRegionKey() const {
return parentRegion;
}
void AddExcludeOption() {
//setting description /*--------------------------------------------------*/
std::string_view desc = "Decide which locations you want to exclude from\n"
"the location pool. Locations that require an item\n"
"to be placed at them based on your current\n"
"settings cannot be excluded and won't be shown\n"
"unless you change your settings.\n"
"\n"
"If you exclude too many locations, it might not\n"
"be possible to fill the world.";
//add option to forbid any location from progress items
if (name.length() < 23) {
excludedOption = Option::Bool(name, {"Include", "Exclude"}, {desc});
} else {
//insert a newline character if the text is too long for one row
size_t lastSpace = name.rfind(' ', 23);
std::string settingText = name;
settingText.replace(lastSpace, 1, "\n ");
excludedOption = Option::Bool(settingText, {"Include", "Exclude"}, {desc});
}
Settings::excludeLocationsOptionsVector[collectionCheckGroup].push_back(&excludedOption);
}
static auto Base(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheck collectionCheck = SpoilerCollectionCheck(), SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::Base, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, collectionCheck, collectionCheckGroup};
}
static auto Chest(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::Chest, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_CHEST, scene, flag), collectionCheckGroup};
}
static auto Chest(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheck collectionCheck, SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::Chest, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, collectionCheck, collectionCheckGroup};
}
static auto Collectable(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::Collectable, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE, scene, flag), collectionCheckGroup};
}
static auto Collectable(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheck collectionCheck, SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::Collectable, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, collectionCheck, collectionCheckGroup};
}
static auto GSToken(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, std::vector<Category>&& categories, SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::GSToken, flag, std::move(name), hintKey, GOLD_SKULLTULA_TOKEN, std::move(categories), 0, SpoilerCollectionCheck(SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA, scene, flag), collectionCheckGroup};
}
static auto GrottoScrub(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheck collectionCheck = SpoilerCollectionCheck(), SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::GrottoScrub, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, collectionCheck, collectionCheckGroup};
}
static auto Delayed(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheck collectionCheck = SpoilerCollectionCheck(), SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::Delayed, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, collectionCheck, collectionCheckGroup};
}
static auto Reward(uint8_t scene, uint8_t flag, std::string&& name, const uint32_t hintKey, const uint32_t vanillaItem, std::vector<Category>&& categories, SpoilerCollectionCheck collectionCheck = SpoilerCollectionCheck(), SpoilerCollectionCheckGroup collectionCheckGroup = SpoilerCollectionCheckGroup::GROUP_NO_GROUP) {
return ItemLocation{scene, ItemLocationType::TempleReward, flag, std::move(name), hintKey, vanillaItem, std::move(categories), 0, collectionCheck, collectionCheckGroup};
}
static auto OtherHint(uint8_t scene, uint8_t flag, std::string&& name, std::vector<Category>&& categories) {
return ItemLocation{scene, ItemLocationType::OtherHint, flag, std::move(name), NONE, NONE, std::move(categories)};
}
static auto HintStone(uint8_t scene, uint8_t flag, std::string&& name, std::vector<Category>&& categories) {
return ItemLocation{scene, ItemLocationType::HintStone, flag, std::move(name), NONE, NONE, std::move(categories)};
}
void ResetVariables() {
checked = false;
addedToPool = false;
placedItem = NONE;
delayedItem = NONE;
hintedAt = false;
isHintable = false;
price = 0;
hasShopsanityPrice = false;
hidden = false;
}
private:
uint8_t scene;
ItemLocationType type;
uint8_t flag;
bool checked = false;
std::string name;
uint32_t hintKey = NONE;
uint32_t vanillaItem = NONE;
bool hintedAt = false;
std::vector<Category> categories;
bool addedToPool = false;
uint32_t placedItem = NONE;
uint32_t delayedItem = NONE;
Option excludedOption = Option::Bool(name, {"Include", "Exclude"}, {"", ""});
uint16_t price = 0;
SpoilerCollectionCheck collectionCheck;
SpoilerCollectionCheckGroup collectionCheckGroup;
bool isHintable = false;
uint32_t parentRegion = NONE;
bool hasShopsanityPrice = false;
bool hidden = false;
};
class ItemOverride_Compare {
public:
bool operator()(ItemOverride lhs, ItemOverride rhs) const {
return lhs.key.all < rhs.key.all;
}
};
void LocationTable_Init();
ItemLocation* Location(uint32_t locKey);
extern std::vector<std::vector<uint32_t>> ShopLocationLists;
extern std::vector<uint32_t> gossipStoneLocations;
extern std::vector<uint32_t> dungeonRewardLocations;
extern std::vector<uint32_t> overworldLocations;
extern std::vector<uint32_t> allLocations;
extern std::vector<uint32_t> everyPossibleLocation;
//set of overrides to write to the patch
extern std::set<ItemOverride, ItemOverride_Compare> overrides;
extern std::vector<std::vector<uint32_t>> playthroughLocations;
extern std::vector<uint32_t> wothLocations;
extern bool playthroughBeatable;
extern bool allLocationsReachable;
extern bool showItemProgress;
extern uint16_t itemsPlaced;
void GenerateLocationPool();
void PlaceItemInLocation(uint32_t loc, uint32_t item, bool applyEffectImmediately = false, bool setHidden = false);
std::vector<uint32_t> GetLocations(const std::vector<uint32_t>& locationPool, Category categoryInclude,
Category categoryExclude = Category::cNull);
void LocationReset();
void ItemReset();
void HintReset();
void AddExcludedOptions();
void CreateItemOverrides();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
#pragma once
#include "keys.hpp"
#include <cstddef>
#include <vector>
#include <stdint.h>
class ItemLocation;
void AddItemToPool(std::vector<uint32_t>& pool, const uint32_t item, size_t count = 1);
uint32_t GetJunkItem();
void PlaceJunkInExcludedLocation(const uint32_t il);
void GenerateItemPool();
void AddJunk();
extern std::vector<uint32_t> ItemPool;
extern std::vector<uint8_t> IceTrapModels;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,231 @@
#pragma once
#include <string>
#include <vector>
#include <list>
#include "logic.hpp"
#include "hint_list.hpp"
#include "keys.hpp"
#include "fill.hpp"
typedef bool (*ConditionFn)();
class EventAccess {
public:
explicit EventAccess(bool* event_, std::vector<ConditionFn> conditions_met_)
: event(event_) {
conditions_met.resize(2);
for (size_t i = 0; i < conditions_met_.size(); i++) {
conditions_met[i] = conditions_met_[i];
}
}
bool ConditionsMet() const {
if (Settings::Logic.Is(LOGIC_NONE) || Settings::Logic.Is(LOGIC_VANILLA)) {
return true;
} else if (Settings::Logic.Is(LOGIC_GLITCHLESS)) {
return conditions_met[0]();
} else if (Settings::Logic.Is(LOGIC_GLITCHED)) {
if (conditions_met[0]()) {
return true;
} else if (conditions_met[1] != NULL) {
return conditions_met[1]();
}
}
return false;
}
bool CheckConditionAtAgeTime(bool& age, bool& time) {
Logic::IsChild = false;
Logic::IsAdult = false;
Logic::AtDay = false;
Logic::AtNight = false;
time = true;
age = true;
Logic::UpdateHelpers();
return ConditionsMet();
}
void EventOccurred() {
*event = true;
}
bool GetEvent() const {
return *event;
}
private:
bool* event;
std::vector<ConditionFn> conditions_met;
};
//this class is meant to hold an item location with a boolean function to determine its accessibility from a specific area
class LocationAccess {
public:
explicit LocationAccess(uint32_t location_, std::vector<ConditionFn> conditions_met_)
: location(location_) {
conditions_met.resize(2);
for (size_t i = 0; i < conditions_met_.size(); i++) {
conditions_met[i] = conditions_met_[i];
}
}
bool GetConditionsMet() const {
if (Settings::Logic.Is(LOGIC_NONE) || Settings::Logic.Is(LOGIC_VANILLA)) {
return true;
} else if (Settings::Logic.Is(LOGIC_GLITCHLESS)) {
return conditions_met[0]();
} else if (Settings::Logic.Is(LOGIC_GLITCHED)) {
if (conditions_met[0]()) {
return true;
} else if (conditions_met[1] != NULL) {
return conditions_met[1]();
}
}
return false;
}
bool CheckConditionAtAgeTime(bool& age, bool& time) const;
bool ConditionsMet() const;
uint32_t GetLocation() const {
return location;
}
private:
uint32_t location;
std::vector<ConditionFn> conditions_met;
//Makes sure shop locations are buyable
bool CanBuy() const;
};
class Entrance;
enum class EntranceType;
class Area {
public:
Area();
Area(std::string regionName_, std::string scene_, uint32_t hintKey_,
bool timePass_,
std::vector<EventAccess> events_,
std::vector<LocationAccess> locations_,
std::list<Entrance> exits_);
~Area();
std::string regionName;
std::string scene;
uint32_t hintKey;
bool timePass;
std::vector<EventAccess> events;
std::vector<LocationAccess> locations;
std::list<Entrance> exits;
std::list<Entrance*> entrances;
//^ The above exits are now stored in a list instead of a vector because
//the entrance randomization algorithm plays around with pointers to these
//entrances a lot. By putting the entrances in a list, we don't have to
//worry about a vector potentially reallocating itself and invalidating all our
//entrance pointers.
bool childDay = false;
bool childNight = false;
bool adultDay = false;
bool adultNight = false;
bool addedToPool = false;
bool UpdateEvents(SearchMode mode);
void AddExit(uint32_t parentKey, uint32_t newExitKey, ConditionFn condition);
void RemoveExit(Entrance* exitToRemove);
void SetAsPrimary(uint32_t exitToBePrimary);
Entrance* GetExit(uint32_t exit);
bool Child() const {
return childDay || childNight;
}
bool Adult() const {
return adultDay || adultNight;
}
bool BothAgesCheck() const {
return Child() && Adult();
}
bool HasAccess() const {
return Child() || Adult();
}
bool AllAccess() const {
return childDay && childNight && adultDay && adultNight;
}
//Check to see if an exit can be access as both ages at both times of day
bool CheckAllAccess(uint32_t exitKey);
const HintText& GetHint() const {
return Hint(hintKey);
}
//Here checks conditional access based on whether or not both ages have
//access to this area. For example: if there are rocks that block a path
//which both child and adult can access, adult having hammer can give
//both child and adult access to the path.
bool HereCheck(ConditionFn condition) {
//store current age variables
bool pastAdult = Logic::IsAdult;
bool pastChild = Logic::IsChild;
//set age access as this areas ages
Logic::IsChild = Child();
Logic::IsAdult = Adult();
//update helpers and check condition as well as having at least child or adult access
Logic::UpdateHelpers();
bool hereVal = condition() && (Logic::IsAdult || Logic::IsChild);
//set back age variables
Logic::IsChild = pastChild;
Logic::IsAdult = pastAdult;
Logic::UpdateHelpers();
return hereVal;
}
bool CanPlantBeanCheck() const;
bool AllAccountedFor() const;
void ResetVariables();
void printAgeTimeAccess() const {
auto message = "Child Day: " + std::to_string(childDay) + "\t"
"Child Night: " + std::to_string(childNight) + "\t"
"Adult Day: " + std::to_string(adultDay) + "\t"
"Adult Night: " + std::to_string(adultNight);
CitraPrint(message);
}
};
namespace Areas {
extern void AccessReset();
extern void ResetAllLocations();
extern bool HasTimePassAccess(uint8_t age);
extern void DumpWorldGraph(std::string str);
} //namespace Exits
void AreaTable_Init();
Area* AreaTable(const uint32_t areaKey);
std::vector<Entrance*> GetShuffleableEntrances(EntranceType type, bool onlyPrimary = true);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,387 @@
#pragma once
#include "keys.hpp"
#include <cstdint>
namespace Logic {
extern bool noVariable;
// Child item logic
extern bool KokiriSword;
extern bool Slingshot;
extern bool ZeldasLetter;
extern bool WeirdEgg;
extern bool HasBottle;
extern bool BombBag;
extern bool Bombchus;
extern bool Bombchus5;
extern bool Bombchus10;
extern bool Bombchus20;
extern bool MagicBean;
extern bool MagicBeanPack;
extern bool RutosLetter;
extern bool Boomerang;
extern bool DinsFire;
extern bool FaroresWind;
extern bool NayrusLove;
extern bool LensOfTruth;
extern bool ShardOfAgony;
extern bool SkullMask;
extern bool MaskOfTruth;
// Adult logic
extern bool Bow;
extern bool Hammer;
extern bool IronBoots;
extern bool HoverBoots;
extern bool MirrorShield;
extern bool GoronTunic;
extern bool ZoraTunic;
extern bool Epona;
extern bool BigPoe;
extern bool GerudoToken;
extern bool FireArrows;
extern bool IceArrows;
extern bool LightArrows;
extern bool MasterSword;
extern bool BiggoronSword;
// Trade Quest
extern bool PocketEgg;
extern bool Cojiro;
extern bool OddMushroom;
extern bool OddPoultice;
extern bool PoachersSaw;
extern bool BrokenSword;
extern bool Prescription;
extern bool EyeballFrog;
extern bool Eyedrops;
extern bool ClaimCheck;
// Trade Quest Events
extern bool WakeUpAdultTalon;
extern bool CojiroAccess;
extern bool OddMushroomAccess;
extern bool OddPoulticeAccess;
extern bool PoachersSawAccess;
extern bool BrokenSwordAccess;
extern bool PrescriptionAccess;
extern bool EyeballFrogAccess;
extern bool EyedropsAccess;
extern bool DisableTradeRevert;
// Songs
extern bool ZeldasLullaby;
extern bool SariasSong;
extern bool SunsSong;
extern bool SongOfStorms;
extern bool EponasSong;
extern bool SongOfTime;
extern bool MinuetOfForest;
extern bool BoleroOfFire;
extern bool SerenadeOfWater;
extern bool RequiemOfSpirit;
extern bool NocturneOfShadow;
extern bool PreludeOfLight;
// Stones and Meddallions
extern bool ForestMedallion;
extern bool FireMedallion;
extern bool WaterMedallion;
extern bool SpiritMedallion;
extern bool ShadowMedallion;
extern bool LightMedallion;
extern bool KokiriEmerald;
extern bool GoronRuby;
extern bool ZoraSapphire;
// Dungeon Clears
extern bool DekuTreeClear;
extern bool DodongosCavernClear;
extern bool JabuJabusBellyClear;
extern bool ForestTempleClear;
extern bool FireTempleClear;
extern bool WaterTempleClear;
extern bool SpiritTempleClear;
extern bool ShadowTempleClear;
// Trial Clears
extern bool ForestTrialClear;
extern bool FireTrialClear;
extern bool WaterTrialClear;
extern bool SpiritTrialClear;
extern bool ShadowTrialClear;
extern bool LightTrialClear;
// Progression Items
extern uint8_t ProgressiveBulletBag;
extern uint8_t ProgressiveBombBag;
extern uint8_t ProgressiveScale;
extern uint8_t ProgressiveHookshot;
extern uint8_t ProgressiveBow;
extern uint8_t ProgressiveStrength;
extern uint8_t ProgressiveWallet;
extern uint8_t ProgressiveMagic;
extern uint8_t ProgressiveOcarina;
extern uint8_t ProgressiveGiantKnife;
// Keysanity
extern bool IsKeysanity;
// Keys
extern uint8_t ForestTempleKeys;
extern uint8_t FireTempleKeys;
extern uint8_t WaterTempleKeys;
extern uint8_t SpiritTempleKeys;
extern uint8_t ShadowTempleKeys;
extern uint8_t BottomOfTheWellKeys;
extern uint8_t GerudoTrainingGroundsKeys;
extern uint8_t GerudoFortressKeys;
extern uint8_t GanonsCastleKeys;
extern uint8_t TreasureGameKeys;
// Boss Keys
extern bool BossKeyForestTemple;
extern bool BossKeyFireTemple;
extern bool BossKeyWaterTemple;
extern bool BossKeySpiritTemple;
extern bool BossKeyShadowTemple;
extern bool BossKeyGanonsCastle;
// Gold Skulltula Count
extern uint8_t GoldSkulltulaTokens;
// Bottle Count, with and without Ruto's Letter
extern uint8_t Bottles;
extern uint8_t NumBottles;
extern bool NoBottles;
// item and bottle drops
extern bool DekuNutDrop;
extern bool NutPot;
extern bool NutCrate;
extern bool DekuBabaNuts;
extern bool DekuStickDrop;
extern bool StickPot;
extern bool DekuBabaSticks;
extern bool BugsAccess;
extern bool BugShrub;
extern bool WanderingBugs;
extern bool BugRock;
extern bool BlueFireAccess;
extern bool FishAccess;
extern bool FishGroup;
extern bool LoneFish;
extern bool FairyAccess;
extern bool GossipStoneFairy;
extern bool BeanPlantFairy;
extern bool ButterflyFairy;
extern bool FairyPot;
extern bool FreeFairies;
extern bool FairyPond;
extern bool BombchuDrop;
extern bool BuyBombchus5;
extern bool BuyBombchus10;
extern bool BuyBombchus20;
extern bool BuyArrow;
extern bool BuyBomb;
extern bool BuyGPotion;
extern bool BuyBPotion;
extern bool BuySeed;
extern bool MagicRefill;
extern uint8_t PieceOfHeart;
extern uint8_t HeartContainer;
extern bool DoubleDefense;
/* --- HELPERS --- */
/* These are used to simplify reading the logic, but need to be updated
/ every time a base value is updated. */
extern bool Ocarina;
extern bool OcarinaOfTime;
extern bool MagicMeter;
extern bool Hookshot;
extern bool Longshot;
extern bool GoronBracelet;
extern bool SilverGauntlets;
extern bool GoldenGauntlets;
extern bool SilverScale;
extern bool GoldScale;
extern bool AdultsWallet;
extern bool ChildScarecrow;
extern bool AdultScarecrow;
extern bool ScarecrowSong;
extern bool Scarecrow;
extern bool DistantScarecrow;
extern bool Bombs;
extern bool DekuShield;
extern bool HylianShield;
extern bool Nuts;
extern bool Sticks;
extern bool Bugs;
extern bool BlueFire;
extern bool Fish;
extern bool Fairy;
extern bool BottleWithBigPoe;
extern bool Bombs;
extern bool FoundBombchus;
extern bool CanPlayBowling;
extern bool HasBombchus;
extern bool HasExplosives;
extern bool HasBoots;
extern bool IsChild;
extern bool IsAdult;
extern bool IsGlitched;
extern bool CanBlastOrSmash;
extern bool CanChildAttack;
extern bool CanChildDamage;
extern bool CanCutShrubs;
extern bool CanDive;
extern bool CanLeaveForest;
extern bool CanPlantBugs;
extern bool CanRideEpona;
extern bool CanStunDeku;
extern bool CanSummonGossipFairy;
extern bool CanSummonGossipFairyWithoutSuns;
extern bool NeedNayrusLove;
extern bool CanSurviveDamage;
extern bool CanTakeDamage;
extern bool CanTakeDamageTwice;
// extern bool CanPlantBean;
extern bool CanOpenBombGrotto;
extern bool CanOpenStormGrotto;
extern bool HookshotOrBoomerang;
extern bool CanGetNightTimeGS;
extern bool BigPoeKill;
extern uint8_t BaseHearts;
extern uint8_t Hearts;
extern uint8_t Multiplier;
extern uint8_t EffectiveHealth;
extern uint8_t FireTimer;
extern uint8_t WaterTimer;
extern bool GuaranteeTradePath;
extern bool GuaranteeHint;
extern bool HasFireSource;
extern bool HasFireSourceWithTorch;
// Gerudo Fortress
extern bool CanFinishGerudoFortress;
extern bool HasShield;
extern bool CanShield;
extern bool CanJumpslash;
extern bool CanUseProjectile;
extern bool CanUseMagicArrow;
// Bridge Requirements
extern bool HasAllStones;
extern bool HasAllMedallions;
extern bool CanBuildRainbowBridge;
extern bool CanTriggerLACS;
// Other
extern bool AtDay;
extern bool AtNight;
extern bool LinksCow;
extern uint8_t Age;
// Events
extern bool ShowedMidoSwordAndShield;
extern bool CarpenterRescue;
extern bool DampesWindmillAccess;
extern bool GF_GateOpen;
extern bool GtG_GateOpen;
extern bool DrainWell;
extern bool GoronCityChildFire;
extern bool GCWoodsWarpOpen;
extern bool GCDaruniasDoorOpenChild;
extern bool StopGCRollingGoronAsAdult;
extern bool WaterTempleLow;
extern bool WaterTempleMiddle;
extern bool WaterTempleHigh;
extern bool KingZoraThawed;
extern bool AtDampeTime;
extern bool DeliverLetter;
extern bool KakarikoVillageGateOpen;
extern bool ForestTempleJoelle;
extern bool ForestTempleBeth;
extern bool ForestTempleJoAndBeth;
extern bool ForestTempleAmy;
extern bool ForestTempleMeg;
extern bool ForestTempleAmyAndMeg;
extern bool FireLoopSwitch;
extern bool TimeTravel;
/* --- END OF HELPERS --- */
extern uint8_t AddedProgressiveBulletBags;
extern uint8_t AddedProgressiveBombBags;
extern uint8_t AddedProgressiveMagics;
extern uint8_t AddedProgressiveScales;
extern uint8_t AddedProgressiveHookshots;
extern uint8_t AddedProgressiveBows;
extern uint8_t AddedProgressiveWallets;
extern uint8_t AddedProgressiveStrengths;
extern uint8_t AddedProgressiveOcarinas;
extern uint8_t TokensInPool;
enum class HasProjectileAge {
Adult,
Child,
Both,
Either,
};
enum class GlitchType {
RestrictedItems,
SuperStab,
ISG,
BombHover,
BombOI,
OutdoorBombOI,
WindmillBombOI,
IndoorBombOI,
DungeonBombOI,
HoverBoost,
SuperSlide,
Megaflip,
ASlide,
HammerSlide,
LedgeCancel,
ActionSwap,
QPA,
HookshotClip,
HookshotJump_Bonk,
HookshotJump_Boots,
CutsceneDive,
NaviDive_Stick,
TripleSlashClip,
LedgeClip,
SeamWalk,
};
enum class GlitchDifficulty {
NOVICE = 1,
INTERMEDIATE,
ADVANCED,
EXPERT,
HERO,
};
void UpdateHelpers();
bool CanPlay(bool song);
bool CanUse(uint32_t itemName);
bool HasProjectile(HasProjectileAge age);
bool SmallKeys(Key dungeon, uint8_t requiredAmount);
bool SmallKeys(Key dungeon, uint8_t requiredAmountGlitchless, uint8_t requiredAmountGlitched);
bool CanDoGlitch(GlitchType glitch, GlitchDifficulty difficulty);
bool EventsUpdated();
void LogicReset();
} // namespace Logic

View file

@ -0,0 +1,549 @@
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>
#include "cosmetics.hpp"
#include "menu.hpp"
#include "patch.hpp"
#include "preset.hpp"
#include "randomizer.hpp"
#include "settings.hpp"
#include "spoiler_log.hpp"
#include "location_access.hpp"
#include "debug.hpp"
#include <Lib/spdlog/include/spdlog/spdlog.h>
namespace {
bool seedChanged;
uint16_t pastSeedLength;
std::vector<std::string> presetEntries;
std::vector<Menu*> menuList;
Option* currentSetting;
Menu* currentMenu;
} // namespace
void PrintTopScreen() {
SPDLOG_INFO("\x1b[2;11H%sOcarina of Time 3D Randomizer%s", CYAN, RESET);
SPDLOG_INFO("\x1b[3;18H%s%s-%s%s", CYAN, RANDOMIZER_VERSION, COMMIT_NUMBER, RESET);
SPDLOG_INFO("\x1b[4;10HA/B/D-pad: Navigate Menu\n");
SPDLOG_INFO(" Select: Exit to Homebrew Menu\n");
SPDLOG_INFO(" Y: New Random Seed\n");
SPDLOG_INFO(" X: Input Custom Seed\n");
SPDLOG_INFO("\x1b[11;7HCurrent Seed: %s", Settings::seed.c_str());
}
void MenuInit() {
Settings::InitSettings();
seedChanged = false;
pastSeedLength = Settings::seed.length();
Menu* main = new Menu("Main", MenuType::MainMenu, &Settings::mainMenu, MAIN_MENU);
menuList.push_back(main);
currentMenu = main;
srand(time(NULL));
if (!CreatePresetDirectories()) {
printf("\x1b[20;5Failed to create preset directories.");
printf("\x1b[21;5Loading presets might crash.");
}
// If cached presets exist, load them
LoadCachedSettings();
LoadCachedCosmetics();
// If Randomize all settings in a category is selected
// Re-randomize them
Settings::RandomizeAllSettings();
PrintTopScreen();
PrintMainMenu();
}
#define KEY_DUP 0
#define KEY_DDOWN 1
#define KEY_DLEFT 2
#define KEY_DRIGHT 3
#define KEY_A 4
#define KEY_B 5
#define KEY_Y 6
#define KEY_X 7
void MoveCursor(uint32_t kDown, bool updatedByHeld) {
// Option sub menus need special checking for locked options
if (currentMenu->mode == OPTION_SUB_MENU) {
// Cancel if holding and reached first/last selectable option
if (updatedByHeld) {
bool noSelectableOption = true;
if (kDown & KEY_DUP) {
for (int i = currentMenu->menuIdx - 1; i >= 0; i--) {
if (!currentMenu->settingsList->at(i)->IsHidden() &&
!currentMenu->settingsList->at(i)->IsLocked()) {
noSelectableOption = false;
break;
}
}
}
if (kDown & KEY_DDOWN) {
for (size_t i = currentMenu->menuIdx + 1; i < currentMenu->settingsList->size(); i++) {
if (!currentMenu->settingsList->at(i)->IsHidden() &&
!currentMenu->settingsList->at(i)->IsLocked()) {
noSelectableOption = false;
break;
}
}
}
if (noSelectableOption) {
return;
}
}
// Loop through settings until an unlocked one is reached
do {
if ((kDown & KEY_DUP) != 0) {
currentMenu->menuIdx--;
}
if ((kDown & KEY_DDOWN) != 0) {
currentMenu->menuIdx++;
}
// Bounds checking
if (currentMenu->menuIdx == currentMenu->settingsList->size()) {
currentMenu->menuIdx = 0;
} else if (currentMenu->menuIdx == 0xFFFF) {
currentMenu->menuIdx = static_cast<uint16_t>(currentMenu->settingsList->size() - 1);
}
currentSetting = currentMenu->settingsList->at(currentMenu->menuIdx);
} while (currentSetting->IsLocked() || currentSetting->IsHidden());
}
// All other menus except reset-to-defaults confirmation
else if (currentMenu->mode != RESET_TO_DEFAULTS) {
// Cancel if holding and reached first/last menu
if (updatedByHeld) {
if ((kDown & KEY_DUP && currentMenu->menuIdx == 0) ||
(kDown & KEY_DDOWN && currentMenu->menuIdx == currentMenu->itemsList->size() - 1)) {
return;
}
}
if (kDown & KEY_DUP) {
currentMenu->menuIdx--;
}
if (kDown & KEY_DDOWN) {
currentMenu->menuIdx++;
}
// Bounds checking
uint16_t max = -1;
if (currentMenu->mode == LOAD_PRESET || currentMenu->mode == DELETE_PRESET) { // Number of presets if applicable
max = presetEntries.size();
} else if (currentMenu->mode == GENERATE_MODE) { // Generate menu: 2 options
max = 2;
} else if (currentMenu->itemsList != nullptr) {
max = currentMenu->itemsList->size(); // Default max: Number of items in menu
}
if (currentMenu->menuIdx == max) {
currentMenu->menuIdx = 0;
} else if (currentMenu->menuIdx == 0xFFFF) {
currentMenu->menuIdx = max - 1;
}
// Scroll Check
if (currentMenu->menuIdx > currentMenu->settingBound + (MAX_SUBMENUS_ON_SCREEN - 1)) {
currentMenu->settingBound = currentMenu->menuIdx - (MAX_SUBMENUS_ON_SCREEN - 1);
} else if (currentMenu->menuIdx < currentMenu->settingBound) {
currentMenu->settingBound = currentMenu->menuIdx;
}
}
}
void MenuUpdate(uint32_t kDown, bool updatedByHeld) {
// Check for menu change
// If user pressed A on a non-option, non-action menu, they're navigating to a new menu
if (kDown & KEY_A && currentMenu->mode != OPTION_SUB_MENU && currentMenu->type != MenuType::Action) {
if (currentMenu->itemsList->size() > currentMenu->menuIdx) {
Menu* newMenu;
newMenu = currentMenu->itemsList->at(currentMenu->menuIdx);
menuList.push_back(newMenu);
currentMenu = menuList.back();
ModeChangeInit();
kDown = 0;
}
// If they pressed B on any menu other than main, go backwards to the previous menu
} else if (kDown & KEY_B && currentMenu->mode != MAIN_MENU) {
// Want to reset generate menu when leaving
if (currentMenu->mode == POST_GENERATE) {
currentMenu->mode = GENERATE_MODE;
}
PrintTopScreen();
menuList.pop_back();
currentMenu = menuList.back();
ModeChangeInit();
kDown = 0;
}
if (currentMenu->mode != GENERATE_MODE) {
// New Random Seed
if (kDown & KEY_Y) {
pastSeedLength = Settings::seed.length();
Settings::seed = std::to_string(rand());
seedChanged = true;
}
// Input Custom Seed
if (kDown & KEY_X) {
pastSeedLength = Settings::seed.length();
Settings::seed = GetInput("Enter Seed");
seedChanged = true;
}
// Reprint seed if it changed
if (seedChanged) {
std::string spaces = "";
spaces.append(pastSeedLength, ' ');
printf("\x1b[11;21H%s", spaces.c_str());
printf("\x1b[11;21H%s", Settings::seed.c_str());
seedChanged = false;
}
}
// Print current menu (if applicable)
MoveCursor(kDown, updatedByHeld); // Move cursor, if applicable
if (currentMenu->mode == MAIN_MENU) {
PrintMainMenu();
ClearDescription();
} else if (currentMenu->mode == OPTION_SUB_MENU) {
UpdateOptionSubMenu(kDown);
PrintOptionSubMenu();
} else if (currentMenu->mode == LOAD_PRESET) {
UpdatePresetsMenu(kDown);
PrintPresetsMenu();
} else if (currentMenu->mode == DELETE_PRESET) {
UpdatePresetsMenu(kDown);
PrintPresetsMenu();
} else if (currentMenu->mode == RESET_TO_DEFAULTS) {
UpdateResetToDefaultsMenu(kDown);
PrintResetToDefaultsMenu();
} else if (currentMenu->mode == GENERATE_MODE) {
UpdateGenerateMenu(kDown);
if (currentMenu->mode != POST_GENERATE) {
PrintGenerateMenu();
}
} else if (currentMenu->mode == SUB_MENU) {
PrintSubMenu();
}
}
void ModeChangeInit() {
if (currentMenu->mode == OPTION_SUB_MENU) {
// loop through until we reach an unlocked setting
while (currentMenu->settingsList->at(currentMenu->menuIdx)->IsLocked() ||
currentMenu->settingsList->at(currentMenu->menuIdx)->IsHidden()) {
currentMenu->menuIdx++;
}
currentSetting = currentMenu->settingsList->at(currentMenu->menuIdx);
} else if (currentMenu->mode == SAVE_PRESET) {
ClearDescription();
if (SaveSpecifiedPreset(GetInput("Preset Name").substr(0, 19), OptionCategory::Setting)) {
printf("\x1b[24;5HPreset Saved!");
printf("\x1b[26;5HPress B to return to the preset menu.");
} else {
printf("\x1b[24;5HFailed to save preset.");
printf("\x1b[26;5HPress B to return to the preset menu.");
}
} else if (currentMenu->mode == LOAD_PRESET || currentMenu->mode == DELETE_PRESET) {
presetEntries = GetSettingsPresets();
} else if (currentMenu->mode == GENERATE_MODE) {
}
}
void UpdateCustomCosmeticColors(uint32_t kDown) {
if (kDown & KEY_A) {
if (currentSetting->GetSelectedOptionText().compare(0, 8, Cosmetics::CUSTOM_COLOR_PREFIX) == 0) {
std::string newColor = GetInput("Enter a 6 digit hex color").substr(0, 6);
if (Cosmetics::ValidHexString(newColor)) {
currentSetting->SetSelectedOptionText(Cosmetics::CustomColorOptionText(newColor));
}
}
}
}
void UpdateOptionSubMenu(uint32_t kDown) {
if ((kDown & KEY_DRIGHT) != 0) {
currentSetting->NextOptionIndex();
}
if ((kDown & KEY_DLEFT) != 0) {
currentSetting->PrevOptionIndex();
}
// Bounds checking
currentSetting->SanitizeSelectedOptionIndex();
currentSetting->SetVariable();
Settings::ForceChange(kDown, currentSetting);
UpdateCustomCosmeticColors(kDown);
}
void UpdatePresetsMenu(uint32_t kDown) {
// clear any potential message
ClearDescription();
if (kDown & KEY_A && currentMenu->mode == LOAD_PRESET && !presetEntries.empty()) {
if (LoadPreset(presetEntries[currentMenu->menuIdx], OptionCategory::Setting)) {
Settings::ResolveExcludedLocationConflicts();
for (Menu* menu : Settings::GetAllOptionMenus()) {
menu->ResetMenuIndex();
}
printf("\x1b[24;5HPreset Loaded!");
} else {
printf("\x1b[24;5HFailed to load preset.");
}
} else if (kDown & KEY_A && currentMenu->mode == DELETE_PRESET && !presetEntries.empty()) {
if (DeletePreset(presetEntries[currentMenu->menuIdx], OptionCategory::Setting)) {
presetEntries.erase(presetEntries.begin() + currentMenu->menuIdx);
if (currentMenu->menuIdx == presetEntries.size()) { // Catch when last preset is deleted
currentMenu->menuIdx--;
}
printf("\x1b[24;5HPreset Deleted.");
} else {
printf("\x1b[24;5HFailed to delete preset.");
}
}
}
void UpdateResetToDefaultsMenu(uint32_t kDown) {
// clear any potential message
ClearDescription();
if (kDown & KEY_A) {
Settings::SetDefaultSettings();
printf("\x1b[24;5HSettings have been reset to defaults.");
}
}
void UpdateGenerateMenu(uint32_t kDown) {
if ((kDown & KEY_A) != 0) {
Settings::PlayOption = currentMenu->menuIdx;
GenerateRandomizer();
// This is just a dummy mode to stop the prompt from appearing again
currentMenu->mode = POST_GENERATE;
}
}
void PrintMainMenu() {
printf("\x1b[0;%dHMain Settings", 1 + (BOTTOM_WIDTH - 13) / 2);
for (uint8_t i = 0; i < MAX_SUBMENUS_ON_SCREEN; i++) {
if (i >= Settings::mainMenu.size())
break;
Menu* menu = Settings::mainMenu[i];
uint8_t row = 3 + i;
// make the current menu green
if (currentMenu->menuIdx == i) {
printf("\x1b[%d;%dH%s>", row, 2, GREEN);
printf("\x1b[%d;%dH%s%s", row, 3, menu->name.c_str(), RESET);
} else {
printf("\x1b[%d;%dH%s", row, 3, menu->name.c_str());
}
}
}
void PrintOptionSubMenu() {
// bounds checking incase settings go off screen
// this is complicated to account for hidden settings and there's probably a better way to do it
uint16_t hiddenSettings = 0;
uint16_t visibleSettings = 0;
for (uint16_t i = currentMenu->settingBound; visibleSettings < MAX_SUBMENU_SETTINGS_ON_SCREEN; i++) {
if (i >= currentMenu->settingsList->size()) {
break;
}
if (currentMenu->settingsList->at(i)->IsHidden()) {
hiddenSettings++;
} else {
visibleSettings++;
}
}
bool isLastVisibleSetting = true;
for (size_t i = currentMenu->menuIdx + 1; i < currentMenu->settingsList->size(); i++) {
if (!currentMenu->settingsList->at(i)->IsHidden()) {
isLastVisibleSetting = false;
break;
}
}
if (currentMenu->menuIdx >=
currentMenu->settingBound - (isLastVisibleSetting ? 0 : 1) + MAX_SUBMENU_SETTINGS_ON_SCREEN + hiddenSettings) {
currentMenu->settingBound = currentMenu->menuIdx;
uint8_t offset = 0;
// skip over hidden settings
while (offset < MAX_SUBMENU_SETTINGS_ON_SCREEN - (isLastVisibleSetting ? 1 : 2)) {
currentMenu->settingBound--;
if (currentMenu->settingBound == 0) {
break;
}
offset += currentMenu->settingsList->at(currentMenu->settingBound)->IsHidden() ? 0 : 1;
}
} else if (currentMenu->menuIdx < currentMenu->settingBound + 1) {
currentMenu->settingBound = std::max(currentMenu->menuIdx - 1, 0);
}
// print menu name
printf("\x1b[0;%dH%s", 1 + (BOTTOM_WIDTH - currentMenu->name.length()) / 2, currentMenu->name.c_str());
// keep count of hidden settings to not make blank spaces appear in the list
hiddenSettings = 0;
for (uint8_t i = 0; i - hiddenSettings < MAX_SUBMENU_SETTINGS_ON_SCREEN; i++) {
// break if there are no more settings to print
if (i + currentMenu->settingBound >= currentMenu->settingsList->size())
break;
Option* setting = currentMenu->settingsList->at(i + currentMenu->settingBound);
uint8_t row = 3 + ((i - hiddenSettings) * 2);
// make the current setting green
if (currentMenu->menuIdx == i + currentMenu->settingBound) {
printf("\x1b[%d;%dH%s>", row, 1, GREEN);
printf("\x1b[%d;%dH%s:", row, 2, setting->GetName().data());
printf("\x1b[%d;%dH%s%s", row, 26, setting->GetSelectedOptionText().data(), RESET);
// dim to make a locked setting grey
} else if (setting->IsLocked()) {
printf("\x1b[%d;%dH%s%s:", row, 2, DIM, setting->GetName().data());
printf("\x1b[%d;%dH%s%s", row, 26, setting->GetSelectedOptionText().data(), RESET);
// don't display hidden settings
} else if (setting->IsHidden()) {
hiddenSettings++;
continue;
} else {
printf("\x1b[%d;%dH%s:", row, 2, setting->GetName().data());
printf("\x1b[%d;%dH%s", row, 26, setting->GetSelectedOptionText().data());
}
}
PrintOptionDescription();
}
void PrintSubMenu() {
printf("\x1b[0;%dH%s", 1 + (BOTTOM_WIDTH - currentMenu->name.length()) / 2, currentMenu->name.c_str());
for (uint8_t i = 0; i < MAX_SUBMENUS_ON_SCREEN; i++) {
if (i >= currentMenu->itemsList->size())
break;
uint8_t row = 3 + i;
// make the current menu green
if (currentMenu->menuIdx == currentMenu->settingBound + i) {
printf("\x1b[%d;%dH%s>", row, 2, GREEN);
printf("\x1b[%d;%dH%s%s", row, 3, currentMenu->itemsList->at(currentMenu->settingBound + i)->name.c_str(),
RESET);
} else {
printf("\x1b[%d;%dH%s", row, 3, currentMenu->itemsList->at(currentMenu->settingBound + i)->name.c_str());
}
}
}
void PrintPresetsMenu() {
if (presetEntries.empty()) {
printf("\x1b[10;4HNo Presets Detected!");
printf("\x1b[12;4HPress B to return to the preset menu.");
return;
}
if (currentMenu->mode == LOAD_PRESET) {
printf("\x1b[0;%dHSelect a Preset to Load", 1 + (BOTTOM_WIDTH - 23) / 2);
} else if (currentMenu->mode == DELETE_PRESET) {
printf("\x1b[0;%dHSelect a Preset to Delete", 1 + (BOTTOM_WIDTH - 25) / 2);
}
for (uint8_t i = 0; i < MAX_SUBMENU_SETTINGS_ON_SCREEN; i++) {
if (i >= presetEntries.size())
break;
std::string preset = presetEntries[i];
uint8_t row = 3 + (i * 2);
// make the current preset green
if (currentMenu->menuIdx == i) {
printf("\x1b[%d;%dH%s>", row, 14, GREEN);
printf("\x1b[%d;%dH%s%s", row, 15, preset.c_str(), RESET);
} else {
printf("\x1b[%d;%dH%s", row, 15, preset.c_str());
}
}
}
void PrintResetToDefaultsMenu() {
printf("\x1b[10;4HPress A to reset to default settings.");
printf("\x1b[12;4HPress B to return to the preset menu.");
}
void PrintGenerateMenu() {
printf("\x1b[3;%dHHow will you play?", 1+(BOTTOM_WIDTH-18)/2);
std::vector<std::string> playOptions = {"3ds Console", "Citra Emulator"};
for (uint8_t i = 0; i < playOptions.size(); i++) {
std::string option = playOptions[i];
uint8_t row = 6 + (i * 2);
//make the current selection green
if (currentMenu->menuIdx == i) {
printf("\x1b[%d;%dH%s>", row, 14, GREEN);
printf("\x1b[%d;%dH%s%s", row, 15, option.c_str(), RESET);
} else {
printf("\x1b[%d;%dH%s", row, 15, option.c_str());
}
}
}
void ClearDescription() {
//clear the previous description
std::string spaces = "";
spaces.append(9 * TOP_WIDTH, ' ');
printf("\x1b[22;0H%s", spaces.c_str());
}
void PrintOptionDescription() {
ClearDescription();
std::string_view description = currentSetting->GetSelectedOptionDescription();
printf("\x1b[22;0H%s", description.data());
}
std::string GenerateRandomizer() {
// if a blank seed was entered, make a random one
srand(time(NULL));
Settings::seed = std::to_string(rand());
int ret = Playthrough::Playthrough_Init(std::hash<std::string>{}(Settings::seed));
if (ret < 0) {
if (ret == -1) { // Failed to generate after 5 tries
printf("\n\nFailed to generate after 5 tries.\nPress B to go back to the menu.\nA different seed might be "
"successful.");
SPDLOG_INFO("\nRANDOMIZATION FAILED COMPLETELY. PLZ FIX\n");
return "";
} else {
printf("\n\nError %d with fill.\nPress Select to exit or B to go back to the menu.\n", ret);
return "";
}
}
// Restore settings that were set to a specific value for vanilla logic
if (Settings::Logic.Is(LOGIC_VANILLA)) {
for (Option* setting : Settings::vanillaLogicDefaults) {
setting->RestoreDelayedOption();
}
Settings::Keysanity.RestoreDelayedOption();
}
return "./randomizer/" + Settings::seed + ".json";
}
std::string GetInput(const char* hintText) {
return std::string();
}

View file

@ -0,0 +1,50 @@
#pragma once
#include <string>
#define MAIN_MENU 0
#define OPTION_SUB_MENU 1
#define SUB_MENU 2
#define GENERATE_MODE 3
#define LOAD_PRESET 4
#define SAVE_PRESET 5
#define DELETE_PRESET 6
#define POST_GENERATE 7
#define RESET_TO_DEFAULTS 8
#define MAX_SUBMENUS_ON_SCREEN 27
#define MAX_SUBMENU_SETTINGS_ON_SCREEN 13
#define TOP_WIDTH 50
#define BOTTOM_WIDTH 40
#define SCREEN_HEIGHT 30
#define RESET "\x1b[0m"
#define DIM "\x1b[2m"
#define BLACK "\x1b[30m"
#define RED "\x1b[31m"
#define GREEN "\x1b[32m"
#define YELLOW "\x1b[33m"
#define BLUE "\x1b[34m"
#define MEGANTA "\x1b[35m"
#define CYAN "\x1b[36m"
#define WHITE "\x1b[37m"
void ModeChangeInit();
void UpdateOptionSubMenu(uint32_t kDown);
void UpdatePresetsMenu(uint32_t kdown);
void UpdateResetToDefaultsMenu(uint32_t kdown);
void UpdateGenerateMenu(uint32_t kDown);
void PrintMainMenu();
void PrintOptionSubMenu();
void PrintSubMenu();
void PrintPresetsMenu();
void PrintResetToDefaultsMenu();
void PrintGenerateMenu();
void ClearDescription();
void PrintOptionDescription();
std::string GenerateRandomizer();
std::string GetInput(const char* hintText);
extern void MenuInit();
extern void MenuUpdate(uint32_t kDown, bool updatedByHeld);

View file

@ -0,0 +1,128 @@
#include "music.hpp"
#include <cstdlib>
namespace Music {
const std::array<SeqType, SEQ_COUNT> seqTypesMusic = {
/* NA_BGM_FIELD */ SEQ_BGM_WORLD,
/* NA_BGM_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_KAKARIKO_ADULT */ SEQ_BGM_WORLD,
/* NA_BGM_ENEMY */ SEQ_NOSHUFFLE, // Temporarily unshuffled: Override plays incorrect in some areas, like Lake Hylia, by continuously repeating the start
/* NA_BGM_BOSS00 */ SEQ_BGM_BATTLE,
/* NA_BGM_FAIRY_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_MARKET */ SEQ_BGM_WORLD,
/* NA_BGM_TITLE */ SEQ_BGM_WORLD,
/* NA_BGM_LINK_HOUSE */ SEQ_BGM_WORLD,
/* NA_BGM_GAME_OVER */ SEQ_FANFARE,
/* NA_BGM_BOSS_CLEAR */ SEQ_FANFARE,
/* NA_BGM_ITEM_GET */ SEQ_FANFARE,
/* NA_BGM_OPENING_GANON */ SEQ_FANFARE,
/* NA_BGM_HEART_GET */ SEQ_FANFARE,
/* NA_BGM_OCA_LIGHT */ SEQ_OCARINA,
/* NA_BGM_BUYO_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_KAKARIKO_KID */ SEQ_BGM_WORLD,
/* NA_BGM_GODESS */ SEQ_BGM_EVENT,
/* NA_BGM_HIME */ SEQ_BGM_EVENT,
/* NA_BGM_FIRE_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_OPEN_TRE_BOX */ SEQ_FANFARE,
/* NA_BGM_FORST_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_HIRAL_GARDEN */ SEQ_BGM_WORLD,
/* NA_BGM_GANON_TOWER */ SEQ_BGM_WORLD,
/* NA_BGM_RONRON */ SEQ_BGM_WORLD,
/* NA_BGM_GORON */ SEQ_BGM_WORLD,
/* NA_BGM_SPIRIT_STONE */ SEQ_FANFARE,
/* NA_BGM_OCA_FLAME */ SEQ_OCARINA,
/* NA_BGM_OCA_WIND */ SEQ_OCARINA,
/* NA_BGM_OCA_WATER */ SEQ_OCARINA,
/* NA_BGM_OCA_SOUL */ SEQ_OCARINA,
/* NA_BGM_OCA_DARKNESS */ SEQ_OCARINA,
/* NA_BGM_MIDDLE_BOSS */ SEQ_BGM_ERROR,
/* NA_BGM_S_ITEM_GET */ SEQ_FANFARE,
/* NA_BGM_SHRINE_OF_TIME */ SEQ_BGM_WORLD,
/* NA_BGM_EVENT_CLEAR */ SEQ_FANFARE,
/* NA_BGM_KOKIRI */ SEQ_BGM_WORLD,
/* NA_BGM_OCA_YOUSEI */ SEQ_FANFARE,
/* NA_BGM_MAYOIMORI */ SEQ_BGM_WORLD,
/* NA_BGM_SOUL_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_HORSE */ SEQ_BGM_EVENT,
/* NA_BGM_HORSE_GOAL */ SEQ_FANFARE,
/* NA_BGM_INGO */ SEQ_BGM_WORLD,
/* NA_BGM_MEDAL_GET */ SEQ_FANFARE,
/* NA_BGM_OCA_SARIA */ SEQ_OCARINA,
/* NA_BGM_OCA_EPONA */ SEQ_OCARINA,
/* NA_BGM_OCA_ZELDA */ SEQ_OCARINA,
/* NA_BGM_OCA_SUNMOON */ SEQ_NOSHUFFLE, /* Remove Sun's Song from the Ocarina pool for now due to bugs */
/* NA_BGM_OCA_TIME */ SEQ_OCARINA,
/* NA_BGM_OCA_STORM */ SEQ_OCARINA,
/* NA_BGM_NAVI */ SEQ_BGM_EVENT,
/* NA_BGM_DEKUNOKI */ SEQ_BGM_EVENT,
/* NA_BGM_FUSHA */ SEQ_BGM_WORLD,
/* NA_BGM_HIRAL_DEMO */ SEQ_NOSHUFFLE,
/* NA_BGM_MINI_GAME */ SEQ_BGM_EVENT,
/* NA_BGM_SEAK */ SEQ_BGM_EVENT,
/* NA_BGM_ZORA */ SEQ_BGM_WORLD,
/* NA_BGM_APPEAR */ SEQ_FANFARE,
/* NA_BGM_ADULT_LINK */ SEQ_BGM_EVENT,
/* NA_BGM_MASTER_SWORD */ SEQ_FANFARE,
/* NA_BGM_INTRO_GANON */ SEQ_BGM_EVENT,
/* NA_BGM_SHOP */ SEQ_BGM_WORLD,
/* NA_BGM_KENJA */ SEQ_BGM_EVENT,
/* NA_BGM_FILE_SELECT */ SEQ_NOSHUFFLE,
/* NA_BGM_ICE_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_GATE_OPEN */ SEQ_NOSHUFFLE,
/* NA_BGM_OWL */ SEQ_BGM_EVENT,
/* NA_BGM_DARKNESS_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_AQUA_DUNGEON */ SEQ_BGM_WORLD,
/* NA_BGM_BRIDGE */ SEQ_NOSHUFFLE,
/* NA_BGM_SARIA */ SEQ_NOSHUFFLE,
/* NA_BGM_GERUDO */ SEQ_BGM_WORLD,
/* NA_BGM_DRUGSTORE */ SEQ_BGM_WORLD,
/* NA_BGM_KOTAKE_KOUME */ SEQ_BGM_EVENT,
/* NA_BGM_ESCAPE */ SEQ_BGM_EVENT,
/* NA_BGM_UNDERGROUND */ SEQ_BGM_WORLD,
/* NA_BGM_GANON_BATTLE_1 */ SEQ_BGM_BATTLE,
/* NA_BGM_GANON_BATTLE_2 */ SEQ_BGM_BATTLE,
/* NA_BGM_END_DEMO */ SEQ_NOSHUFFLE,
/* NA_BGM_STAFF_1 */ SEQ_NOSHUFFLE,
/* NA_BGM_STAFF_2 */ SEQ_NOSHUFFLE,
/* NA_BGM_STAFF_3 */ SEQ_NOSHUFFLE,
/* NA_BGM_STAFF_4 */ SEQ_NOSHUFFLE,
/* NA_BGM_BOSS01 */ SEQ_BGM_BATTLE,
/* NA_BGM_MINI_GAME_2 */ SEQ_BGM_ERROR,
};
std::array<uint32_t, SEQ_COUNT> seqOverridesMusic;
/* Initializes the list of music overrides to unshuffled */
void InitMusicRandomizer() {
for(int i = 0; i < SEQ_COUNT; i++)
seqOverridesMusic[i] = BGM_BASE + i;
}
/* Shuffles the sequences grouping them by type */
/* type is a bitmask of SeqType */
void ShuffleSequences(int type) {
std::vector<uint32_t> seqs;
// Get all sequences of the desired type(s) into a vector
for (int i = 0; i < SEQ_COUNT; i++) {
if (seqTypesMusic[i] & type) {
seqs.push_back(seqOverridesMusic[i]);
}
}
// Shuffle the vector...
for (std::size_t i = 0; i < seqs.size(); i++)
{
std::swap(seqs[i], seqs[rand() % seqs.size()]);
}
// ...and feed it back into the overrides array
for (int i = 0; i < SEQ_COUNT; i++) {
if (seqTypesMusic[i] & type)
{
seqOverridesMusic[i] = seqs.back();
seqs.pop_back();
}
}
}
} // namespace Music

View file

@ -0,0 +1,29 @@
#pragma once
#include <array>
#include <vector>
#include <stdint.h>
namespace Music {
const uint32_t BGM_BASE = 0x1000585;
const int SEQ_COUNT = 85;
enum SeqType {
SEQ_NOSHUFFLE = 0,
SEQ_BGM_WORLD = 1 << 0,
SEQ_BGM_EVENT = 1 << 1,
SEQ_BGM_BATTLE = 1 << 2,
SEQ_OCARINA = 1 << 3,
SEQ_FANFARE = 1 << 4,
// A soundtrack in this category has the issue where if another soundtrack that isn't
// in this category overrides it, it will keep playing when it should be stopped.
// For example when beating a mini-boss or finishing the zora diving game.
SEQ_BGM_ERROR = 1 << 5,
};
extern const std::array<SeqType, SEQ_COUNT> seqTypesMusic;
extern std::array<uint32_t, SEQ_COUNT> seqOverridesMusic;
void InitMusicRandomizer();
void ShuffleSequences(int type);
} // namespace Music

View file

@ -0,0 +1,25 @@
#include "patch.hpp"
#include "cosmetics.hpp"
#include "custom_messages.hpp"
#include "music.hpp"
#include "sound_effects.hpp"
#include "shops.hpp"
#include "spoiler_log.hpp"
#include "entrance.hpp"
#include "hints.hpp"
#include <array>
#include <cstring>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
// For specification on the IPS file format, visit: https://zerosoft.zophar.net/ips.php
using FILEPtr = std::unique_ptr<FILE, decltype(&std::fclose)>;
void WriteFloatToBuffer(std::vector<char>& buffer, float f, size_t offset) {
memcpy(buffer.data() + offset, &f, sizeof(float));
}

View file

@ -0,0 +1,12 @@
#pragma once
#include <set>
#include "playthrough.hpp"
#include "settings.hpp"
#define V_TO_P(addr) (addr - 0x100000)
#define P_TO_V(offset) (offset + 0x100000)
#define PATCH_CONSOLE 0
#define PATCH_CITRA 1
#define PATCH_SIZE_MAX 65535
bool WriteAllPatches();

View file

@ -0,0 +1,96 @@
#include "playthrough.hpp"
#include "custom_messages.hpp"
#include "fill.hpp"
#include "location_access.hpp"
#include "logic.hpp"
#include "random.hpp"
#include "spoiler_log.hpp"
namespace Playthrough {
int Playthrough_Init(uint32_t seed) {
// initialize the RNG with just the seed incase any settings need to be
// resolved to something random
Random_Init(seed);
overrides.clear();
CustomMessages::ClearMessages();
ItemReset();
HintReset();
Areas::AccessReset();
Settings::UpdateSettings();
// once the settings have been finalized turn them into a string for hashing
std::string settingsStr;
for (Menu* menu : Settings::GetAllOptionMenus()) {
// don't go through non-menus
if (menu->mode != OPTION_SUB_MENU) {
continue;
}
for (size_t i = 0; i < menu->settingsList->size(); i++) {
Option* setting = menu->settingsList->at(i);
if (setting->IsCategory(OptionCategory::Setting)) {
settingsStr += setting->GetSelectedOptionText();
}
}
}
unsigned int finalHash = std::hash<std::string>{}(Settings::seed + settingsStr);
Random_Init(finalHash);
Logic::UpdateHelpers();
if (Settings::Logic.Is(LOGIC_VANILLA)) {
VanillaFill(); // Just place items in their vanilla locations
} else { // Fill locations with logic
int ret = Fill();
if (ret < 0) {
return ret;
}
}
GenerateHash();
WriteIngameSpoilerLog();
if (Settings::GenerateSpoilerLog) {
// write logs
printf("\x1b[11;10HWriting Spoiler Log...");
if (SpoilerLog_Write()) {
printf("Done");
} else {
printf("Failed");
}
#ifdef ENABLE_DEBUG
printf("\x1b[11;10HWriting Placement Log...");
if (PlacementLog_Write()) {
printf("Done\n");
} else {
printf("Failed\n");
}
#endif
}
playthroughLocations.clear();
wothLocations.clear();
playthroughBeatable = false;
return 1;
}
// used for generating a lot of seeds at once
int Playthrough_Repeat(int count /*= 1*/) {
printf("\x1b[0;0HGENERATING %d SEEDS", count);
uint32_t repeatedSeed = 0;
for (int i = 0; i < count; i++) {
repeatedSeed = rand() % 0xFFFFFFFF;
Settings::seed = std::to_string(repeatedSeed);
CitraPrint("testing seed: " + Settings::seed);
ClearProgress();
Playthrough_Init(std::hash<std::string>{}(Settings::seed));
printf("\x1b[15;15HSeeds Generated: %d\n", i + 1);
}
return 1;
}
} // namespace Playthrough

View file

@ -0,0 +1,9 @@
#pragma once
#include <string>
#include <set>
#include "item_location.hpp"
namespace Playthrough {
int Playthrough_Init(uint32_t seed);
int Playthrough_Repeat(int count = 1);
}

View file

@ -0,0 +1,38 @@
#pragma once
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
template <typename T, typename Predicate>
static void erase_if(std::vector<T>& vector, Predicate pred) {
vector.erase(std::remove_if(begin(vector), end(vector), pred), end(vector));
}
template <typename T, typename Predicate>
std::vector<T> FilterFromPool(std::vector<T>& vector, Predicate pred, bool eraseAfterFilter = false) {
std::vector<T> filteredPool = {};
std::copy_if(vector.begin(), vector.end(), std::back_inserter(filteredPool), pred);
if (eraseAfterFilter) {
erase_if(vector, pred);
}
return filteredPool;
}
template <typename T, typename Predicate>
std::vector<T> FilterAndEraseFromPool(std::vector<T>& vector, Predicate pred) {
return FilterFromPool(vector, pred, true);
}
template <typename T, typename FromPool>
void AddElementsToPool(std::vector<T>& toPool, const FromPool& fromPool) {
toPool.insert(toPool.end(), std::cbegin(fromPool), std::cend(fromPool));
}
template <typename T, typename Container>
bool ElementInContainer(T& element, const Container& container) {
return std::find(container.begin(), container.end(), element) != container.end();
}

View file

@ -0,0 +1,202 @@
#include "preset.hpp"
#include <array>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <vector>
#include "category.hpp"
#include "settings.hpp"
#include "tinyxml2.h"
#include "utils.hpp"
namespace fs = std::filesystem;
static const std::string CACHED_SETTINGS_FILENAME = "CACHED_SETTINGS";
static const std::string CACHED_COSMETICS_FILENAME = "CACHED_COSMETICS";
static std::string_view GetBasePath(OptionCategory category) {
static constexpr std::array<std::string_view, 2> paths{
"/3ds/presets/oot3dr/settings/",
"/3ds/presets/oot3dr/cosmetics/",
};
switch(category) {
case OptionCategory::Setting :
case OptionCategory::Cosmetic :
return paths[static_cast<size_t>(category)];
case OptionCategory::Toggle :
break;
}
return "";
}
//Creates preset directories if they don't exist
bool CreatePresetDirectories() {
//Create the 3ds directory if it doesn't exist
std::filesystem::create_directory("./3ds");
//Create the presets directory if it doesn't exist
std::filesystem::create_directory("./3ds/presets");
//Create the oot3d directory if it doesn't exist
std::filesystem::create_directory("./3ds/presets/oot3dr");
//Create the cosmetics directory if it doesn't exist
std::filesystem::create_directory("./3ds/presets/oot3dr/cosmetics");
//Create the settings directory if it doesn't exist
std::filesystem::create_directory("./3ds/presets/oot3dr/settings");
return true;
}
//Gets the preset filenames
std::vector<std::string> GetSettingsPresets() {
std::vector<std::string> presetEntries = {};
for (const auto& entry : fs::directory_iterator(GetBasePath(OptionCategory::Setting))) {
if(entry.path().stem().string() != CACHED_SETTINGS_FILENAME) {
presetEntries.push_back(entry.path().stem().string());
}
}
return presetEntries;
}
static std::string PresetPath(std::string_view presetName, OptionCategory category) {
return std::string(GetBasePath(category)).append(presetName).append(".xml");
}
// Presets are now saved as XML files using the tinyxml2 library.
// Documentation: https://leethomason.github.io/tinyxml2/index.html
bool SavePreset(std::string_view presetName, OptionCategory category) {
using namespace tinyxml2;
XMLDocument preset = XMLDocument(false);
// Create and insert the XML declaration
preset.InsertEndChild(preset.NewDeclaration());
// Create the root node
XMLElement* rootNode = preset.NewElement("settings");
preset.InsertEndChild(rootNode);
for (Menu* menu : Settings::GetAllOptionMenus()) {
if (menu->mode != OPTION_SUB_MENU) {
continue;
}
for (const Option* setting : *menu->settingsList) {
if (!setting->IsCategory(category)) {
continue;
}
XMLElement* newSetting = rootNode->InsertNewChildElement("setting");
newSetting->SetAttribute("name", RemoveLineBreaks(setting->GetName()).c_str());
newSetting->SetText(setting->GetSelectedOptionText().c_str());
}
}
XMLError e = preset.SaveFile(PresetPath(presetName, category).c_str());
return e == XML_SUCCESS;
}
//Read the preset XML file
bool LoadPreset(std::string_view presetName, OptionCategory category) {
using namespace tinyxml2;
XMLDocument preset;
XMLError e = preset.LoadFile(PresetPath(presetName, category).c_str());
if (e != XML_SUCCESS) {
return false;
}
XMLElement* rootNode = preset.RootElement();
if (strcmp(rootNode->Name(), "settings") != 0) {
// We do not have our <settings> root node, so it may be the old structure. We don't support that one anymore.
return false;
}
XMLElement* curNode = rootNode->FirstChildElement();
for (Menu* menu : Settings::GetAllOptionMenus()) {
if (menu->mode != OPTION_SUB_MENU) {
continue;
}
for (Option* setting : *menu->settingsList) {
if (!setting->IsCategory(category)) {
continue;
}
// Since presets are saved linearly, we can simply loop through the nodes as
// we loop through the settings to find most of the matching elements.
const std::string& settingToFind = RemoveLineBreaks(setting->GetName());
if (settingToFind == RemoveLineBreaks(curNode->Attribute("name"))) {
setting->SetSelectedIndexByString(curNode->GetText());
curNode = curNode->NextSiblingElement();
} else {
// If the current setting and element don't match, then search
// linearly from the beginning. This will get us back on track if the
// next setting and element line up with each other.
curNode = rootNode->FirstChildElement();
while (curNode != nullptr) {
if (settingToFind == RemoveLineBreaks(curNode->Attribute("name"))) {
setting->SetSelectedIndexByString(curNode->GetText());
curNode = curNode->NextSiblingElement();
break;
}
curNode = curNode->NextSiblingElement();
}
}
// Reset to the beginning if we reached the end.
if (curNode == nullptr) {
curNode = rootNode->FirstChildElement();
}
}
}
return true;
}
//Delete the selected preset
bool DeletePreset(std::string_view presetName, OptionCategory category) {
const std::string filepath = PresetPath(presetName, category);
std::filesystem::remove(filepath);
return true;
}
//Saves the new preset to a file
bool SaveSpecifiedPreset(std::string_view presetName, OptionCategory category) {
//don't save if the user cancelled
if (presetName.empty()) {
return false;
}
return SavePreset(presetName, category);
}
void SaveCachedSettings() {
SavePreset(CACHED_SETTINGS_FILENAME, OptionCategory::Setting);
}
void LoadCachedSettings() {
//If cache file exists, load it
for (const auto& entry : fs::directory_iterator(GetBasePath(OptionCategory::Setting))) {
if(entry.path().stem().string() == CACHED_SETTINGS_FILENAME) {
//File exists, open
LoadPreset(CACHED_SETTINGS_FILENAME, OptionCategory::Setting);
}
}
}
bool SaveCachedCosmetics() {
return SavePreset(CACHED_COSMETICS_FILENAME, OptionCategory::Cosmetic);
}
void LoadCachedCosmetics() {
//If cache file exists, load it
for (const auto& entry : fs::directory_iterator(GetBasePath(OptionCategory::Cosmetic))) {
if(entry.path().stem().string() == CACHED_COSMETICS_FILENAME) {
//File exists, open
LoadPreset(CACHED_COSMETICS_FILENAME, OptionCategory::Cosmetic);
}
}
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <vector>
enum class OptionCategory;
bool CreatePresetDirectories();
std::vector<std::string> GetSettingsPresets();
bool SavePreset(std::string_view presetName, OptionCategory category);
bool LoadPreset(std::string_view presetName, OptionCategory category);
bool DeletePreset(std::string_view presetName, OptionCategory category);
bool SaveSpecifiedPreset(std::string_view presetName, OptionCategory category);
void SaveCachedSettings();
void LoadCachedSettings();
bool SaveCachedCosmetics();
void LoadCachedCosmetics();

View file

@ -0,0 +1,23 @@
#include "menu.hpp"
#include "hint_list.hpp"
#include "item_list.hpp"
#include "item_location.hpp"
#include "location_access.hpp"
#include "rando_main.hpp"
// #include <soh/Enhancements/randomizer.h>
#include <Cvar.h>
#include <GameSettings.h>
#define TICKS_PER_SEC 268123480.0
void RandoMain::GenerateRando() {
HintTable_Init();
ItemTable_Init();
LocationTable_Init();
std::string fileName = GenerateRandomizer();
CVar_SetString("gSpoilerLog", fileName.c_str());
Game::SaveSettings();
Game::LoadSettings();
CVar_SetS32("gDroppedNewSpoilerFile", 1);
}

View file

@ -0,0 +1,5 @@
#pragma once
namespace RandoMain {
void GenerateRando();
}

View file

@ -0,0 +1,29 @@
#include "random.hpp"
#include <random>
static bool init = false;
static std::mt19937_64 generator;
//Initialize with seed specified
void Random_Init(uint32_t seed) {
init = true;
generator = std::mt19937_64{seed};
}
//Returns a random integer in range [min, max-1]
uint32_t Random(int min, int max) {
if (!init) {
//No seed given, get a random number from device to seed
const auto seed = static_cast<uint32_t>(std::random_device{}());
Random_Init(seed);
}
std::uniform_int_distribution<uint32_t> distribution(min, max-1);
return distribution(generator);
}
//Returns a random floating point number in [0.0, 1.0]
double RandomDouble() {
std::uniform_real_distribution<double> distribution(0.0, 1.0);
return distribution(generator);
}

View file

@ -0,0 +1,46 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
void Random_Init(uint32_t seed);
uint32_t Random(int min, int max);
double RandomDouble();
//Get a random element from a vector or array
template <typename T>
T RandomElement(std::vector<T>& vector, bool erase) {
const auto idx = Random(0, vector.size());
const T selected = vector[idx];
if (erase) {
vector.erase(vector.begin() + idx);
}
return selected;
}
template <typename Container>
auto& RandomElement(Container& container) {
return container[Random(0, std::size(container))];
}
template <typename Container>
const auto& RandomElement(const Container& container) {
return container[Random(0, std::size(container))];
}
//Shuffle items within a vector or array
template <typename T>
void Shuffle(std::vector<T>& vector) {
for (std::size_t i = 0; i + 1 < vector.size(); i++)
{
std::swap(vector[i], vector[Random(i, vector.size())]);
}
}
template <typename T, std::size_t size>
void Shuffle(std::array<T, size>& arr) {
for (std::size_t i = 0; i + 1 < arr.size(); i++)
{
std::swap(arr[i], arr[Random(i, arr.size())]);
}
}

View file

@ -0,0 +1,4 @@
#pragma once
#define RANDOMIZER_VERSION "v3.1"
#define COMMIT_NUMBER "develop"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,541 @@
#pragma once
#include <string_view>
#include <vector>
using string_view = std::string_view;
extern string_view openRandomize;
extern string_view worldRandomize;
extern string_view shuffleRandomize;
extern string_view dungeonRandomize;
extern string_view logicGlitchless;
extern string_view logicGlitched;
extern string_view logicNoLogic;
extern string_view logicVanilla;
extern string_view forestOpen;
extern string_view forestClosed;
extern string_view forestClosedDeku;
extern string_view kakGateOpen;
extern string_view kakGateClosed;
extern string_view doorOfTimeOpen;
extern string_view doorOfTimeClosed;
extern string_view doorOfTimeIntended;
extern string_view fountainNormal;
extern string_view fountainAdult;
extern string_view fountainOpen;
extern string_view gerudoNormal;
extern string_view gerudoFast;
extern string_view gerudoOpen;
extern string_view bridgeOpen;
extern string_view bridgeVanilla;
extern string_view bridgeStones;
extern string_view bridgeMedallions;
extern string_view bridgeRewards;
extern string_view bridgeDungeons;
extern string_view bridgeTokens;
extern string_view bridgeStoneCountDesc;
extern string_view bridgeMedallionCountDesc;
extern string_view bridgeRewardCountDesc;
extern string_view bridgeDungeonCountDesc;
extern string_view bridgeTokenCountDesc;
extern string_view randomGanonsTrialsDesc;
extern string_view ganonsTrialCountDesc;
extern string_view ageDesc;
extern string_view shuffleEntrancesDesc;
extern string_view dungeonEntrancesDesc;
extern string_view overworldEntrancesDesc;
extern string_view grottoEntrancesDesc;
extern string_view interiorEntrancesOff;
extern string_view interiorEntrancesSimple;
extern string_view interiorEntrancesAll;
extern string_view bombchuLogicDesc;
extern string_view defaultAmmoDropsDesc;
extern string_view bombchuDropsDesc;
extern string_view noAmmoDropsDesc;
extern string_view defaultHeartDropsDesc;
extern string_view noHeartDropsDesc;
extern string_view noHeartRefillDesc;
extern string_view scarceHeartsDesc;
extern string_view mqDungeonCountDesc;
extern string_view setDungeonTypesDesc;
extern string_view shuffleRewardsEndOfDungeon;
extern string_view shuffleRewardsAnyDungeon;
extern string_view shuffleRewardsOverworld;
extern string_view shuffleRewardsAnywhere;
extern string_view linksPocketDungeonReward;
extern string_view linksPocketAdvancement;
extern string_view linksPocketAnything;
extern string_view linksPocketNothing;
extern string_view songsSongLocations;
extern string_view songsDungeonRewards;
extern string_view songsAllLocations;
extern string_view shopsOff;
extern string_view shopsZero;
extern string_view shopsOne;
extern string_view shopsTwo;
extern string_view shopsThree;
extern string_view shopsFour;
extern string_view shopsRandom;
extern string_view tokensOff;
extern string_view tokensDungeon;
extern string_view tokensOverworld;
extern string_view tokensAllTokens;
extern string_view scrubsOff;
extern string_view scrubsAffordable;
extern string_view scrubsExpensive;
extern string_view scrubsRandomPrices;
extern string_view shuffleCowsDesc;
extern string_view kokiriSwordDesc;
extern string_view ocarinasDesc;
extern string_view weirdEggDesc;
extern string_view gerudoTokenDesc;
extern string_view magicBeansDesc;
extern string_view merchantsDesc;
extern string_view merchantsHintsDesc;
extern string_view adultTradeDesc;
extern string_view chestMinigameDesc;
extern string_view mapCompassStartWith;
extern string_view mapCompassVanilla;
extern string_view mapCompassOwnDungeon;
extern string_view mapCompassAnyDungeon;
extern string_view mapCompassOverworld;
extern string_view mapCompassAnywhere;
extern string_view smallKeyStartWith;
extern string_view smallKeyVanilla;
extern string_view smallKeyOwnDungeon;
extern string_view smallKeyAnyDungeon;
extern string_view smallKeyOverworld;
extern string_view smallKeyAnywhere;
extern string_view gerudoKeysVanilla;
extern string_view gerudoKeysAnyDungeon;
extern string_view gerudoKeysOverworld;
extern string_view gerudoKeysAnywhere;
extern string_view keyRingDesc;
extern string_view bossKeyStartWith;
extern string_view bossKeyVanilla;
extern string_view bossKeyOwnDungeon;
extern string_view bossKeyAnyDungeon;
extern string_view bossKeyOverworld;
extern string_view bossKeyAnywhere;
extern string_view ganonKeyStartWith;
extern string_view ganonKeyVanilla;
extern string_view ganonKeyOwnDungeon;
extern string_view ganonKeyAnyDungeon;
extern string_view ganonKeyOverworld;
extern string_view ganonKeyAnywhere;
extern string_view ganonKeyLACS;
extern string_view lacsMedallionCountDesc;
extern string_view lacsStoneCountDesc;
extern string_view lacsRewardCountDesc;
extern string_view lacsDungeonCountDesc;
extern string_view lacsTokenCountDesc;
extern string_view childStealthDesc;
extern string_view skipTowerEscapeDesc;
extern string_view skipEponaRaceDesc;
extern string_view skipMinigamePhasesDesc;
extern string_view freeScarecrowDesc;
extern string_view fourPoesDesc;
extern string_view lakeHyliaOwlDesc;
extern string_view bigPoeTargetCountDesc;
extern string_view numRequiredCuccosDesc;
extern string_view kingZoraSpeedFast;
extern string_view kingZoraSpeedVanilla;
extern string_view kingZoraSpeedRandom;
extern string_view completeMaskDesc;
extern string_view quickTextDesc0;
extern string_view quickTextDesc1;
extern string_view quickTextDesc2;
extern string_view quickTextDesc3;
extern string_view skipSongReplaysDesc;
extern string_view keepFWWarpPointDesc;
extern string_view fastBunnyHoodDesc;
extern string_view gossipStonesHintsDesc;
extern string_view obscureHintsDesc;
extern string_view ambiguousHintsDesc;
extern string_view clearHintsDesc;
extern string_view uselessHintsDesc;
extern string_view balancedHintsDesc;
extern string_view strongHintsDesc;
extern string_view veryStrongHintsDesc;
extern string_view compassesShowRewardsDesc;
extern string_view compassesShowWotHDesc;
extern string_view mapsShowDungeonModesDesc;
extern string_view damageMultiDesc;
extern string_view startingTimeDesc;
extern string_view locationsReachableDesc;
extern string_view nightGSDesc;
extern string_view chestAnimDesc;
extern string_view chestSizeDesc;
extern string_view ingameSpoilersShowDesc;
extern string_view ingameSpoilersHideDesc;
extern string_view menuButtonDesc;
extern string_view startWithConsumablesDesc;
extern string_view startWithMaxRupeesDesc;
extern string_view itemPoolPlentiful;
extern string_view itemPoolBalanced;
extern string_view itemPoolScarce;
extern string_view itemPoolMinimal;
extern string_view iceTrapsOff;
extern string_view iceTrapsNormal;
extern string_view iceTrapsExtra;
extern string_view iceTrapsMayhem;
extern string_view iceTrapsOnslaught;
extern string_view removeDDDesc;
extern string_view progGoronSword;
extern string_view faroresWindAnywhereDesc;
extern string_view ageRestrictionsDesc;
extern string_view adultStickDesc;
extern string_view adultBoomerangDesc;
extern string_view childHammerDesc;
extern string_view adultSlingshotDesc;
extern string_view childBowDesc;
extern string_view childHookshotDesc;
extern string_view childIronBootsDesc;
extern string_view childHoverBootsDesc;
extern string_view adultMasksDesc;
extern string_view adultKokiriSwordDesc;
extern string_view childMasterSwordDesc;
extern string_view childBiggoronSwordDesc;
extern string_view adultDekuShieldDesc;
extern string_view childMirrorShieldDesc;
extern string_view childGoronTunicDesc;
extern string_view childZoraTunicDesc;
extern string_view gkDurabilityVanilla;
extern string_view gkDurabilityRandomRisk;
extern string_view gkDurabilityRandomSafe;
extern string_view mp_EnabledDesc;
extern string_view mp_SharedProgressDesc;
extern string_view mp_SyncIdDesc;
extern string_view mp_SharedHealthDesc;
extern string_view mp_SharedRupeesDesc;
extern string_view mp_SharedAmmoDesc;
extern string_view zTargetingDesc;
extern string_view cameraControlDesc;
extern string_view motionControlDesc;
extern string_view togglePlayMusicDesc;
extern string_view togglePlaySFXDesc;
extern string_view silenceNaviDesc;
extern string_view ignoreMaskReactionDesc;
extern string_view naviColorsDesc;
extern string_view necessarySimpleModeDesc;
extern string_view alwaysSimpleModeDesc;
extern string_view coloredKeysDesc;
extern string_view coloredBossKeysDesc;
extern string_view mirrorWorldDesc;
extern string_view musicRandoDesc;
extern string_view shuffleBGMDesc;
extern string_view fanfaresOffDesc;
extern string_view onlyFanfaresDesc;
extern string_view fanfaresOcarinaDesc;
extern string_view shuffleOcaMusicDesc;
extern string_view shuffleSFXOff;
extern string_view shuffleSFXAll;
extern string_view shuffleSFXSceneSpecific;
extern string_view shuffleSFXChaos;
extern string_view shuffleSFXCategorically;
extern string_view randomTrapDmgDesc;
extern string_view basicTrapDmgDesc;
extern string_view advancedTrapDmgDesc;
extern string_view ToggleAllTricksDesc;
extern string_view ToggleLogicNoneDesc;
extern string_view ToggleLogicNoviceDesc;
extern string_view ToggleLogicIntermediateDesc;
extern string_view ToggleLogicExpertDesc;
extern string_view LogicGrottosWithoutAgonyDesc;
extern string_view LogicVisibleCollisionDesc;
extern string_view LogicFewerTunicRequirementsDesc;
extern string_view LogicLostWoodsGSBeanDesc;
extern string_view LogicLabDivingDesc;
extern string_view LogicLabWallGSDesc;
extern string_view LogicGraveyardPoHDesc;
extern string_view LogicChildDampeRacePoHDesc;
extern string_view LogicGVHammerChestDesc;
extern string_view LogicGerudoKitchenDesc;
extern string_view LogicLensWastelandDesc;
extern string_view LogicReverseWastelandDesc;
extern string_view LogicColossusGSDesc;
extern string_view LogicOutsideGanonsGSDesc;
extern string_view LogicManOnRoofDesc;
extern string_view LogicWindmillPoHHookshotDesc;
extern string_view LogicDMTBombableDesc;
extern string_view LogicDMTSoilGSDesc;
extern string_view LogicDMTSummitHoverDesc;
extern string_view LogicLinkGoronDinsDesc;
extern string_view LogicGoronCityLeftMostDesc;
extern string_view LogicGoronCityPotDesc;
extern string_view LogicGoronCityPotWithStrengthDesc;
extern string_view LogicChildRollingWithStrengthDesc;
extern string_view LogicCraterUpperToLowerDesc;
extern string_view LogicCraterBeanPoHWithHoversDesc;
extern string_view LogicBiggoronBoleroDesc;
extern string_view LogicZoraRiverLowerDesc;
extern string_view LogicZoraRiverUpperDesc;
extern string_view LogicZFGreatFairyDesc;
extern string_view LogicDekuB1WebsWithBowDesc;
extern string_view LogicDekuB1SkipDesc;
extern string_view LogicDekuBasementGSDesc;
extern string_view LogicDCStaircaseDesc;
extern string_view LogicDCJumpDesc;
extern string_view LogicDCSlingshotSkipDesc;
extern string_view LogicDCScarecrowGSDesc;
extern string_view LogicJabuBossGSAdultDesc;
extern string_view LogicJabuScrubJumpDiveDesc;
extern string_view LogicForestOutsideBackdoorDesc;
extern string_view LogicForestDoorFrameDesc;
extern string_view LogicForestOutdoorEastGSDesc;
extern string_view LogicFireBossDoorJumpDesc;
extern string_view LogicFireStrengthDesc;
extern string_view LogicFireScarecrowDesc;
extern string_view LogicFireFlameMazeDesc;
extern string_view LogicFireSongOfTimeDesc;
extern string_view LogicWaterTempleTorchLongshotDesc;
extern string_view LogicWaterTempleUpperBoostDesc;
extern string_view LogicWaterCentralBowDesc;
extern string_view LogicWaterCentralGSFWDesc;
extern string_view LogicWaterCrackedWallNothingDesc;
extern string_view LogicWaterCrackedWallHoversDesc;
extern string_view LogicWaterBossKeyRegionDesc;
extern string_view LogicWaterBKJumpDiveDesc;
extern string_view LogicWaterNorthBasementLedgeJumpDesc;
extern string_view LogicWaterDragonAdultDesc;
extern string_view LogicWaterDragonJumpDiveDesc;
extern string_view LogicWaterRiverGSDesc;
extern string_view LogicWaterFallingPlatformGSDesc;
extern string_view LogicSpiritLowerAdultSwitchDesc;
extern string_view LogicSpiritChildBombchuDesc;
extern string_view LogicSpiritWallDesc;
extern string_view LogicSpiritLobbyGSDesc;
extern string_view LogicSpiritMapChestDesc;
extern string_view LogicSpiritSunChestDesc;
extern string_view LogicShadowFireArrowEntryDesc;
extern string_view LogicShadowUmbrellaDesc;
extern string_view LogicShadowFreestandingKeyDesc;
extern string_view LogicShadowStatueDesc;
extern string_view LogicChildDeadhandDesc;
extern string_view LogicGtgWithoutHookshotDesc;
extern string_view LogicGtgFakeWallDesc;
extern string_view LogicLensSpiritDesc;
extern string_view LogicLensShadowDesc;
extern string_view LogicLensShadowBackDesc;
extern string_view LogicLensBotwDesc;
extern string_view LogicLensGtgDesc;
extern string_view LogicLensCastleDesc;
extern string_view LogicLensJabuMQDesc;
extern string_view LogicLensSpiritMQDesc;
extern string_view LogicLensShadowMQDesc;
extern string_view LogicLensShadowMQBackDesc;
extern string_view LogicLensBotwMQDesc;
extern string_view LogicLensGtgMQDesc;
extern string_view LogicLensCastleMQDesc;
extern string_view LogicSpiritTrialHookshotDesc;
extern string_view LogicFlamingChestsDesc;
extern const std::vector<string_view> GlitchDifficulties;
extern string_view GlitchRestrictedItemsDescDisabled;
extern string_view GlitchRestrictedItemsDescNovice;
extern string_view GlitchSuperStabDescDisabled;
extern string_view GlitchSuperStabDescNovice;
extern string_view GlitchISGDescDisabled;
extern string_view GlitchISGDescNovice;
extern string_view GlitchISGDescIntermediate;
extern string_view GlitchISGDescAdvanced;
extern string_view GlitchHoverDescDisabled;
extern string_view GlitchHoverDescNovice;
extern string_view GlitchHoverDescIntermediate;
extern string_view GlitchHoverDescAdvanced;
extern string_view GlitchBombOIDescDisabled;
extern string_view GlitchBombOIDescNovice;
extern string_view GlitchBombOIDescIntermediate;
extern string_view GlitchBombOIDescAdvanced;
extern string_view GlitchBombOIDescExpert;
extern string_view GlitchHoverBoostDescDisabled;
extern string_view GlitchHoverBoostDescNovice;
extern string_view GlitchHoverBoostDescIntermediate;
extern string_view GlitchHoverBoostDescAdvanced;
extern string_view GlitchSuperSlideDescDisabled;
extern string_view GlitchSuperSlideDescNovice;
extern string_view GlitchSuperSlideDescIntermediate;
extern string_view GlitchSuperSlideDescAdvanced;
extern string_view GlitchSuperSlideDescExpert;
extern string_view GlitchMegaflipDescDisabled;
extern string_view GlitchMegaflipDescNovice;
extern string_view GlitchMegaflipDescIntermediate;
extern string_view GlitchMegaflipDescAdvanced;
extern string_view GlitchMegaflipDescExpert;
extern string_view GlitchMegaflipDescHero;
extern string_view GlitchASlideDescDisabled;
extern string_view GlitchASlideDescNovice;
extern string_view GlitchASlideDescIntermediate;
extern string_view GlitchASlideDescAdvanced;
extern string_view GlitchASlideDescExpert;
extern string_view GlitchHammerSlideDescDisabled;
extern string_view GlitchHammerSlideDescNovice;
extern string_view GlitchHammerSlideDescIntermediate;
extern string_view GlitchLedgeCancelDescDisabled;
extern string_view GlitchLedgeCancelDescNovice;
extern string_view GlitchLedgeCancelDescIntermediate;
extern string_view GlitchLedgeCancelDescAdvanced;
extern string_view GlitchActionSwapDescDisabled;
extern string_view GlitchActionSwapDescNovice;
extern string_view GlitchActionSwapDescAdvanced;
extern string_view GlitchQPADescDisabled;
extern string_view GlitchQPADescNovice;
extern string_view GlitchQPADescIntermediate;
extern string_view GlitchQPADescAdvanced;
extern string_view GlitchQPADescExpert;
extern string_view GlitchHookshotClipDescDisabled;
extern string_view GlitchHookshotClipDescNovice;
extern string_view GlitchHookshotClipDescIntermediate;
extern string_view GlitchHookshotJump_BonkDescDisabled;
extern string_view GlitchHookshotJump_BonkDescNovice;
extern string_view GlitchHookshotJump_BonkDescIntermediate;
extern string_view GlitchHookshotJump_BonkDescAdvanced;
extern string_view GlitchHookshotJump_BootsDescDisabled;
extern string_view GlitchHookshotJump_BootsDescNovice;
extern string_view GlitchHookshotJump_BootsDescIntermediate;
extern string_view GlitchHookshotJump_BootsDescAdvanced;
extern string_view GlitchCutsceneDiveDescDisabled;
extern string_view GlitchCutsceneDiveDescNovice;
extern string_view GlitchCutsceneDiveDescIntermediate;
extern string_view GlitchCutsceneDiveDescAdvanced;
extern string_view GlitchNaviDive_StickDescDisabled;
extern string_view GlitchNaviDive_StickDescNovice;
extern string_view GlitchNaviDive_StickDescIntermediate;
extern string_view GlitchNaviDive_StickDescAdvanced;
extern string_view GlitchTripleSlashClipDescDisabled;
extern string_view GlitchTripleSlashClipDescNovice;
extern string_view GlitchTripleSlashClipDescIntermediate;
extern string_view GlitchTripleSlashClipDescAdvanced;
extern string_view GlitchTripleSlashClipDescExpert;
extern string_view GlitchLedgeClipDescDisabled;
extern string_view GlitchLedgeClipDescNovice;
extern string_view GlitchLedgeClipDescIntermediate;
extern string_view GlitchLedgeClipDescAdvanced;
extern string_view GlitchSeamWalkDescDisabled;
extern string_view GlitchSeamWalkDescNovice;
extern string_view GlitchSeamWalkDescIntermediate;
extern string_view GlitchSeamWalkDescAdvanced;
extern string_view GlitchSeamWalkDescExpert;
extern string_view GlitchSeamWalkDescHero;
extern string_view GlitchWWTEscapeDesc;
extern string_view GlitchGVTentAsChildDesc;
extern string_view GlitchGFGuardSneakDesc;
extern string_view GlitchItemlessWastelandDesc;
extern string_view GlitchOccamsStatueDesc;
extern string_view GlitchZDOoBJumpSlashDesc;
extern string_view GlitchJabuStickRecoilDesc;
extern string_view GlitchJabuAdultDesc;
extern string_view GlitchBlueFireWallDesc;
extern string_view GlitchClassicHalfieDesc;
extern string_view GlitchModernHalfieDesc;
extern string_view GlitchJabuSwitchDesc;
extern string_view GlitchForestBKSkipDesc;
extern string_view GlitchFireGrunzClipDesc;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,735 @@
#include "item_location.hpp"
#include "item_pool.hpp"
#include "location_access.hpp"
#include "random.hpp"
#include "item.hpp"
#include "shops.hpp"
#include "debug.hpp"
#include <math.h>
#include <map>
#include "z64item.h"
using namespace Settings;
std::vector<ItemAndPrice> NonShopItems = {};
static std::array<std::array<Text, 3>, 0xD5> trickNameTable; //Table of trick names for ice traps
bool initTrickNames = false; //Indicates if trick ice trap names have been initialized yet
//Set vanilla shop item locations before potentially shuffling
void PlaceVanillaShopItems() {
//Loop to place vanilla items in each location
for (size_t i = 0; i < ShopLocationLists.size(); i++) {
for (size_t j = 0; j < ShopLocationLists[i].size(); j++) {
Location(ShopLocationLists[i][j])->PlaceVanillaItem();
}
}
}
//These are the vanilla shop items, but in a priority order of importance
//However many shop item slots were cleared, this will return 64 minus that number of vanilla shop items to be placed with assumed fill
//The first 32 items here will always be present in shops
//Shopsanity 4 will only have the first 32, shopsanity 1 will have the first 56, etc.
//Shopsanity random will have anywhere from the first 32 to the first 56, so the order of items after 32 is relevant
std::vector<uint32_t> GetMinVanillaShopItems(int total_replaced) {
std::vector<uint32_t> minShopItems = {
BUY_DEKU_SHIELD,
BUY_HYLIAN_SHIELD,
BUY_GORON_TUNIC,
BUY_ZORA_TUNIC,
BUY_DEKU_NUT_5,
BUY_DEKU_NUT_5,
BUY_DEKU_NUT_10,
BUY_DEKU_STICK_1,
BUY_DEKU_STICK_1,
BUY_DEKU_SEEDS_30,
BUY_ARROWS_10,
BUY_ARROWS_10,
BUY_ARROWS_30,
BUY_ARROWS_50,
BUY_BOMBCHU_5,
BUY_BOMBCHU_10,
BUY_BOMBCHU_10,
BUY_BOMBCHU_20,
BUY_BOMBS_525,
BUY_BOMBS_535,
BUY_BOMBS_10,
BUY_BOMBS_20,
BUY_GREEN_POTION,
BUY_RED_POTION_30,
BUY_BLUE_FIRE,
BUY_FAIRYS_SPIRIT,
BUY_BOTTLE_BUG,
BUY_FISH,
//^First 28 items from OoTR
BUY_HYLIAN_SHIELD,
BUY_BOTTLE_BUG,
BUY_DEKU_STICK_1,
BUY_FAIRYS_SPIRIT,
//^First 32 items: Always guaranteed
BUY_BLUE_FIRE,
BUY_FISH,
BUY_BOMBCHU_10,
BUY_DEKU_NUT_5,
BUY_ARROWS_10,
BUY_BOMBCHU_20,
BUY_BOMBS_535,
BUY_RED_POTION_30,
//^First 40 items: Exist on shopsanity 3 or less
BUY_BOMBS_30,
BUY_BOMBCHU_20,
BUY_DEKU_NUT_5,
BUY_ARROWS_10,
BUY_DEKU_NUT_5,
BUY_ARROWS_30,
BUY_RED_POTION_40,
BUY_FISH,
//^First 48 items: Exist on shopsanity 2 or less
BUY_BOMBCHU_20,
BUY_ARROWS_30,
BUY_RED_POTION_50,
BUY_ARROWS_30,
BUY_DEKU_NUT_5,
BUY_ARROWS_50,
BUY_ARROWS_50,
BUY_GREEN_POTION,
//^First 56 items: Exist on shopsanity 1 or less
BUY_POE,
BUY_POE,
BUY_HEART,
BUY_HEART,
BUY_HEART,
BUY_HEART,
BUY_HEART,
BUY_HEART,
//^All 64 items: Only exist with shopsanity 0
};
//Now delete however many items there are to replace
for (int i = 0; i < total_replaced; i++) {
minShopItems.pop_back();
}
return minShopItems;
}
//This table contains a cumulative probability for each possible shop price based on
// a beta distribution with alpha = 1.5, beta = 2, and the result of the distribution, a float in [0.0, 1.0),
// being mutliplied by 60, casted to an integer, then multiplied by 5 to give a value in range [0, 295] in increments of 5.
// Meaning the first value is the probability of 0, the next value is the probability of 0 plus the probability of 5, etc.
//Probabilities generated using a python script with 1 billion trials, so should hopefully be pretty good
//Average price ~126
//~38% chance of needing no wallet, ~45% chance of needing 1, ~17% chance of needing 2
static constexpr std::array<double, 60> ShopPriceProbability= {
0.005326994, 0.014908518, 0.027114719, 0.041315285, 0.057136304, 0.074325887, 0.092667151, 0.112002061, 0.132198214, 0.153125390,
0.174696150, 0.196810540, 0.219388148, 0.242361379, 0.265657012, 0.289205134, 0.312970402, 0.336877590, 0.360881110, 0.384932772,
0.408976198, 0.432982176, 0.456902494, 0.480686053, 0.504313389, 0.527746488, 0.550938554, 0.573856910, 0.596465330, 0.618736235,
0.640646600, 0.662162782, 0.683240432, 0.703859801, 0.724001242, 0.743631336, 0.762722631, 0.781259986, 0.799198449, 0.816521905,
0.833208595, 0.849243398, 0.864579161, 0.879211177, 0.893112051, 0.906263928, 0.918639420, 0.930222611, 0.940985829, 0.950914731,
0.959992180, 0.968187000, 0.975495390, 0.981884488, 0.987344345, 0.991851853, 0.995389113, 0.997937921, 0.999481947, 1.000000000,
};
int GetRandomShopPrice() {
double random = RandomDouble(); //Randomly generated probability value
for (size_t i = 0; i < ShopPriceProbability.size(); i++) {
if (random < ShopPriceProbability[i]) {
//The randomly generated value has surpassed the total probability up to this point, so this is the generated price
return i * 5; //i in range [0, 59], output in range [0, 295] in increments of 5
}
}
return -1; //Shouldn't happen
}
//Similar to above, beta distribution with alpha = 1, beta = 2,
// multiplied by 20 instead of 60 to give values in rage [0, 95] in increments of 5
//Average price ~31
static constexpr std::array<double, 20> ScrubPriceProbability = {
0.097500187, 0.190002748, 0.277509301, 0.360018376, 0.437522571, 0.510021715, 0.577520272, 0.640029304, 0.697527584, 0.750024535,
0.797518749, 0.840011707, 0.877508776, 0.910010904, 0.937504342, 0.960004661, 0.977502132, 0.989998967, 0.997500116, 1.000000000,
};
int16_t GetRandomScrubPrice() {
double random = RandomDouble();
for (size_t i = 0; i < ScrubPriceProbability.size(); i++) {
if (random < ScrubPriceProbability[i]) {
return i * 5; // i in range [0, 19], output in range [0, 95] in increments of 5
}
}
return -1;
}
//Get 1 to 4, or a random number from 1-4 depending on shopsanity setting
int GetShopsanityReplaceAmount() {
if (Settings::Shopsanity.Is(SHOPSANITY_ONE)) {
return 1;
} else if (Settings::Shopsanity.Is(SHOPSANITY_TWO)) {
return 2;
} else if (Settings::Shopsanity.Is(SHOPSANITY_THREE)) {
return 3;
} else if (Settings::Shopsanity.Is(SHOPSANITY_FOUR)) {
return 4;
} else { //Random, get number in [1, 4]
return Random(1, 5);
}
}
//Initialize the table of trick names with an easy, medium, and hard name for each language
void InitTrickNames() {
trickNameTable[GI_SWORD_KOKIRI] = {
Text{"Korok Sword", "Épée Korok", "Espada Korok"},
Text{"Hero's Sword", "Épée du héros", "Espada del héroe"},
Text{"Razor Sword", "Lame rasoir", "Espada de esmeril"}};
/* trickNameTable[GI_SWORD_MASTER] = {
Text{"Goddess Sword", "Épée de la déesse", "Espada Divina"},
Text{"Gilded Sword", "Excalibur", "Espada de los Sabios"},
Text{"Magical Sword", "Lame dorée", "Fay"}};*/
trickNameTable[GI_SWORD_KNIFE] = {
Text{"Big Goron's Sword", "Épée de gros Goron", "Espada de Big Goron"},
Text{"Fierce Deity's Sword", "Épée du dieu démon", "Espada de la Fiera Deidad"},
Text{"Biggoron's Knife", "Lame de Grogoron", "Daga de Biggoron"}};
trickNameTable[GI_SWORD_BGS] = {
Text{"Big Goron's Sword", "Épée de gros Goron", "Espada de Big Goron"},
Text{"Fierce Deity's Sword", "Épée du dieu démon", "Espada de la Fiera Deidad"},
Text{"Biggoron's Knife", "Lame de Grogoron", "Daga de Biggoron"}};
trickNameTable[GI_SHIELD_DEKU] = {
Text{"Boko Shield", "Bouclier Boko", "Escudo Boko"},
Text{"Ordon Shield", "Bouclier de Toal", "Escudo de Ordon"},
Text{"Wooden Shield", "Bouclier de bois", "Escudo de madera"}};
trickNameTable[GI_SHIELD_HYLIAN] = {
Text{"Hyrule Shield", "Bouclier d'Hyrule", "Escudo Hylian"},
Text{"Goddess Shield", "Bouclier sacré", "Escudo Divino"},
Text{"Hero's Shield", "Bouclier du héros", "Escudo del héroe"}};
trickNameTable[GI_SHIELD_MIRROR] = {
Text{"Magic Mirror", "Miroir magique", "Escudo mágico"},
Text{"Magical Shield", "Bouclier magique", "Escudo arcano"},
Text{"Mirror of Twilight", "Miroir des ombres", "Espejo del Crepúsculo"}};
trickNameTable[GI_TUNIC_GORON] = {
Text{"Gerudo Tunic", "Tunique Gerudo", "Sayo gerudo"},
Text{"Magic Armor", "Armure magique", "Túnica Goron"},
Text{"Red Mail", "Habits rouges", "Ropas rojas"}};
trickNameTable[GI_TUNIC_ZORA] = {
Text{"Rito Tunic", "Tunique Rito", "Sayo rito"},
Text{"Zora Armor", "Armure Zora", "Túnica Zora"},
Text{"Blue Mail", "Habits bleus", "Ropas azules"}};
trickNameTable[GI_BOOTS_IRON] = {
Text{"Iron Hoofs", "Patins de plomb", "Botas férreas"},
Text{"Snow Boots", "Bottes de neige", "Botas de nieve"},
Text{"Boots of Power", "Bottes de puissance", "Botas de plomo"}};
trickNameTable[GI_BOOTS_HOVER] = {
Text{"Hover Hoofs", "Patins des airs", "Botas flotadoras"},
Text{"Pegasus Boots", "Bottes pégase", "Botas de Pegaso"},
Text{"Boots of Speed", "Bottes de vitesse", "Botas del desierto"}};
trickNameTable[GI_WEIRD_EGG] = {
Text{"Poached Egg", "Œuf à la coque", "Huevo pasado"},
Text{"Lon Lon Egg", "Œuf Lon Lon", "Huevo Lon Lon"},
Text{"Zora Egg", "Œuf Zora", "Huevo Zora"}};
trickNameTable[GI_LETTER_ZELDA] = {
Text{"Ruto's Letter", "Lettre de Ruto", "Carta de Ruto"},
Text{"Royal Letter", "Lettre royale", "Carta para Kafei"},
Text{"Zelda's Business Card", "Carte d'affaires de Zelda", "Carta"}};
trickNameTable[GI_BOOMERANG] = {
Text{"Prank Fetch Toy", "Inséparable bâtonnet", "Bumerang"},
Text{"Gale Boomerang", "Boomerang tornade", "Bumerán tornado"},
Text{"Magic Boomerang", "Boomerang magique", "Bumerán mágico"}};
trickNameTable[GI_LENS] = {
Text{"Sheikah-leidoscope", "Sheikah-léidoscope", "Monóculo de la Verdad"},
Text{"Sheikah Sensor", "Sonar Sheikah", "Sensor Sheikah"},
Text{"Magnifying Lens", "Loupe", "Lente Aumentadora"}};
trickNameTable[GI_HAMMER] = {
Text{"Goron Gavel", "Masse perforatrice", "Mazo Goron"},
Text{"Magic Hammer", "Marteau magique", "Martillo mágico"},
Text{"Skull Hammer", "Maillet ressort", "Martillo de hierro"}};
trickNameTable[GI_STONE_OF_AGONY] = {
Text{"Shard of Agahnim", "Fragment d'agonie", "Piedra de Agahnim"},
Text{"Stone of Agony", "Fragment de cristal", "Fragmento de la Agonía"},
Text{"Pirate's Charm", "Pierre de souffrance", "Amuleto Pirata"}};
trickNameTable[GI_DINS_FIRE] = {
Text{"Eldin's Fire", "Feu d'Eldin", "Fuego de Eldin"},
Text{"Din's Blaze", "Flamme de Din", "Poder de Din"},
Text{"Din's Pearl", "Perle de Din", "Orbe de Din"}};
trickNameTable[GI_FARORES_WIND] = {
Text{"Faron's Wind", "Vent de Firone", "Viento de Farone"},
Text{"Farore's Windfall", "Zéphyr de Farore", "Valor de Farore"},
Text{"Farore's Pearl", "Perle de Farore", "Orbe de Farore"}};
trickNameTable[GI_NAYRUS_LOVE] = {
Text{"Lanayru's Love", "Amour de Lanelle", "Amor de Lanayru"},
Text{"Nayru's Passion", "Passion de Nayru", "Sabiduría de Nayru"},
Text{"Nayru's Pearl", "Perle de Nayru", "Orbe de Nayru"}};
trickNameTable[GI_ARROW_FIRE] = {
Text{"Soul Arrow", "Flèche des esprits", "Flecha del Espíritu"},
Text{"Bomb Arrow", "Flèche-bombe", "Flecha bomba"},
Text{"Fire Candy", "Bonbon de feu", "Cetro de fuego"}};
trickNameTable[GI_ARROW_ICE] = {
Text{"Shadow Arrow", "Flèche d'ombre", "Flecha de las Sombras"},
Text{"Ancient Arrow", "Flèche archéonique", "Flecha ancestral"},
Text{"Ice Trap Arrow", "Flèche de piège de glace", "Cetro de hielo"}};
trickNameTable[GI_ARROW_LIGHT] = {
Text{"Wind Arrow", "Flèche de vent", "Flecha del Viento"},
Text{"Shock Arrow", "Flèches électriques", "Flecha eléctrica"},
Text{"Silver Arrow", "Flèches d'argent", "Flecha de plata"}};
trickNameTable[GI_GERUDO_CARD] = {
Text{"Desert Title Deed", "Abonnement Gerudo", "Escritura del desierto"},
Text{"Gerudo's Card", "Carte Goron", "Tóken Gerudo"},
Text{"Gerudo's Membership Card", "Autographe de Nabooru", "Tarjeta Gerudo"}};
trickNameTable[0xC9] = {
Text{"Funky Bean Pack", "Paquet de fèves magiques", "Lote de frijoles mágicos"},
Text{"Crenel Bean Pack", "Paquet de haricots Gonggle", "Lote de alubias mágicas"},
Text{"Mystic Bean Pack", "Paquet de haricots mystiques", "Lote de porotos mágicos"}};
trickNameTable[0xB8] = {
Text{"Diamond Hearts", "Cœurs de diamant", "Contenedor de diamante"},
Text{"Double Damage", "Double souffrance", "Doble daño receptivo"},
Text{"Quadruple Defence", "Quadruple défence", "Defensa cuádruple"}};
trickNameTable[GI_POCKET_EGG] = {
Text{"Poached Egg", "Œuf à la coque", "Huevo pasado"},
Text{"Lon Lon Egg", "Œuf Lon Lon", "Huevo Lon Lon"},
Text{"Zora Egg", "Œuf Zora", "Huevo del Pez Viento"}};
trickNameTable[GI_POCKET_CUCCO] = {
Text{"D.I.Y. Alarm Clock", "Réveille-matin improvisé", "Alarma emplumada portátil"},
Text{"Kakariko Cucco", "Cocotte Cocorico", "Cuco de Kakariko"},
Text{"Hatched Cucco", "Cocotte éclose", "Pollo de bolsillo"}};
trickNameTable[GI_COJIRO] = {
Text{"Blucco", "Chair-Qui-Poule", "Cucazul"},
Text{"Grog's Cucco", "Cocotte de Grog", "Cuco de Grog"},
Text{"Corijo", "Cojiro", "Corijo"}};
trickNameTable[GI_ODD_MUSHROOM] = {
Text{"Magic Mushroom", "Champignon magique", "Champiñón mágico"},
Text{"Endura Shroom", "Champi Vigueur", "Champiñón del bosque"},
Text{"Mushroom", "Champignon", "Seta"}};
trickNameTable[GI_ODD_POTION] = {
Text{"Odd Medicine", "Élixir suspect", "Poción rara"},
Text{"Granny's Poultice", "Mixture de Granny", "Medicina de la abuela"},
Text{"Mushroom Poultice", "Mixture de champignon", "Medicina de champiñones"}};
trickNameTable[GI_SAW] = {
Text{"Carpenter's Saw", "Scie du charpentier", "Sierra del carpintero"},
Text{"Poacher's Sword", "Hache du chasseur", "Espada del capataz"},
Text{"Grog's Saw", "Scie de Grog", "Sierra del Cazador Furtivo"}};
trickNameTable[GI_SWORD_BROKEN] = {
Text{"Broken Biggoron's Sword", "Épée brisée de Grogoron", "Espada de Biggoron rota"},
Text{"Broken Giant's Knife", "Lame des géants brisée", "Daga gigante rota"},
Text{"Biggoron's Sword", "Épée de Grogoron", "Espada de Biggoron"}};
trickNameTable[GI_PRESCRIPTION] = {
Text{"Biggoron's Prescription", "Ordonnance de Grogoron", "Receta de Biggoron"},
Text{"Eyedrop Prescription", "Ordonnance de gouttes", "Receta ocular"},
Text{"Urgent Prescription", "Ordonnance urgente", "Prescripción"}};
trickNameTable[GI_FROG] = {
Text{"Don Gero", "Don Gero", "Don Gero"},
Text{"Eyedrop Frog", "Grenouille-qui-louche", "Globo Ocular de Rana"},
Text{"Frog", "Crapaud", "Rana"}};
trickNameTable[GI_EYEDROPS] = {
Text{"Biggoron's Eyedrops", "Gouttes de Grogoron", "Gotas de Biggoron"},
Text{"Hyrule's Finest Eyedrops", "Eau du lac Hylia", "Gotas oculares"},
Text{"Zora Perfume", "Parfum Zora", "Perfume Zora"}};
trickNameTable[GI_CLAIM_CHECK] = {
Text{"Clay Check", "Certificat Grogoron", "Comprobante de Reclamación"},
Text{"Sheikah Slate", "Tablette Sheikah", "Piedra Sheikah"},
Text{"Cyclone Slate", "Ardoise des tornades", "Pizarra de los Torbellinos"}};
trickNameTable[GI_SKULL_TOKEN] = {
Text{"Skulltula Token", "Bon de Skulltula dorée", "Símbolo de Skulltula"},
Text{"Golden Skulltula Spirit", "Pièce de Skulltula dorée", "Tóken de Skulltula Dorada"},
Text{"Gold Walltula Token", "Jeton de Walltula dorée", "Skulltula dorada"}};
trickNameTable[0x80] = {
Text{"Progressive Grappling Hook", "Lance-chaîne (prog.)", "Garra progresiva"},
Text{"Progressive Clawshot", "Grappin-griffe (prog.)", "Zarpa progresiva"},
Text{"Progressive Gripshot", "Grappince (prog.)", "Enganchador progresivo"}};
trickNameTable[0x81] = {
Text{"Progressive Glove", "Gant de puissance (prog.)", "Guanteletes progresivos"},
Text{"Progressive Power Bracelet", "Bracelet de force (prog.)", "Brasaletes progresivos"},
Text{"Progressive Magic Bracelet", "Bracelet magique (prog.)", "Manoplas progresivas"}};
trickNameTable[0x82] = {
Text{"Progressive Bomb Capacity", "Capacité de bombes (prog.)", "Mayor capacidad de bombas"},
Text{"Progressive Bomb Pack", "Paquet de bombes (prog.)", "Zurrón de bombas progresivo"},
Text{"Progressive Bomb Box", "Boîte à bombes (prog.)", "Bolsa de bombas progresiva"}};
trickNameTable[0x83] = {
Text{"Progressive Arrow Capacity", "Capacité de flèches (prog.)", "Mayor capacidad de flechas"},
Text{"Progressive Hero's Bow", "Arc du héros (prog.)", "Arco del héroe progresivo"},
Text{"Progressive Arrow Holder", "Arbalète (prog.)", "Ballesta progresiva"}};
trickNameTable[0x84] = {
Text{"Progressive Seed Capacity", "Capacité de graines (prog.)", "Mayor capacidad de semillas"},
Text{"Progressive Scattershot", "Lance-pierre rafale (prog.)", "Resortera múltiple progresiva"},
Text{"Progressive Seed Satchel", "Sac de graines (prog.)", "Bolsa de semillas progresiva"}};
trickNameTable[0x85] = {
Text{"Progressive Rupee Capacity", "Capacité de rubis (prog.)", "Mayor capacidad de rupias"},
Text{"Progressive Purse", "Sacoche (prog.)", "Cartera de rupias progresiva"},
Text{"Progressive Rupee Bag", "Sac à rubis (prog.)", "Zurrón de rupias progresivo"}};
trickNameTable[0x86] = {
Text{"Progressive Diving Ability", "Plongée (prog.)", "Buceo progresivo"},
Text{"Progressive Pearl", "Perle (prog.)", "Perla progresiva"},
Text{"Progressive Scute", "Bulle (prog.)", "Fragmento Zora progresivo"}};
trickNameTable[0x87] = {
Text{"Progressive Nut Pack", "Paquet de noix (prog.)", "Mayor capacidad de semillas"},
Text{"Progressive Nut Bag", "Sac de noix (prog.)", "Bolsa de nueces progresiva"},
Text{"Progressive Husk Capacity", "Capacité de noisettes (prog.)", "Mayor capacidad de castañas"}};
trickNameTable[0x88] = {
Text{"Progressive Stick Pack", "Paquet de bâtons Mojo (prog.)", "Mayor capacidad de bastones"},
Text{"Progressive Stick Bag", "Sac de bâtons (prog.)", "Mayor capacidad de ramas deku"},
Text{"Progressive Rod Capacity", "Capacité de tiges (prog.)", "Mayor capacidad de cetros deku"}};
trickNameTable[0x89] = {
Text{"Progressive Bomblings", "Bombinsectes (prog.)", "Bombinsectos progresivos"},
Text{"Progressive Missiles", "Missiles (prog.)", "Misiles progresivos"},
Text{"Progressive Bombchu Bag", "Sac à Bombchu (prog.)", "Bombachus progresivos"}};
trickNameTable[0x8A] = {
Text{"Progressive Stamina Meter", "Jauge d'endurance (prog.)", "Medidor de vigor progresivo"},
Text{"Progressive Energy Meter", "Jauge d'énergie (prog.)", "Medidor de energía progresivo"},
Text{"Progressive Magic Powder", "Poudre magique (prog.)", "Medidor de carga progresivo"}};
trickNameTable[0x8B] = {
Text{"Progressive Memento", "Souvenir (prog.)", "Silbato progresivo"},
Text{"Progressive Flute", "Flûte (prog.)", "Flauta progresiva"},
Text{"Progressive Recorder", "Harmonica (prog.)", "Armónica progresiva"}};
trickNameTable[0xD4] = {
Text{"Progressive Titan Blade", "Lame des Titans (prog.)", "Hoja del Titán progresiva"},
Text{"Progressive Goron Knife", "Lame Goron (prog.)", "Daga Goron progresiva"},
Text{"Progressive Giant Sword", "Épée géante (prog.)", "Espada gigante progresiva"}};
trickNameTable[0x0F] = {
Text{"Magic Bottle", "Flacon magique", "Frasco feérico"},
Text{"Glass Bottle", "Flacon de verre", "Botella de cristal"},
Text{"Bottle with Water", "Flacon d'eau", "Botella Tingle"}};
trickNameTable[0x14] = {
Text{"Bottle with Chateau Romani", "Flacon de cuvée Romani", "Botella de Reserva Romani"},
Text{"Bottle with Fresh Milk", "Flacon de lait frais", "Botella de leche fresca"},
Text{"Bottle with Mystery Milk", "Flacon de lait grand cru", "Botella de leche extra"}};
trickNameTable[0x8C] = {
Text{"Bottle with Red Chu Jelly", "Flacon de gelée Chuchu rouge", "Jugo de Chuchu Rojo"},
Text{"Bottle with Medicine of Life", "Flacon d'élixir rouge", "Botella de medicina de la vida"},
Text{"Bottle with Heart Potion", "Flacon de potion de soin", "Botella de poción de salud"}};
trickNameTable[0x8D] = {
Text{"Bottle with Green Chu Jelly", "Flacon de gelée Chuchu verte", "Jugo de Chuchu Verde"},
Text{"Bottle with Medicine of Magic", "Flacon d'élixir vert", "Botella de medicina mágica"},
Text{"Bottle with Stamina Potion", "Flacon d'Endurol", "Botella de elixir vigorizante"}};
trickNameTable[0x8E] = {
Text{"Bottle with Blue Chu Jelly", "Flacon de gelée Chuchu bleue", "Jugo de Chuchu Azul"},
Text{"Bottle with Water of Life", "Flacon d'élixir bleu", "Botella de agua de la vida"},
Text{"Bottle with Air Potion", "Flacon de potion d'oxygène", "Botella de oxígeno"}};
trickNameTable[0x8F] = {
Text{"Bottle with Forest Firefly", "Flacon avec une luciole", "Luciérnaga del bosque"},
Text{"Bottle with Faerie", "Flacon de poudre féérique", "Gran Hada embotellada"},
Text{"Bottle with Stray Fairy", "Flacon avec une fée perdue", "Hada perdida en una botella"}};
trickNameTable[0x90] = {
Text{"Bottle with Small Jabu-Jabu", "Flacon avec mini Jabu-Jabu", "Lord Chapu-Chapu embotellado"},
Text{"Bottle with Hyrule Bass", "Flacon avec perche d'Hyrule", "Locha de Hyrule embotellada"},
Text{"Bottle with Hyrule Loach", "Flacon avec loche d'Hyrule", "Perca de Términa embotellada"}};
trickNameTable[0x91] = {
Text{"Bottle with Will-O-Wisp", "Flacon avec feu follet", "Botella de llama azul"},
Text{"Bottle with Ancient Flame", "Flacon de flamme ancienne", "Botella de fuego ancestral"},
Text{"Bottle with Nayru's Flame", "Flacon de flamme de Nayru", "Botella de llamas de Nayru"}};
trickNameTable[0x92] = {
Text{"Bottle with Baby Tektites", "Flacon de bébé Araknon", "Tektites en una botella"},
Text{"Bottle with Lanayru Ants", "Flacon de fourmis de Lanelle", "Celestarabajo embotellado"},
Text{"Bottle with Insects", "Flacon de bibittes", "Saltabosques embotellados"}};
trickNameTable[0x94] = {
Text{"Bottle with Ghini", "Flacon avec Ghini", "Ghini en una botella"},
Text{"Bottle with Imp Poe", "Flacon avec Spectre", "Espectro en una botella"},
Text{"Bottle with Anti-Fairy", "Flacon avec Tetdoss", "Whisp en una botella"}};
trickNameTable[0x15] = {
Text{"Bottle with Maggie's Letter", "Flacon avec lettre de Maggy", "Carta de Dolores"},
Text{"Bottle with Letter to Kafei", "Flacon avec lettre pour Kafei", "Carta para Kafei"},
Text{"Bottle with Zelda's Letter", "Flacon avec lettre de Zelda", "Carta náutica"}};
trickNameTable[0x93] = {
Text{"Bottle with Composer Brother", "Flacon avec un compositeur", "Hermana Poe embotellada"},
Text{"Bottle with Jalhalla", "Flacon avec Jalhalla", "Yaihalla embotellado"},
Text{"Bottle with Grim Repoe", "Flacon avec le Faucheur", "Bubble en una botella"}};
trickNameTable[0xC1] = {
Text{"Ballad of the Goddess", "Chant de la déesse", "Cántico de la Diosa"},
Text{"Song of Healing", "Chant de l'apaisement", "Canción de curación"},
Text{"Bolero of Fire", "Boléro du feu", "Bolero del fuego"}};
trickNameTable[0xC2] = {
Text{"Earth God's Lyric", "Hymne du dieu de la terre", "Melodía del Espíritu de la Tierra"},
Text{"Song of Soaring", "Chant de l'envol", "Canción del viento"},
Text{"Requiem of Spirit", "Requiem des esprits", "Réquiem del espíritu"}};
trickNameTable[0xC3] = {
Text{"Wind God's Aria", "Hymne du dieu du vent", "Melodía del Espíritu del Viento"},
Text{"Wind's Requiem", "Mélodie du vent", "Melodía del Viento"},
Text{"Minuet of Forest", "Menuet de la forêt", "Minueto del bosque"}};
trickNameTable[0xC4] = {
Text{"Song of Passing", "Mambo de Manbo", "Melodía del transcurrir"},
Text{"Command Melody", "Air du marionnettiste", "Cara al Sol"},
Text{"Prelude of Light", "Prélude de la lumière", "Preludio de la luz"}};
trickNameTable[0xC5] = {
Text{"Song of Double Time", "Chant accéléré", "Canción del doble tiempo"},
Text{"Inverted Song of Time", "Chant du temps inversé", "Canción del tiempo invertida"},
Text{"Serenade of Water", "Sérénade de l'eau", "Serenata del agua"}};
trickNameTable[0xC6] = {
Text{"Ballad of Gales", "Requiem de la tornade", "Melodía del Tornado"},
Text{"Frog's Song of Soul", "Rap des grenouilles", "Canción del alma de la rana"},
Text{"Nocturne of Shadow", "Nocturne de l'ombre", "Nocturno de la sombra"}};
trickNameTable[0xBB] = {
Text{"Saria's Karaoke", "Karaoké de Saria", "Dueto del bosque"},
Text{"Sonata of Awakening", "Sonate de l'éveil", "Sonata del despertar"},
Text{"Saria's Song", "Chant de Saria", "Canción de Saria"}};
trickNameTable[0xBC] = {
Text{"Darunia's Tango", "Tango de Darunia", "Coro del fuego"},
Text{"Goron Lullaby", "Berceuse des Gorons", "Nana goron"},
Text{"Zelda's Lullaby", "Berceuse de Zelda", "Nana de Zelda"}};
trickNameTable[0xBD] = {
Text{"Ruto's Blues", "Blues de Ruto", "Sonata del agua"},
Text{"New Wave Bossa Nova", "Bossa-nova des flots", "Bossanova de las olas"},
Text{"Song of Time", "Chant du temps", "Canción del tiempo"}};
trickNameTable[0xBE] = {
Text{"Nabooru's Reggae", "Reggae de Nabooru", "Reggae del espíritu"},
Text{"Elegy of Emptiness", "Hymne du vide", "Elegía al vacío"},
Text{"Epona's Song", "Chant d'Épona", "Canción de Epona"}};
trickNameTable[0xBF] = {
Text{"Impa's Death Metal", "Death métal d'Impa", "Diurno de la sombra"},
Text{"Oath to Order", "Ode de l'appel", "Oda al orden"},
Text{"Song of Storms", "Chant des tempêtes", "Canción de la tormenta"}};
trickNameTable[0xC0] = {
Text{"Rauru's Sing-Along", "Chansonnette de Rauru", "Predulio de luz"},
Text{"Ballad of the Wind Fish", "Ballade sur Poisson-Rêve", "Balada del Piez Viento"},
Text{"Sun's Song", "Chant du soleil", "Canción del Sol"}};
trickNameTable[0xCB] = {
Text{"Pendant of Courage", "Pendentif du courage", "Colgante del valor"},
Text{"Farore's Emerald", "Émeraude de Farore", "Esmeralda de Farore"},
Text{"Kokiri's Peridot", "Péridot Kokiri", "Ágata de los Kokiri"}};
trickNameTable[0xCC] = {
Text{"Pendant of Power", "Pendentif de la force", "Colgante del poder"},
Text{"Din's Ruby", "Rubis de Din", "Rubí de Din"},
Text{"Goron's Garnet", "Grenat Goron", "Topacio de los Goron"}};
trickNameTable[0xCD] = {
Text{"Pendant of Wisdom", "Pendentif de la sagesse", "Colgante de la sabiduría"},
Text{"Nayru's Sapphire", "Saphir de Nayru", "Zafiro de Nayru"},
Text{"Zora's Aquamarine", "Aquamarine Zora", "Lapislázuli de los Zora"}};
trickNameTable[0xCE] = {
Text{"Wind Medallion", "Médaillon du vent", "Medallón del Viento"},
Text{"Saria's Medallion", "Médaillon de Saria", "Medallón de Saria"},
Text{"Medallion of Forest", "Médaillon du temple de la forêt", "Medalla del Bosque"}};
trickNameTable[0xCF] = {
Text{"Bombos Medallion", "Médaillon des flammes", "Medallón del Temblor"},
Text{"Darunia's Medallion", "Médaillon de Darunia", "Medallón de Darunia"},
Text{"Medallion of Fire", "Médaillon du temple du feu", "Medalla del Fuego"}};
trickNameTable[0xD0] = {
Text{"Ice Medallion", "Médaillon de glace", "Medallón Helado"},
Text{"Ruto's Medallion", "Médaillon de Ruto", "Medallón de Ruto"},
Text{"Medallion of Water", "Médaillon du temple de l'eau", "Medalla del Agua"}};
trickNameTable[0xD1] = {
Text{"Quake Medallion", "Médaillon des secousses", "Medallón Llamarada"},
Text{"Nabooru's Medallion", "Médaillon de Nabooru", "Medallón de Nabooru"},
Text{"Medallion of Spirit", "Médaillon du temple de l'esprit", "Medalla del Espíritu"}};
trickNameTable[0xD2] = {
Text{"Travel Medallion", "Amulette de téléportation", "Medallón Maligno"},
Text{"Impa's Medallion", "Médaillon d'Impa", "Medallón de Impa"},
Text{"Medallion of Shadow", "Médaillon du temple de l'ombre", "Medalla de la Sombra"}};
trickNameTable[0xD3] = {
Text{"Ether Medallion", "Médaillon d'éther", "Medallón de Tesoro"},
Text{"Rauru's Medallion", "Médaillon de Rauru", "Medallón de Rauru"},
Text{"Medallion of Light", "Médaillon du temple de lumière", "Medalla de la Luz"}};
trickNameTable[GI_HEART] = {
Text{"Love", "Bisou", "Te amo"},
Text{"Heart Container", "Réceptacle de cœur", "Contenedor de corazón"},
Text{"Piece of Heart", "Quart de cœur", "Pieza de corazón"}};
trickNameTable[GI_RUPEE_GREEN] = {
Text{"Green Rupy", "Rupee vert", "Rubia verde"},
Text{"One Rupee", "Un rubis", "Guaraní hyliano"},
Text{"Rupee (1)", "Rubis (1)", "Peso hyliano"}};
trickNameTable[GI_RUPEE_BLUE] = {
Text{"Blue Rupy", "Rupee bleu", "Rubia azul"},
Text{"Five Rupees", "Cinq rubis", "Bolívar hyliano"},
Text{"Rupee (5)", "Rubis (5)", "Peso hyliano"}};
trickNameTable[GI_RUPEE_RED] = {
Text{"Red Rupy", "Rupee rouge", "Rubia roja"},
Text{"Twenty Rupees", "Vingt rubis", "Colon hyliano"},
Text{"Rupee (20)", "Rubis (20)", "Peso hyliano"}};
trickNameTable[GI_RUPEE_PURPLE] = {
Text{"Purple Rupy", "Rupee pourpre", "Rubia morada"},
Text{"Fifty Rupees", "Cinquante rubis", "Balboa hyliano"},
Text{"Rupee (50)", "Rubis (50)", "Peso hyliano"}};
trickNameTable[GI_RUPEE_GOLD] = {
Text{"Huge Rupy", "Énorme Rupee", "Rubia gigante"},
Text{"Two Hundred Rupees", "Deux cent rubis", "Euro hyliano"},
Text{"Rupee (200)", "Rubis (200)", "Dólar hyliano"}};
trickNameTable[GI_HEART_PIECE] = {
Text{"Piece of Health", "Quart d'énergie", "Pieza de amor"},
Text{"Recovery Heart", "Cœur d'énergie", "Corazón"},
Text{"Heart Container", "Réceptacle de cœur", "Contenedor de corazón"}};
trickNameTable[GI_HEART_CONTAINER_2] = {
Text{"Health Container", "Réceptacle d'énergie", "Contenedor de amor"},
Text{"Recovery Heart", "Quart de cœur", "Corazón"},
Text{"Piece of Heart", "Cœur d'énergie", "Pieza de corazón"}};
/*
//Names for individual upgrades, in case progressive names are replaced
trickNameTable[GI_HOOKSHOT] = {
Text{"Grappling Hook", "Grappin-griffe", "Gancho lanzable"},
Text{"Clawshot", "Lance-chaîne", "Zarpa"},
Text{"Gripshot", "Grappince", "Enganchador"}};
trickNameTable[GI_LONGSHOT] = {
Text{"Longshot, no strings attached", "Grappin sans attrape", "Gancho lanzable más largo"},
Text{"Double Clawshot", "Double-grappin", "Superzarpa"},
Text{"Switch Hook", "Great grappin", "Gancho chulo"}};
trickNameTable[GI_BOMB_BAG_1] = {
Text{"Bomb Capacity (20)", "Capacité de bombes (20)", "Bolsa de bombas (contiene 20)"},
Text{"Bronze Bomb Bag", "Sac de bombes de bronze", "Saco de bronce de bombas"},
Text{"Small Bomb Bag", "Petit sac de bombes", "Zurrón de bombas pequeño"}};
trickNameTable[GI_BOMB_BAG_2] = {
Text{"Bomb Capacity (30)", "Capacité de bombes (30)", "Bolsa de bombas (contiene 30)"},
Text{"Silver Bomb Bag", "Sac de bombes d'argent", "Saco plateado de bombas"},
Text{"Medium Bomb Bag", "Sac de bombes moyen", "Zurrón de bombas mediano"}};
trickNameTable[GI_BOMB_BAG_3] = {
Text{"Bomb Capacity (40)", "Capacité de bombes (40)", "Bolsa de bombas (contiene 40)"},
Text{"Golden Bomb Bag", "Sac de bombes d'or", "Saco dorado de bombas"},
Text{"Large Bomb Bag", "Gros sac de bombes", "Zurrón de bombas grande"}};
trickNameTable[GI_BOW_1] = {
Text{"Bow", "Arc", "Arco del Hada"},
Text{"Hero's Bow", "Arc du héros", "Arco del héroe"},
Text{"Small Quiver", "Petit carquois", "Saco de flechas pequeño"}};
trickNameTable[GI_BOW_2] = {
Text{"Arrow Capacity (40)", "Capacité de flèches (40)", "Capacidad de flechas (40)"},
Text{"Silver Quiver", "Carquois d'argent", "Carcaj plateado"},
Text{"Medium Quiver", "Carquois moyen", "Saco de flechas mediano"}};
trickNameTable[GI_BOW_3] = {
Text{"Arrow Capacity (50)", "Capacité de flèches (50)", "Capacidad de flechas (50)"},
Text{"Golden Quiver", "Carquois d'or", "Carcaj dorado"},
Text{"Large Quiver", "Gros carquois", "Saco de flechas grande"}};
trickNameTable[GI_SLINGSHOT_1] = {
Text{"Slingshot", "Lance-pierre", "Tirachinas del Hada"},
Text{"Scattershot", "Lance-pierre rafale", "Tirachinas múltiple"},
Text{"Small Seed Satchel", "Petit sac de graines", "Bolsa de semillas pequeña"}};
trickNameTable[GI_SLINGSHOT_2] = {
Text{"Deku Seed Capacity (40)", "Capacité de graines (40)", "Capacidad de semillas (40)"},
Text{"Silver Deku Seed Bullet Bag", "Sac de graines d'argent", "Bolsa de balas (contiene 40)"},
Text{"Medium Seed Satchel", "Sac de graines moyen", "Bolsa de semillas mediana"}};
trickNameTable[GI_SLINGSHOT_3] = {
Text{"Deku Seed Capacity (50)", "Capacité de graines (50)", "Capacidad de semillas (50)"},
Text{"Golden Deku Seed Bullet Bag", "Sac de graines d'or", "Bolsa de balas (contiene 50)"},
Text{"Large Seed Satchel", "Gros sac de graines", "Bolsa de semillas grande"}};
trickNameTable[GI_STRENGTH_1] = {
Text{"Goron's Gauntlet", "Gantelet Goron", "Brazalete amarillo"},
Text{"Power Bracelet", "Bracelet de force", "Brazalete de fuerza"},
Text{"Magic Bracelet", "Bracelet de Lavio", "Brazalete de Ravio"}};
trickNameTable[GI_STRENGTH_2] = {
Text{"Silver Bracelets", "Bracelets d'argent", "Guantes Moguma"},
Text{"Power Gloves", "Gant de puissance", "Guante del Poder"},
Text{"Magic Gauntlets", "Gantelet magique", "Guante mágico"}};
trickNameTable[GI_STRENGTH_3] = {
Text{"Golden Bracelets", "Bracelets d'or", "Guantelete de Thanos"},
Text{"Titan's Mitts", "Moufle de titan", "Guantes de Titán"},
Text{"Magnetic Gloves", "Magnéto-gants", "Guantes de fuego"}};
trickNameTable[GI_SCALE_1] = {
Text{"Silver Pearl", "Perle d'argent", "Perla de Plata progresiva"},
Text{"Adult Scale", "Écaille d'adulte", "Bola de bolos zora"},
Text{"Zora Scale", "Écaille Zora", "Escama de Zora"}};
trickNameTable[GI_SCALE_2] = {
Text{"Golden Pearl", "Perle d'or", "Perla de Oro progresiva"},
Text{"Giant Scale", "Écaille de géant", "Escama de Faren"},
Text{"Water Dragon Scale", "Écaille du dragon de l'eau", "Escama de dragón acuático"}};
trickNameTable[GI_WALLET_1] = {
Text{"Rupee Capacity (200)", "Capacité de rubis (200)", "Capacidad de rupias (200)"},
Text{"Silver Wallet", "Bourse d'argent", "Cartera de rupias de adulto"},
Text{"Medium Wallet", "Bourse moyenne", "Zurrón de rupias mediano"}};
trickNameTable[GI_WALLET_2] = {
Text{"Rupee Capacity (500)", "Capacité de rubis (500)", "Capacidad de rupias (500)"},
Text{"Golden Wallet", "Bourse d'or", "Cartera de rupias gigante"},
Text{"Large Wallet", "Grosse bourse", "Zurrón de rupias grande"}};
trickNameTable[GI_WALLET_3] = {
Text{"Rupee Capacity (999)", "Capacité de rubis (999)", "Capacidad de rupias (999)"},
Text{"Golden Wallet", "Bourse d'or", "Cartera de ricachón"},
Text{"Large Wallet", "Grosse bourse", "Zurrón de rupias gigante"}};
trickNameTable[GI_DEKU_NUT_UPGRADE_1] = {
Text{"Deku Bomb Capacity (30)", "Capacité de bombes Mojo (30)", "Capacidad de semillas deku (40)"},
Text{"Baba Nut Capacity (30)", "Capacité de noix Baba (30)", "Capacidad de nueces baba (40)"},
Text{"Deku Nut Pack (30)", "Paquet de noix Mojo (30)", "Capacidad de nueces mojo (40)"}};
trickNameTable[GI_DEKU_NUT_UPGRADE_2] = {
Text{"Deku Bomb Capacity (40)", "Capacité de bombes Mojo (40)", "Capacidad de semillas deku (50)"},
Text{"Baba Nut Capacity (40)", "Capacité de noix Baba (40)", "Capacidad de nueces baba (50)"},
Text{"Deku Nut Pack (40)", "Paquet de noix Mojo (40)", "Capacidad de nueces mojo (50)"}};
trickNameTable[GI_DEKU_STICK_UPGRADE_1] = {
Text{"Deku Rod Capacity (20)", "Capacité de tiges Mojo (20)", "Capacidad de palos mojo (20)"},
Text{"Boko Stick Capacity (20)", "Capacité de bâtons Boko (20)", "Capacidad de palos boko (20)"},
Text{"Deku Stick Pack (20)", "Paquet de bâtons Mojo (20)", "Capacidad de bastones deku (20)"}};
trickNameTable[GI_DEKU_STICK_UPGRADE_2] = {
Text{"Deku Rod Capacity (30)", "Capacité de tiges Mojo (30)", "Capacidad de palos mojo (30)"},
Text{"Boko Stick Capacity (30)", "Capacité de bâtons Boko (30)", "Capacidad de palos boko (30)"},
Text{"Deku Stick Pack (30)", "Paquet de bâtons Mojo (30)", "Capacidad de bastones deku (30)"}};
trickNameTable[GI_MAGIC_1] = {
Text{"Stamina Meter", "Jauge d'endurance", "Medidor de vigor"},
Text{"Energy Meter", "Jauge d'énergie", "Medidor de energía"},
Text{"Magic Powder", "Poudre magique", "Medidor de carga"}};
trickNameTable[GI_MAGIC_2] = {
Text{"Enhanced Stamina Meter", "Jauge d'endurance améliorée", "Medidor de vigor mejorado"},
Text{"Enhanced Energy Meter", "Jauge d'énergie améliorée", "Medidor de energía mejorado"},
Text{"Enhanced Magic Powder", "Poudre magique améliorée", "Medidor de carga mejorado"}};
trickNameTable[GI_OCARINA_1] = {
Text{"Ocarina", "Ocarina", "Ocarina"},
Text{"Saria's Ocarina", "Ocarina de Saria", "Ocarina de Saria"},
Text{"Wood Ocarina", "Ocarina de bois", "Ocarina del Hada"}};
trickNameTable[GI_OCARINA_2] = {
Text{"Flute", "Flûte", "Flauta"},
Text{"Zelda's Ocarina", "Ocarina de Zelda", "Ocarina de Zelda"},
Text{"Ocarina of Winds", "Ocarina des vents", "Ocarina del Viento"}};
trickNameTable[GI_CUCCO] = {
Text{"D.I.Y. Alarm Clock", "Réveille-matin improvisé", "Alarma emplumada"},
Text{"Kakariko Cucco", "Cocotte Cocorico", "Cuco de Kakariko"},
Text{"Hatched Cucco", "Cocotte éclose", "Pollo"}};
trickNameTable[GI_MASK_KEATON] = {
Text{"Kee... Something Mask", "Masque de Quiche", "Máscara Kealgo"},
Text{"Kitsune Mask", "Masque de Kitsune", "Máscara Kitsune"},
Text{"Kafei's Mask", "Masque de Kafei", "Máscara de Kafei"}};
trickNameTable[GI_MASK_SKULL] = {
Text{"Skull Kid's Mask", "Masque de Skull Kid", "Máscara de Skull Kid"},
Text{"Stalfos Mask", "Masque de squelette", "Máscara de Stalfos"},
Text{"Captain's Hat", "Heaume du capitaine", "Casco del capitán"}};
trickNameTable[GI_MASK_SPOOKY] = {
Text{"Skrik Mask", "Masque Skrik", "Máscara Escalofriante"},
Text{"ReDead Mask", "Masque de Remort", "Máscara de ReDead"},
Text{"Gibdo Mask", "Masque de Gibdo", "Careta de Gibdo"}};
trickNameTable[GI_MASK_BUNNY] = {
Text{"Peppy Mask", "Masque de Peppy", "Capucha de Pascua"},
Text{"Bunny Ears", "Oreilles de lapin", "Orejas de conejo"},
Text{"Postman's Hat", "Casquette du facteur", "Gorra de cartero"}};
trickNameTable[GI_MASK_GORON] = {
Text{"Goro Mask", "Masque Goro", "Máscara Goro"},
Text{"Mask of Goron", "Masque des Gorons", "Máscara de los Goron"},
Text{"Darunia Mask", "Masque de Darunia", "Máscara de Darmani"}};
trickNameTable[GI_MASK_ZORA] = {
Text{"Zola Mask", "Masque Zola", "Máscara Zola"},
Text{"Mask of Zora", "Masque des Zoras", "Máscara de los Zora"},
Text{"Ruto Mask", "Masque de Ruto", "Máscara de Mikau"}};
trickNameTable[GI_MASK_GERUDO] = {
Text{"Ganguro Mask", "Masque de Ganguro", "Máscara Canguro"},
Text{"Mask of Gerudo", "Masque des Gerudos", "Máscara de las Gerudo"},
Text{"Nabooru Mask", "Masque de Nabooru", "Máscara de Nabooru"}};
trickNameTable[GI_MASK_TRUTH] = {
Text{"Sheikah Mask", "Masque Sheikah", "Máscara Sheikah"},
Text{"Mask of Gossip", "Masque de potins", "Máscara chismosa"},
Text{"Eye of Truth", "Œil de vérité", "Ojo de la Verdad"}};*/
}
//Generate a fake name for the ice trap based on the item it's displayed as
Text GetIceTrapName(uint8_t id) {
//If the trick names table has not been initialized, do so
if (!initTrickNames) {
InitTrickNames();
initTrickNames = true;
}
//Randomly get the easy, medium, or hard name for the given item id
return RandomElement(trickNameTable[id]);
}
//Get shop index based on a given location
static std::map<std::string_view, int> ShopNameToNum = {{"KF Shop", 0}, {"Kak Potion Shop", 1}, {"MK Bombchu Shop", 2}, {"MK Potion Shop", 3},
{"MK Bazaar", 4}, {"Kak Bazaar", 5}, {"ZD Shop", 6}, {"GC Shop", 7}};
int GetShopIndex(uint32_t loc) {
//Kind of hacky, but extract the shop and item position from the name
const std::string& name(Location(loc)->GetName());
int split = name.find(" Item ");
std::string_view shop(name.c_str(), split);
int pos = std::stoi(name.substr(split+6, 1)) - 1;
int shopnum = ShopNameToNum[shop];
return shopnum*8 + pos;
}
//Without this transformed index, shop-related tables and arrays would need 64 entries- But only half of that is needed for shopsanity
//So we use this transformation to map only important indices to an array with 32 entries in the following manner:
//Shop index: 4 5 6 7 12 13 14 15 20 21 22 23...
//Transformed: 0 1 2 3 4 5 6 7 8 9 10 11...
//So we first divide the shop index by 4, then by 2 which basically tells us the index of the shop it's in,
//then multiply by 4 since there are 4 items per shop
//And finally we use a modulo by 4 to get the index within the "shop" of 4 items, and add
int TransformShopIndex(int index) {
return 4*((index / 4) / 2) + index % 4;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "item.hpp"
#include "item_location.hpp"
#include <vector>
#include <array>
struct ItemAndPrice {
Text Name;
int Price;
bool Repurchaseable;
};
extern void PlaceVanillaShopItems();
extern std::vector<uint32_t> GetMinVanillaShopItems(int total_replaced);
extern int GetRandomShopPrice();
extern int16_t GetRandomScrubPrice();
extern int GetShopsanityReplaceAmount();
extern Text GetIceTrapName(uint8_t id);
extern int GetShopIndex(uint32_t loc);
extern int TransformShopIndex(int index);
extern std::vector<ItemAndPrice> NonShopItems;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,76 @@
#pragma once
#include <array>
#include <vector>
#include <stdint.h>
#define SFX_BASE 0x1000001
#define SFX_COUNT 1388
#define SFX_COUNT_TRIMMED (1388 - 233)
namespace SFX {
typedef enum {
// Movement
SEQ_WALK,
SEQ_WALK_LOUD,
SEQ_JUMP,
SEQ_LAND,
SEQ_SLIP,
SEQ_SLIP_LOOP,
SEQ_BOUND,
SEQ_CRAWL,
SEQ_MOVE_LOOP,
// Combat
SEQ_WEAPON_SWING,
SEQ_WEAPON_HIT,
SEQ_THROW_LOOP,
SEQ_PROJECTILE_SHOT,
SEQ_MAGIC_CHARGE_LOOP,
SEQ_EXPLOSION,
// Monsters
SEQ_MONSTER_CRY_SHORT,
SEQ_MONSTER_CRY_MEDIUM,
SEQ_MONSTER_CRY_LONG,
SEQ_MONSTER_DAMAGED,
SEQ_MONSTER_DEAD,
SEQ_MONSTER_ATTACK,
// Voice
SEQ_VOICE_SHORT,
SEQ_VOICE_MEDIUM,
SEQ_VOICE_LONG,
// Etc
SEQ_DOOR_OPEN,
SEQ_DOOR_CLOSE,
SEQ_AMBIENCE,
// Meta
SEQTYPE_COUNT,
SEQ_NOCAT = 0xFE, // For sound effects that doesn't fit into any category
SEQ_NOSHUFFLE = 0xFF, // For DUMMYs and YOBIs that are either blank or duplicates, and for system sound effects
} SeqType;
typedef struct {
/// Contains the amount of sound effects in each group, excluding SEQ_NOCAT and SEQ_NOSHUFFLE.
uint16_t rSeqMaxes[SEQTYPE_COUNT];
/// Contains the original list of SeqTypes.
/// Can be used to check which type a sound effect is.
SeqType rSeqTypesSFX[SFX_COUNT];
/// Contains all sound effects.
uint32_t rSFXOverrides_All[SFX_COUNT];
/// Contains all sound effects excluding SEQ_NOSHUFFLE.
uint32_t rSFXOverrides_AllTrimmed[SFX_COUNT_TRIMMED];
/// Contains all sound effects grouped into their SeqTypes.
/// The size of the second dimension should be at least the amount in the largest group.
uint32_t rSFXOverrides_Types[SEQTYPE_COUNT][100];
} SFXData;
extern const std::array<SeqType, SFX_COUNT> seqTypesSFX;
const SFXData& GetSFXData();
void InitSFXRandomizer();
void ShuffleSequences(bool shuffleCategorically);
} // namespace SFX

View file

@ -0,0 +1,633 @@
#include "spoiler_log.hpp"
#include "dungeon.hpp"
#include "item_list.hpp"
#include "item_location.hpp"
#include "entrance.hpp"
#include "random.hpp"
#include "settings.hpp"
#include "trial.hpp"
#include "tinyxml2.h"
#include "utils.hpp"
#include "shops.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include <json.hpp>
#include <iostream>
#include <fstream>
using json = nlohmann::json;
namespace {
std::string placementtxt;
} // namespace
static RandomizerHash randomizerHash;
static SpoilerData spoilerData;
void GenerateHash() {
for (size_t i = 0; i < Settings::seed.size(); i++) {
int number = Settings::seed[i] - '0';
Settings::hashIconIndexes[i] = number;
}
// Clear out spoiler log data here, in case we aren't going to re-generate it
// spoilerData = { 0 };
}
const RandomizerHash& GetRandomizerHash() {
return randomizerHash;
}
// Returns the randomizer hash as concatenated string, separated by comma.
const std::string GetRandomizerHashAsString() {
std::string hash = "";
for (const std::string& str : randomizerHash) {
hash += str + ", ";
}
hash.erase(hash.length() - 2); // Erase last comma
return hash;
}
const SpoilerData& GetSpoilerData() {
return spoilerData;
}
static auto GetGeneralPath() {
return "./randomizer/haha.xml";
}
static auto GetSpoilerLogPath() {
return GetGeneralPath();
}
static auto GetPlacementLogPath() {
return GetGeneralPath();
}
void WriteIngameSpoilerLog() {
uint16_t spoilerItemIndex = 0;
uint32_t spoilerStringOffset = 0;
uint16_t spoilerSphereItemoffset = 0;
uint16_t spoilerGroupOffset = 0;
// Intentionally junk value so we trigger the 'new group, record some stuff' code
uint8_t currentGroup = SpoilerCollectionCheckGroup::SPOILER_COLLECTION_GROUP_COUNT;
bool spoilerOutOfSpace = false;
// Create map of string data offsets for all _unique_ item locations and names in the playthrough
// Some item names, like gold skulltula tokens, can appear many times in a playthrough
std::unordered_map<uint32_t, uint16_t>
itemLocationsMap; // Map of LocationKey to an index into spoiler data item locations
itemLocationsMap.reserve(allLocations.size());
std::unordered_map<std::string, uint16_t>
stringOffsetMap; // Map of strings to their offset into spoiler string data array
stringOffsetMap.reserve(allLocations.size() * 2);
// Sort all locations by their group, so the in-game log can show a group of items by simply starting/ending at
// certain indices
std::stable_sort(allLocations.begin(), allLocations.end(), [](const uint32_t& a, const uint32_t& b) {
auto groupA = Location(a)->GetCollectionCheckGroup();
auto groupB = Location(b)->GetCollectionCheckGroup();
return groupA < groupB;
});
for (const uint32_t key : allLocations) {
auto loc = Location(key);
// Hide excluded locations from ingame tracker
if (loc->IsExcluded()) {
continue;
}
// Cows
else if (!Settings::ShuffleCows && loc->IsCategory(Category::cCow)) {
continue;
}
// Merchants
else if (Settings::ShuffleMerchants.Is(SHUFFLEMERCHANTS_OFF) && loc->IsCategory(Category::cMerchant)) {
continue;
}
// Adult Trade
else if (!Settings::ShuffleAdultTradeQuest && loc->IsCategory(Category::cAdultTrade)) {
continue;
}
// Chest Minigame
else if (Settings::ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_OFF) &&
loc->IsCategory(Category::cChestMinigame)) {
continue;
}
// Gerudo Fortress
else if ((Settings::GerudoFortress.Is(GERUDOFORTRESS_OPEN) &&
(loc->IsCategory(Category::cVanillaGFSmallKey) || loc->GetHintKey() == GF_GERUDO_TOKEN)) ||
(Settings::GerudoFortress.Is(GERUDOFORTRESS_FAST) && loc->IsCategory(Category::cVanillaGFSmallKey) &&
loc->GetHintKey() != GF_NORTH_F1_CARPENTER)) {
continue;
}
// Copy at most 51 chars from the name and location name to avoid issues with names that don't fit on screen
const char* nameFormatStr = "%.51s";
auto locName = loc->GetName();
if (stringOffsetMap.find(locName) == stringOffsetMap.end()) {
if (spoilerStringOffset + locName.size() + 1 >= SPOILER_STRING_DATA_SIZE) {
spoilerOutOfSpace = true;
break;
} else {
stringOffsetMap[locName] = spoilerStringOffset;
spoilerStringOffset +=
sprintf(&spoilerData.StringData[spoilerStringOffset], nameFormatStr, locName.c_str()) + 1;
}
}
auto locItem = loc->GetPlacedItemName().GetEnglish();
if (loc->GetPlacedItemKey() == ICE_TRAP && loc->IsCategory(Category::cShop)) {
locItem = NonShopItems[TransformShopIndex(GetShopIndex(key))].Name.GetEnglish();
}
if (stringOffsetMap.find(locItem) == stringOffsetMap.end()) {
if (spoilerStringOffset + locItem.size() + 1 >= SPOILER_STRING_DATA_SIZE) {
spoilerOutOfSpace = true;
break;
} else {
stringOffsetMap[locItem] = spoilerStringOffset;
spoilerStringOffset +=
sprintf(&spoilerData.StringData[spoilerStringOffset], nameFormatStr, locItem.c_str()) + 1;
}
}
spoilerData.ItemLocations[spoilerItemIndex].LocationStrOffset = stringOffsetMap[locName];
spoilerData.ItemLocations[spoilerItemIndex].ItemStrOffset = stringOffsetMap[locItem];
spoilerData.ItemLocations[spoilerItemIndex].LocationStr = locName;
spoilerData.ItemLocations[spoilerItemIndex].ItemStr = locItem;
spoilerData.ItemLocations[spoilerItemIndex].CollectionCheckType = loc->GetCollectionCheck().type;
spoilerData.ItemLocations[spoilerItemIndex].LocationScene = loc->GetCollectionCheck().scene;
spoilerData.ItemLocations[spoilerItemIndex].LocationFlag = loc->GetCollectionCheck().flag;
// Collect Type and Reveal Type
if (key == GANON) {
spoilerData.ItemLocations[spoilerItemIndex].CollectType = COLLECTTYPE_NEVER;
spoilerData.ItemLocations[spoilerItemIndex].RevealType = REVEALTYPE_ALWAYS;
} else if (key == MARKET_BOMBCHU_BOWLING_BOMBCHUS) {
spoilerData.ItemLocations[spoilerItemIndex].CollectType = COLLECTTYPE_REPEATABLE;
spoilerData.ItemLocations[spoilerItemIndex].RevealType = REVEALTYPE_ALWAYS;
}
// Shops
else if (loc->IsShop()) {
if (Settings::Shopsanity.Is(SHOPSANITY_OFF)) {
spoilerData.ItemLocations[spoilerItemIndex].RevealType = REVEALTYPE_ALWAYS;
} else {
spoilerData.ItemLocations[spoilerItemIndex].RevealType = REVEALTYPE_SCENE;
}
if (loc->GetPlacedItem().GetItemType() == ITEMTYPE_REFILL ||
loc->GetPlacedItem().GetItemType() == ITEMTYPE_SHOP ||
loc->GetPlacedItem().GetHintKey() == PROGRESSIVE_BOMBCHUS) {
spoilerData.ItemLocations[spoilerItemIndex].CollectType = COLLECTTYPE_REPEATABLE;
}
}
// Gold Skulltulas
else if (loc->IsCategory(Category::cSkulltula) &&
((Settings::Tokensanity.Is(TOKENSANITY_OFF)) ||
(Settings::Tokensanity.Is(TOKENSANITY_DUNGEONS) && !loc->IsDungeon()) ||
(Settings::Tokensanity.Is(TOKENSANITY_OVERWORLD) && loc->IsDungeon()))) {
spoilerData.ItemLocations[spoilerItemIndex].RevealType = REVEALTYPE_ALWAYS;
}
// Deku Scrubs
else if (loc->IsCategory(Category::cDekuScrub) && !loc->IsCategory(Category::cDekuScrubUpgrades) &&
Settings::Scrubsanity.Is(SCRUBSANITY_OFF)) {
spoilerData.ItemLocations[spoilerItemIndex].CollectType = COLLECTTYPE_REPEATABLE;
spoilerData.ItemLocations[spoilerItemIndex].RevealType = REVEALTYPE_ALWAYS;
}
auto checkGroup = loc->GetCollectionCheckGroup();
spoilerData.ItemLocations[spoilerItemIndex].Group = checkGroup;
// Group setup
if (checkGroup != currentGroup) {
currentGroup = checkGroup;
spoilerData.GroupOffsets[currentGroup] = spoilerGroupOffset;
}
++spoilerData.GroupItemCounts[currentGroup];
++spoilerGroupOffset;
itemLocationsMap[key] = spoilerItemIndex++;
}
spoilerData.ItemLocationsCount = spoilerItemIndex;
if (Settings::IngameSpoilers) {
bool playthroughItemNotFound = false;
// Write playthrough data to in-game spoiler log
if (!spoilerOutOfSpace) {
for (uint32_t i = 0; i < playthroughLocations.size(); i++) {
if (i >= SPOILER_SPHERES_MAX) {
spoilerOutOfSpace = true;
break;
}
spoilerData.Spheres[i].ItemLocationsOffset = spoilerSphereItemoffset;
for (uint32_t loc = 0; loc < playthroughLocations[i].size(); ++loc) {
if (spoilerSphereItemoffset >= SPOILER_ITEMS_MAX) {
spoilerOutOfSpace = true;
break;
}
const auto foundItemLoc = itemLocationsMap.find(playthroughLocations[i][loc]);
if (foundItemLoc != itemLocationsMap.end()) {
spoilerData.SphereItemLocations[spoilerSphereItemoffset++] = foundItemLoc->second;
} else {
playthroughItemNotFound = true;
}
++spoilerData.Spheres[i].ItemCount;
}
++spoilerData.SphereCount;
}
}
if (spoilerOutOfSpace || playthroughItemNotFound) {
printf("%sError!%s ", YELLOW, WHITE);
}
}
}
// Writes the location to the specified node.
static void WriteLocation(
tinyxml2::XMLElement* parentNode, const uint32_t locationKey,
const bool withPadding = false
) {
ItemLocation* location = Location(locationKey);
auto node = parentNode->InsertNewChildElement("location");
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->IsCategory(Category::cShop)) {
// 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->IsCategory(Category::cShop)) {
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
static void WriteShuffledEntrance(
tinyxml2::XMLElement* parentNode,
Entrance* entrance,
const bool withPadding = false
) {
auto node = parentNode->InsertNewChildElement("entrance");
node->SetAttribute("name", entrance->GetName().c_str());
auto text = entrance->GetConnectedRegion()->regionName + " from " + entrance->GetReplacement()->GetParentRegion()->regionName;
node->SetText(text.c_str());
if (withPadding) {
constexpr int16_t LONGEST_NAME = 56; //The longest name of a vanilla entrance
//Insert padding so we get a kind of table in the XML document
int16_t requiredPadding = LONGEST_NAME - entrance->GetName().length();
if (requiredPadding > 0) {
std::string padding(requiredPadding, ' ');
node->SetAttribute("_", padding.c_str());
}
}
}
// Writes the settings (without excluded locations, starting inventory and tricks) to the spoilerLog document.
static void WriteSettings(tinyxml2::XMLDocument& spoilerLog, const bool printAll = false) {
auto parentNode = spoilerLog.NewElement("settings");
std::vector<Menu*> allMenus = Settings::GetAllOptionMenus();
for (const Menu* menu : allMenus) {
//This is a menu of settings, write them
if (menu->mode == OPTION_SUB_MENU && menu->printInSpoiler) {
for (const Option* setting : *menu->settingsList) {
if (printAll || (!setting->IsHidden() && setting->IsCategory(OptionCategory::Setting))) {
auto node = parentNode->InsertNewChildElement("setting");
node->SetAttribute("name", RemoveLineBreaks(setting->GetName()).c_str());
node->SetText(setting->GetSelectedOptionText().c_str());
}
}
}
}
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
// Writes the excluded locations to the spoiler log, if there are any.
static void WriteExcludedLocations(tinyxml2::XMLDocument& spoilerLog) {
auto parentNode = spoilerLog.NewElement("excluded-locations");
for (size_t i = 1; i < Settings::excludeLocationsOptionsVector.size(); i++) {
for (const auto& location : Settings::excludeLocationsOptionsVector[i]) {
if (location->GetSelectedOptionIndex() == INCLUDE) {
continue;
}
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.
static void WriteStartingInventory(tinyxml2::XMLDocument& spoilerLog) {
auto parentNode = spoilerLog.NewElement("starting-inventory");
std::vector<std::vector<Option *>*> startingInventoryOptions = {
&Settings::startingItemsOptions,
&Settings::startingSongsOptions,
&Settings::startingEquipmentOptions,
&Settings::startingStonesMedallionsOptions,
};
for (std::vector<Option *>* menu : startingInventoryOptions) {
for (size_t i = 0; i < menu->size(); ++i) {
const auto setting = menu->at(i);
//Ignore no starting bottles and the Choose/All On toggles
if (setting->IsDefaultSelected()) {
continue;
}
auto node = parentNode->InsertNewChildElement("item");
node->SetAttribute("name", setting->GetName().c_str());
node->SetText(setting->GetSelectedOptionText().c_str());
}
}
if (!parentNode->NoChildren()) {
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
}
// Writes the enabled tricks to the spoiler log, if there are any.
static void WriteEnabledTricks(tinyxml2::XMLDocument& spoilerLog) {
auto parentNode = spoilerLog.NewElement("enabled-tricks");
for (const auto& setting : Settings::trickOptions) {
if (setting->GetSelectedOptionIndex() != TRICK_ENABLED || !setting->IsCategory(OptionCategory::Setting)) {
continue;
}
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.
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) {
auto parentNode = spoilerLog.NewElement("master-quest-dungeons");
for (const auto* dungeon : Dungeon::dungeonList) {
if (dungeon->IsVanilla()) {
continue;
}
auto node = parentNode->InsertNewChildElement("dungeon");
node->SetAttribute("name", dungeon->GetName().c_str());
}
if (!parentNode->NoChildren()) {
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
}
// Writes the required trails to the spoiler log, if there are any.
static void WriteRequiredTrials(tinyxml2::XMLDocument& spoilerLog) {
auto parentNode = spoilerLog.NewElement("required-trials");
for (const auto* trial : Trial::trialList) {
if (trial->IsSkipped()) {
continue;
}
auto node = parentNode->InsertNewChildElement("trial");
std::string name = trial->GetName().GetEnglish();
name[0] = toupper(name[0]); // Capitalize T in "The"
node->SetAttribute("name", name.c_str());
}
if (!parentNode->NoChildren()) {
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
}
// Writes the intended playthrough to the spoiler log, separated into spheres.
static void WritePlaythrough(tinyxml2::XMLDocument& spoilerLog) {
auto playthroughNode = spoilerLog.NewElement("playthrough");
for (uint32_t i = 0; i < playthroughLocations.size(); ++i) {
auto sphereNode = playthroughNode->InsertNewChildElement("sphere");
sphereNode->SetAttribute("level", i + 1);
for (const uint32_t key : playthroughLocations[i]) {
WriteLocation(sphereNode, key, true);
}
}
spoilerLog.RootElement()->InsertEndChild(playthroughNode);
}
//Write the randomized entrance playthrough to the spoiler log, if applicable
static void WriteShuffledEntrances(tinyxml2::XMLDocument& spoilerLog) {
if (!Settings::ShuffleEntrances || noRandomEntrances) {
return;
}
auto playthroughNode = spoilerLog.NewElement("entrance-playthrough");
for (uint32_t i = 0; i < playthroughEntrances.size(); ++i) {
auto sphereNode = playthroughNode->InsertNewChildElement("sphere");
sphereNode->SetAttribute("level", i + 1);
for (Entrance* entrance : playthroughEntrances[i]) {
WriteShuffledEntrance(sphereNode, entrance, true);
}
}
spoilerLog.RootElement()->InsertEndChild(playthroughNode);
}
// Writes the WOTH locations to the spoiler log, if there are any.
static void WriteWayOfTheHeroLocation(tinyxml2::XMLDocument& spoilerLog) {
auto parentNode = spoilerLog.NewElement("way-of-the-hero-locations");
for (const uint32_t key : wothLocations) {
WriteLocation(parentNode, key, true);
}
if (!parentNode->NoChildren()) {
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
}
// Writes the hints to the spoiler log, if they are enabled.
static void WriteHints(tinyxml2::XMLDocument& spoilerLog) {
if (Settings::GossipStoneHints.Is(HINTS_NO_HINTS)) {
return;
}
auto parentNode = spoilerLog.NewElement("hints");
for (const uint32_t key : gossipStoneLocations) {
ItemLocation* location = Location(key);
auto node = parentNode->InsertNewChildElement("hint");
node->SetAttribute("location", location->GetName().c_str());
auto text = location->GetPlacedItemName().GetEnglish();
std::replace(text.begin(), text.end(), '&', ' ');
std::replace(text.begin(), text.end(), '^', ' ');
node->SetText(text.c_str());
}
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
json jsonData;
static void WriteAllLocations() {
for (const uint32_t key : allLocations) {
ItemLocation* location = Location(key);
jsonData["locations"][location->GetName()] = location->GetPlacedItemName().english;
}
}
const char* SpoilerLog_Write() {
auto spoilerLog = tinyxml2::XMLDocument(false);
spoilerLog.InsertEndChild(spoilerLog.NewDeclaration());
auto rootNode = spoilerLog.NewElement("spoiler-log");
spoilerLog.InsertEndChild(rootNode);
rootNode->SetAttribute("version", Settings::version.c_str());
rootNode->SetAttribute("seed", Settings::seed.c_str());
rootNode->SetAttribute("hash", GetRandomizerHashAsString().c_str());
// Write Hash
int index = 0;
for (uint8_t seed_value : Settings::hashIconIndexes) {
jsonData["file_hash"][index] = seed_value;
index++;
}
//WriteSettings(spoilerLog);
//WriteExcludedLocations(spoilerLog);
//WriteStartingInventory(spoilerLog);
//WriteEnabledTricks(spoilerLog);
//if (Settings::Logic.Is(LOGIC_GLITCHED)) {
// WriteEnabledGlitches(spoilerLog);
//}
//WriteMasterQuestDungeons(spoilerLog);
//WriteRequiredTrials(spoilerLog);
//WritePlaythrough(spoilerLog);
//WriteWayOfTheHeroLocation(spoilerLog);
playthroughLocations.clear();
playthroughBeatable = false;
wothLocations.clear();
//WriteHints(spoilerLog);
//WriteShuffledEntrances(spoilerLog);
WriteAllLocations();
std::string jsonString = jsonData.dump(4);
std::ofstream jsonFile("./randomizer/" + Settings::seed + ".json");
jsonFile << std::setw(4) << jsonString << std::endl;
jsonFile.close();
return Settings::seed.c_str();
}
void PlacementLog_Msg(std::string_view msg) {
placementtxt += msg;
}
void PlacementLog_Clear() {
placementtxt = "";
}
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.c_str());
rootNode->SetAttribute("hash", GetRandomizerHashAsString().c_str());
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;
}

View file

@ -0,0 +1,111 @@
#pragma once
#include <array>
#include <string>
#include <string_view>
using RandomizerHash = std::array<std::string, 5>;
typedef enum {
SPOILER_CHK_NONE,
SPOILER_CHK_ALWAYS_COLLECTED,
SPOILER_CHK_CHEST,
SPOILER_CHK_COLLECTABLE,
SPOILER_CHK_GOLD_SKULLTULA,
SPOILER_CHK_ITEM_GET_INF,
SPOILER_CHK_EVENT_CHK_INF,
SPOILER_CHK_INF_TABLE,
SPOILER_CHK_COW,
SPOILER_CHK_MINIGAME,
SPOILER_CHK_SCRUB,
SPOILER_CHK_BIGGORON,
SPOILER_CHK_GERUDO_TOKEN,
SPOILER_CHK_POE_POINTS,
SPOILER_CHK_SHOP_ITEM,
SPOILER_CHK_MAGIC_BEANS,
} SpoilerCollectionCheckType;
// Location groups for checks, used to group the checks by logical location
typedef enum {
GROUP_NO_GROUP,
GROUP_KOKIRI_FOREST, // 0x55, 0x28
GROUP_LOST_WOODS, // 0x5B, 0x56
GROUP_DUNGEON_DEKU_TREE, // 0x00, 0x11
GROUP_DUNGEON_FOREST_TEMPLE, // 0x03
GROUP_KAKARIKO, // 0x37, 0x42, 0x3F, 0x40, 0x41, 0x48, 0x52, 0x53
GROUP_DUNGEON_BOTTOM_OF_THE_WELL, // 0x08
GROUP_DUNGEON_SHADOW_TEMPLE, // 0x07
GROUP_DEATH_MOUNTAIN, // 0x60, 0x61
GROUP_GORON_CITY, // 0x62
GROUP_DUNGEON_DODONGOS_CAVERN, // 0x01, 0x12
GROUP_DUNGEON_FIRE_TEMPLE, // 0x04
GROUP_ZORAS_RIVER, // 0x54
GROUP_ZORAS_DOMAIN, // 0x58, 0x59
GROUP_DUNGEON_JABUJABUS_BELLY, // 0x02, 0x13
GROUP_DUNGEON_ICE_CAVERN, // 0x09
GROUP_HYRULE_FIELD, // 0x51
GROUP_LON_LON_RANCH, // 0x4C
GROUP_LAKE_HYLIA, // 0x57
GROUP_DUNGEON_WATER_TEMPLE, // 0x05
GROUP_GERUDO_VALLEY, // 0x5A, 0x5D, 0x0C, 0x5E, 0x5C
GROUP_GERUDO_TRAINING_GROUND, // 0x0B
GROUP_DUNGEON_SPIRIT_TEMPLE, // 0x06
GROUP_HYRULE_CASTLE, // 0x10, 0x4B, 0x35, 0x42, 0x4D, 0x5F, 0x4A
GROUP_DUNGEON_GANONS_CASTLE, // 0x0A, 0x0D, 0x0E, 0x0F
SPOILER_COLLECTION_GROUP_COUNT,
// Grottos are all 0x3E
} SpoilerCollectionCheckGroup;
typedef enum {
COLLECTTYPE_NORMAL,
COLLECTTYPE_REPEATABLE,
COLLECTTYPE_NEVER,
} SpoilerItemCollectType;
typedef enum {
REVEALTYPE_NORMAL,
REVEALTYPE_SCENE,
REVEALTYPE_ALWAYS,
} SpoilerItemRevealType;
#define SPOILER_SPHERES_MAX 50
#define SPOILER_ITEMS_MAX 512
#define SPOILER_STRING_DATA_SIZE 16384
typedef struct {
std::string LocationStr;
std::string ItemStr;
uint16_t LocationStrOffset;
uint16_t ItemStrOffset;
SpoilerCollectionCheckType CollectionCheckType;
uint8_t LocationScene;
uint8_t LocationFlag;
SpoilerCollectionCheckGroup Group;
SpoilerItemCollectType CollectType;
SpoilerItemRevealType RevealType;
} SpoilerItemLocation;
typedef struct {
uint8_t ItemCount;
uint16_t ItemLocationsOffset;
} SpoilerSphere;
typedef struct {
uint8_t SphereCount;
uint16_t ItemLocationsCount;
SpoilerSphere Spheres[SPOILER_SPHERES_MAX];
SpoilerItemLocation ItemLocations[SPOILER_ITEMS_MAX];
uint16_t SphereItemLocations[SPOILER_ITEMS_MAX];
char StringData[SPOILER_STRING_DATA_SIZE];
uint16_t GroupItemCounts[SPOILER_COLLECTION_GROUP_COUNT];
uint16_t GroupOffsets[SPOILER_COLLECTION_GROUP_COUNT];
} SpoilerData;
void GenerateHash();
const RandomizerHash& GetRandomizerHash();
void WriteIngameSpoilerLog();
const char* SpoilerLog_Write();
const SpoilerData& GetSpoilerData();

View file

@ -0,0 +1,193 @@
#include "starting_inventory.hpp"
#include "debug.hpp"
#include "dungeon.hpp"
#include "item_list.hpp"
#include "settings.hpp"
using namespace Settings;
using namespace Dungeon;
std::vector<uint32_t> StartingInventory;
uint8_t AdditionalHeartContainers;
static void AddItemToInventory(uint32_t item, size_t count = 1) {
StartingInventory.insert(StartingInventory.end(), count, item);
}
void GenerateStartingInventory() {
StartingInventory.clear();
if (MapsAndCompasses.Is(MAPSANDCOMPASSES_START_WITH)) {
for (auto* dungeon : dungeonList) {
if (dungeon->GetMap() != NONE) {
AddItemToInventory(dungeon->GetMap());
}
if (dungeon->GetCompass() != NONE) {
AddItemToInventory(dungeon->GetCompass());
}
}
}
if (Keysanity.Is(KEYSANITY_START_WITH)) {
for (auto* dungeon : dungeonList) {
if (dungeon->GetSmallKeyCount() > 0) {
AddItemToInventory(dungeon->GetSmallKey(), dungeon->GetSmallKeyCount());
}
}
} else if (Keysanity.Is(KEYSANITY_VANILLA)) {
// Logic cannot handle vanilla key layout in some dungeons
// this is because vanilla expects the dungeon major item to be
// locked behind the keys, which is not always true in rando.
// We can resolve this by starting with some extra keys
// - OoT Randomizer
if (SpiritTemple.IsMQ()) {
AddItemToInventory(SPIRIT_TEMPLE_SMALL_KEY, 3);
}
}
if (BossKeysanity.Is(BOSSKEYSANITY_START_WITH)) {
AddItemToInventory(FOREST_TEMPLE_BOSS_KEY);
AddItemToInventory(FIRE_TEMPLE_BOSS_KEY);
AddItemToInventory(WATER_TEMPLE_BOSS_KEY);
AddItemToInventory(SPIRIT_TEMPLE_BOSS_KEY);
AddItemToInventory(SHADOW_TEMPLE_BOSS_KEY);
}
if (GanonsBossKey.Is(GANONSBOSSKEY_START_WITH)) {
AddItemToInventory(GANONS_CASTLE_BOSS_KEY);
}
if (GerudoFortress.Is(GERUDOFORTRESS_OPEN) && !ShuffleGerudoToken) {
AddItemToInventory(GERUDO_TOKEN);
}
//Starting Inventory Menu
//Values are associated so that the count of items matches the index of
//the option selected. If None is selected, the value will be zero and
//zero of the item will be added to the starting inventory.
AddItemToInventory(PROGRESSIVE_STICK_UPGRADE, StartingStickCapacity.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_NUT_UPGRADE, StartingNutCapacity.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_BOMB_BAG, StartingBombBag.Value<uint8_t>());
AddItemToInventory((BombchusInLogic ? PROGRESSIVE_BOMBCHUS : BOMBCHU_20), StartingBombchus.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_BOW, StartingBow.Value<uint8_t>());
AddItemToInventory(FIRE_ARROWS, StartingFireArrows.Value<uint8_t>());
AddItemToInventory(ICE_ARROWS, StartingIceArrows.Value<uint8_t>());
AddItemToInventory(LIGHT_ARROWS, StartingLightArrows.Value<uint8_t>());
AddItemToInventory(DINS_FIRE, StartingDinsFire.Value<uint8_t>());
AddItemToInventory(FARORES_WIND, StartingFaroresWind.Value<uint8_t>());
AddItemToInventory(NAYRUS_LOVE, StartingNayrusLove.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_SLINGSHOT, StartingSlingshot.Value<uint8_t>());
AddItemToInventory(BOOMERANG, StartingBoomerang.Value<uint8_t>());
AddItemToInventory(LENS_OF_TRUTH, StartingLensOfTruth.Value<uint8_t>());
AddItemToInventory(MAGIC_BEAN_PACK, StartingMagicBean.Value<uint8_t>());
AddItemToInventory(MEGATON_HAMMER, StartingMegatonHammer.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_HOOKSHOT, StartingHookshot.Value<uint8_t>());
AddItemToInventory(IRON_BOOTS, StartingIronBoots.Value<uint8_t>());
AddItemToInventory(HOVER_BOOTS, StartingHoverBoots.Value<uint8_t>());
//For starting bottles, we need to check if they are a big poe and add that if so
// since a big poe bottle is not logically equivalent to an empty bottle.
if (StartingBottle1.Value<uint8_t>() == STARTINGBOTTLE_BIG_POE) {
AddItemToInventory(BOTTLE_WITH_BIG_POE, 1);
} else if (StartingBottle1.Value<uint8_t>()) {
AddItemToInventory(EMPTY_BOTTLE, 1);
}
if (StartingBottle2.Value<uint8_t>() == STARTINGBOTTLE_BIG_POE) {
AddItemToInventory(BOTTLE_WITH_BIG_POE, 1);
} else if (StartingBottle2.Value<uint8_t>()) {
AddItemToInventory(EMPTY_BOTTLE, 1);
}
if (StartingBottle3.Value<uint8_t>() == STARTINGBOTTLE_BIG_POE) {
AddItemToInventory(BOTTLE_WITH_BIG_POE, 1);
} else if (StartingBottle3.Value<uint8_t>()) {
AddItemToInventory(EMPTY_BOTTLE, 1);
}
if (StartingBottle4.Value<uint8_t>() == STARTINGBOTTLE_BIG_POE) {
AddItemToInventory(BOTTLE_WITH_BIG_POE, 1);
} else if (StartingBottle4.Value<uint8_t>()) {
AddItemToInventory(EMPTY_BOTTLE, 1);
}
AddItemToInventory(RUTOS_LETTER, StartingRutoBottle.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_OCARINA, StartingOcarina.Value<uint8_t>());
AddItemToInventory(ZELDAS_LULLABY, StartingZeldasLullaby.Value<uint8_t>());
AddItemToInventory(EPONAS_SONG, StartingEponasSong.Value<uint8_t>());
AddItemToInventory(SARIAS_SONG, StartingSariasSong.Value<uint8_t>());
AddItemToInventory(SUNS_SONG, StartingSunsSong.Value<uint8_t>());
AddItemToInventory(SONG_OF_TIME, StartingSongOfTime.Value<uint8_t>());
AddItemToInventory(SONG_OF_STORMS, StartingSongOfStorms.Value<uint8_t>());
AddItemToInventory(MINUET_OF_FOREST, StartingMinuetOfForest.Value<uint8_t>());
AddItemToInventory(BOLERO_OF_FIRE, StartingBoleroOfFire.Value<uint8_t>());
AddItemToInventory(SERENADE_OF_WATER, StartingSerenadeOfWater.Value<uint8_t>());
AddItemToInventory(REQUIEM_OF_SPIRIT, StartingRequiemOfSpirit.Value<uint8_t>());
AddItemToInventory(NOCTURNE_OF_SHADOW, StartingNocturneOfShadow.Value<uint8_t>());
AddItemToInventory(PRELUDE_OF_LIGHT, StartingPreludeOfLight.Value<uint8_t>());
AddItemToInventory(KOKIRI_SWORD, StartingKokiriSword.Value<uint8_t>());
if (ProgressiveGoronSword) {
AddItemToInventory(PROGRESSIVE_GORONSWORD, StartingBiggoronSword.Value<uint8_t>());
} else {
AddItemToInventory(GIANTS_KNIFE, (StartingBiggoronSword.Is(STARTINGBGS_GIANTS_KNIFE)) ? 1 : 0);
AddItemToInventory(BIGGORON_SWORD, (StartingBiggoronSword.Is(STARTINGBGS_BIGGORON_SWORD)) ? 1 : 0);
}
AddItemToInventory(DEKU_SHIELD, StartingDekuShield.Value<uint8_t>());
AddItemToInventory(HYLIAN_SHIELD, StartingHylianShield.Value<uint8_t>());
AddItemToInventory(MIRROR_SHIELD, StartingMirrorShield.Value<uint8_t>());
AddItemToInventory(GORON_TUNIC, StartingGoronTunic.Value<uint8_t>());
AddItemToInventory(ZORA_TUNIC, StartingZoraTunic.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_MAGIC_METER, StartingMagicMeter.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_STRENGTH, StartingStrength.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_SCALE, StartingScale.Value<uint8_t>());
AddItemToInventory(PROGRESSIVE_WALLET, StartingWallet.Value<uint8_t>());
AddItemToInventory(SHARD_OF_AGONY, StartingShardOfAgony.Value<uint8_t>());
AddItemToInventory(DOUBLE_DEFENSE, StartingDoubleDefense.Value<uint8_t>());
AddItemToInventory(KOKIRI_EMERALD, StartingKokiriEmerald.Value<uint8_t>());
AddItemToInventory(GORON_RUBY, StartingGoronRuby.Value<uint8_t>());
AddItemToInventory(ZORA_SAPPHIRE, StartingZoraSapphire.Value<uint8_t>());
AddItemToInventory(FOREST_MEDALLION, StartingForestMedallion.Value<uint8_t>());
AddItemToInventory(FIRE_MEDALLION, StartingFireMedallion.Value<uint8_t>());
AddItemToInventory(WATER_MEDALLION, StartingWaterMedallion.Value<uint8_t>());
AddItemToInventory(SPIRIT_MEDALLION, StartingSpiritMedallion.Value<uint8_t>());
AddItemToInventory(SHADOW_MEDALLION, StartingShadowMedallion.Value<uint8_t>());
AddItemToInventory(LIGHT_MEDALLION, StartingLightMedallion.Value<uint8_t>());
AddItemToInventory(GOLD_SKULLTULA_TOKEN, StartingSkulltulaToken.Value<uint8_t>());
int8_t hearts = StartingHearts.Value<uint8_t>() - 2;
AdditionalHeartContainers = 0;
if (hearts < 0) {
AddItemToInventory(PIECE_OF_HEART, 4);
// Plentiful and minimal have less than 4 standard pieces of heart so also replace the winner heart
if (ItemPoolValue.Value<uint8_t>() == 0 || ItemPoolValue.Value<uint8_t>() == 3) {
AddItemToInventory(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 - ItemPoolValue.Value<uint8_t>());
if (hearts <= maxContainers) {
AddItemToInventory(HEART_CONTAINER, hearts);
} else {
AddItemToInventory(HEART_CONTAINER, maxContainers);
AddItemToInventory(PIECE_OF_HEART, (hearts - maxContainers) * 4);
}
if (hearts == 17) {
AddItemToInventory(TREASURE_GAME_HEART);
}
}
}
bool StartingInventoryHasBottle() {
uint32_t bottle = EMPTY_BOTTLE;
return ElementInContainer(bottle, StartingInventory);
}
void ApplyStartingInventory() {
for (uint32_t item : StartingInventory) {
if (item == PIECE_OF_HEART || item == HEART_CONTAINER || item == TREASURE_GAME_HEART)
continue;
ItemTable(item).ApplyEffect();
}
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "keys.hpp"
#include <vector>
#include <stdint.h>
extern std::vector<uint32_t> StartingInventory;
extern uint8_t AdditionalHeartContainers;
void GenerateStartingInventory();
bool StartingInventoryHasBottle();
void ApplyStartingInventory();

View file

@ -0,0 +1,90 @@
#pragma once
#include <string>
#define PLURAL 0
#define SINGULAR 1
class Text {
public:
Text() = default;
Text(std::string english_, std::string french_, std::string spanish_)
: english(std::move(english_)),
french(std::move(french_)),
spanish(std::move(spanish_)) {}
const std::string& GetEnglish() const {
return english;
}
const std::string& GetFrench() const {
if (french.length() > 0) {
return french;
}
return english;
}
const std::string& GetSpanish() const {
if (spanish.length() > 0) {
return spanish;
}
return english;
}
Text operator+ (const Text& right) const {
return Text{english + right.GetEnglish(), french + right.GetFrench(), spanish + right.GetSpanish()};
}
Text operator+ (const std::string& right) const {
return Text{english + right, french + right, spanish + right};
}
bool operator==(const Text& right) const {
return english == right.english;
}
bool operator!=(const Text& right) const {
return !operator==(right);
}
void Replace(std::string oldStr, std::string newStr) {
for (std::string* str : {&english, &french, &spanish}) {
size_t position = str->find(oldStr);
while (position != std::string::npos) {
str->replace(position, oldStr.length(), newStr);
position = str->find(oldStr);
}
}
}
//find the appropriate bars that separate singular from plural
void SetForm(int form) {
for (std::string* str : {&english, &french, &spanish}) {
size_t firstBar = str->find('|');
if (firstBar != std::string::npos) {
size_t secondBar = str->find('|', firstBar + 1);
if (secondBar != std::string::npos) {
size_t thirdBar = str->find('|', secondBar + 1);
if (thirdBar != std::string::npos) {
if (form == SINGULAR) {
str->erase(secondBar, thirdBar - secondBar);
} else {
str->erase(firstBar, secondBar - firstBar);
}
}
}
}
}
//remove the remaining bar
this->Replace("|", "");
}
std::string english = "";
std::string french = "";
std::string spanish = "";
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
#include "trial.hpp"
namespace Trial {
TrialInfo::TrialInfo(Text name_)
: name(std::move(name_)) {}
TrialInfo::~TrialInfo() = default;
TrialInfo ForestTrial = TrialInfo(Text{"the Forest Trial", /*french*/"l'épreuve de la forêt", /*spanish*/"la prueba del bosque"});
TrialInfo FireTrial = TrialInfo(Text{"the Fire Trial", /*french*/"l'épreuve du feu", /*spanish*/"la prueba del fuego"});
TrialInfo WaterTrial = TrialInfo(Text{"the Water Trial", /*french*/"l'épreuve de l'eau", /*spanish*/"la prueba del agua"});
TrialInfo SpiritTrial = TrialInfo(Text{"the Spirit Trial", /*french*/"l'épreuve de l'esprit", /*spanish*/"la prueba del espíritu"});
TrialInfo ShadowTrial = TrialInfo(Text{"the Shadow Trial", /*french*/"l'épreuve de l'ombre", /*spanish*/"la prueba de las sombras"});
TrialInfo LightTrial = TrialInfo(Text{"the Light Trial", /*french*/"l'épreuve de la lumière", /*spanish*/"la prueba de la luz"});
const TrialArray trialList = {
&ForestTrial,
&FireTrial,
&WaterTrial,
&SpiritTrial,
&ShadowTrial,
&LightTrial,
};
} //namespace Trial

View file

@ -0,0 +1,51 @@
#pragma once
#include <array>
#include "text.hpp"
//This is probably overkill for a small amount of information, but we can add
//stuff later if we want
namespace Trial {
class TrialInfo {
public:
TrialInfo(const Text name_);
~TrialInfo();
Text GetName() const {
return name;
}
bool IsSkipped() const {
return skipped;
}
bool IsRequired() const {
return !skipped;
}
void SetAsRequired() {
skipped = false;
}
void SetAsSkipped() {
skipped = true;
}
private:
Text name;
bool skipped = true;
};
extern TrialInfo ForestTrial;
extern TrialInfo FireTrial;
extern TrialInfo WaterTrial;
extern TrialInfo SpiritTrial;
extern TrialInfo ShadowTrial;
extern TrialInfo LightTrial;
using TrialArray = std::array<TrialInfo*, 6>;
extern const TrialArray trialList;
} //namespace Trial

View file

@ -0,0 +1,7 @@
#include "utils.hpp"
// Removes any line breaks from s.
std::string RemoveLineBreaks(std::string s) {
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
return s;
}

View file

@ -0,0 +1,6 @@
#pragma once
#include <algorithm>
#include <string>
std::string RemoveLineBreaks(std::string s);

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,10 @@
#include <unordered_map>
#include <string>
#include "../../include/ultra64.h"
#include "../../include/z64item.h"
#include "../../../include/ultra64.h"
#include "../../../include/z64item.h"
// #include "randomizer/keys.hpp"
// #include <randomizer/spoiler_log.hpp>
#include <randomizerTypes.h>
class Randomizer {
@ -20,9 +22,20 @@ class Randomizer {
s16 GetItemModelFromId(s16 itemId);
void LoadItemLocations(const char* spoilerFileName);
void ParseItemLocations(const char* spoilerFileName);
void ParseItemLocationsFile(const char* spoilerFileName);
GetItemID GetRandomizedItemIdFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId);
GetItemID GetRandomizedItemId(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum);
};
#ifdef __cplusplus
extern "C" {
#endif
void Rando_Init(void);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -29,6 +29,7 @@
#include "AudioPlayer.h"
#include "Enhancements/debugconsole.h"
#include "Enhancements/debugger/debugger.h"
#include "Enhancements/randomizer/randomizer.h"
#include "soh/frame_interpolation.h"
#include "Utils/BitConverter.h"
#include "variables.h"
@ -36,6 +37,7 @@
#include <Utils/StringHelper.h>
#include <SDL2/SDL_scancode.h>
// #include <randomizer/spoiler_log.hpp>
OTRGlobals* OTRGlobals::Instance;
@ -75,6 +77,7 @@ extern "C" void InitOTR() {
OTRMessage_Init();
DebugConsole_Init();
Debug_Init();
Rando_Init();
}
#ifdef _WIN32
@ -1032,8 +1035,8 @@ extern "C" void LoadItemLocations(const char* spoilerFileName) {
OTRGlobals::Instance->gRandomizer->LoadItemLocations(spoilerFileName);
}
extern "C" void ParseItemLocations(const char* spoilerFileName) {
OTRGlobals::Instance->gRandomizer->ParseItemLocations(spoilerFileName);
extern "C" void ParseItemLocationsFile(const char* spoilerFileName) {
OTRGlobals::Instance->gRandomizer->ParseItemLocationsFile(spoilerFileName);
}
extern "C" s32 GetRandomizedItemId(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum) {

View file

@ -7,7 +7,9 @@
#ifdef __cplusplus
#include "Enhancements/savestates.h"
#include "Enhancements/randomizer.h"
#include "Enhancements/randomizer/randomizer.h"
// #include "randomizer/spoiler_log.hpp"
class OTRGlobals
{
public:
@ -75,8 +77,7 @@ void AudioPlayer_Play(const uint8_t* buf, uint32_t len);
void AudioMgr_CreateNextAudioBuffer(s16* samples, u32 num_samples);
int Controller_ShouldRumble(size_t i);
void LoadItemLocations(const char* spoilerFileName);
void ParseItemLocations(const char* spoilerfilename);
ItemID GetItemIdFromGetItem(GetItemID getItemId);
void ParseItemLocationsFile(const char* spoilerFileName);
s16 GetItemModelFromId(s16 itemId);
s32 GetRandomizedItemId(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum);
s32 GetRandomizedItemIdFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId);

View file

@ -1923,6 +1923,24 @@ u32 Actor_HasParent(Actor* actor, GlobalContext* globalCtx) {
}
}
s32 GiveItemWithoutActor(GlobalContext* globalCtx, s32 getItemId) {
Player* player = GET_PLAYER(globalCtx);
if (!(player->stateFlags1 & 0x3C7080) && Player_GetExplosiveHeld(player) < 0) {
if (((player->heldActor != NULL) && (getItemId > GI_NONE) && (getItemId < GI_MAX)) ||
(!(player->stateFlags1 & 0x20000800))) {
if ((getItemId != GI_NONE)) {
player->getItemId = getItemId;
player->interactRangeActor = &player->actor;
player->getItemDirection = player->actor.shape.rot.y;
return true;
}
}
}
return false;
}
s32 func_8002F434(Actor* actor, GlobalContext* globalCtx, s32 getItemId, f32 xzRange, f32 yRange) {
Player* player = GET_PLAYER(globalCtx);

View file

@ -2050,6 +2050,11 @@ void Cutscene_HandleEntranceTriggers(GlobalContext* globalCtx) {
u8 requiredAge;
s16 i;
if (gSaveContext.n64ddFlag) {
gSaveContext.showTitleCard = false;
return;
}
for (i = 0; i < ARRAY_COUNT(sEntranceCutsceneTable); i++) {
entranceCutscene = &sEntranceCutsceneTable[i];
@ -2085,10 +2090,12 @@ void Cutscene_HandleConditionalTriggers(GlobalContext* globalCtx) {
Flags_SetEventChkInf(0xAA);
gSaveContext.cutsceneIndex = 0xFFF0;
} else if ((gSaveContext.entranceIndex == 0x05E0) && !Flags_GetEventChkInf(0xC1)) {
if (!gSaveContext.n64ddFlag) {
Flags_SetEventChkInf(0xC1);
Item_Give(globalCtx, ITEM_OCARINA_FAIRY);
gSaveContext.entranceIndex = 0x011E;
gSaveContext.cutsceneIndex = 0xFFF0;
}
} else if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) &&
LINK_IS_ADULT && !Flags_GetEventChkInf(0xC4) &&
(gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_TOKINOMA)) {

View file

@ -193,59 +193,14 @@ void Gameplay_Destroy(GameState* thisx) {
gGlobalCtx = NULL;
}
void GiveLinksPocketMedallion(GlobalContext* globalCtx) {
if (gSaveContext.n64ddFlag) {
RandomizerGet get = gSaveContext.itemLocations[LINKS_POCKET].get;
void GivePlayerRandoRewardSariaGift(GlobalContext* globalCtx, RandomizerCheck check) {
Player* player = GET_PLAYER(globalCtx);
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(check, GI_ZELDAS_LULLABY);
s16 item;
u8 medallion = 0;
switch (get) {
case FOREST_MEDALLION:
item = ITEM_MEDALLION_FOREST;
medallion = 1;
break;
case FIRE_MEDALLION:
item = ITEM_MEDALLION_FIRE;
medallion = 1;
break;
case WATER_MEDALLION:
item = ITEM_MEDALLION_WATER;
medallion = 1;
break;
case SHADOW_MEDALLION:
item = ITEM_MEDALLION_SHADOW;
medallion = 1;
break;
case SPIRIT_MEDALLION:
item = ITEM_MEDALLION_SPIRIT;
medallion = 1;
break;
case LIGHT_MEDALLION:
item = ITEM_MEDALLION_LIGHT;
medallion = 1;
break;
case KOKIRI_EMERALD:
item = ITEM_KOKIRI_EMERALD;
break;
case GORON_RUBY:
item = ITEM_GORON_RUBY;
break;
case ZORA_SAPPHIRE:
item = ITEM_ZORA_SAPPHIRE;
break;
}
if (medallion == 1) {
gSaveContext.inventory.questItems |= gBitFlags[item - ITEM_MEDALLION_FOREST + QUEST_MEDALLION_FOREST];
if (item == ITEM_MEDALLION_WATER) {
func_8006D0AC(globalCtx);
}
} else {
gSaveContext.inventory.questItems |= gBitFlags[item - ITEM_KOKIRI_EMERALD + QUEST_KOKIRI_EMERALD];
}
if (gSaveContext.entranceIndex == 0x05E0 && !Flags_GetEventChkInf(0xC1) && player != NULL &&
!Player_InBlockingCsMode(globalCtx, player)) {
GiveItemWithoutActor(globalCtx, getItemId);
Flags_SetEventChkInf(0xC1);
}
}
@ -1101,6 +1056,8 @@ skip:
Environment_Update(globalCtx, &globalCtx->envCtx, &globalCtx->lightCtx, &globalCtx->pauseCtx, &globalCtx->msgCtx,
&globalCtx->gameOverCtx, globalCtx->state.gfxCtx);
GivePlayerRandoRewardSariaGift(globalCtx, RC_LW_GIFT_FROM_SARIA);
}
void Gameplay_DrawOverlayElements(GlobalContext* globalCtx) {

View file

@ -676,6 +676,56 @@ void Sram_VerifyAndLoadAllSaves(FileChooseContext* fileChooseCtx, SramContext* s
osSyncPrintf("now_life=%d, %d, %d\n", fileChooseCtx->health[0], fileChooseCtx->health[1], fileChooseCtx->health[2]);
}
void GiveLinksPocketMedallion() {
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(RC_LINKS_POCKET, RG_NONE);
s16 item;
u8 medallion = 0;
switch (getItemId) {
case GI_MEDALLION_FOREST:
item = ITEM_MEDALLION_FOREST;
medallion = 1;
break;
case GI_MEDALLION_FIRE:
item = ITEM_MEDALLION_FIRE;
medallion = 1;
break;
case GI_MEDALLION_WATER:
item = ITEM_MEDALLION_WATER;
medallion = 1;
break;
case GI_MEDALLION_SHADOW:
item = ITEM_MEDALLION_SHADOW;
medallion = 1;
break;
case GI_MEDALLION_SPIRIT:
item = ITEM_MEDALLION_SPIRIT;
medallion = 1;
break;
case GI_MEDALLION_LIGHT:
item = ITEM_MEDALLION_LIGHT;
medallion = 1;
break;
case GI_STONE_KOKIRI:
item = ITEM_KOKIRI_EMERALD;
break;
case GI_STONE_GORON:
item = ITEM_GORON_RUBY;
break;
case GI_STONE_ZORA:
item = ITEM_ZORA_SAPPHIRE;
break;
}
if (medallion == 1) {
gSaveContext.inventory.questItems |= gBitFlags[item - ITEM_MEDALLION_FOREST + QUEST_MEDALLION_FOREST];
} else {
gSaveContext.inventory.questItems |= gBitFlags[item - ITEM_KOKIRI_EMERALD + QUEST_KOKIRI_EMERALD];
}
}
void Sram_InitSave(FileChooseContext* fileChooseCtx, SramContext* sramCtx) {
u16 offset;
u16 j;
@ -714,6 +764,22 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx, SramContext* sramCtx) {
gSaveContext.newf[3], gSaveContext.newf[4], gSaveContext.newf[5]);
osSyncPrintf("\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
// If the save file is a randomizer one
if (CVar_GetS32("gRandomizer", 0) != 0) {
// Set N64DD Flags for save file
fileChooseCtx->n64ddFlags[fileChooseCtx->buttonIndex] = 1;
fileChooseCtx->n64ddFlag = 1;
gSaveContext.n64ddFlag = 1;
// Set Cutscene flags to skip them
gSaveContext.infTable[0] |= 1;
gSaveContext.cutsceneIndex = 0;
Flags_SetEventChkInf(5);
// Give Link's pocket item
GiveLinksPocketMedallion();
}
ptr = (u16*)&gSaveContext;
j = 0;
checksum = 0;

View file

@ -898,17 +898,15 @@ void func_80986BF8(DemoIm* this, GlobalContext* globalCtx) {
}
}
u8 successImpa;
void GivePlayerRandoRewardImpa(Actor* impa, GlobalContext* globalCtx, RandomizerCheck check) {
if (!Player_InBlockingCsMode(globalCtx, GET_PLAYER(globalCtx))) {
if (successImpa == 0 && (globalCtx->actorCtx.titleCtx.delayTimer == 0) &&
(globalCtx->actorCtx.titleCtx.alpha == 0)) {
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(check, GI_LETTER_ZELDA);
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(check, GI_ZELDAS_LULLABY);
if (func_8002F434(impa, globalCtx, getItemId, 100.0f, 50.0f) == true) {
successImpa = 1;
}
} else if (successImpa == 1) {
if (impa->parent != NULL && impa->parent->id == GET_PLAYER(globalCtx)->actor.id &&
!Flags_GetTreasure(globalCtx, 0x1F)) {
Flags_SetTreasure(globalCtx, 0x1F);
} else if (!Flags_GetTreasure(globalCtx, 0x1F)) {
func_8002F434(impa, globalCtx, getItemId, 75.0f, 50.0f);
} else if (!Player_InBlockingCsMode(globalCtx, GET_PLAYER(globalCtx))) {
gSaveContext.eventChkInf[5] |= 0x200;
globalCtx->sceneLoadFlag = 0x14;
globalCtx->fadeTransition = 3;
@ -917,12 +915,11 @@ void GivePlayerRandoRewardImpa(Actor* impa, GlobalContext* globalCtx, Randomizer
gSaveContext.nextCutsceneIndex = 0;
}
}
}
void func_80986C30(DemoIm* this, GlobalContext* globalCtx) {
if (func_80986A5C(this, globalCtx)) {
if (gSaveContext.n64ddFlag) {
GivePlayerRandoRewardImpa(this, globalCtx, SONG_FROM_IMPA);
GivePlayerRandoRewardImpa(this, globalCtx, RC_SONG_FROM_IMPA);
} else {
globalCtx->csCtx.segment = SEGMENTED_TO_VIRTUAL(gZeldasCourtyardLullabyCs);
gSaveContext.cutsceneTrigger = 1;

View file

@ -127,8 +127,7 @@ void func_809B0558(EnAni* this, GlobalContext* globalCtx) {
gSaveContext.itemGetInf[1] |= 0x20;
} else {
if (gSaveContext.n64ddFlag) {
s32 getItemId =
GetRandomizedItemId(GI_HEART_PIECE, this->actor.id, this->actor.params, globalCtx->sceneNum);
s32 getItemId = GetRandomizedItemIdFromKnownCheck(RC_KAK_MAN_ON_ROOF, GI_HEART_PIECE);
func_8002F434(&this->actor, globalCtx, getItemId, 10000.0f, 200.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_HEART_PIECE, 10000.0f, 200.0f);
@ -142,7 +141,7 @@ void func_809B05F0(EnAni* this, GlobalContext* globalCtx) {
}
if (gSaveContext.n64ddFlag) {
s32 getItemId = GetRandomizedItemId(GI_HEART_PIECE, this->actor.id, this->actor.params, globalCtx->sceneNum);
s32 getItemId = GetRandomizedItemIdFromKnownCheck(RC_KAK_MAN_ON_ROOF, GI_HEART_PIECE);
func_8002F434(&this->actor, globalCtx, getItemId, 10000.0f, 200.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_HEART_PIECE, 10000.0f, 200.0f);

View file

@ -453,7 +453,7 @@ void func_809EEA00(EnDivingGame* this, GlobalContext* globalCtx) {
if ((this->unk_292 == Message_GetState(&globalCtx->msgCtx) && Message_ShouldAdvance(globalCtx))) {
Message_CloseTextbox(globalCtx);
this->actor.parent = NULL;
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(ZD_DIVING_MINIGAME, GI_SCALE_SILVER) : GI_SCALE_SILVER, 90.0f, 10.0f);
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(RC_ZD_DIVING_MINIGAME, GI_SCALE_SILVER) : GI_SCALE_SILVER, 90.0f, 10.0f);
this->actionFunc = func_809EEA90;
}
}
@ -463,7 +463,7 @@ void func_809EEA90(EnDivingGame* this, GlobalContext* globalCtx) {
if (Actor_HasParent(&this->actor, globalCtx)) {
this->actionFunc = func_809EEAF8;
} else {
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(ZD_DIVING_MINIGAME, GI_SCALE_SILVER) : GI_SCALE_SILVER, 90.0f, 10.0f);
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(RC_ZD_DIVING_MINIGAME, GI_SCALE_SILVER) : GI_SCALE_SILVER, 90.0f, 10.0f);
}
}

View file

@ -344,7 +344,7 @@ s16 EnGo2_GetStateGoronCityRollingBig(GlobalContext* globalCtx, EnGo2* this) {
if(!gSaveContext.n64ddFlag) {
bombBagUpgrade = CUR_CAPACITY(UPG_BOMB_BAG) == 30 ? GI_BOMB_BAG_40 : GI_BOMB_BAG_30;
} else {
bombBagUpgrade = GetRandomizedItemIdFromKnownCheck(GC_ROLLING_GORON_AS_CHILD, GI_BOMB_BAG_40);
bombBagUpgrade = GetRandomizedItemIdFromKnownCheck(RC_GC_ROLLING_GORON_AS_CHILD, GI_BOMB_BAG_40);
}
EnGo2_GetItem(this, globalCtx, bombBagUpgrade);
Message_CloseTextbox(globalCtx);
@ -513,7 +513,7 @@ s16 EnGo2_GetStateGoronCityLink(GlobalContext* globalCtx, EnGo2* this) {
case TEXT_STATE_CLOSING:
switch (this->actor.textId) {
case 0x3036:
EnGo2_GetItem(this, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(GC_ROLLING_GORON_AS_ADULT, GI_TUNIC_GORON) : GI_TUNIC_GORON);
EnGo2_GetItem(this, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(RC_GC_ROLLING_GORON_AS_ADULT, GI_TUNIC_GORON) : GI_TUNIC_GORON);
this->actionFunc = EnGo2_SetupGetItem;
return 2;
case 0x3037:

View file

@ -659,7 +659,7 @@ s16 func_80A70058(GlobalContext* globalCtx, Actor* thisx) {
gSaveContext.dogParams = 0;
break;
case 0x709F:
func_80A6F7CC(this, globalCtx, (gSaveContext.infTable[25] & 2) ? GI_RUPEE_BLUE : gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(MARKET_LOST_DOG, GI_HEART_PIECE) : GI_HEART_PIECE);
func_80A6F7CC(this, globalCtx, (gSaveContext.infTable[25] & 2) ? GI_RUPEE_BLUE : gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(RC_MARKET_LOST_DOG, GI_HEART_PIECE) : GI_HEART_PIECE);
this->actionFunc = func_80A714C4;
break;
}

View file

@ -95,9 +95,11 @@ u16 EnMa1_GetText(GlobalContext* globalCtx, Actor* thisx) {
if (faceReaction != 0) {
return faceReaction;
}
if (!gSaveContext.n64ddFlag) {
if (CHECK_QUEST_ITEM(QUEST_SONG_EPONA)) {
return 0x204A;
}
}
if (gSaveContext.eventChkInf[1] & 0x40) {
return 0x2049;
}
@ -271,8 +273,10 @@ void EnMa1_Init(Actor* thisx, GlobalContext* globalCtx) {
Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInit);
CollisionCheck_SetInfo2(&this->actor.colChkInfo, DamageTable_Get(22), &sColChkInfoInit);
if (gSaveContext.n64ddFlag) {
if (gSaveContext.n64ddFlag) { // Skip Malon's multiple textboxes before getting an item
gSaveContext.infTable[8] |= 0x800;
gSaveContext.infTable[8] |= 0x10;
gSaveContext.eventChkInf[1] |= 1;
}
if (!func_80AA08C4(this, globalCtx)) {
@ -285,11 +289,11 @@ void EnMa1_Init(Actor* thisx, GlobalContext* globalCtx) {
this->actor.targetMode = 6;
this->unk_1E8.unk_00 = 0;
if (!(gSaveContext.eventChkInf[1] & 0x10) || CHECK_QUEST_ITEM(QUEST_SONG_EPONA)) {
if (!(gSaveContext.eventChkInf[1] & 0x10) || (CHECK_QUEST_ITEM(QUEST_SONG_EPONA) && !gSaveContext.n64ddFlag)) {
this->actionFunc = func_80AA0D88;
EnMa1_ChangeAnim(this, ENMA1_ANIM_2);
} else {
if (gSaveContext.n64ddFlag) {
if (gSaveContext.n64ddFlag) { // Skip straight to "let's sing it together" textbox in the ranch
gSaveContext.eventChkInf[1] |= 0x40;
}
@ -318,7 +322,7 @@ void func_80AA0D88(EnMa1* this, GlobalContext* globalCtx) {
if ((globalCtx->sceneNum == SCENE_SPOT15) && (gSaveContext.eventChkInf[1] & 0x10)) {
Actor_Kill(&this->actor);
} else if (!(gSaveContext.eventChkInf[1] & 0x10) || CHECK_QUEST_ITEM(QUEST_SONG_EPONA)) {
} else if (!(gSaveContext.eventChkInf[1] & 0x10) || (CHECK_QUEST_ITEM(QUEST_SONG_EPONA) && !gSaveContext.n64ddFlag)) {
if (this->unk_1E8.unk_00 == 2) {
this->actionFunc = func_80AA0EA0;
globalCtx->msgCtx.stateTimer = 4;
@ -333,7 +337,7 @@ void func_80AA0EA0(EnMa1* this, GlobalContext* globalCtx) {
this->actionFunc = func_80AA0EFC;
} else {
if (gSaveContext.n64ddFlag) {
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(HC_MALON_EGG, GI_WEIRD_EGG);
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(RC_HC_MALON_EGG, GI_WEIRD_EGG);
func_8002F434(&this->actor, globalCtx, getItemId, 120.0f, 10.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_WEIRD_EGG, 120.0f, 10.0f);
@ -351,19 +355,16 @@ void func_80AA0EFC(EnMa1* this, GlobalContext* globalCtx) {
}
void GivePlayerRandoRewardMalon(EnMa1* malon, GlobalContext* globalCtx, RandomizerCheck check) {
Player* player = GET_PLAYER(globalCtx);
if (!Flags_GetTreasure(globalCtx, 0x1F) &&
(INV_CONTENT(ITEM_OCARINA_FAIRY) != ITEM_NONE || INV_CONTENT(ITEM_OCARINA_TIME) != ITEM_NONE) &&
Actor_TextboxIsClosing(&malon->actor, globalCtx)) {
GetItemID getItemId = GetRandomizedItemIdFromKnownCheck(check, GI_EPONAS_SONG);
if (func_8002F434(&malon->actor, globalCtx, getItemId, 100.0f, 50.0f) == true) {
if (malon->actor.parent != NULL && malon->actor.parent->id == GET_PLAYER(globalCtx)->actor.id &&
!Flags_GetTreasure(globalCtx, 0x1F)) {
Flags_SetTreasure(globalCtx, 0x1F);
}
} else if (Flags_GetTreasure(globalCtx, 0x1F) && !Player_InBlockingCsMode(globalCtx, player)) {
gSaveContext.unk_13EE = 0x32;
gSaveContext.eventChkInf[4] |= 1;
} else if (!Flags_GetTreasure(globalCtx, 0x1F) &&
(INV_CONTENT(ITEM_OCARINA_FAIRY) != ITEM_NONE || INV_CONTENT(ITEM_OCARINA_TIME) != ITEM_NONE) &&
Actor_TextboxIsClosing(&malon->actor, globalCtx) &&
(globalCtx->msgCtx.textId == 0x2049 || globalCtx->msgCtx.textId == 0x204A)) {
func_8002F434(&malon->actor, globalCtx, getItemId, 10000.0f, 100.0f);
}
}
@ -381,7 +382,7 @@ void func_80AA0F44(EnMa1* this, GlobalContext* globalCtx) {
}
if (gSaveContext.n64ddFlag) {
GivePlayerRandoRewardMalon(this, globalCtx, SONG_FROM_MALON);
GivePlayerRandoRewardMalon(this, globalCtx, RC_SONG_FROM_MALON);
return;
}

View file

@ -198,14 +198,14 @@ void func_80AACFA0(EnMk* this, GlobalContext* globalCtx) {
gSaveContext.itemGetInf[1] |= 1;
} else {
// not sure when/how/if this is getting called
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(LH_LAB_DIVE, GI_HEART_PIECE) : GI_HEART_PIECE, 10000.0f, 50.0f);
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(RC_LH_LAB_DIVE, GI_HEART_PIECE) : GI_HEART_PIECE, 10000.0f, 50.0f);
}
}
void func_80AAD014(EnMk* this, GlobalContext* globalCtx) {
if (Actor_TextboxIsClosing(&this->actor, globalCtx)) {
this->actionFunc = func_80AACFA0;
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(LH_LAB_DIVE, GI_HEART_PIECE) : GI_HEART_PIECE, 10000.0f, 50.0f);
func_8002F434(&this->actor, globalCtx, gSaveContext.n64ddFlag ? GetRandomizedItemIdFromKnownCheck(RC_LH_LAB_DIVE, GI_HEART_PIECE) : GI_HEART_PIECE, 10000.0f, 50.0f);
}
this->flags |= 1;

View file

@ -305,7 +305,7 @@ void func_80ABA654(EnNiwLady* this, GlobalContext* globalCtx) {
this->actor.parent = NULL;
if (gSaveContext.n64ddFlag) {
s32 itemId = GetRandomizedItemIdFromKnownCheck(KAK_ANJU_AS_CHILD, GI_BOTTLE);
s32 itemId = GetRandomizedItemIdFromKnownCheck(RC_KAK_ANJU_AS_CHILD, GI_BOTTLE);
func_8002F434(&this->actor, globalCtx, itemId, 100.0f, 50.0f);
} else {
this->getItemId = GI_BOTTLE;
@ -395,7 +395,7 @@ void func_80ABA9B8(EnNiwLady* this, GlobalContext* globalCtx) {
this->actor.parent = NULL;
if (gSaveContext.n64ddFlag) {
s32 itemId = GetRandomizedItemIdFromKnownCheck(KAK_ANJU_AS_ADULT, GI_POCKET_EGG);
s32 itemId = GetRandomizedItemIdFromKnownCheck(RC_KAK_ANJU_AS_ADULT, GI_POCKET_EGG);
func_8002F434(&this->actor, globalCtx, itemId, 200.0f, 100.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_POCKET_EGG, 200.0f, 100.0f);
@ -455,7 +455,7 @@ void func_80ABAC00(EnNiwLady* this, GlobalContext* globalCtx) {
getItemId = !(gSaveContext.itemGetInf[2] & 0x1000) ? GI_POCKET_EGG : GI_COJIRO;
if (gSaveContext.n64ddFlag && getItemId == GI_POCKET_EGG) {
getItemId = GetRandomizedItemIdFromKnownCheck(KAK_ANJU_AS_ADULT, GI_POCKET_EGG);
getItemId = GetRandomizedItemIdFromKnownCheck(RC_KAK_ANJU_AS_ADULT, GI_POCKET_EGG);
}
}
func_8002F434(&this->actor, globalCtx, getItemId, 200.0f, 100.0f);

View file

@ -136,7 +136,7 @@ void EnOwl_Init(Actor* thisx, GlobalContext* globalCtx) {
// "conversation owl %4x no = %d, sv = %d"
osSyncPrintf(VT_FGCOL(CYAN) " 会話フクロウ %4x no = %d, sv = %d\n" VT_RST, this->actor.params, owlType, switchFlag);
if ((owlType != OWL_DEFAULT) && (switchFlag < 0x20) && Flags_GetSwitch(globalCtx, switchFlag)) {
if (((owlType != OWL_DEFAULT) && (switchFlag < 0x20) && Flags_GetSwitch(globalCtx, switchFlag)) || gSaveContext.n64ddFlag) {
osSyncPrintf("savebitでフクロウ退避\n"); // "Save owl with savebit"
Actor_Kill(&this->actor);
return;

Some files were not shown because too many files have changed in this diff Show more