From ca5c80fc47ee427dd204f5d8db4367fff5095d55 Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Fri, 3 Apr 2026 17:51:28 -0600 Subject: [PATCH] Add translation support for Randomizer UI components - Add LanguageManager integration to Plandomizer.cpp, randomizer_check_tracker.cpp, randomizer_item_tracker.cpp, and randomizer_entrance_tracker.cpp - Add ~100 new translation keys to Espanol.json for Randomizer UI - Include lenguajes folder in AppImage/DEB packaging via CMakeLists.txt - Update PLAN_TRADUCCION.md with Randomizer translation status and packaging info --- CMakeLists.txt | 302 +++ PLAN_TRADUCCION.md | 23 +- lenguajes/Espanol.json | 790 +++++- .../Enhancements/randomizer/Plandomizer.cpp | 47 +- .../randomizer/randomizer_check_tracker.cpp | 2336 ++++++++++++++++ .../randomizer_entrance_tracker.cpp | 1146 ++++++++ .../randomizer/randomizer_item_tracker.cpp | 2400 +++++++++++++++++ 7 files changed, 6906 insertions(+), 138 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp create mode 100644 soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp create mode 100644 soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..459b508e2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,302 @@ +cmake_minimum_required(VERSION 3.26.0 FATAL_ERROR) + +set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE) +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") +set(CMAKE_C_STANDARD 23 CACHE STRING "The C standard to use") + +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") + +project(Ship VERSION 9.2.0 LANGUAGES C CXX) +include(CMake/soh-cvars.cmake) +include(CMake/lus-cvars.cmake) +set(SPDLOG_LEVEL_TRACE 0) +set(SPDLOG_LEVEL_OFF 6) +set(SPDLOG_MIN_CUTOFF SPDLOG_LEVEL_TRACE CACHE STRING "cutoff at trace") + +option(SUPPRESS_WARNINGS "Suppress warnings in LUS and src (decomp)" ON) +if(SUPPRESS_WARNINGS) + MESSAGE("Suppressing warnings in LUS and src") + if(MSVC) + set(WARNING_OVERRIDE /w) + else() + set(WARNING_OVERRIDE -w) + endif() +else() + MESSAGE("Skipping warning suppression") +endif() + +set(NATO_PHONETIC_ALPHABET + "Alfa" "Bravo" "Charlie" "Delta" "Echo" "Foxtrot" "Golf" "Hotel" + "India" "Juliett" "Kilo" "Lima" "Mike" "November" "Oscar" "Papa" + "Quebec" "Romeo" "Sierra" "Tango" "Uniform" "Victor" "Whiskey" + "Xray" "Yankee" "Zulu" +) + +# Get the patch version number from the project version +math(EXPR PATCH_INDEX "${PROJECT_VERSION_PATCH}") + +# Use the patch number to select the correct word +list(GET NATO_PHONETIC_ALPHABET ${PATCH_INDEX} PROJECT_PATCH_WORD) + +set(PROJECT_BUILD_NAME "Ackbar ${PROJECT_PATCH_WORD}" CACHE STRING "" FORCE) +set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "" FORCE) + +execute_process( + COMMAND git branch --show-current + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +set(CMAKE_PROJECT_GIT_BRANCH "${GIT_BRANCH}" CACHE STRING "Git branch" FORCE) + +execute_process( + COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# Get only the first 7 characters of the hash +string(SUBSTRING "${GIT_COMMIT_HASH}" 0 7 SHORT_COMMIT_HASH) + +set(CMAKE_PROJECT_GIT_COMMIT_HASH "${SHORT_COMMIT_HASH}" CACHE STRING "Git commit hash" FORCE) + +execute_process( + COMMAND git describe --tags --abbrev=0 --exact-match HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_TAG + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +if(NOT GIT_COMMIT_TAG) + set(GIT_COMMIT_TAG "" CACHE STRING "Git commit tag" FORCE) +endif() + +set(CMAKE_PROJECT_GIT_COMMIT_TAG "${GIT_COMMIT_TAG}" CACHE STRING "Git commit tag" FORCE) + +set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) +add_compile_options($<$:/MP>) +add_compile_options($<$:/utf-8>) +add_compile_options($<$:/Zc:preprocessor>) + +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + include(CMake/automate-vcpkg.cmake) + + set(VCPKG_TRIPLET x64-windows-static) + set(VCPKG_TARGET_TRIPLET x64-windows-static) + + vcpkg_bootstrap() + vcpkg_install_packages(zlib bzip2 libzip libpng sdl2 sdl2-net glew glfw3 nlohmann-json tinyxml2 spdlog libogg libvorbis opus opusfile) + if (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache|sccache") + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT Embedded) + endif() +endif() + +################################################################################ +# Set target arch type if empty. Visual studio solution generator provides it. +################################################################################ +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + if(NOT CMAKE_VS_PLATFORM_NAME) + set(CMAKE_VS_PLATFORM_NAME "x64") + endif() + message("${CMAKE_VS_PLATFORM_NAME} architecture in use") + + if(NOT ("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64" + OR "${CMAKE_VS_PLATFORM_NAME}" STREQUAL "Win32")) + message(FATAL_ERROR "${CMAKE_VS_PLATFORM_NAME} arch is not supported!") + endif() +endif() + +################################################################################ +# Global configuration types +################################################################################ +if (CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") +set(CMAKE_C_FLAGS_DEBUG "-g -ffast-math -DDEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "-g -ffast-math -DDEBUG") +set(CMAKE_C_FLAGS_RELEASE "-O3 -ffast-math -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -ffast-math -DNDEBUG") +else() +set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") +set(CMAKE_OBJCXX_FLAGS_RELEASE "-O2 -DNDEBUG") +endif() + +if(NOT CMAKE_BUILD_TYPE ) + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build." FORCE) +endif() + +################################################################################ +# Common utils +################################################################################ +include(CMake/Utils.cmake) + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + get_linux_lsb_release_information() + message(STATUS "Linux ${LSB_RELEASE_ID_SHORT} ${LSB_RELEASE_VERSION_SHORT} ${LSB_RELEASE_CODENAME_SHORT}") +else() + message(STATUS ${CMAKE_SYSTEM_NAME}) +endif() + +################################################################################ +# Additional Global Settings(add specific info there) +################################################################################ +include(CMake/GlobalSettingsInclude.cmake OPTIONAL) + +################################################################################ +# Use solution folders feature +################################################################################ +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +################################################################################ +# Set LUS vars +################################################################################ + +# Enable the Gfx debugger in LUS to use libgfxd from ZAPDTR +set(GFX_DEBUG_DISASSEMBLER ON) + +# Tell LUS we're using F3DEX_GBI_2 (in a way that doesn't break libgfxd) +set(GBI_UCODE F3DEX_GBI_2) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") + +# Enable MPQ and OTR support +set(INCLUDE_MPQ_SUPPORT ON) + +################################################################################ +# Set CONTROLLERBUTTONS_T +################################################################################ +add_compile_definitions(CONTROLLERBUTTONS_T=uint32_t) + +################################################################################ +# Sub-projects +################################################################################ +add_subdirectory(libultraship ${CMAKE_BINARY_DIR}/libultraship) +target_compile_options(libultraship PRIVATE "${WARNING_OVERRIDE}") +target_compile_definitions(libultraship PUBLIC INCLUDE_MPQ_SUPPORT) +add_subdirectory(ZAPDTR/ZAPD ${CMAKE_BINARY_DIR}/ZAPD) +add_subdirectory(OTRExporter) +add_subdirectory(soh) + +set_property(TARGET soh PROPERTY APPIMAGE_DESKTOP_FILE_TERMINAL YES) +set_property(TARGET soh PROPERTY APPIMAGE_DESKTOP_FILE "${CMAKE_SOURCE_DIR}/scripts/linux/appimage/soh.desktop") +set_property(TARGET soh PROPERTY APPIMAGE_ICON_FILE "${CMAKE_BINARY_DIR}/sohIcon.png") + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") +install(FILES "${CMAKE_BINARY_DIR}/soh/soh.o2r" DESTINATION . COMPONENT ship) +install(TARGETS ZAPD DESTINATION ./assets/extractor COMPONENT extractor) +install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/extractor/" DESTINATION ./assets COMPONENT extractor) +install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/xml/" DESTINATION ./assets/xml COMPONENT extractor) +endif() + +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") +install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/extractor/" DESTINATION ./assets/ COMPONENT ship) +install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/xml/" DESTINATION ./assets/xml COMPONENT ship) +endif() + +# Install language files for AppImage/deb packages +install(DIRECTORY "${CMAKE_SOURCE_DIR}/lenguajes/" DESTINATION ./lenguajes COMPONENT ship OPTIONAL) + +find_package(Python3 COMPONENTS Interpreter) + +# Target to generate OTRs +add_custom_target( + ExtractAssets + COMMAND ${CMAKE_COMMAND} -E rm -f oot.o2r oot-mq.o2r soh.o2r + + # copy LUS default shaders into assets/custom + COMMAND ${CMAKE_COMMAND} -E rm -r -f ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom/shaders/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/libultraship/src/fast/shaders/ ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom/shaders/ + + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --non-interactive --xml-root assets/xml --custom-otr-file soh.o2r "--custom-assets-path" ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom --port-ver "${CMAKE_PROJECT_VERSION}" + COMMAND ${CMAKE_COMMAND} -DSYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DTARGET_DIR="$" -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DBINARY_DIR=${CMAKE_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/copy-existing-otrs.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/soh + COMMENT "Running asset extraction..." + DEPENDS ZAPD + BYPRODUCTS oot.o2r ${CMAKE_SOURCE_DIR}/oot.o2r oot-mq.o2r ${CMAKE_SOURCE_DIR}/oot-mq.o2r ${CMAKE_SOURCE_DIR}/soh.o2r +) + +# Target to generate headers +add_custom_target( + ExtractAssetHeaders + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --non-interactive --xml-root assets/xml --gen-headers + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/soh + COMMENT "Generating asset headers..." + DEPENDS ZAPD +) + +# Target to generate only soh.o2r +add_custom_target( + GenerateSohOtr + COMMAND ${CMAKE_COMMAND} -E rm -f soh.o2r + + # copy LUS default shaders into assets/custom + COMMAND ${CMAKE_COMMAND} -E rm -r -f ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom/shaders/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/libultraship/src/fast/shaders/ ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom/shaders/ + + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets.py -z "$" --norom --custom-otr-file soh.o2r "--custom-assets-path" ${CMAKE_CURRENT_SOURCE_DIR}/soh/assets/custom --port-ver "${CMAKE_PROJECT_VERSION}" + COMMAND ${CMAKE_COMMAND} -DSYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DTARGET_DIR="$" -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DBINARY_DIR=${CMAKE_BINARY_DIR} -DONLYSOHOTR=On -P ${CMAKE_CURRENT_SOURCE_DIR}/copy-existing-otrs.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/soh + COMMENT "Generating soh.o2r..." + DEPENDS ZAPD +) + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + find_package(ImageMagick COMPONENTS convert) + if (ImageMagick_FOUND) + execute_process ( + COMMAND ${ImageMagick_convert_EXECUTABLE} ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png -resize 512x512 ${CMAKE_BINARY_DIR}/sohIcon.png + OUTPUT_VARIABLE outVar + ) + endif() +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Darwin") +add_custom_target(CreateOSXIcons + COMMAND mkdir -p ${CMAKE_BINARY_DIR}/macosx/soh.iconset + COMMAND sips -z 16 16 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_16x16.png + COMMAND sips -z 32 32 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_16x16@2x.png + COMMAND sips -z 32 32 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_32x32.png + COMMAND sips -z 64 64 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_32x32@2x.png + COMMAND sips -z 128 128 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_128x128.png + COMMAND sips -z 256 256 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_128x128@2x.png + COMMAND sips -z 256 256 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_256x256.png + COMMAND sips -z 512 512 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_256x256@2x.png + COMMAND sips -z 512 512 ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png --out ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_512x512.png + COMMAND cp ${CMAKE_SOURCE_DIR}/soh/macosx/sohIcon.png ${CMAKE_BINARY_DIR}/macosx/soh.iconset/icon_512x512@2x.png + COMMAND iconutil -c icns -o ${CMAKE_BINARY_DIR}/macosx/soh.icns ${CMAKE_BINARY_DIR}/macosx/soh.iconset + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Creating OSX icons ..." + ) +add_dependencies(soh CreateOSXIcons) + +install(TARGETS ZAPD DESTINATION ${CMAKE_BINARY_DIR}/assets) + +set(PROGRAM_PERMISSIONS_EXECUTE OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) + +install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/extractor/" DESTINATION ./assets/) +install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/xml/" DESTINATION ./assets/xml) + +# Rename the installed soh binary to drop the macos suffix +INSTALL(CODE "FILE(RENAME \${CMAKE_INSTALL_PREFIX}/../MacOS/soh-macos \${CMAKE_INSTALL_PREFIX}/../MacOS/soh)") + +install(CODE " + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/../MacOS/soh\" \"\" \"${dirs}\") + ") + +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Windows|NintendoSwitch|CafeOS") +install(FILES ${CMAKE_SOURCE_DIR}/README.md DESTINATION . COMPONENT ship RENAME readme.txt ) +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + set(CPACK_GENERATOR "External") +elseif(CMAKE_SYSTEM_NAME MATCHES "Windows|NintendoSwitch|CafeOS") + set(CPACK_GENERATOR "ZIP") +elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") + set(CPACK_GENERATOR "Bundle") +endif() + +set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_SOURCE_DIR}/CMake/Packaging-2.cmake) +include(CMake/Packaging.cmake) diff --git a/PLAN_TRADUCCION.md b/PLAN_TRADUCCION.md index 0113180c1..cd7182cc2 100644 --- a/PLAN_TRADUCCION.md +++ b/PLAN_TRADUCCION.md @@ -43,6 +43,11 @@ Crear un sistema de traducción dinámico que permita cargar idiomas desde archi |---------|---------| | `soh/soh/SohGui/SohMenuSettings.cpp` | Agregar selector de idioma dinámico + carga automática al inicio | | `soh/soh/SohGui/SohMenu.cpp` | Aplicar traducción automática en AddWidget | +| `soh/soh/Enhancements/randomizer/Plandomizer.cpp` | Traducir textos de UI con LanguageManager | +| `soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp` | Traducir textos de UI con LanguageManager | +| `soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp` | Traducir textos de UI con LanguageManager | +| `soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp` | Traducir textos de UI con LanguageManager | +| `CMakeLists.txt` | Incluir carpeta `lenguajes` en AppImage/DEB | --- @@ -62,6 +67,15 @@ cp -r lenguajes build-cmake/soh/ # Linux: ~/.local/share/com.shipofharkinian.soh/lenguajes/ ``` +### 4.3 Empaquetado en AppImage/DEB +La carpeta `lenguajes` se incluye automáticamente en los paquetes AppImage y DEB gracias a la directiva de instalación en `CMakeLists.txt`: + +```cmake +install(DIRECTORY "${CMAKE_SOURCE_DIR}/lenguajes/" DESTINATION ./lenguajes COMPONENT ship OPTIONAL) +``` + +Esto significa que al generar un AppImage con `cpack`, la carpeta `lenguajes` con todos los archivos `.json` se incluirá automáticamente dentro del paquete, sin necesidad de copiarla manualmente. El flag `OPTIONAL` evita errores de compilación si la carpeta no existe. + --- ## 5. Formato de Archivos JSON @@ -300,8 +314,15 @@ cp -r lenguajes build-cmake/soh/ - [x] Fix de compilación en Menu.cpp (SohGui::LanguageManager) - [x] Compilación exitosa +### Traducción del Randomizer ✅ +- [x] Plandomizer.cpp - Textos de UI traducidos (tablas, popups, tooltips, tabs) +- [x] randomizer_check_tracker.cpp - Textos de UI traducidos (checkboxes, botones, búsqueda, settings) +- [x] randomizer_item_tracker.cpp - Textos de UI traducidos (settings table, checks counter) +- [x] randomizer_entrance_tracker.cpp - Textos de UI traducidos (sort, group, legend, tooltips) +- [x] Claves de traducción añadidas a Espanol.json (~100 nuevas claves) + ### Pendientes / Mejoras Futuras -- [ ] Agregar más traducciones al JSON (actualmente ~250 claves) +- [ ] Agregar más traducciones al JSON (actualmente ~350+ claves) - [ ] Crear archivo Portugues.json - [ ] Posibilidad de recargar traducciones en tiempo real sin cerrar el juego diff --git a/lenguajes/Espanol.json b/lenguajes/Espanol.json index ff3ea4b26..edc8b46d1 100644 --- a/lenguajes/Espanol.json +++ b/lenguajes/Espanol.json @@ -28,7 +28,7 @@ "Warn": "Advertencia", "Error": "Error", "Critical": "Crítico", - "Off": "Desactivado", + "Off": "Apagado", "Top Left": "Superior Izquierda", "Top Right": "Superior Derecha", "Bottom Left": "Inferior Izquierda", @@ -72,7 +72,6 @@ "Both Ages": "Ambas Edades", "Consistent Vanish": "Desvanecer Consistente", "No Vanish": "Sin Desvanecer", - "Always": "Siempre", "Random": "Aleatorio", "Random (Seeded)": "Aleatorio (Semilla)", "Dungeons": "Mazmorras", @@ -80,7 +79,6 @@ "Dungeons (MQ)": "Mazmorras (MQ)", "Dungeons Random": "Mazmorras Aleatorio", "Dungeons Random (Seeded)": "Mazmorras Aleatorio (Semilla)", - "Off": "Apagado", "Vanilla": "Original", "Maxed": "Maximizado", "Default": "Por Defecto", @@ -198,7 +196,6 @@ "Play Zelda's Lullaby to Open Sleeping Waterfall": "Tocar Canción de Zelda para Abrir Cascada Dormida", "Skips & Speed-ups": "Saltos y Aceleraciones", "Cutscenes": "Cinematicas", - "All": "Todos", "Skip Intro": "Saltar Introducción", "Skip Entrance Cutscenes": "Saltar Cinemáticas de Entrada", "Skip Story Cutscenes": "Saltar Cinemáticas de Historia", @@ -374,7 +371,6 @@ "OpenGL": "OpenGL", "Metal": "Metal", "Resolution Presets": "Preajustes de Resolución", - "Off": "Apagado", "Custom": "Personalizado", "Original (4:3)": "Original (4:3)", "Widescreen (16:9)": "Pantalla Amplia (16:9)", @@ -428,119 +424,683 @@ "Frame Advance is Disabled": "Avance de Cuadro está Desactivado", "Advanced Resolution is Disabled": "Resolución Avanzada está Desactivada", "Vertical Resolution Toggle is Off": "Alternar Resolución Vertical está Apagado", - "Allow background inputs": "Permitir entradas en segundo plano", - "Boot": "Arranque", - "Languages": "Idiomas", - "Language": "Idioma", - "Accessibility": "Accesibilidad", + "A Wallmaster follows Link everywhere, don't get caught!": "¡Un Wallmaster sigue a Link a todas partes, no te dejes atrapar!", "About": "Acerca de", - "Position": "Posición", - "Convenience": "Comodidad", - "Bottles": "Botellas", - "Health": "Salud", - "Drops": "Soltos", - "Miscellaneous": "Varios", - "Enemies": "Enemigos", - "Fishing": "Pescar", - "Money": "Dinero", - "Toggle modifier instead of holding": "Alternar modificador en lugar de mantener", - "Skips & Speed-ups": "Saltos y Aceleraciones", - "All##Skips": "Todos", - "None##Skips": "Ninguno", - "Skip Feeding Jabu-Jabu": "Saltar Alimentar a Jabu-Jabu", - "Reworked Targeting": "Objetivo Reformado", - "Target Switch Button Combination:": "Combinación de Botón para Cambiar Objetivo:", - "Map Select Button Combination:": "Combinación de Botón para Seleccionar Mapa:", - "No Clip Button Combination:": "Combinación de Botón para No Clip:", - "Skip Bottle Pickup Messages": "Saltar Mensajes de Recolección de Botellas", - "Skip Consumable Item Pickup Messages": "Saltar Mensajes de Objeto Consumible", - "Skip One Point Cutscenes (Chests, Door Unlocks, etc.)": "Saltar Cinemáticas de Un Punto (Cofres, Puertas, etc.)", - "Manual seed entry": "Entrada manual de semilla", - "Seed Entry": "Entrada de Semilla", - "Seed": "Semilla", - "Generate Randomizer": "Generar Randomizer", - "Spoiler File": "Archivo Spoiler", - "Excluded Locations": "Ubicaciones Excluidas", - "Tricks/Glitches": "Trucos/Glitches", - "Plandomizer": "Plandomizer", - "Item Tracker": "Rastreador de Objetos", - "Entrance Tracker": "Rastreador de Entradas", - "Check Tracker": "Rastreador de Checks", - "Warping": "Teletransporte", - "Warp Points": "Puntos de Teletransporte", - "Log Level": "Nivel de Registro", - "Stats": "Estadísticas", - "Console": "Consola", - "Save Editor": "Editor de Guardado", - "Hook Debugger": "Depurador de Hooks", - "Collision Viewer": "Visor de Colisiones", - "Actor Viewer": "Visor de Actores", - "Display List Viewer": "Visor de Lista de Display", - "Value Viewer": "Visor de Valores", - "Message Viewer": "Visor de Mensajes", - "Gfx Debugger": "Depurador de Gráficos", - "Graphics": "Gráficos", - "Fixes": "Arreglos", - "Difficulty": "Dificultad", - "Minigames": "Minijuegos", - "Extra Modes": "Modos Extra", - "Cheats": "Trucos", - "Cosmetics Editor": "Editor de Cosméticos", - "Audio Editor": "Editor de Audio", - "Gameplay Stats": "Estadísticas de Juego", - "Time Splits": "Divisiones de Tiempo", - "Timers": "Temporizadores", - "General": "General", - "Audio": "Audio", - "Controls": "Controles", - "Info": "Información", - "Sail": "Navegación", - "Crowd Control": "Crowd Control", - "Anchor": "Ancla", "About Crowd Control": "Acerca de Crowd Control", + "Accessibility": "Accesibilidad", + "Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of": "Activa MSAA (antialiasing multiamuestra) de 2x hasta 8x, para suavizar los bordes de", + "Additional Traps": "Trampas Adicionales", + "Adds a prompt to equip newly-obtained Swords, Shields, and Tunics.": "Agrega un aviso para equipar Espadas, Escudos y Túnicas recién obtenidas.", + "Adds back in a delay after unpausing before the game resumes playing again,": "Agrega un retraso después de despausar antes de que el juego se reanude,", + "Adjust the number of notes the Skull Kids play to start the first round.": "Ajusta el número de notas que tocan los Skull Kids para iniciar la primera ronda.", + "Adjust the number of notes you need to play to end the first round.": "Ajusta el número de notas que debes tocar para terminar la primera ronda.", + "Adjust the number of notes you need to play to end the second round.": "Ajusta el número de notas que debes tocar para terminar la segunda ronda.", + "Adjust the number of notes you need to play to end the third round.": "Ajusta el número de notas que debes tocar para terminar la tercera ronda.", + "Adjusts rate Dampe drops flames during race.": "Ajusta la velocidad a la que Dampe lanza llamas durante la carrera.", + "Adjusts the Horizontal Culling Plane to account for Widescreen Resolutions.": "Ajusta el Plano de Recorte Horizontal para tener en cuenta Resoluciones Panorámicas.", + "Adult Minimum Weight: %d lbs.": "Peso Mínimo Adulto: %d lbs.", + "Adult Starting Ammunition: %d arrows": "Munición Inicial Adulto: %d flechas", + "Advance 1 frame.": "Avanzar 1 cuadro.", + "Advance frames while the button is held.": "Avanzar cuadros mientras se mantiene el botón.", + "Aiming with a Bow or Slingshot will display a reticle as with the Hookshot": "Apuntar con Arco o Honda mostrará una retícula como con el Gancho", + "Aiming with the Boomerang will display a reticle as with the Hookshot.": "Apuntar con el Bumerán mostrará una retícula como con el Gancho.", + "All Dogs are Richard": "Todos los Perros son Richard", + "All Fish are Hyrule Loaches": "Todos los Peces son Lochas de Hyrule", + "All Major Bosses move and act twice as fast.": "Todos los Jefes Mayores se mueven y actúan el doble de rápido.", + "All Regular Enemies and Mini-Bosses move and act twice as fast.": "Todos los Enemigos Regulares y Mini-Jefes se mueven y actúan el doble de rápido.", + "All dogs can be traded in and will count as Richard.": "Todos los perros pueden ser intercambiados y contarán como Richard.", + "All fish will be caught instantly.": "Todos los peces se atrapan instantáneamente.", + "All##Skips": "Todos##Saltos", + "Allow Link to enter Jabu-Jabu without feeding him a fish.": "Permitir que Link entre a Jabu-Jabu sin alimentarlo con un pez.", + "Allow Link to put items away without having to wait around.": "Permitir que Link guarde objetos sin tener que esperar.", + "Allow background inputs": "Permitir entradas de fondo", + "Allow multi-windows": "Permitir múltiples ventanas", + "Allow unequipping Items": "Permitir Desequipar Objetos", + "Allows Bombchus to explode out of bounds. Similar to GameCube and Wii VC.": "Permite que las Bombchus exploten fuera de límites. Similar a GameCube y Wii VC.", + "Allows Child Link to use a Bow with Arrows.\n": "Permite que Link Niño use un Arco con Flechas.\n", + "Allows Link to bounce off walls when linear velocity is high enough, this is": "Permite que Link rebote en paredes cuando la velocidad lineal es suficientemente alta, esto es", + "Allows Link to freely change age by playing the Song of Time.\n": "Permite que Link cambie de edad libremente tocando la Canción del Tiempo.\n", + "Allows Link to unsheathe sword without slashing automatically.": "Permite que Link desenvaine la espada sin cortar automáticamente.", + "Allows Z-Targeting Gold Skulltulas.": "Permite Apuntar a las Gold Skulltulas.", + "Allows any item to be equipped, regardless of age.\n": "Permite equipar cualquier objeto, sin importar la edad.\n", + "Allows controller inputs to be picked up by the game even when the game window isn't": "Permite que el juego capture entradas del controlador incluso cuando la ventana del juego no está", + "Allows dogs to follow you anywhere you go, even if you leave the Market.": "Permite que los perros te sigan a donde vayas, incluso si sales del Mercado.", + "Allows equipping Shields, Tunics and Boots to C-Buttons/D-pad.": "Permite equipar Escudos, Túnicas y Botas a los Botones-C/D-pad.", + "Allows graves to be pulled when child during the day.": "Permite tumbar lápidas siendo niño durante el día.", + "Allows masks to be equipped normally from the pause menu as adult.": "Permite que las máscaras se equipen normalmente desde el menú de pausa como adulto.", + "Allows multiple windows to be opened at once. Requires a reload to take effect.": "Permite abrir múltiples ventanas a la vez. Requiere recargar para tener efecto.", + "Allows the cursor on the pause menu to be over any slot. Sometimes required in Randomizer": "Permite que el cursor del menú de pausa esté sobre cualquier ranura. A veces requerido en Randomizer", + "Allows unequipping items from C-Buttons/D-pad by hovering over an equipped": "Permite desequipar objetos de los Botones-C/d-pad pasando el cursor sobre un equipado", + "Allows you to control a Bombchu after dropping it.\n": "Permite controlar una Bombchu después de soltarla.\n", + "Allows you to have \"Link\" as a premade file name.": "Permite tener \"Link\" como nombre de archivo predeterminado.", + "Allows you to use any item at any location": "Permite usar cualquier objeto en cualquier ubicación", + "Allows you to walk through walls.": "Permite caminar a través de paredes.", + "Always Win Dampe Digging Game": "Ganar Siempre Juego de Excavación de Dampe", + "Always Win Goron Pot": "Ganar Siempre Olla Goron", + "Always get the Heart Piece/Purple Rupee from the Spinning Goron Pot.": "Obtener siempre la Pieza de Corazón/Rupia Púrpura de la Olla Giratoria Goron.", + "Always shows dungeon entrance icons on the Minimap.": "Siempre muestra iconos de entrada de mazmorras en el Minimapa.", + "Ammo": "Munición", + "Ammo Traps": "Trampas de Munición", + "Amy's block pushing puzzle instantly solved.": "Puzzle de empujar bloques de Amy resuelto instantáneamente.", + "Anti-aliasing (MSAA)": "Antialiasing (MSAA)", + "Any Ocarina + Master Sword": "Cualquier Ocarina + Espada Maestra", + "Arrow Cycle": "Ciclo de Flechas", + "Assignable Shields, Tunics and Boots": "Escudos, Túnicas y Botas Asignables", + "Audio": "Audio", + "Audio Fixes": "Arreglos de Audio", + "Autosave": "Autoguardado", + "Be sure to explore the Presets and Enhancements Menus for various Speedups and Quality of life changes!": "¡Asegúrate de explorar los menús de Preajustes y Mejoras para varias aceleraciones y cambios de calidad de vida!", + "Beta Quest": "Misión Beta", + "Beta Quest World: %d": "Mundo de Misión Beta: %d", + "Blue Fire dropped from bottle can be bottled.": "Fuego Azul derramado de botella puede ser embotellado.", + "Bomb Timer Multiplier: %.2fx": "Multiplicador de Temporizador de Bomba: %.2fx", + "Bomb Traps": "Trampas de Bomba", + "Bombchu Bowling": "Bolos de Bombchu", + "Bombchu Count: %d bombchus": "Cantidad de Bombchus: %d bombchus", + "Bombchus Out of Bounds": "Bombchus Fuera de Límites", + "Bombchus do not sell out when bought, and a 10 pack of Bombchus costs 99 rupees": "Las Bombchus no se agotan al comprarlas, y un paquete de 10 cuesta 99 rupias", + "Bombchus will sometimes drop in place of Bombs.": "Las Bombchus a veces caerán en lugar de Bombas.", + "Bonk Damage Multiplier": "Multiplicador de Daño por Golpe", + "Boot": "Inicio", + "Boot Sequence": "Secuencia de Inicio", + "Bottles": "Botellas", + "Bounce off Walls": "Rebotar en Paredes", + "Buffers your inputs to be executed a specified amount of frames later.": "Almacena tus entradas para ser ejecutadas una cantidad específica de cuadros después.", + "Bugs don't Despawn": "Los Bichos no Desaparecen", + "Burn Traps": "Trampas de Quemadura", + "Button Combination:": "Combinación de Botones:", + "Buttons that activate Speed Modifier 1.\n\n": "Botones que activan el Modificador de Velocidad 1.\n\n", + "Buttons to activate target switching.": "Botones para activar cambio de objetivo.", + "Camera Fixes": "Arreglos de Cámara", + "Cancel": "Cancelar", + "Causes your Wallet to fill and empty faster when you gain or lose money.": "Hace que tu Billetera se llene y vacíe más rápido al ganar o perder dinero.", + "Change Age": "Cambiar Edad", + "Changes Heart Piece and Heart Container functionality.\n\n": "Cambia la funcionalidad de Piezas de Corazón y Contenedores de Corazón.\n\n", + "Changes the behavior of debug file select creation (creating a save file on slot 1": "Cambia el comportamiento de creación de archivo de depuración (crear un archivo en la ranura 1", + "Changes the menu display from overlay to windowed.": "Cambia la visualización del menú de superpuesto a ventana.", + "Child Minimum Weight: %d lbs.": "Peso Mínimo Niño: %d lbs.", + "Child Starting Ammunition: %d seeds": "Munición Inicial Niño: %d semillas", + "Clear": "Limpiar", + "Clear Config": "Limpiar Configuración", + "Clear Cutscene Pointer": "Limpiar Puntero de Cinemática", + "Clear Devices": "Limpiar Dispositivos", + "Clears the cutscene pointer to a value safe for wrong warps.": "Limpia el puntero de cinemática a un valor seguro para wrong warps.", + "Climb Everything": "Escalar Todo", + "Configure what happens when starting or resetting the game.\n\n": "Configura qué sucede al iniciar o reiniciar el juego.\n\n", "Connect to Crowd Control": "Conectar a Crowd Control", - "Additional Settings": "Configuración Adicional", - "Enemy Name Tags": "Etiquetas de Nombre de Enemigo", - "Spawned Enemies Ignored Ingame": "Enemigos Generados Ignorados en Juego", - "Host & Port": "Servidor y Puerto", - "Enable##Sail": "Habilitar", - "Disable##Sail": "Deshabilitar", - "Connected##Sail": "Conectado", - "Connecting...##Sail": "Conectando...", - "Enable##CrowdControl": "Habilitar", - "Disable##CrowdControl": "Deshabilitar", - "Connected": "Conectado", "Connecting...": "Conectando...", - "Popout Menu": "Menú Emergente", - "OoT Registry Editor": "Editor de Registro de OoT", - "OoT Skulltula Debug": "Depuración de Skulltula de OoT", - "Debug Save File Mode": "Modo de Archivo de Guardado de Depuración", - "Advance 1": "Avanzar 1", - "Advance (Hold)": "Avanzar (Mantener)", - "Warp Points": "Puntos de Teletransporte", - "Popout Stats Window": "Abrir Ventana de Estadísticas", - "Popout Console": "Abrir Consola", - "Popout Save Editor": "Abrir Editor de Guardado", - "Popout Hook Debugger": "Abrir Depurador de Hooks", - "Popout Collision Viewer": "Abrir Visor de Colisiones", - "Popout Actor Viewer": "Abrir Visor de Actores", - "Popout Display List Viewer": "Abrir Visor de Lista de Display", - "Popout Value Viewer": "Abrir Visor de Valores", - "Popout Message Viewer": "Abrir Visor de Mensajes", - "Popout Gfx Debugger": "Abrir Depurador de Gráficos", - "Better Debug Warp Screen": "Mejor Pantalla de Teletransporte de Depuración", - "Debug Warp Screen Translation": "Traducción de Pantalla de Teletransporte", - "Be sure to explore the Presets and Enhancements Menus for various Speedups and Quality of life changes!": "¡Asegúrate de explorar los menús de Presets y Enhancements para varias aceleraciones y cambios de calidad de vida!", - "These enhancements are only useful in the Randomizer mode but do not affect the randomizer logic.": "Estas mejoras solo son útiles en el modo Randomizer pero no afectan la lógica del randomizer.", - "Leave blank for random seed": "Dejar en blanco para semilla aleatoria", - "Must be on File Select to generate a randomizer seed.": "Debes estar en Selección de Archivo para generar una semilla de randomizer.", - "Spoiler File: %s": "Archivo Spoiler: %s", + "Connecting...##Sail": "Conectando...##Sail", + "Containers of Agony": "Contenedores de Agonía", + "Convenience": "Conveniencia", + "Correctly centers the Navi text prompt on the HUD's C-Up button.": "Centra correctamente el aviso de texto de Navi en el botón C-Arriba del HUD.", + "Creates a new random seed value to be used when generating a randomizer": "Crea un nuevo valor de semilla aleatoria para usar al generar un randomizer", + "Crowd Control is a platform that allows viewers to interact": "Crowd Control es una plataforma que permite a los espectadores interactuar", + "Cuccos Needed By Anju: %d": "Cuccos Necesarios por Anju: %d", + "Cuccos Stay Put Multiplier: %dx": "Multiplicador de Cuccos Quietos: %dx", + "Current FPS": "FPS Actuales", + "Cursor Always Visible": "Cursor Siempre Visible", + "Customize Behavior##Bowling": "Personalizar Comportamiento##Bolos", + "Customize Behavior##Fishing": "Personalizar Comportamiento##Pesca", + "Customize Behavior##Frogs": "Personalizar Comportamiento##Ranas", + "Customize Behavior##LostWoods": "Personalizar Comportamiento##BosquePerdido", + "Customize Behavior##Shooting": "Personalizar Comportamiento##Tiro", + "Damage Multiplier": "Multiplicador de Daño", + "Dampe Drop Rate": "Tasa de Caída de Dampe", + "Dampe's Inferno": "Infierno de Dampe", + "Death Traps": "Trampas de Muerte", + "Debug Warp Screen": "Pantalla de Teletransporte Debug", + "Deku Sticks:": "Palos Deku:", + "Delete File on Death": "Eliminar Archivo al Morir", + "Despawn Timers": "Temporizadores de Desaparición", + "Desync Fixes": "Arreglos de Desincronización", + "Dev Tools": "Htas. Desarrollo", + "Disable 2D Pre-Rendered Scenes": "Desactivar Escenas Pre-renderizadas 2D", + "Disable Fixed Camera": "Desactivar Cámara Fija", + "Disable Haunted Wasteland Sandstorm": "Desactivar Tormenta de Arena del Páramo", + "Disable Idle Camera Re-Centering": "Desactivar Recentrado de Cámara en Reposo", + "Disable Jabu Wobble": "Desactivar Bamboleo de Jabu", + "Disable Kokiri Fade": "Desactivar Desvanecimiento Kokiri", + "Disable Link Spinning With Goron Pot": "Desactivar Giro de Link con Olla Goron", + "Disable Link's Sword Trail": "Desactivar Estela de Espada de Link", + "Disable Random Camera Wiggle at Low Health.": "Desactivar Movimiento Aleatorio de Cámara con Poca Salud.", + "Disable Screen Flash for Finishing Blow": "Desactivar Flash de Pantalla para Golpe Final", + "Disable the geometry wobble and camera distortion inside Jabu.": "Desactivar el bamboleo de geometría y distorsión de cámara dentro de Jabu.", + "Disabled: Paths vanish more the higher the resolution (Z-Fighting is based on resolution).\n": "Desactivado: Los caminos desaparecen más cuanto mayor es la resolución (Z-Fighting depende de la resolución).\n", + "Disables 2D pre-rendered backgrounds. Enable this when using a mod that": "Desactiva fondos pre-renderizados 2D. Habilita esto al usar un mod que", + "Disables Random Drops, except from the Goron Pot, Dampe, and Bosses.": "Desactiva Caídas Aleatorias, excepto de la Olla Goron, Dampe y Jefes.", + "Disables sandstorm effect in Haunted Wasteland.": "Desactiva el efecto de tormenta de arena en el Páramo Encantado.", + "Disables the Beating Animation of the Hearts on the HUD.": "Desactiva la Animación de Latido de los Corazones en el HUD.", + "Disables the sword trail effect when swinging Link's sword. Useful when": "Desactiva el efecto de estela de espada al balancear la espada de Link. Útil cuando", + "Disables the white screen flash on enemy kill.": "Desactiva el flash de pantalla blanca al matar enemigos.", + "Disables warning text when you don't have on the Goron/Zora Tunic": "Desactiva texto de advertencia cuando no tienes puesta la Túnica Goron/Zora", + "Dogs Follow You Everywhere": "Los Perros te Siguen a Todas Partes", + "Don't affect jump distance/velocity": "No afecta distancia/velocidad de salto", + "Don't increase crawl speed when exiting glitch-useful crawlspaces.": "No aumentar velocidad de arrastre al salir de espacios de arrastre útiles para glitches.", + "Don't scale image to fill window.": "No escalar imagen para llenar ventana.", + "Don't skip cutscenes that are associated with useful glitches. Currently, it is": "No saltar cinemáticas asociadas con glitches útiles. Actualmente, es", + "Drops": "Caídas", + "Drops Don't Despawn": "Las Caídas no Desaparecen", + "Drops from enemies, grass, etc. don't disappear after a set amount of time.": "Las caídas de enemigos, hierba, etc. no desaparecen después de un tiempo establecido.", + "Dying will delete your file.\n\n": "Morir eliminará tu archivo.\n\n", + "Early Eyeball Frog": "Rana Ojo Temprana", + "Easy Frame Advancing with Pause": "Avance de Cuadro Fácil con Pausa", + "Easy ISG": "ISG Fácil", + "Easy QPA": "QPA Fácil", + "Empty Bottles Faster": "Vaciar Botellas Más Rápido", + "Enable Beta Quest": "Habilitar Misión Beta", + "Enable Bombchu Drops": "Habilitar Caída de Bombchus", + "Enable Visual Guard Vision": "Habilitar Visión Visual de Guardia", + "Enable Vsync": "Habilitar Vsync", + "Enable##CrowdControl": "Habilitar##CrowdControl", + "Enable##Sail": "Habilitar##Sail", + "Enables Debug Mode, allowing you to select maps with L + R + Z, noclip": "Habilita el Modo Debug, permitiéndote seleccionar mapas con L + R + Z, noclip", + "Enables Skulltula Debug, when moving the cursor in the menu above various": "Habilita Debug de Skulltula, al mover el cursor en el menú sobre varios", + "Enables additional Trap variants.": "Habilita variantes adicionales de Trampas.", + "Enables the registry editor.": "Habilita el editor de registro.", + "Enables the separate Actor Viewer Window.": "Habilita la ventana separada de Visor de Actores.", + "Enables the separate Additional Timers Window.": "Habilita la ventana separada de Temporizadores Adicionales.", + "Enables the separate Audio Editor Window.": "Habilita la ventana separada de Editor de Audio.", + "Enables the separate Check Tracker Settings Window.": "Habilita la ventana separada de Configuración de Rastreador de Checks.", + "Enables the separate Collision Viewer Window.": "Habilita la ventana separada de Visor de Colisiones.", + "Enables the separate Console Window.": "Habilita la ventana separada de Consola.", + "Enables the separate Cosmetics Editor Window.": "Habilita la ventana separada de Editor de Cosméticos.", + "Enables the separate Display List Viewer Window.": "Habilita la ventana separada de Visor de Lista de Display.", + "Enables the separate Entrance Tracker Settings Window.": "Habilita la ventana separada de Configuración de Rastreador de Entradas.", + "Enables the separate Gameplay Stats Window.": "Habilita la ventana separada de Estadísticas de Jugabilidad.", + "Enables the separate Gfx Debugger Window.": "Habilita la ventana separada de Depurador de Gráficos.", + "Enables the separate Hook Debugger Window.": "Habilita la ventana separada de Depurador de Hooks.", + "Enables the separate Input Viewer Settings Window.": "Habilita la ventana separada de Configuración de Visor de Entradas.", + "Enables the separate Item Tracker Settings Window.": "Habilita la ventana separada de Configuración de Rastreador de Objetos.", + "Enables the separate Message Viewer Window.": "Habilita la ventana separada de Visor de Mensajes.", + "Enables the separate Mod Menu Window.": "Habilita la ventana separada de Menú de Mods.", + "Enables the separate Save Editor Window.": "Habilita la ventana separada de Editor de Guardado.", + "Enables the separate Stats Window.": "Habilita la ventana separada de Estadísticas.", + "Enables the separate Time Splits Window.": "Habilita la ventana separada de Divisiones de Tiempo.", + "Enables the separate Value Viewer Window.": "Habilita la ventana separada de Visor de Valores.", + "Enemies": "Enemigos", + "Enemies spawned by CrowdControl won't be considered for \"clear enemy": "Los enemigos generados por CrowdControl no se considerarán para \"enemigo despejado\"", + "Enemy Name Tags": "Etiquetas de Nombre de Enemigos", + "Enhancements": "Mejoras", + "Epona Boost": "Impulso de Epona", + "Every fish in the Fishing Pond will always be a Hyrule Loach.\n\n": "Todos los peces en el Estanque de Pesca siempre serán Loachas de Hyrule.\n\n", + "Exclude Glitch-Aiding Crawlspaces": "Excluir Espacios de Arrastre para Glitches", + "Excluded Locations": "Ubicaciones Excluidas", + "F5 to save, F6 to change slots, F7 to load": "F5 para guardar, F6 para cambiar ranuras, F7 para cargar", + "Fall Damage Multiplier": "Multiplicador de Daño por Caída", + "Faster + Longer Jump": "Salto Más Rápido + Largo", + "Faster Pause Menu": "Menú de Pausa Más Rápido", + "Faster Run": "Carrera Más Rápida", + "Faster Shadow Ship": "Barco de Sombra Más Rápido", + "Fireproof Deku Shield": "Escudo Deku Ignífugo", + "Fish don't Despawn": "Los Peces no Desaparecen", + "Fish never Escape": "Los Peces Nunca Escapan", + "Fish while Hovering": "Pescar mientras Flotas", + "Fishing": "Pesca", + "Fix Anubis Fireballs": "Arreglar Bolas de Fuego de Anubis", + "Fix Broken Giant's Knife Bug": "Arreglar Bug de Espada del Gigante Rota", + "Fix Bush Item Drops": "Arreglar Caídas de Objetos de Arbustos", + "Fix Camera Drift": "Arreglar Deriva de Cámara", + "Fix Camera Swing": "Arreglar Balanceo de Cámara", + "Fix Credits Timing (PAL)": "Arreglar Tiempo de Créditos (PAL)", + "Fix Dampé Going Backwards": "Arreglar Dampe Yendo Hacia Atrás", + "Fix Darunia Dancing too Fast": "Arreglar Darunia Bailando Demasiado Rápido", + "Fix Deku Nut Upgrade": "Arreglar Mejora de Nuez Deku", + "Fix Dungeon Entrances": "Arreglar Entradas de Mazmorras", + "Fix Enemies not Spawning Near Water": "Arreglar Enemigos que no Aparecen Cerca del Agua", + "Fix Falling from Vine Edges": "Arreglar Caída desde Bordes de Enredadera", + "Fix Gerudo Warrior's Clothing Colors": "Arreglar Colores de Ropa de Guerrera Gerudo", + "Fix Goron City Doors After Fire Temple": "Arreglar Puertas de Ciudad Goron Después del Templo del Fuego", + "Fix Hand Holding Hammer": "Arreglar Mano Sosteniendo Martillo", + "Fix Hanging Ledge Swing Rate": "Arreglar Velocidad de Balanceo de Borde Colgante", + "Fix Kokiri Forest Quest State": "Arreglar Estado de Misión del Bosque Kokiri", + "Fix L&R Pause Menu": "Arreglar Menú de Pausa L&R", + "Fix L&Z Page Switch in Pause Menu": "Arreglar Cambio de Página L&Z en Menú de Pausa", + "Fix Link's Eyes Open while Sleeping": "Arreglar Ojos de Link Abiertos mientras Duerme", + "Fix Megaton Hammer Crouch Stab": "Arreglar Puñalada Agachada con Martillo Megatón", + "Fix Missing Jingle after 5 Silver Rupees": "Arreglar Jingle Faltante después de 5 Rupias de Plata", + "Fix Navi Text HUD Position": "Arreglar Posición de Texto de Navi en HUD", + "Fix Out of Bounds Textures": "Arreglar Texturas Fuera de Límites", + "Fix Poacher's Saw Softlock": "Arreglar Softlock de Sierra de Cazador Furtivo", + "Fix Two-Handed Idle Animations": "Arreglar Animaciones de Inactividad a Dos Manos", + "Fix Vanishing Paths": "Arreglar Caminos que Desaparecen", + "Fix Zora Hint Dialogue": "Arreglar Diálogo de Pista Zora", + "Fixes camera slightly drifting to the left when standing still due to a": "Arregla la deriva leve de la cámara hacia la izquierda al estar quieto debido a un", + "Fixes camera swing rate when the player falls off a ledge and the camera": "Arregla la velocidad de balanceo de cámara cuando el jugador cae de un borde y la cámara", + "Fixes kokiri animation state to match their text state when getting": "Arregla el estado de animación de los kokiri para coincidir con su estado de texto al obtener", + "Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it.": "Arregla la bandera de Espada del Gigante Rota no siendo reiniciada cuando Medigoron la arregla.", + "Font Scale: %.2fx": "Escala de Fuente: %.2fx", + "Force aspect ratio:": "Forzar proporción de aspecto:", + "Forest Temple": "Templo del Bosque", + "Freeze Time": "Congelar Tiempo", + "Freeze Traps": "Trampas de Congelación", + "Freezes the time of day.": "Congela la hora del día.", + "Frogs' Ocarina Game": "Juego de Ocarina de las Ranas", + "General": "General", + "General Settings": "Configuración General", + "Generate Randomizer": "Generar Randomizer", + "Ghost Pepper": "Chile Fantasma", + "Gives you the glitched damage value of the quick put away glitch.": "Te da el valor de daño glitcheado del glitch de guardado rápido.", + "Glitch Aids": "Ayudas de Glitch", + "Glitch Restorations": "Restauraciones de Glitch", + "Graphical Fixes": "Arreglos Gráficos", + "Graphical Restorations": "Restauraciones Gráficas", + "Graphics": "Gráficos", + "Graphics Options": "Opciones de Gráficos", + "Grave Hole Jumps": "Saltos de Agujero de Tumba", + "Greatly decreases cast time of Farore's Wind magic spell.": "Disminuye enormemente el tiempo de lanzamiento del hechizo Viento de Farore.", + "Guarantee Bite": "Garantizar Mordida", + "Habanero": "Habanero", + "Health": "Salud", + "Hide Background": "Ocultar Fondo", + "Hides most of the UI when not needed.\n": "Oculta la mayor parte de la interfaz cuando no es necesaria.\n", + "Holding L makes you float into the air.": "Mantener L te hace flotar en el aire.", + "Holding down B skips text.": "Mantener presionado B salta el texto.", + "Hookshot Everything": "Gancho a Todo", + "Hookshot Reach Multiplier: %.2fx": "Multiplicador de Alcance del Gancho: %.2fx", + "Host & Port": "Anfitrión y Puerto", + "Hurt Container Mode": "Modo Contenedor Dañino", + "Hyper Bosses": "Jefes Hiper", + "Hyper Enemies": "Enemigos Hiper", + "I promise I have read the warning": "Prometo que he leído la advertencia", + "I understand, enable save states": "Entiendo, habilitar estados de guardado", + "If enabled, signs near loading zones will tell you where they lead to.": "Si está habilitado, los letreros cerca de zonas de carga te dirán a dónde llevan.", + "ImGui Menu Scaling": "Escalado de Menú ImGui", + "Infinite...": "Infinito...", + "Instant Age Change": "Cambio de Edad Instantáneo", + "Instant Fishing": "Pesca Instantánea", + "Instant Win": "Victoria Instantánea", + "Instant Win##Frogs": "Victoria Instantánea##Ranas", + "Instant Win##LostWoods": "Victoria Instantánea##BosquePerdido", + "Instantly return the Boomerang to Link by pressing its item button while": "Devuelve instantáneamente el Bumerán a Link presionando su botón de objeto mientras", + "Integer scales the image. Only available in Pixel Perfect Mode.": "Escala la imagen en números enteros. Solo disponible en Modo Pixel Perfect.", + "Internal Resolution": "Resolución Interna", + "Interval between Rupee reduction in Rupee Dash Mode.": "Intervalo entre reducción de Rupias en Modo Rupee Dash.", + "Introduces Options for unequipping Link's sword\n\n": "Introduce Opciones para desequipar la espada de Link\n\n", + "Item-related Fixes": "Arreglos Relacionados con Objetos", + "Ivan the Fairy (Coop Mode)": "Ivan el Hada (Modo Coop)", + "Jabber Nut Colors Match Kind": "Colores de Nueces Jabber Coinciden con Tipo", + "Jalapeño": "Jalapeño", + "Keese/Guay don't Target You": "Keese/Guay no te Apuntan", + "King Zora Speed: %.2fx": "Velocidad de Rey Zora: %.2fx", + "Knockback Traps": "Trampas de Retroceso", + "Language": "Idioma", + "Languages": "Idiomas", + "Leever Spawn Rate: %d seconds": "Tasa de Aparición de Leever: %d segundos", + "Link will not spin when the Goron Pot starts to spin.": "Link no girará cuando la Olla Goron comience a girar.", + "Loaches always Appear": "Las Lochas Siempre Aparecen", + "Loaches will always appear in the fishing pond instead of every four visits.": "Las lochas siempre aparecerán en el estanque de pesca en lugar de cada cuatro visitas.", + "Log Level": "Nivel de Registro", + "Logs some resources as XML when they're loaded in binary format.": "Registra algunos recursos como XML cuando se cargan en formato binario.", + "Lost Woods Ocarina Game": "Juego de Ocarina del Bosque Perdido", + "Magic": "Magia", + "Make Deku Nuts explode Bombs, similar to how they interact with Bombchus.": "Hace que las Nueces Deku exploten Bombas, similar a como interactúan con Bombchus.", + "Make crouch stabbing always do the same damage as a regular slash.": "Hace que la puñalada agachada siempre haga el mismo daño que un corte regular.", + "Makes Link always kick the chest to open it, instead of doing the longer": "Hace que Link siempre patee el cofre para abrirlo, en lugar de hacer la animación más larga", + "Makes all equipment visible, regardless of age.": "Hace que todo el equipo sea visible, sin importar la edad.", + "Makes every surface in the game climbable.": "Hace que cada superficie en el juego sea escalable.", + "Makes every surface in the game hookshotable.": "Hace que cada superficie en el juego sea alcanzable con el gancho.", + "Makes every tunic have the effects of every other tunic.": "Hace que cada túnica tenga los efectos de todas las demás túnicas.", + "Makes the L and R buttons in the pause menu the same color.": "Hace que los botones L y R en el menú de pausa sean del mismo color.", + "Manual seed entry": "Entrada manual de semilla", + "Map Select Button Combination:": "Combinación de Botones de Selección de Mapa:", + "Match Refresh Rate": "Coincidir Tasa de Refresco", + "Matches interpolation value to the refresh rate of your display.": "Coincide el valor de interpolación con la tasa de refresco de tu pantalla.", + "Matches the color of maps & compasses to the dungeon they belong to.": "Coincide el color de mapas y brújulas con la mazmorra a la que pertenecen.", + "Menu Background Opacity": "Opacidad de Fondo del Menú", + "Menu Controller Navigation": "Navegación de Controlador del Menú", + "Menu Settings": "Configuración del Menú", + "Menu Theme": "Tema del Menú", + "Mirrored World": "Mundo Espejado", + "Misc Restorations": "Restauraciones Varias", + "Miscellaneous": "Misceláneo", + "Modifies Damage taken after Bonking.": "Modifica el Daño recibido después de Golpearse.", + "Modifies damage taken after falling into a void:\n": "Modifica el daño recibido al caer en un vacío:\n", + "Modify Note Timer: %dx": "Modificar Temporizador de Notas: %dx", + "Money": "Dinero", + "Moon Jump on L": "Salto Lunar en L", + "MoreResolutionSettings": "MásConfiguracionesDeResolución", + "Multiplier:": "Multiplicador:", + "Multiplies your output resolution by the value inputted, as a more intensive but effective": "Multiplica tu resolución de salida por el valor ingresado, como una forma más intensiva pero efectiva", + "Mute Notification Sound": "Silenciar Sonido de Notificación", + "N64 Weird Frames": "Cuadros Extraños N64", + "Nayru's Love": "Amor de Nayru", + "Network": "Red", + "Nighttime Skulltulas will spawn during both day and night.": "Las Skulltulas nocturnas aparecerán tanto de día como de noche.", + "No Clip": "Sin Colisión", + "No Clip Button Combination:": "Combinación de Botones Sin Colisión:", + "No Heart Drops": "Sin Caídas de Corazones", + "No Random Drops": "Sin Caídas Aleatorias", + "No ReDead/Gibdo Freeze": "Sin Congelación de ReDead/Gibdo", + "No Rupee Randomization": "Sin Aleatorización de Rupias", + "None##Skips": "Ninguno##Saltos", + "Note Play Speed: %dx": "Velocidad de Reproducción de Notas: %dx", + "Notification on Autosave": "Notificación de Autoguardado", + "Number of Starting Notes: %d notes": "Número de Notas Iniciales: %d notas", + "OHKO": "KO", + "Ocarina of Time + Master Sword": "Ocarina del Tiempo + Espada Maestra", + "Once a hook as been set, Fish will never let go while being reeled in.": "Una vez que se ha puesto un gancho, los peces nunca soltarán mientras se recogen.", + "Only change the texture of containers if you have the Stone of Agony.": "Cambiar solo la textura de contenedores si tienes la Piedra de Agonía.", + "Open App Files Folder": "Abrir Carpeta de Archivos de la App", + "Optimized Debug Warp Screen, with the added ability to chose entrances and time of day.": "Pantalla de Teletransporte Debug optimizada, con la capacidad añadida de elegir entradas y hora del día.", + "Override the resolution scale slider and use the settings below, irrespective of window size.": "Anular el control deslizante de escala de resolución y usar las configuraciones de abajo, independientemente del tamaño de ventana.", + "Passive Infinite Sword Glitch\n": "Glitch de Espada Infinita Pasiva\n", + "Permanent Heart Loss": "Pérdida Permanente de Corazón", + "Popout Audio Editor Window": "Abrir Ventana de Editor de Audio", + "Popout Bindings Window": "Abrir Ventana de Controles", + "Popout Cosmetics Editor Window": "Abrir Ventana de Editor de Cosméticos", + "Popout Gameplay Stats Window": "Abrir Ventana de Estadísticas de Jugabilidad", + "Popout Input Viewer Settings": "Abrir Configuración de Visor de Entradas", + "Popout Mod Menu Window": "Abrir Ventana de Menú de Mods", + "Popout Time Splits Window": "Abrir Ventana de Divisiones de Tiempo", + "Position": "Posición", + "Prevent dropping inputs when playing the Ocarina too quickly.": "Prevenir pérdida de entradas al tocar la Ocarina demasiado rápido.", + "Prevent forced conversations with Navi and/or other NPCs.": "Prevenir conversaciones forzadas con Navi y/o otros NPCs.", + "Prevent notifications from playing a sound.": "Prevenir que las notificaciones reproduzcan un sonido.", + "Prevents ReDeads and Gibdos from being able to freeze you with their scream.": "Previene que ReDeads y Gibdos puedan congelarte con su grito.", + "Prevents bugs from automatically despawning after a while when dropped.": "Previene que los bichos desaparezcan automáticamente después de un tiempo al soltarlos.", + "Prevents fish from automatically despawning after a while when dropped.": "Previene que los peces desaparezcan automáticamente después de un tiempo al soltarlos.", + "Prevents immediately falling off climbable surfaces if climbing on the edges.": "Previene caer inmediatamente de superficies escalables si estás escalando en los bordes.", + "Prevents integer scaling factor from exceeding screen bounds.\n\n": "Previene que el factor de escala entero exceda los límites de la pantalla.\n\n", + "Prevents the Deku Shield from burning on contact with fire.": "Previene que el Escudo Deku se queme al contacto con fuego.", + "Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable": "Previene que la mejora de Nuez Deku del Bosque se vuelva inalcanzable", + "Prevents the big Cucco from appearing in the Bombchu Bowling minigame.": "Previene que el Cucco grande aparezca en el minijuego de Bolos de Bombchu.", + "Prevents the small Cucco from appearing in the Bombchu Bowling minigame.": "Previene que el Cucco pequeño aparezca en el minijuego de Bolos de Bombchu.", + "Pulsate Boss Icon": "Icono de Jefe Pulsante", + "Quick Bongo Kill": "Muerte Rápida de Bongo", + "Quick Putaway": "Guardado Rápido", + "Randomize All Settings": "Aleatorizar Todas las Configuraciones", + "Randomizes all randomizer settings to random valid values (excludes tricks).": "Aleatoriza todas las configuraciones del randomizer a valores válidos aleatorios (excluye trucos).", + "Rebottle Blue Fire": "Reembottellar Fuego Azul", + "Red Ganon Blood": "Sangre de Ganon Roja", + "Remember Save Location": "Recordar Ubicación de Guardado", + "Remote Bombchu": "Bombchu Remota", + "Remove Big Cucco": "Eliminar Cucco Grande", + "Remove Power Crouch Stab": "Eliminar Puñalada Agachada de Poder", + "Remove Small Cucco": "Eliminar Cucco Pequeño", + "Remove the Darkness that appears when charging a Spin Attack.": "Eliminar la Oscuridad que aparece al cargar un Ataque Giratorio.", + "Removes the cap of 3 active explosives being deployed at once.": "Elimina el límite de 3 explosivos activos desplegados a la vez.", + "Removes the timer to play back the song.": "Elimina el temporizador para reproducir la canción.", + "Renders Gauntlets when using the Bow and Hookshot like in OoT3D.": "Renderiza Guanteletes al usar el Arco y Gancho como en OoT3D.", + "Renders a health bar for Enemies when Z-Targeted.": "Renderiza una barra de salud para Enemigos cuando se Apunta con Z.", + "Replace Navi's overworld quest hints with rando-related gameplay hints.": "Reemplaza las pistas de misión del mundo de Navi con pistas de jugabilidad relacionadas con rando.", + "Reset Button Combination:": "Combinación de Botones de Reinicio:", + "Resets the Navi timer on scene change. If you have already talked to her,": "Reinicia el temporizador de Navi al cambiar de escena. Si ya has hablado con ella,", + "Respawn with Full Health instead of 3 hearts.": "Reaparecer con Salud Completa en lugar de 3 corazones.", + "Restore Old Gold Skulltula Cutscene": "Restaurar Cinemática de Gold Skulltula Antigua", + "Restore the original red blood from NTSC 1.0/1.1. Disable for Green blood.": "Restaurar la sangre roja original de NTSC 1.0/1.1. Desactivar para sangre verde.", + "Restores the wider range of certain shutter doors from NTSC 1.0.\n": "Restaura el rango más amplio de ciertas puertas correderas de NTSC 1.0.\n", + "Reworked Targeting": "Apuntado Retrabajado", + "Reworks targeting functionality\n": "Retrabaja la funcionalidad de apuntado\n", + "Round One Notes: %d notes": "Notas de la Ronda Uno: %d notas", + "Round Three Notes: %d notes": "Notas de la Ronda Tres: %d notas", + "Round Two Notes: %d notes": "Notas de la Ronda Dos: %d notas", + "Rupee Dash Interval %d seconds": "Intervalo de Rupee Dash %d segundos", + "Rupee Dash Mode": "Modo Rupee Dash", + "Rupees reduce over time, Link suffers damage when the count hits 0.": "Las rupias se reducen con el tiempo, Link sufre daño cuando el conteo llega a 0.", + "Sail": "Sail", + "Sail is a networking protocol designed to facilitate remote": "Sail es un protocolo de red diseñado para facilitar remoto", + "Save States": "Estados de Guardado", + "Search In Sidebar": "Buscar en Barra Lateral", + "Search Input Autofocus": "Autoenfoque de Entrada de Búsqueda", + "Seed": "Semilla", "Seed Entry": "Entrada de Semilla", - "Presets": "Preajustes", - "Logic": "Lógica", - "Dungeons": "Mazmorras", - "Shuffles": "Mezclas", - "Hints & Traps": "Pistas y Trampas", - "Starting Items": "Objetos Iniciales" + "Select the UI translation language...": "Seleccionar el idioma de traducción de la interfaz...", + "Serrano": "Serrano", + "Settings": "Configuración", + "Shadow Tag Mode": "Modo Sombra Pegajosa", + "Shield with Two-Handed Weapons": "Escudo con Armas a Dos Manos", + "Shock Traps": "Trampas de Choque", + "Shooting Gallery": "Galería de Tiro", + "Shops and Minigames are open both day and night. Requires a scene reload to take effect.": "Tiendas y Minijuegos están abiertos de día y de noche. Requiere recargar escena para tener efecto.", + "Show Gauntlets in First-Person": "Mostrar Guanteletes en Primera Persona", + "Show a notification when the game is autosaved.": "Mostrar una notificación cuando el juego se autoguarda.", + "Signs Hint Entrances": "Letreros Indican Entradas", + "Skip Bottle Pickup Messages": "Saltar Mensajes de Recoger Botella", + "Skip Consumable Item Pickup Messages": "Saltar Mensajes de Recoger Objetos Consumibles", + "Skip Feeding Jabu-Jabu": "Saltar Alimentar a Jabu-Jabu", + "Skip Keep Confirmation": "Saltar Confirmación de Keep", + "Skip One Point Cutscenes (Chests, Door Unlocks, etc.)": "Saltar Cinemáticas de Un Punto (Cofres, Apertura de Puertas, etc.)", + "Skip Pickup Messages for Bottle Swipes.": "Saltar Mensajes de Recoger para Botellas.", + "Skip Pickup Messages for new Consumable Items.": "Saltar Mensajes de Recoger para Objetos Consumibles nuevos.", + "Skip Playing Scarecrow's Song": "Saltar Tocar Canción del Espantapájaros", + "Skip the \"Game Saved\" confirmation screen.": "Saltar la pantalla de confirmación \"Juego Guardado\".", + "Skip the part where the Ocarina Playback is called when you play a song.": "Saltar la parte donde se llama la Reproducción de Ocarina al tocar una canción.", + "Skip the tower escape sequence between Ganondorf and Ganon.": "Saltar la secuencia de escape de la torre entre Ganondorf y Ganon.", + "Skips Link's taking breath animation after coming up from water.": "Salta la animación de Link tomando aire al salir del agua.", + "Skips the Frogs' Ocarina Game.": "Salta el Juego de Ocarina de las Ranas.", + "Skips the Lost Woods Ocarina Memory Game.": "Salta el Juego de Memoria de Ocarina del Bosque Perdido.", + "Skips the Shooting Gallery minigame.": "Salta el minijuego de la Galería de Tiro.", + "Solve Amy's Puzzle": "Resolver Puzzle de Amy", + "Spawn Bean Skulltula Faster": "Aparecer Bean Skulltula Más Rápido", + "Spawn with Full Health": "Aparecer con Salud Completa", + "Spawned Enemies Ignored Ingame": "Enemigos Generados Ignorados en el Juego", + "Speak to Navi with L but enter First-Person Camera with C-Up.": "Hablar con Navi con L pero entrar a Cámara en Primera Persona con C-Arriba.", + "Speed Modifier": "Modificador de Velocidad", + "Speed Traps": "Trampas de Velocidad", + "Speeds up animation of the pause menu, similar to Majora's Mask": "Acelera la animación del menú de pausa, similar a Majora's Mask", + "Speeds up emptying animation when dumping out the contents of a bottle.": "Acelera la animación de vaciado al verter el contenido de una botella.", + "Speeds up lifting Silver Rocks and Obelisks.": "Acelera el levantamiento de Rocas de Plata y Obeliscos.", + "Speeds up ship in Shadow Temple.": "Acelera el barco en el Templo de las Sombras.", + "Spoiler File": "Archivo de Spoiler", + "Stops masks from automatically unequipping on certain situations:\n": "Detiene que las máscaras se desequen automáticamente en ciertas situaciones:\n", + "Super Tunic": "Super Túnica", + "Switch Timer Multiplier": "Multiplicador de Temporizador de Cambio", + "Switches Link's age and reloads the area.": "Cambia la edad de Link y recarga el área.", + "Syncs the in-game time with the real world time.": "Sincroniza el tiempo del juego con el tiempo del mundo real.", + "Target Switch Button Combination:": "Combinación de Botones de Cambio de Objetivo:", + "Targetable Gold Skulltula": "Gold Skulltula Apuntable", + "Teleport Traps": "Trampas de Teletransporte", + "Text to Speech": "Texto a Voz", + "Texture Filter (Needs reload)": "Filtro de Textura (Necesita recargar)", + "The Pond Owner will not ask to confirm if you want to keep a smaller Fish.": "El Dueño del Estanque no pedirá confirmar si quieres quedarte con un Pez más pequeño.", + "The ammunition at the start of the Shooting Gallery minigame as Adult.": "La munición al inicio del minijuego de Galería de Tiro como Adulto.", + "The ammunition at the start of the Shooting Gallery minigame as Child.": "La munición al inicio del minijuego de Galería de Tiro como Niño.", + "The log level determines which messages are printed to the console.": "El nivel de registro determina qué mensajes se imprimen en la consola.", + "The number of Bombchus available at the start of the Bombchu Bowling minigame.": "El número de Bombchus disponibles al inicio del minijuego de Bolos de Bombchu.", + "The skybox in the background of the File Select screen will go through the": "El skybox en el fondo de la pantalla de Selección de Archivo pasará por el", + "The time between groups of Leevers spawning.": "El tiempo entre grupos de Leevers apareciendo.", + "These are NOT like emulator states. They do not save your game progress": "Estos NO son como estados de emulador. No guardan tu progreso del juego", + "These enhancements are only useful in the Randomizer mode but do not affect the randomizer logic.": "Estas mejoras solo son útiles en el modo Randomizer pero no afectan la lógica del randomizer.", + "This will completely erase the controls config, including registered devices.\nContinue?": "Esto borrará completamente la configuración de controles, incluyendo dispositivos registrados.\n¿Continuar?", + "Tier 1 Traps:": "Trampas de Nivel 1:", + "Tier 2 Traps:": "Trampas de Nivel 2:", + "Tier 3 Traps:": "Trampas de Nivel 3:", + "Time Sync": "Sincronización de Tiempo", + "Timeless Equipment": "Equipo Atemporal", + "Toggle Fullscreen": "Alternar Pantalla Completa", + "Toggle Input Viewer": "Alternar Visor de Entradas", + "Toggle Timers Window": "Alternar Ventana de Temporizadores", + "Toggle modifier instead of holding": "Alternar modificador en lugar de mantener", + "Toggles the Check Tracker.": "Alterna el Rastreador de Checks.", + "Toggles the Entrance Tracker.": "Alterna el Rastreador de Entradas.", + "Toggles the Item Tracker.": "Alterna el Rastreador de Objetos.", + "Translate Title Screen": "Traducir Pantalla de Título", + "Translate the Debug Warp Screen based on the game language.": "Traducir la Pantalla de Teletransporte Debug según el idioma del juego.", + "Trap Options": "Opciones de Trampas", + "Trees Drop Sticks": "Los Árboles Suelten Palos", + "Tricks/Glitches": "Trucos/Glitches", + "Turn on/off changes to the Bombchu Bowling behavior.": "Activar/desactivar cambios al comportamiento de Bolos de Bombchu.", + "Turn on/off changes to the Fishing behavior.": "Activar/desactivar cambios al comportamiento de Pesca.", + "Turn on/off changes to the Frogs' Ocarina Game behavior.": "Activar/desactivar cambios al comportamiento del Juego de Ocarina de las Ranas.", + "Turn on/off changes to the Lost Woods Ocarina Game behavior.": "Activar/desactivar cambios al comportamiento del Juego de Ocarina del Bosque Perdido.", + "Turn on/off changes to the shooting gallery behavior.": "Activar/desactivar cambios al comportamiento de la galería de tiro.", + "Turns Bunny Hood Invisible while still maintaining its effects.": "Vuelve invisible la Máscara de Conejo mientras mantiene sus efectos.", + "Turns on OoT Beta Quest. *WARNING*: This will reset your game!": "Activa la Misión Beta de OoT. *ADVERTENCIA*: ¡Esto reiniciará tu juego!", + "Turns the Static Image of Link in the Pause Menu's Equipment Subscreen": "Convierte la Imagen Estática de Link en la Subpantalla de Equipo del Menú de Pausa", + "UI Translation": "Traducción de Interfaz", + "Unlimited Playback Time##Frogs": "Tiempo de Reproducción Ilimitado##Ranas", + "Unlimited Playback Time##LostWoods": "Tiempo de Reproducción Ilimitado##BosquePerdido", + "Unrestricted Items": "Objetos Sin Restricciones", + "Unsheathe Sword Without Slashing": "Desenvainar Espada sin Cortar", + "Use Custom graphics for Dungeon Keys, Big and Small, so that they can be easily told apart.": "Usar gráficos personalizados para Llaves de Mazmorra, Grandes y Pequeñas, para que puedan distinguirse fácilmente.", + "Void Damage Multiplier": "Multiplicador de Daño de Vacío", + "Void Traps": "Trampas de Vacío", + "Warp Point": "Punto de Teletransporte", + "Warp Points": "Puntos de Teletransporte", + "Warping": "Teletransporte", + "Wearing the Bunny Hood grants a speed and jump boost like in Majora's Mask.\n": "Usar la Máscara de Conejo otorga un aumento de velocidad y salto como en Majora's Mask.\n", + "When a line is stable, guarantee bite. Otherwise use Default logic.": "Cuando una línea es estable, garantizar mordida. De lo contrario usar lógica predeterminada.", + "When obtaining Rupees, randomize what the Rupee is called in the textbox.": "Al obtener Rupias, aleatorizar cómo se llama la Rupia en el cuadro de texto.", + "Wide Door Ranges": "Rangos de Puertas Anchos", + "Windowed Fullscreen": "Pantalla Completa en Ventana", + "With Shuffle Speak, jabber nut model & color will be generic.": "Con Shuffle Speak, el modelo y color de nuez jabber será genérico.", + "https://github.com/HarbourMasters/sail": "https://github.com/HarbourMasters/sail", + "AspectRatioCustom": "ProporciónPersonalizada", + "AspectSep": "SepProporción", + "Automatically sets scale factor to fit window. Only available in Pixel Perfect Mode.": "Establece automáticamente el factor de escala para ajustar a la ventana. Solo disponible en Modo Pixel Perfect.", + "Resources": "Recursos", + "Spoiler Log Rewards": "Recompensas del Spoiler Log", + "Model": "Modelo", + "Trap Options": "Opciones de Trampa", + "Name: ": "Nombre: ", + "Load/Save Spoiler Log": "Cargar/Guardar Spoiler Log", + "No Spoiler Logs found.": "No se encontraron Spoiler Logs.", + "Current Seed Hash": "Hash de Semilla Actual", + "Icon": "Icono", + "No Spoiler Log Loaded": "No hay Spoiler Log Cargado", + "Options": "Opciones", + "Please Load Spoiler Data...": "Por favor Carga Datos del Spoiler...", + "Spoiler Log Check Name": "Nombre de Check del Spoiler Log", + "Spoiler Log Reward": "Recompensa del Spoiler Log", + "New Reward": "Nueva Recompensa", + "Additional Options": "Opciones Adicionales", + "Shop Price": "Precio de Tienda", + "Gossip Stones": "Piedras de Chisme", + "Locations": "Ubicaciones", + "Clear Item Filter": "Limpiar Filtro de Objeto", + "Current Hint: ": "Pista Actual: ", + "New Hint: ": "Nueva Pista: ", + "Hint Entries": "Entradas de Pistas", + "Waiting for file load...": "Esperando carga de archivo...", + "Show Hidden Items": "Mostrar Objetos Ocultos", + "When active, items will show hidden checks by default when updated to this state.": "Cuando está activo, los objetos mostrarán checks ocultos por defecto al actualizarse a este estado.", + "Only Show Available Checks": "Mostrar Solo Checks Disponibles", + "When active, unavailable checks will be hidden.": "Cuando está activo, los checks no disponibles serán ocultados.", + "Expand All": "Expandir Todo", + "Collapse All": "Colapsar Todo", + "Search...": "Buscar...", + "Available": "Disponible", + "Checked": "Verificado", + "Total": "Total", + "General settings": "Configuración general", + "Section settings": "Configuración de sección", + "Hidden": "Oculto", + "Main Window": "Ventana Principal", + "Misc Window": "Ventana Miscelánea", + "Separate": "Separado", + "Sort By": "Ordenar Por", + "To": "A", + "From": "Desde", + "List Items": "Elementos de Lista", + "Auto scroll": "Desplazamiento automático", + "Automatically scroll to the first available entrance in the current scene": "Desplazar automáticamente a la primera entrada disponible en la escena actual", + "Highlight previous": "Resaltar anterior", + "Highlight the previous entrance that Link came from": "Resaltar la entrada anterior de la que vino Link", + "Highlight available": "Resaltar disponibles", + "Highlight available entrances in the current scene": "Resaltar entradas disponibles en la escena actual", + "Hide undiscovered": "Ocultar no descubiertos", + "Collapse undiscovered entrances towards the bottom of each group": "Colapsar entradas no descubiertas hacia el fondo de cada grupo", + "Hide reverse": "Ocultar reversa", + "Hide reverse entrance transitions when Decouple Entrances is off": "Ocultar transiciones de entrada reversa cuando Desacoplar Entradas está desactivado", + "This option is disabled because \"Decouple Entrances\" is enabled.": "Esta opción está desactivada porque \"Desacoplar Entradas\" está habilitado.", + "Group By": "Agrupar Por", + "Area": "Área", + "Type": "Tipo", + "Spoiler Reveal": "Revelar Spoiler", + "Show Source": "Mostrar Origen", + "Reveal the source for undiscovered entrances": "Revelar el origen de entradas no descubiertas", + "Show Destination": "Mostrar Destino", + "Reveal the destination for undiscovered entrances": "Revelar el destino de entradas no descubiertas", + "Legend": "Leyenda", + "Last Entrance": "Última Entrada", + "Available Entrances": "Entradas Disponibles", + "Undiscovered Entrances": "Entradas No Descubiertas", + "Sort entrances by the original source entrance": "Ordenar entradas por la entrada de origen original", + "Sort entrances by the overrided destination": "Ordenar entradas por el destino sobrescrito", + "Group entrances by their area": "Agrupar entradas por su área", + "Group entrances by their entrance type": "Agrupar entradas por su tipo de entrada", + "Enable Dragging": "Habilitar Arrastre", + "Only Enable While Paused": "Habilitar Solo en Pausa", + "Display Mode": "Modo de Visualización", + "Combo Button 1": "Botón Combinado 1", + "Combo Button 2": "Botón Combinado 2", + "Icon size : %dpx": "Tamaño de icono: %dpx", + "Icon margins : %dpx": "Márgenes de icono: %dpx", + "Text size : %dpx": "Tamaño de texto: %dpx", + "Align count to left side": "Alinear conteo al lado izquierdo", + "Inventory": "Inventario", + "Equipment": "Equipo", + "Aiming Reticle for the Bow/Slingshot": "Retícula de Puntería para Arco/Honda", + "Aiming Reticle for Boomerang": "Retícula de Puntería para Bumerán", + "Targetable Hookshot Reticle": "Retícula de Gancho Dirigible", + "Boss Souls": "Almas de Jefes", + "Ocarina Buttons": "Botones de Ocarina", + "Overworld Keys": "Llaves de Mundo", + "Dungeon Items": "Objetos de Mazmorra", + "Checks: %d/%d": "Checks: %d/%d", + "Tracker Notes": "Notas del Rastreador", + "Write notes for your playthrough here...": "Escribe notas para tu partida aquí...", + "Include hidden checks in totals": "Incluir checks ocultos en totales", + "Include unavailable checks in totals": "Incluir checks no disponibles en totales", + "Show area totals": "Mostrar totales por área", + "Show check names on hover": "Mostrar nombres de check al pasar el cursor", + "Show check names always": "Mostrar nombres de check siempre", + "Show check icons": "Mostrar iconos de check", + "Show check counts": "Mostrar conteos de check", + "Show search input": "Mostrar entrada de búsqueda", + "Show check totals": "Mostrar totales de check", + "Show expand/collapse buttons": "Mostrar botones de expandir/colapsar", + "Show hidden items checkbox": "Mostrar checkbox de objetos ocultos", + "Show available checks checkbox": "Mostrar checkbox de checks disponibles", + "Tracker Header Visibility": "Visibilidad del Encabezado del Rastreador", + "Window Type": "Tipo de Ventana", + "Font Size": "Tamaño de Fuente", + "Background Color": "Color de Fondo", + "Column Count": "Cantidad de Columnas", + "Checks per Column": "Checks por Columna", + "Show unchecked": "Mostrar no verificados", + "Show checked": "Mostrar verificados", + "Show hidden": "Mostrar ocultos", + "Show available": "Mostrar disponibles", + "Show unavailable": "Mostrar no disponibles", + "Show completed areas": "Mostrar áreas completadas", + "Show empty areas": "Mostrar áreas vacías", + "Sort areas by progress": "Ordenar áreas por progreso", + "Sort areas alphabetically": "Ordenar áreas alfabéticamente", + "Sort areas by original order": "Ordenar áreas por orden original", + "Filter by age": "Filtrar por edad", + "Filter by time": "Filtrar por tiempo", + "Filter by region": "Filtrar por región", + "Clear All Filters": "Limpiar Todos los Filtros", + "A-Z": "A-Z", + "Include": "Incluir", + "Exclude": "Excluir", + "Included": "Incluido", + "Excluded": "Excluido", + "Off": "Apagado", + "On": "Encendido", + "Disabled": "Desactivado", + "Enabled": "Activado", + "Apply": "Aplicar", + "Cancel": "Cancelar", + "Clear": "Limpiar", + "Reset": "Reiniciar", + "Save": "Guardar", + "Load": "Cargar", + "Delete": "Eliminar", + "Import": "Importar", + "Export": "Exportar", + "Generate": "Generar", + "Refresh": "Actualizar", + "None": "Ninguno", + "All": "Todos", + "Both": "Ambos", + "Default": "Por Defecto", + "Custom": "Personalizado", + "Random": "Aleatorio", + "Vanilla": "Original", + "Unknown": "Desconocido", + "Yes": "Sí", + "No": "No" } -} +} \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index 0d0991889..63b65249e 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -1,6 +1,7 @@ #include "Plandomizer.h" #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/LanguageManager.h" #include "soh/util.h" #include #include @@ -19,6 +20,8 @@ #include "soh/Enhancements/randomizer/Traps.h" #include "soh/Enhancements/randomizer/3drando/shops.hpp" +using namespace SohGui; + extern "C" { #include "include/z64item.h" #include "objects/gameplay_keep/gameplay_keep.h" @@ -727,7 +730,7 @@ void PlandomizerOverlayText(std::pair drawObject) { void PlandomizerDrawItemPopup(uint32_t index) { if (shouldPopup && ImGui::BeginPopup("ItemList")) { PlandoPushImageButtonStyle(); - ImGui::SeparatorText("Resources"); + ImGui::SeparatorText(LanguageManager::Instance().GetString("Resources").c_str()); ImGui::BeginTable("Infinite Item Table", 7); for (auto& item : infiniteItemList) { ImGui::PushID(item); @@ -752,7 +755,7 @@ void PlandomizerDrawItemPopup(uint32_t index) { } ImGui::EndTable(); - ImGui::SeparatorText("Spoiler Log Rewards"); + ImGui::SeparatorText(LanguageManager::Instance().GetString("Spoiler Log Rewards").c_str()); ImGui::BeginTable("Item Button Table", 8); uint32_t itemIndex = 0; @@ -875,8 +878,8 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { ImGui::PushID(index); ImGui::BeginTable("IceTrap", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInner); - ImGui::TableSetupColumn("Model", ImGuiTableColumnFlags_WidthFixed, 36.0f); - ImGui::TableSetupColumn("Trap Options"); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Model").c_str(), ImGuiTableColumnFlags_WidthFixed, 36.0f); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Trap Options").c_str()); ImGui::TableHeadersRow(); ImGui::TableNextColumn(); @@ -896,7 +899,7 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { PlandomizerDrawIceTrapPopUp(index); ImGui::SameLine(); ImGui::TableNextColumn(); - ImGui::Text("Name: "); + ImGui::Text("%s", LanguageManager::Instance().GetString("Name: ").c_str()); ImGui::SameLine(); if (plandoLogData[index].iceTrapModel.GetRandomizerGet() != RG_NONE && plandoLogData[index].iceTrapModel.GetRandomizerGet() != RG_SOLD_OUT) { @@ -964,7 +967,7 @@ void PlandomizerDrawOptions() { ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextColumn(); - ImGui::SeparatorText("Load/Save Spoiler Log"); + ImGui::SeparatorText(LanguageManager::Instance().GetString("Load/Save Spoiler Log").c_str()); PlandomizerPopulateSeedList(); static size_t selectedList = 0; if (existingSeedList.size() != 0) { @@ -972,7 +975,7 @@ void PlandomizerDrawOptions() { "##JsonFiles", &selectedList, existingSeedList, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::None)); } else { - ImGui::Text("No Spoiler Logs found."); + ImGui::Text(LanguageManager::Instance().GetString("No Spoiler Logs found.").c_str()); } ImGui::BeginDisabled(existingSeedList.empty()); if (UIWidgets::Button("Load", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { @@ -988,7 +991,7 @@ void PlandomizerDrawOptions() { ImGui::EndDisabled(); ImGui::TableNextColumn(); - ImGui::SeparatorText("Current Seed Hash"); + ImGui::SeparatorText(LanguageManager::Instance().GetString("Current Seed Hash").c_str()); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetContentRegionAvail().x * 0.5f) - (34.0f * 5.0f)); if (spoilerLogData.size() > 0) { if (ImGui::BeginTable("HashIcons", 5)) { @@ -1040,14 +1043,14 @@ void PlandomizerDrawOptions() { ImGui::EndTable(); } } else { - ImGui::Text("No Spoiler Log Loaded"); + ImGui::Text(LanguageManager::Instance().GetString("No Spoiler Log Loaded").c_str()); } ImGui::EndTable(); } - ImGui::SeparatorText("Options"); + ImGui::SeparatorText(LanguageManager::Instance().GetString("Options").c_str()); if (plandoLogData.size() == 0) { - ImGui::Text("Please Load Spoiler Data..."); + ImGui::Text(LanguageManager::Instance().GetString("Please Load Spoiler Data...").c_str()); return; } @@ -1098,7 +1101,7 @@ void PlandomizerDrawOptions() { } } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Type to search and press Enter"); + ImGui::SetTooltip(LanguageManager::Instance().GetString("Type to search and press Enter").c_str()); } ImGui::SameLine(); if (UIWidgets::Button("A-Z", UIWidgets::ButtonOptions() @@ -1147,7 +1150,7 @@ void PlandomizerDrawHintsWindow() { ImGui::BeginChild("Hints"); if (ImGui::BeginTable("Hints Window", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY)) { - ImGui::TableSetupColumn("Hint Entries"); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Hint Entries").c_str()); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); @@ -1155,14 +1158,14 @@ void PlandomizerDrawHintsWindow() { ImGui::PushID(index); ImGui::TableNextColumn(); ImGui::SeparatorText(hintData.hintName.c_str()); - ImGui::Text("Current Hint: "); + ImGui::Text("%s", LanguageManager::Instance().GetString("Current Hint: ").c_str()); ImGui::SameLine(); ImGui::TextWrapped("%s", hintData.hintText.c_str()); if (spoilerHintData.size() > 0) { hintInputText = plandoHintData[index].hintText.c_str(); } - ImGui::Text("New Hint: "); + ImGui::Text("%s", LanguageManager::Instance().GetString("New Hint: ").c_str()); ImGui::SameLine(); if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions() .Color(THEME_COLOR) @@ -1193,11 +1196,11 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) { uint32_t index = 0; ImGui::BeginChild("Locations"); if (ImGui::BeginTable("Locations Window", 4, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY)) { - ImGui::TableSetupColumn("Spoiler Log Check Name", ImGuiTableColumnFlags_WidthFixed, 250.0f); - ImGui::TableSetupColumn("Spoiler Log Reward", ImGuiTableColumnFlags_WidthFixed, 190.0f); - ImGui::TableSetupColumn("New Reward", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Spoiler Log Check Name").c_str(), ImGuiTableColumnFlags_WidthFixed, 250.0f); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Spoiler Log Reward").c_str(), ImGuiTableColumnFlags_WidthFixed, 190.0f); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("New Reward").c_str(), ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); - ImGui::TableSetupColumn("Additional Options"); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Additional Options").c_str()); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); @@ -1223,7 +1226,7 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) { } else if (spoilerData.shopPrice != -1) { ImGui::TableNextColumn(); ImGui::BeginTable("Shops", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInner); - ImGui::TableSetupColumn("Shop Price"); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Shop Price").c_str()); ImGui::TableHeadersRow(); ImGui::TableNextColumn(); PlandomizerDrawShopSlider(index); @@ -1243,12 +1246,12 @@ void PlandomizerDrawSpoilerTable() { ImGui::BeginChild("Main"); UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Check Tabs")) { - if (ImGui::BeginTabItem("Gossip Stones")) { + if (ImGui::BeginTabItem(LanguageManager::Instance().GetString("Gossip Stones").c_str())) { getTabID = TAB_HINTS; PlandomizerDrawHintsWindow(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Locations")) { + if (ImGui::BeginTabItem(LanguageManager::Instance().GetString("Locations").c_str())) { getTabID = TAB_LOCATIONS; PlandomizerDrawLocationsWindow(selectedArea); ImGui::EndTabItem(); diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp new file mode 100644 index 000000000..a10d5962e --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -0,0 +1,2336 @@ +#include "randomizer_check_tracker.h" +#include "randomizer_entrance_tracker.h" +#include "randomizer_item_tracker.h" +#include "randomizerTypes.h" +#include "soh/OTRGlobals.h" +#include "soh/cvar_prefixes.h" +#include "soh/SaveManager.h" +#include "soh/ResourceManagerHelpers.h" +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/LanguageManager.h" +#include "dungeon.h" +#include "entrance.h" +#include "location_access.h" +#include "3drando/fill.hpp" +#include "soh/Enhancements/debugger/performanceTimer.h" + +using namespace SohGui; + +#include +#include +#include +#include +#include +#include +#include "location.h" +#include "item_location.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "z64item.h" +#include "fishsanity.h" + +extern "C" { +#include "variables.h" +#include "functions.h" +#include "macros.h" +extern PlayState* gPlayState; +} +extern "C" GetItemEntry ItemTable_RetrieveEntry(s16 modIndex, s16 getItemID); + +extern std::vector dungeonRewardStones; +extern std::vector dungeonRewardMedallions; +extern std::vector songItems; +extern std::vector equipmentItems; + +using json = nlohmann::json; +using namespace UIWidgets; + +namespace CheckTracker { +static WidgetInfo backgroundColorWidget; +static WidgetInfo windowTypeWidget; +static WidgetInfo dungeonSpoilerWidget; +static WidgetInfo hideUnshuffledShopWidget; +static WidgetInfo showGSWidget; +static WidgetInfo showLogicWidget; +static WidgetInfo checkAvailabilityWidget; + +// settings +bool showShops; +bool showOverworldTokens; +bool showDungeonTokens; +bool showBeans; +bool showScrubs; +bool showMajorScrubs; +bool showMerchants; +bool showSongs; +bool showBeehives; +bool showCows; +bool showOverworldFreestanding; +bool showDungeonFreestanding; +bool showAdultTrade; +bool showKokiriSword; +bool showMasterSword; +bool showHyruleLoach; +bool showWeirdEgg; +bool showGerudoCard; +bool showOverworldPots; +bool showDungeonPots; +bool showOverworldGrass; +bool showDungeonGrass; +bool showOverworldCrates; +bool showDungeonCrates; +bool showTrees; +bool showBushes; +bool showFrogSongRupees; +bool showFountainFairies; +bool showStoneFairies; +bool showBeanFairies; +bool showSongFairies; +bool showStartingMapsCompasses; +bool showKeysanity; +bool showGerudoFortressKeys; +bool showBossKeysanity; +bool showGanonBossKey; +bool showOcarinas; +bool show100SkullReward; +bool showLinksPocket; +bool fortressFast; +bool fortressNormal; + +u8 fishsanityMode; +u8 fishsanityPondCount; +bool fishsanityAgeSplit; + +// persistent during gameplay +bool initialized; +bool doAreaScroll; +bool previousShowHidden = false; +bool hideShopUnshuffledChecks = false; +bool alwaysShowGS = false; + +static bool presetLoaded = false; +static ImVec2 presetPos; +static ImVec2 presetSize; + +std::map startingShopItem = { + { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 }, + { SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 }, + { SCENE_POTION_SHOP_MARKET, RC_MARKET_POTION_SHOP_ITEM_1 }, + { SCENE_BOMBCHU_SHOP, RC_MARKET_BOMBCHU_SHOP_ITEM_1 }, + { SCENE_POTION_SHOP_KAKARIKO, RC_KAK_POTION_SHOP_ITEM_1 }, + { SCENE_ZORA_SHOP, RC_ZD_SHOP_ITEM_1 }, + { SCENE_GORON_SHOP, RC_GC_SHOP_ITEM_1 }, +}; + +std::map DungeonRCAreasBySceneID = { + { SCENE_DEKU_TREE, RCAREA_DEKU_TREE }, + { SCENE_DODONGOS_CAVERN, RCAREA_DODONGOS_CAVERN }, + { SCENE_JABU_JABU, RCAREA_JABU_JABUS_BELLY }, + { SCENE_FOREST_TEMPLE, RCAREA_FOREST_TEMPLE }, + { SCENE_FIRE_TEMPLE, RCAREA_FIRE_TEMPLE }, + { SCENE_WATER_TEMPLE, RCAREA_WATER_TEMPLE }, + { SCENE_SHADOW_TEMPLE, RCAREA_SHADOW_TEMPLE }, + { SCENE_SPIRIT_TEMPLE, RCAREA_SPIRIT_TEMPLE }, + { SCENE_BOTTOM_OF_THE_WELL, RCAREA_BOTTOM_OF_THE_WELL }, + { SCENE_ICE_CAVERN, RCAREA_ICE_CAVERN }, + { SCENE_GERUDO_TRAINING_GROUND, RCAREA_GERUDO_TRAINING_GROUND }, + { SCENE_INSIDE_GANONS_CASTLE, RCAREA_GANONS_CASTLE }, +}; + +// Dungeon entrances with obvious visual differences between MQ and vanilla qualifying as spoiling on sight +std::vector spoilingEntrances = { + ENTR_DEKU_TREE_ENTRANCE, + ENTR_DODONGOS_CAVERN_BOSS_DOOR, + ENTR_JABU_JABU_ENTRANCE, + ENTR_JABU_JABU_BOSS_DOOR, + ENTR_FOREST_TEMPLE_ENTRANCE, + ENTR_FIRE_TEMPLE_ENTRANCE, + ENTR_FIRE_TEMPLE_BOSS_DOOR, + ENTR_WATER_TEMPLE_BOSS_DOOR, + ENTR_SPIRIT_TEMPLE_ENTRANCE, + ENTR_SHADOW_TEMPLE_BOSS_DOOR, + ENTR_ICE_CAVERN_ENTRANCE, + ENTR_GERUDO_TRAINING_GROUND_ENTRANCE, + ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, +}; + +std::map> checksByArea; +bool areasFullyChecked[RCAREA_INVALID]; +u32 areasSpoiled = 0; +bool showVOrMQ; +s16 areaChecksGotten[RCAREA_INVALID]; //| "Kokiri Forest (4/9)" +s16 areaChecksAvailable[RCAREA_INVALID]; +s16 areaCheckTotals[RCAREA_INVALID]; +uint16_t totalChecks = 0; +uint16_t totalChecksAvailable = 0; +uint16_t totalChecksGotten = 0; +bool optCollapseAll; // A bool that will collapse all checks once +bool optExpandAll; // A bool that will expand all checks once +RandomizerCheck lastLocationChecked = RC_UNKNOWN_CHECK; +RandomizerCheckArea previousArea = RCAREA_INVALID; +RandomizerCheckArea currentArea = RCAREA_INVALID; +OSContPad* trackerButtonsPressed; +std::unordered_map checkNameOverrides; + +bool ShouldShowCheck(RandomizerCheck rc); +bool UpdateFilters(); +bool CompareChecks(RandomizerCheck, RandomizerCheck); +bool CheckByArea(RandomizerCheckArea); +void DrawLocation(RandomizerCheck); +void LoadSettings(); +void RainbowTick(); +void UpdateAreas(RandomizerCheckArea area); +void UpdateInventoryChecks(); +void UpdateOrdering(RandomizerCheckArea); +int sectionId; + +bool hideUnchecked = false; +bool hideScummed = false; +bool hideSeen = false; +bool hideSkipped = false; +bool hideSaved = false; +bool hideCollected = false; +bool showHidden = true; +bool mystery = false; +bool showLogicTooltip = false; +bool enableAvailableChecks = false; +bool onlyShowAvailable = false; + +SceneID DungeonSceneLookupByArea(RandomizerCheckArea area) { + switch (area) { + case RCAREA_DEKU_TREE: + return SCENE_DEKU_TREE; + case RCAREA_DODONGOS_CAVERN: + return SCENE_DODONGOS_CAVERN; + case RCAREA_JABU_JABUS_BELLY: + return SCENE_JABU_JABU; + case RCAREA_FOREST_TEMPLE: + return SCENE_FOREST_TEMPLE; + case RCAREA_FIRE_TEMPLE: + return SCENE_FIRE_TEMPLE; + case RCAREA_WATER_TEMPLE: + return SCENE_WATER_TEMPLE; + case RCAREA_SPIRIT_TEMPLE: + return SCENE_SPIRIT_TEMPLE; + case RCAREA_SHADOW_TEMPLE: + return SCENE_SHADOW_TEMPLE; + case RCAREA_BOTTOM_OF_THE_WELL: + return SCENE_BOTTOM_OF_THE_WELL; + case RCAREA_ICE_CAVERN: + return SCENE_ICE_CAVERN; + case RCAREA_GERUDO_TRAINING_GROUND: + return SCENE_GERUDO_TRAINING_GROUND; + case RCAREA_GANONS_CASTLE: + return SCENE_INSIDE_GANONS_CASTLE; + default: + return SCENE_ID_MAX; + } +} + +const Color_RGBA8 Color_Main_Default = { 255, 255, 255, 255 }; // White +const Color_RGBA8 Color_Area_Incomplete_Extra_Default = { 255, 255, 255, 255 }; // White +const Color_RGBA8 Color_Area_Complete_Extra_Default = { 255, 255, 255, 255 }; // White +const Color_RGBA8 Color_Unchecked_Extra_Default = { 255, 255, 255, 255 }; // White +const Color_RGBA8 Color_Skipped_Main_Default = { 160, 160, 160, 255 }; // Grey +const Color_RGBA8 Color_Skipped_Extra_Default = { 160, 160, 160, 255 }; // Grey +const Color_RGBA8 Color_Seen_Extra_Default = { 255, 255, 255, 255 }; // TODO +const Color_RGBA8 Color_Hinted_Extra_Default = { 255, 255, 255, 255 }; // TODO +const Color_RGBA8 Color_Collected_Extra_Default = { 242, 101, 34, 255 }; // Orange +const Color_RGBA8 Color_Scummed_Extra_Default = { 0, 174, 239, 255 }; // Blue +const Color_RGBA8 Color_Saved_Extra_Default = { 0, 185, 0, 255 }; // Green + +Color_RGBA8 Color_Background = { 0, 0, 0, 255 }; + +Color_RGBA8 Color_Area_Incomplete_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Incomplete_Extra = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Complete_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Complete_Extra = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Unchecked_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Unchecked_Extra = { 255, 255, 255, 255 }; // Useless +Color_RGBA8 Color_Skipped_Main = { 160, 160, 160, 255 }; // Grey +Color_RGBA8 Color_Skipped_Extra = { 160, 160, 160, 255 }; // Grey +Color_RGBA8 Color_Seen_Main = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Seen_Extra = { 160, 160, 160, 255 }; // TODO +Color_RGBA8 Color_Hinted_Main = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Hinted_Extra = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Collected_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Collected_Extra = { 242, 101, 34, 255 }; // Orange +Color_RGBA8 Color_Scummed_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Scummed_Extra = { 0, 174, 239, 255 }; // Blue +Color_RGBA8 Color_Saved_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Saved_Extra = { 0, 185, 0, 255 }; // Green + +static ImGuiTextFilter checkSearch; +static bool recalculateAvailable = false; +static RandomizerRegion availableChecksStartingRegion = RR_ROOT; +static RandoAgeTime availableChecksStartingAgeTime = RAT_NONE; +static int16_t previousEntrance = 0; +std::array filterAreasHidden = { 0 }; +std::array filterChecksHidden = { 0 }; + +void TrySetAreas() { + if (checksByArea.empty()) { + for (int i = RCAREA_KOKIRI_FOREST; i < RCAREA_INVALID; i++) { + checksByArea.emplace(static_cast(i), std::vector()); + } + } +} + +void CalculateTotals() { + totalChecks = 0; + totalChecksAvailable = 0; + totalChecksGotten = 0; + + for (uint8_t i = 0; i < RCAREA_INVALID; i++) { + totalChecks += areaCheckTotals[i]; + totalChecksAvailable += areaChecksAvailable[i]; + totalChecksGotten += areaChecksGotten[i]; + } +} + +uint16_t GetTotalChecks() { + return totalChecks; +} + +uint16_t GetTotalChecksGotten() { + return totalChecksGotten; +} + +bool IsCheckHidden(RandomizerCheck rc) { + Rando::ItemLocation* itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + RandomizerCheckStatus status = itemLocation->GetCheckStatus(); + bool available = itemLocation->IsAvailable(); + bool skipped = itemLocation->GetIsSkipped(); + bool obtained = itemLocation->HasObtained(); + bool seen = status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED; + bool scummed = status == RCSHOW_SCUMMED; + bool unchecked = status == RCSHOW_UNCHECKED; + + return !showHidden && + ((skipped && hideSkipped) || (seen && hideSeen) || (scummed && hideScummed) || (unchecked && hideUnchecked)); +} + +void RecalculateAreaTotals(RandomizerCheckArea rcArea) { + areaChecksGotten[rcArea] = 0; + areaChecksAvailable[rcArea] = 0; + areaCheckTotals[rcArea] = 0; + for (auto rc : checksByArea.at(rcArea)) { + if (!IsVisibleInCheckTracker(rc)) { + continue; + } + areaCheckTotals[rcArea]++; + + Rando::ItemLocation* itemLoc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + + if (itemLoc->GetIsSkipped() || itemLoc->HasObtained()) { + areaChecksGotten[rcArea]++; + } + + if (itemLoc->IsAvailable() && !IsCheckHidden(rc)) { + areaChecksAvailable[rcArea]++; + } + } + CalculateTotals(); +} + +std::map MapRGtoRandomizerCheckArea = { + { RG_DEKU_TREE_MAP, RCAREA_DEKU_TREE }, + { RG_DODONGOS_CAVERN_MAP, RCAREA_DODONGOS_CAVERN }, + { RG_JABU_JABUS_BELLY_MAP, RCAREA_JABU_JABUS_BELLY }, + { RG_FOREST_TEMPLE_MAP, RCAREA_FOREST_TEMPLE }, + { RG_FIRE_TEMPLE_MAP, RCAREA_FIRE_TEMPLE }, + { RG_WATER_TEMPLE_MAP, RCAREA_WATER_TEMPLE }, + { RG_SPIRIT_TEMPLE_MAP, RCAREA_SPIRIT_TEMPLE }, + { RG_SHADOW_TEMPLE_MAP, RCAREA_SHADOW_TEMPLE }, + { RG_BOTTOM_OF_THE_WELL_MAP, RCAREA_BOTTOM_OF_THE_WELL }, + { RG_ICE_CAVERN_MAP, RCAREA_ICE_CAVERN } +}; + +void SpoilAreaFromCheck(RandomizerCheck rc) { + Rando::Location* loc = Rando::StaticData::GetLocation(rc); + Rando::ItemLocation* itemLoc = Rando::Context::GetInstance()->GetItemLocation(rc); + if (itemLoc->GetPlacedItem().GetItemType() == ItemType::ITEMTYPE_MAP) { + RandomizerCheckArea area = MapRGtoRandomizerCheckArea[itemLoc->GetPlacedRandomizerGet()]; + if (!IsAreaSpoiled(area)) { + SetAreaSpoiled(area); + } + } + if (!IsAreaSpoiled(loc->GetArea())) { + SetAreaSpoiled(loc->GetArea()); + } +} + +void RecalculateAllAreaTotals() { + for (auto& [rcArea, checks] : checksByArea) { + if (rcArea == RCAREA_INVALID) { + return; + } + RecalculateAreaTotals(rcArea); + } +} + +void SetCheckCollected(RandomizerCheck rc) { + OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetCheckStatus(RCSHOW_COLLECTED); + Rando::Location* loc = Rando::StaticData::GetLocation(rc); + if (IsVisibleInCheckTracker(rc)) { + if (!OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetIsSkipped()) { + areaChecksGotten[loc->GetArea()]++; + areaChecksAvailable[loc->GetArea()]--; + } else { + OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(false); + } + } + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + + if (!IsAreaSpoiled(loc->GetArea())) { + SetAreaSpoiled(loc->GetArea()); + } + + doAreaScroll = true; + UpdateOrdering(loc->GetArea()); + UpdateInventoryChecks(); +} + +bool IsAreaScene(SceneID sceneNum) { + switch (sceneNum) { + case SCENE_HYRULE_FIELD: + case SCENE_KAKARIKO_VILLAGE: + case SCENE_GRAVEYARD: + case SCENE_ZORAS_RIVER: + case SCENE_KOKIRI_FOREST: + case SCENE_SACRED_FOREST_MEADOW: + case SCENE_LAKE_HYLIA: + case SCENE_ZORAS_DOMAIN: + case SCENE_ZORAS_FOUNTAIN: + case SCENE_GERUDO_VALLEY: + case SCENE_LOST_WOODS: + case SCENE_DESERT_COLOSSUS: + case SCENE_GERUDOS_FORTRESS: + case SCENE_HAUNTED_WASTELAND: + case SCENE_HYRULE_CASTLE: + case SCENE_DEATH_MOUNTAIN_TRAIL: + case SCENE_DEATH_MOUNTAIN_CRATER: + case SCENE_GORON_CITY: + case SCENE_LON_LON_RANCH: + case SCENE_DEKU_TREE: + case SCENE_DODONGOS_CAVERN: + case SCENE_JABU_JABU: + case SCENE_FOREST_TEMPLE: + case SCENE_FIRE_TEMPLE: + case SCENE_WATER_TEMPLE: + case SCENE_SPIRIT_TEMPLE: + case SCENE_SHADOW_TEMPLE: + case SCENE_BOTTOM_OF_THE_WELL: + case SCENE_ICE_CAVERN: + case SCENE_GERUDO_TRAINING_GROUND: + case SCENE_GANONS_TOWER: + case SCENE_INSIDE_GANONS_CASTLE: + case SCENE_BACK_ALLEY_DAY: + case SCENE_BACK_ALLEY_NIGHT: + case SCENE_MARKET_DAY: + case SCENE_MARKET_NIGHT: + case SCENE_MARKET_RUINS: + return true; + default: + return false; + } +} + +RandomizerCheckArea AreaFromEntranceGroup[] = { + RCAREA_INVALID, RCAREA_KOKIRI_FOREST, RCAREA_LOST_WOODS, RCAREA_SACRED_FOREST_MEADOW, + RCAREA_KAKARIKO_VILLAGE, RCAREA_GRAVEYARD, RCAREA_DEATH_MOUNTAIN_TRAIL, RCAREA_DEATH_MOUNTAIN_CRATER, + RCAREA_GORON_CITY, RCAREA_ZORAS_RIVER, RCAREA_ZORAS_DOMAIN, RCAREA_ZORAS_FOUNTAIN, + RCAREA_HYRULE_FIELD, RCAREA_LON_LON_RANCH, RCAREA_LAKE_HYLIA, RCAREA_GERUDO_VALLEY, + RCAREA_GERUDO_FORTRESS, RCAREA_WASTELAND, RCAREA_DESERT_COLOSSUS, RCAREA_MARKET, + RCAREA_HYRULE_CASTLE, +}; + +RandomizerCheckArea GetCheckArea() { + auto scene = static_cast(gPlayState->sceneNum); + bool grottoScene = (scene == SCENE_GROTTOS || scene == SCENE_FAIRYS_FOUNTAIN); + const EntranceData* ent = EntranceTracker::GetEntranceData( + grottoScene ? ENTRANCE_GROTTO_EXIT_START + EntranceTracker::GetCurrentGrottoId() : gSaveContext.entranceIndex); + RandomizerCheckArea area = RCAREA_INVALID; + if (ent != nullptr && !IsAreaScene(scene) && ent->type != ENTRANCE_TYPE_DUNGEON) { + if (ent->source == "Desert Colossus" || ent->destination == "Desert Colossus") { + area = RCAREA_DESERT_COLOSSUS; + } else { + area = AreaFromEntranceGroup[ent->dstGroup]; + } + } + if (area == RCAREA_INVALID) { + if (grottoScene && (EntranceTracker::GetCurrentGrottoId() == -1) && + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) == RO_GENERIC_OFF)) { + area = previousArea; + } else { + area = RandomizerCheckObjects::GetRCAreaBySceneID(scene); + } + } + return area; +} + +bool vector_contains_scene(std::vector vec, const int16_t scene) { + return std::any_of(vec.begin(), vec.end(), [&](const auto& x) { return x == scene; }); +} + +std::vector skipScenes = { + SCENE_GANON_BOSS, + SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR, + SCENE_GANON_BOSS, + SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, + SCENE_GANONS_TOWER_COLLAPSE_INTERIOR, +}; + +void ClearAreaChecksAndTotals() { + for (auto& [rcArea, vec] : checksByArea) { + vec.clear(); + areaChecksGotten[rcArea] = 0; + areaChecksAvailable[rcArea] = 0; + areaCheckTotals[rcArea] = 0; + } + totalChecks = 0; + totalChecksGotten = 0; + totalChecksAvailable = 0; +} + +void SetShopSeen(uint32_t sceneNum, bool prices) { + RandomizerCheck start = startingShopItem.find(sceneNum)->second; + if (sceneNum == SCENE_POTION_SHOP_KAKARIKO && !LINK_IS_ADULT) { + return; + } + if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && sceneNum == SCENE_BAZAAR) { + start = RC_KAK_BAZAAR_ITEM_1; + } + bool statusChanged = false; + for (int i = start; i < start + 8; i++) { + if (OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->GetCheckStatus() == RCSHOW_UNCHECKED) { + OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetCheckStatus(RCSHOW_SEEN); + statusChanged = true; + } + } + if (statusChanged) { + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + } +} + +void CheckTrackerLoadGame(int32_t fileNum) { + if (IS_BOSS_RUSH) { + return; + } + LoadSettings(); + TrySetAreas(); + for (auto& entry : Rando::StaticData::GetLocationTable()) { + RandomizerCheck rc = entry.GetRandomizerCheck(); + if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || rc == RC_LINKS_POCKET || + !Rando::StaticData::GetLocation(rc) != RC_UNKNOWN_CHECK) { + continue; + } + + Rando::Location* entry2 = Rando::StaticData::GetLocation(rc); + Rando::ItemLocation* loc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + + checksByArea.find(entry2->GetArea())->second.push_back(entry2->GetRandomizerCheck()); + if (IsVisibleInCheckTracker(entry2->GetRandomizerCheck())) { + areaCheckTotals[entry2->GetArea()]++; + if (loc->GetCheckStatus() == RCSHOW_SAVED || loc->GetIsSkipped()) { + areaChecksGotten[entry2->GetArea()]++; + } + if (loc->IsAvailable()) { + areaChecksAvailable[entry2->GetArea()]++; + } + } + + if (areaChecksGotten[entry2->GetArea()] != 0 || RandomizerCheckObjects::AreaIsOverworld(entry2->GetArea()) || + loc->GetCheckStatus() == RCSHOW_SCUMMED) { + areasSpoiled |= (1 << entry2->GetArea()); + } + + // Create check name overrides for child pond fish if age split is disabled + if (fishsanityMode != RO_FISHSANITY_OFF && fishsanityMode != RO_FISHSANITY_OVERWORLD && + entry.GetRCType() == RCTYPE_FISH && entry.GetScene() == SCENE_FISHING_POND && + entry.GetActorParams() != 116 && !fishsanityAgeSplit) { + if (entry.GetShortName().starts_with("Child")) { + checkNameOverrides[rc] = entry.GetShortName().substr(6); + } + } + } + for (int i = RCAREA_KOKIRI_FOREST; i < RCAREA_INVALID; i++) { + if (!IsAreaSpoiled(static_cast(i)) && + (RandomizerCheckObjects::AreaIsOverworld(static_cast(i)) || !IS_RANDO || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == RO_MQ_DUNGEONS_NONE || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == + RO_MQ_DUNGEONS_SELECTION && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue( + static_cast(RSK_MQ_DEKU_TREE + (i - RCAREA_DEKU_TREE))) != RO_MQ_SET_RANDOM) || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_SET) == RO_GENERIC_ON && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue( + static_cast(RSK_MQ_DEKU_TREE + (i - RCAREA_DEKU_TREE))) != RO_MQ_SET_RANDOM) || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == + RO_MQ_DUNGEONS_SET_NUMBER && + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == MAX_MQ_DUNGEON_COUNT || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 0)))) { + SetAreaSpoiled(static_cast(i)); + } + } + if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && + IS_RANDO) { + uint8_t startingAge = OTRGlobals::Instance->gRandoContext->GetOption(RSK_SELECTED_STARTING_AGE).Get(); + RandomizerCheckArea startingArea; + switch (startingAge) { + case RO_AGE_CHILD: + startingArea = RCAREA_KOKIRI_FOREST; + break; + case RO_AGE_ADULT: + startingArea = RCAREA_MARKET; + break; + default: + startingArea = RCAREA_KOKIRI_FOREST; + break; + } + + checksByArea.find(startingArea)->second.push_back(RC_LINKS_POCKET); + areaChecksGotten[startingArea]++; + areaCheckTotals[startingArea]++; + } + + showVOrMQ = + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == + RO_MQ_DUNGEONS_RANDOM_NUMBER || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == RO_MQ_DUNGEONS_SET_NUMBER && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < MAX_MQ_DUNGEON_COUNT)); + initialized = true; + UpdateAllOrdering(); + UpdateInventoryChecks(); + UpdateFilters(); + + RegionTable_Init(); + + Rando::Context::GetInstance()->GetEntranceShuffler()->ApplyEntranceOverrides(); + + recalculateAvailable = true; +} + +void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) { + if (gPlayState->sceneNum == SCENE_HAPPY_MASK_SHOP) { // Happy Mask Shop is not used in rando, so is not tracked + return; + } + + auto slot = startingShopItem.find(gPlayState->sceneNum)->second + cursorSlot; + if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && gPlayState->sceneNum == SCENE_BAZAAR) { + slot = RC_KAK_BAZAAR_ITEM_1 + cursorSlot; + } + auto status = OTRGlobals::Instance->gRandoContext->GetItemLocation(slot)->GetCheckStatus(); + if (status == RCSHOW_SEEN) { + OTRGlobals::Instance->gRandoContext->GetItemLocation(slot)->SetCheckStatus(RCSHOW_IDENTIFIED); + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + RecalculateAvailableChecks(); + } +} + +void CheckTrackerTransition(uint32_t sceneNum) { + if (!GameInteractor::IsSaveLoaded()) { + return; + } + doAreaScroll = true; + previousArea = currentArea; + currentArea = GetCheckArea(); + switch (sceneNum) { + case SCENE_KOKIRI_SHOP: + case SCENE_BAZAAR: + case SCENE_POTION_SHOP_MARKET: + case SCENE_BOMBCHU_SHOP: + case SCENE_POTION_SHOP_KAKARIKO: + case SCENE_GORON_SHOP: + case SCENE_ZORA_SHOP: + SetShopSeen(sceneNum, false); + break; + } + if (!IsAreaSpoiled(currentArea) && (RandomizerCheckObjects::AreaIsOverworld(currentArea) || + std::find(spoilingEntrances.begin(), spoilingEntrances.end(), + gPlayState->nextEntranceIndex) != spoilingEntrances.end())) { + SetAreaSpoiled(currentArea); + } +} + +void CheckTrackerItemReceive(GetItemEntry giEntry) { + if (!GameInteractor::IsSaveLoaded() || vector_contains_scene(skipScenes, gPlayState->sceneNum)) { + return; + } + auto scene = static_cast(gPlayState->sceneNum); + // Vanilla special item checks + if (!IS_RANDO) { + if (giEntry.itemId == ITEM_SHIELD_DEKU) { + SetCheckCollected(RC_KF_SHOP_ITEM_1); + return; + } else if (giEntry.itemId == ITEM_KOKIRI_EMERALD) { + SetCheckCollected(RC_QUEEN_GOHMA); + return; + } else if (giEntry.itemId == ITEM_GORON_RUBY) { + SetCheckCollected(RC_KING_DODONGO); + return; + } else if (giEntry.itemId == ITEM_ZORA_SAPPHIRE) { + SetCheckCollected(RC_BARINADE); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_FOREST) { + SetCheckCollected(RC_PHANTOM_GANON); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_FIRE) { + SetCheckCollected(RC_VOLVAGIA); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_WATER) { + SetCheckCollected(RC_MORPHA); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_SHADOW) { + SetCheckCollected(RC_BONGO_BONGO); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_SPIRIT) { + SetCheckCollected(RC_TWINROVA); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_LIGHT) { + SetCheckCollected(RC_GIFT_FROM_RAURU); + return; + } else if (giEntry.itemId == ITEM_SONG_EPONA) { + SetCheckCollected(RC_SONG_FROM_MALON); + return; + } else if (giEntry.itemId == ITEM_SONG_SARIA) { + SetCheckCollected(RC_SONG_FROM_SARIA); + return; + } else if (giEntry.itemId == ITEM_BEAN) { + SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN); + return; + } else if (giEntry.itemId == ITEM_BRACELET) { + SetCheckCollected(RC_GC_DARUNIAS_JOY); + return; + } /* else if (giEntry.itemId == ITEM_SONG_SUN) { + SetCheckCollected(RC_SONG_FROM_ROYAL_FAMILYS_TOMB); + return; + } else if (giEntry.itemId == ITEM_SONG_TIME) { + SetCheckCollected(RC_SONG_FROM_OCARINA_OF_TIME); + return; + } else if (giEntry.itemId == ITEM_SONG_STORMS) { + SetCheckCollected(RC_SONG_FROM_WINDMILL); + return; + } else if (giEntry.itemId == ITEM_SONG_MINUET) { + SetCheckCollected(RC_SHEIK_IN_FOREST); + return; + } else if (giEntry.itemId == ITEM_SONG_BOLERO) { + SetCheckCollected(RC_SHEIK_IN_CRATER); + return; + } else if (giEntry.itemId == ITEM_SONG_SERENADE) { + SetCheckCollected(RC_SHEIK_IN_ICE_CAVERN); + return; + } else if (giEntry.itemId == ITEM_SONG_NOCTURNE) { + SetCheckCollected(RC_SHEIK_IN_KAKARIKO); + return; + } else if (giEntry.itemId == ITEM_SONG_REQUIEM) { + SetCheckCollected(RC_SHEIK_AT_COLOSSUS); + return; + } else if (giEntry.itemId == ITEM_SONG_PRELUDE) { + SetCheckCollected(RC_SHEIK_AT_TEMPLE); + return; + }*/ + } +} + +void CheckTrackerSceneFlagSet(int16_t sceneNum, int16_t flagType, int32_t flag) { + if (IS_RANDO) { + return; + } + + if (flagType != FLAG_SCENE_TREASURE && flagType != FLAG_SCENE_COLLECTIBLE) { + return; + } + if (sceneNum == SCENE_GRAVEYARD && flag == 0x19 && + flagType == FLAG_SCENE_COLLECTIBLE) { // Gravedigging tour special case + SetCheckCollected(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR); + return; + } + for (auto& loc : Rando::StaticData::GetLocationTable()) { + if (!IsVisibleInCheckTracker(loc.GetRandomizerCheck())) { + continue; + } + SpoilerCollectionCheckType checkMatchType = flagType == FLAG_SCENE_TREASURE + ? SpoilerCollectionCheckType::SPOILER_CHK_CHEST + : SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE; + Rando::SpoilerCollectionCheck scCheck = loc.GetCollectionCheck(); + if (scCheck.scene == sceneNum && scCheck.flag == flag && scCheck.type == checkMatchType) { + SetCheckCollected(loc.GetRandomizerCheck()); + return; + } + } +} + +void CheckTrackerFlagSet(int16_t flagType, int32_t flag) { + if (IS_RANDO) { + return; + } + + SpoilerCollectionCheckType checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_NONE; + switch (flagType) { + case FLAG_GS_TOKEN: + checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA; + break; + case FLAG_EVENT_CHECK_INF: + if ((flag == EVENTCHKINF_CARPENTERS_FREE(0) || flag == EVENTCHKINF_CARPENTERS_FREE(1) || + flag == EVENTCHKINF_CARPENTERS_FREE(2) || flag == EVENTCHKINF_CARPENTERS_FREE(3)) && + GET_EVENTCHKINF_CARPENTERS_FREE_ALL()) { + SetCheckCollected(RC_TH_FREED_CARPENTERS); + return; + } + checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_EVENT_CHK_INF; + break; + case FLAG_INF_TABLE: + if (flag == INFTABLE_190) { + SetCheckCollected(RC_GF_HBA_1000_POINTS); + return; + } else if (flag == INFTABLE_11E) { + SetCheckCollected(RC_GC_ROLLING_GORON_AS_CHILD); + return; + } else if (flag == INFTABLE_GORON_CITY_DOORS_UNLOCKED) { + SetCheckCollected(RC_GC_ROLLING_GORON_AS_ADULT); + return; + } else if (flag == INFTABLE_139) { + SetCheckCollected(RC_ZD_KING_ZORA_THAWED); + return; + } else if (flag == INFTABLE_191) { + SetCheckCollected(RC_MARKET_LOST_DOG); + return; + } + if (!IS_RANDO) { + if (flag == INFTABLE_BOUGHT_STICK_UPGRADE) { + SetCheckCollected(RC_LW_DEKU_SCRUB_NEAR_BRIDGE); + return; + } else if (flag == INFTABLE_BOUGHT_NUT_UPGRADE) { + SetCheckCollected(RC_LW_DEKU_SCRUB_GROTTO_FRONT); + return; + } + } + break; + case FLAG_ITEM_GET_INF: + if (!IS_RANDO) { + if (flag == ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE) { + SetCheckCollected(RC_DEKU_THEATER_SKULL_MASK); + return; + } else if (flag == ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE) { + SetCheckCollected(RC_DEKU_THEATER_MASK_OF_TRUTH); + return; + } else if (flag == ITEMGETINF_DEKU_SCRUB_HEART_PIECE) { + SetCheckCollected(RC_HF_DEKU_SCRUB_GROTTO); + return; + } + } + checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_ITEM_GET_INF; + break; + case FLAG_RANDOMIZER_INF: + checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF; + break; + } + if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_NONE) { + return; + } + for (auto& loc : Rando::StaticData::GetLocationTable()) { + if ((!IS_RANDO && ((loc.GetQuest() == RCQUEST_MQ && !IS_MASTER_QUEST) || + (loc.GetQuest() == RCQUEST_VANILLA && IS_MASTER_QUEST))) || + (IS_RANDO && + !(OTRGlobals::Instance->gRandoContext->GetDungeons()->GetDungeonFromScene(loc.GetScene()) == nullptr) && + ((OTRGlobals::Instance->gRandoContext->GetDungeons()->GetDungeonFromScene(loc.GetScene())->IsMQ() && + loc.GetQuest() == RCQUEST_VANILLA) || + OTRGlobals::Instance->gRandoContext->GetDungeons()->GetDungeonFromScene(loc.GetScene())->IsVanilla() && + loc.GetQuest() == RCQUEST_MQ))) { + continue; + } + Rando::SpoilerCollectionCheck scCheck = loc.GetCollectionCheck(); + SpoilerCollectionCheckType scCheckType = scCheck.type; + if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF && + scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF) { + if (flag == OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(loc.GetRandomizerCheck())) { + SetCheckCollected(loc.GetRandomizerCheck()); + return; + } + continue; + } + int16_t checkFlag = scCheck.flag; + if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA) { + checkFlag = loc.GetActorParams(); + } + if (checkFlag == flag && scCheck.type == checkMatchType) { + SetCheckCollected(loc.GetRandomizerCheck()); + return; + } + } +} + +void InitTrackerData(bool isDebug) { + TrySetAreas(); + areasSpoiled = 0; +} + +void SaveTrackerData(SaveContext* saveContext, int sectionID, bool fullSave) { + bool updateOrdering = false; + std::vector checkCount; + for (int i = RC_UNKNOWN_CHECK; i < RC_MAX; i++) { + if (OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->GetCheckStatus() != RCSHOW_UNCHECKED || + OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->GetIsSkipped()) + checkCount.push_back(static_cast(i)); + } + SaveManager::Instance->SaveArray("checkStatus", checkCount.size(), [&](size_t i) { + RandomizerCheck check = checkCount.at(i); + RandomizerCheckStatus savedStatus = + OTRGlobals::Instance->gRandoContext->GetItemLocation(check)->GetCheckStatus(); + bool isSkipped = OTRGlobals::Instance->gRandoContext->GetItemLocation(check)->GetIsSkipped(); + if (savedStatus == RCSHOW_COLLECTED) { + if (fullSave) { + OTRGlobals::Instance->gRandoContext->GetItemLocation(check)->SetCheckStatus(RCSHOW_SAVED); + savedStatus = RCSHOW_SAVED; + updateOrdering = true; + } else { + savedStatus = RCSHOW_SCUMMED; + } + } + if (savedStatus != RCSHOW_UNCHECKED || isSkipped) { + SaveManager::Instance->SaveStruct("", [&]() { + SaveManager::Instance->SaveData("randomizerCheck", check); + SaveManager::Instance->SaveData("status", savedStatus); + SaveManager::Instance->SaveData("skipped", isSkipped); + }); + } + }); + SaveManager::Instance->SaveData("areasSpoiled", areasSpoiled); + if (updateOrdering) { + UpdateAllOrdering(); + UpdateAllAreas(); + } +} + +void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) { + SaveTrackerData(saveContext, sectionID, fullSave); + if (fullSave) { + recalculateAvailable = true; + } +} + +void LoadFile() { + SaveManager::Instance->LoadArray("checkStatus", RC_MAX, [](size_t i) { + SaveManager::Instance->LoadStruct("", [&]() { + RandomizerCheckStatus status; + bool skipped; + RandomizerCheck rc; + SaveManager::Instance->LoadData("randomizerCheck", rc, RC_UNKNOWN_CHECK); + SaveManager::Instance->LoadData("status", status, RCSHOW_UNCHECKED); + SaveManager::Instance->LoadData("skipped", skipped, false); + OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetCheckStatus(status); + OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(skipped); + }); + }); + SaveManager::Instance->LoadData("areasSpoiled", areasSpoiled, (uint32_t)0); + UpdateAllOrdering(); + UpdateAllAreas(); +} + +void Teardown() { + initialized = false; + ClearAreaChecksAndTotals(); + checksByArea.clear(); + areasSpoiled = 0; + filterAreasHidden = { 0 }; + filterChecksHidden = { 0 }; + + lastLocationChecked = RC_UNKNOWN_CHECK; +} + +bool IsAreaSpoiled(RandomizerCheckArea rcArea) { + return areasSpoiled & (1 << rcArea); +} + +void SetAreaSpoiled(RandomizerCheckArea rcArea) { + areasSpoiled |= (1 << rcArea); + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); +} + +void InternalRecalculateAvailableChecks(RandomizerRegion startingRegion, RandoAgeTime startingAgeTime); + +void CheckTrackerWindow::DrawElement() { + Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor.Value"), Color_Bg_Default); + Color_Area_Incomplete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor.Value"), Color_Main_Default); + Color_Area_Incomplete_Extra = + CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor.Value"), Color_Area_Incomplete_Extra_Default); + Color_Area_Complete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor.Value"), Color_Main_Default); + Color_Area_Complete_Extra = + CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor.Value"), Color_Area_Complete_Extra_Default); + Color_Unchecked_Main = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.MainColor.Value"), Color_Main_Default); + Color_Unchecked_Extra = + CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.ExtraColor.Value"), Color_Unchecked_Extra_Default); + Color_Skipped_Main = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.MainColor.Value"), Color_Main_Default); + Color_Skipped_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.ExtraColor.Value"), Color_Skipped_Extra_Default); + Color_Seen_Main = CVarGetColor(CVAR_TRACKER_CHECK("Seen.MainColor.Value"), Color_Main_Default); + Color_Seen_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Seen.ExtraColor.Value"), Color_Seen_Extra_Default); + Color_Hinted_Main = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.MainColor.Value"), Color_Main_Default); + Color_Hinted_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.ExtraColor.Value"), Color_Hinted_Extra_Default); + Color_Collected_Main = CVarGetColor(CVAR_TRACKER_CHECK("Collected.MainColor.Value"), Color_Main_Default); + Color_Collected_Extra = + CVarGetColor(CVAR_TRACKER_CHECK("Collected.ExtraColor.Value"), Color_Collected_Extra_Default); + Color_Scummed_Main = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.MainColor.Value"), Color_Main_Default); + Color_Scummed_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.ExtraColor.Value"), Color_Scummed_Extra_Default); + Color_Saved_Main = CVarGetColor(CVAR_TRACKER_CHECK("Saved.MainColor.Value"), Color_Main_Default); + Color_Saved_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Saved.ExtraColor.Value"), Color_Saved_Extra_Default); + hideUnchecked = CVarGetInteger(CVAR_TRACKER_CHECK("Unchecked.Hide"), 0); + hideScummed = CVarGetInteger(CVAR_TRACKER_CHECK("Scummed.Hide"), 0); + hideSeen = CVarGetInteger(CVAR_TRACKER_CHECK("Seen.Hide"), 0); + hideSkipped = CVarGetInteger(CVAR_TRACKER_CHECK("Skipped.Hide"), 0); + hideSaved = CVarGetInteger(CVAR_TRACKER_CHECK("Saved.Hide"), 0); + hideCollected = CVarGetInteger(CVAR_TRACKER_CHECK("Collected.Hide"), 0); + showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); + mystery = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0); + showLogicTooltip = CVarGetInteger(CVAR_TRACKER_CHECK("ShowLogic"), 0); + enableAvailableChecks = CVarGetInteger(CVAR_TRACKER_CHECK("EnableAvailableChecks"), 0); + onlyShowAvailable = CVarGetInteger(CVAR_TRACKER_CHECK("OnlyShowAvailable"), 0); + + hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 0); + alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0); + if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { + if (CVarGetInteger(CVAR_TRACKER_CHECK("ShowOnlyPaused"), 0) && + (gPlayState == nullptr || gPlayState->pauseCtx.state == 0)) { + return; + } + + if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { + int comboButton1Mask = buttons[CVarGetInteger(CVAR_TRACKER_CHECK("ComboButton1"), TRACKER_COMBO_BUTTON_L)]; + int comboButton2Mask = buttons[CVarGetInteger(CVAR_TRACKER_CHECK("ComboButton2"), TRACKER_COMBO_BUTTON_R)]; + OSContPad* trackerButtonsPressed = + std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetControlDeck())->GetPads(); + bool comboButtonsHeld = trackerButtonsPressed != nullptr && + trackerButtonsPressed[0].button & comboButton1Mask && + trackerButtonsPressed[0].button & comboButton2Mask; + if (!comboButtonsHeld) { + return; + } + } + } + + if (presetLoaded) { + ImGui::SetNextWindowSize(presetSize); + ImGui::SetNextWindowPos(presetPos); + presetLoaded = false; + } else { + ImGui::SetNextWindowSize(ImVec2(400, 540), ImGuiCond_FirstUseEver); + } + if (Trackers::BeginFloatWindows( + "Check Tracker", mIsVisible, Color_Background, + static_cast(CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW)), + CVarGetInteger(CVAR_TRACKER_CHECK("Draggable"), 1), ImGuiWindowFlags_NoScrollbar)) { + if (!GameInteractor::IsSaveLoaded() || !initialized) { + ImGui::Text(LanguageManager::Instance().GetString("Waiting for file load...").c_str()); // TODO Language + Trackers::EndFloatWindows(); + return; + } + + if (recalculateAvailable) { + recalculateAvailable = false; + InternalRecalculateAvailableChecks(availableChecksStartingRegion, availableChecksStartingAgeTime); + availableChecksStartingRegion = RR_ROOT; + availableChecksStartingAgeTime = RAT_NONE; + } + + // Quick Options +#ifdef __WIIU__ + float headerHeight = 40.0f; +#else + float headerHeight = 20.0f; +#endif + if (!ImGui::BeginTable("Check Tracker", 1, 0)) { + Trackers::EndFloatWindows(); + return; + } + + ImGui::SetWindowFontScale(CVarGetFloat(CVAR_TRACKER_CHECK("FontSize"), 1.0f)); + + ImGui::TableNextRow(0, 0); + ImGui::TableNextColumn(); + if (CVarGetInteger(CVAR_TRACKER_CHECK("HiddenItemsToggleVisible"), 1) && + UIWidgets::CVarCheckbox( + LanguageManager::Instance().GetString("Show Hidden Items").c_str(), CVAR_TRACKER_CHECK("ShowHidden"), + UIWidgets::CheckboxOptions( + { { .tooltip = + LanguageManager::Instance().GetString("When active, items will show hidden checks by default when updated to this state.").c_str() } }) + .Color(THEME_COLOR))) { + doAreaScroll = true; + showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); + RecalculateAllAreaTotals(); + } + if (enableAvailableChecks && CVarGetInteger(CVAR_TRACKER_CHECK("AvailableChecksToggleVisible"), 1)) { + if (UIWidgets::CVarCheckbox( + LanguageManager::Instance().GetString("Only Show Available Checks").c_str(), CVAR_TRACKER_CHECK("OnlyShowAvailable"), + UIWidgets::CheckboxOptions({ { .tooltip = LanguageManager::Instance().GetString("When active, unavailable checks will be hidden.").c_str() } }) + .Color(THEME_COLOR))) { + doAreaScroll = true; + RecalculateAllAreaTotals(); + } + } + if (CVarGetInteger(CVAR_TRACKER_CHECK("ExpandCollapseButtonsVisible"), 0)) { + if (UIWidgets::Button(LanguageManager::Instance().GetString("Expand All").c_str(), UIWidgets::ButtonOptions() + .Color(THEME_COLOR) + .Size({ ImGui::GetContentRegionAvail().x / 2 - 6, 0 }))) { + optCollapseAll = false; + optExpandAll = true; + doAreaScroll = true; + } + ImGui::SameLine(); + if (UIWidgets::Button( + LanguageManager::Instance().GetString("Collapse All").c_str(), + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ ImGui::GetContentRegionAvail().x - 6, 0 }))) { + optExpandAll = false; + optCollapseAll = true; + } + } + UIWidgets::PushStyleCombobox(THEME_COLOR); + if (CVarGetInteger(CVAR_TRACKER_CHECK("SearchInputVisible"), 1)) { + if (checkSearch.Draw("", ImGui::GetContentRegionAvail().x - 6)) { + UpdateFilters(); + } + std::string checkSearchText = ""; + checkSearchText = checkSearch.InputBuf; + checkSearchText.erase(std::remove(checkSearchText.begin(), checkSearchText.end(), ' '), + checkSearchText.end()); + if (checkSearchText.length() < 1) { + ImGui::SameLine(20.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), LanguageManager::Instance().GetString("Search...").c_str()); + } + } + UIWidgets::PopStyleCombobox(); + + if (CVarGetInteger(CVAR_TRACKER_CHECK("CheckTotalsVisible"), 1)) { + std::ostringstream totalChecksSS; + totalChecksSS << ""; + if (enableAvailableChecks) { + totalChecksSS << totalChecksAvailable << " Available / "; + } + totalChecksSS << totalChecksGotten << " Checked / " << totalChecks << " Total"; + ImGui::Text("%s", totalChecksSS.str().c_str()); + } + + bool headerPresent = + CVarGetInteger(CVAR_TRACKER_CHECK("HiddenItemsToggleVisible"), 1) || + (enableAvailableChecks && CVarGetInteger(CVAR_TRACKER_CHECK("AvailableChecksToggleVisible"), 1)) || + CVarGetInteger(CVAR_TRACKER_CHECK("ExpandCollapseButtonsVisible"), 0) || + CVarGetInteger(CVAR_TRACKER_CHECK("SearchInputVisible"), 1) || + CVarGetInteger(CVAR_TRACKER_CHECK("CheckTotalsVisible"), 1); + if (headerPresent) { + ImGui::Separator(); + } + + // Checks Section Lead-in + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (!ImGui::BeginTable("CheckTracker##Checks", 1, ImGuiTableFlags_ScrollY)) { + ImGui::EndTable(); + Trackers::EndFloatWindows(); + return; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + // Prep for loop + RainbowTick(); + bool doDraw = false; + bool thisAreaFullyChecked = false; + bool mqSpoilers = CVarGetInteger(CVAR_TRACKER_CHECK("MQSpoilers"), 0); + bool hideIncomplete = CVarGetInteger(CVAR_TRACKER_CHECK("AreaIncomplete.Hide"), 0); + bool hideComplete = CVarGetInteger(CVAR_TRACKER_CHECK("AreaComplete.Hide"), 0); + bool collapseLogic; + bool doingCollapseOrExpand = optExpandAll || optCollapseAll; + bool isThisAreaSpoiled; + RandomizerCheckArea lastArea = RCAREA_INVALID; + Color_RGBA8 mainColor; + Color_RGBA8 extraColor; + std::string stemp; + + bool shouldHideFilteredAreas = CVarGetInteger(CVAR_TRACKER_CHECK("HideFilteredAreas"), 1); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + for (auto& [rcArea, checks] : checksByArea) { + RandomizerCheckArea thisArea = currentArea; + + thisAreaFullyChecked = (areaChecksGotten[rcArea] == areaCheckTotals[rcArea]); + // Last Area needs to be cleaned up + if (lastArea != RCAREA_INVALID && doDraw) { + UIWidgets::PaddedSeparator(); + } + lastArea = rcArea; + if (previousShowHidden != showHidden) { + previousShowHidden = showHidden; + doAreaScroll = true; + } + if ((shouldHideFilteredAreas && filterAreasHidden[rcArea]) || + (!showHidden && + ((hideComplete && thisAreaFullyChecked) || (hideIncomplete && !thisAreaFullyChecked))) || + (enableAvailableChecks && onlyShowAvailable && areaChecksAvailable[rcArea] == 0)) { + doDraw = false; + } else { + // Get the colour for the area + if (thisAreaFullyChecked) { + mainColor = Color_Area_Complete_Main; + extraColor = Color_Area_Complete_Extra; + } else { + mainColor = Color_Area_Incomplete_Main; + extraColor = Color_Area_Incomplete_Extra; + } + + // Draw the area + collapseLogic = !thisAreaFullyChecked; + if (doingCollapseOrExpand) { + if (optExpandAll) { + collapseLogic = true; + } else if (optCollapseAll) { + collapseLogic = false; + } + } + stemp = RandomizerCheckObjects::GetRCAreaName(rcArea) + "##TreeNode"; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(mainColor.r / 255.0f, mainColor.g / 255.0f, + mainColor.b / 255.0f, mainColor.a / 255.0f)); + if (doingCollapseOrExpand) { + ImGui::SetNextItemOpen(collapseLogic, ImGuiCond_Always); + } else { + ImGui::SetNextItemOpen(!thisAreaFullyChecked, ImGuiCond_Once); + } + doDraw = ImGui::TreeNodeEx(stemp.c_str(), ImGuiTreeNodeFlags_NoTreePushOnOpen); + ImGui::PopStyleColor(); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, + extraColor.b / 255.0f, extraColor.a / 255.0f)); + + isThisAreaSpoiled = IsAreaSpoiled(rcArea) || mqSpoilers; + + if (isThisAreaSpoiled) { + std::ostringstream areaTotalsSS; + std::ostringstream areaTotalsTooltipSS; + + areaTotalsSS << "("; + if (enableAvailableChecks) { + areaTotalsSS << static_cast(areaChecksAvailable[rcArea]) << " / "; + areaTotalsTooltipSS << "Available / "; + } + areaTotalsSS << static_cast(areaChecksGotten[rcArea]) << " / " + << static_cast(areaCheckTotals[rcArea]) << ")"; + areaTotalsTooltipSS << "Checked / Total"; + + if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) { + if (OTRGlobals::Instance->gRandoContext->GetDungeons() + ->GetDungeonFromScene(DungeonSceneLookupByArea(rcArea)) + ->IsMQ()) { + areaTotalsSS << " - MQ"; + } else { + areaTotalsSS << " - Vanilla"; + } + } + + ImGui::Text("%s", areaTotalsSS.str().c_str()); + UIWidgets::Tooltip(areaTotalsTooltipSS.str().c_str()); + } else { + ImGui::Text("???"); + } + + ImGui::PopStyleColor(); + + // Keep areas loaded between transitions + if (thisArea == rcArea && doAreaScroll) { + ImGui::SetScrollHereY(0.0f); + doAreaScroll = false; + } + for (auto rc : checks) { + if (doDraw && isThisAreaSpoiled && !filterChecksHidden[rc]) { + DrawLocation(rc); + } + } + } + } + ImGui::PopStyleVar(); + + ImGui::EndTable(); // Checks Lead-out + ImGui::EndTable(); // Quick Options Lead-out + if (doingCollapseOrExpand) { + optCollapseAll = false; + optExpandAll = false; + } + } + Trackers::EndFloatWindows(); +} + +bool UpdateFilters() { + for (auto& [rcArea, checks] : checksByArea) { + filterAreasHidden[rcArea] = !checkSearch.PassFilter(RandomizerCheckObjects::GetRCAreaName(rcArea).c_str()); + for (auto check : checks) { + if (ShouldShowCheck(check)) { + filterAreasHidden[rcArea] = false; + filterChecksHidden[check] = false; + } else { + filterChecksHidden[check] = true; + } + } + } + + return true; +} + +bool ShouldShowCheck(RandomizerCheck check) { + auto itemLoc = Rando::Context::GetInstance()->GetItemLocation(check); + std::string search = (Rando::StaticData::GetLocation(check)->GetShortName() + " " + + Rando::StaticData::GetLocation(check)->GetName() + " " + + RandomizerCheckObjects::GetRCAreaName(Rando::StaticData::GetLocation(check)->GetArea())); + if (itemLoc->HasObtained() || itemLoc->GetCheckStatus() == RCSHOW_SCUMMED || + (!mystery && (itemLoc->GetCheckStatus() == RCSHOW_IDENTIFIED || itemLoc->GetCheckStatus() == RCSHOW_SEEN) && + itemLoc->GetPlacedRandomizerGet() != RG_ICE_TRAP)) { + search += " " + itemLoc->GetPlacedItemName().GetForLanguage(gSaveContext.language); + } else if (itemLoc->GetCheckStatus() == RCSHOW_IDENTIFIED && !mystery) { + search += + OTRGlobals::Instance->gRandoContext->overrides[check].GetTrickName().GetForLanguage(gSaveContext.language); + } else if (itemLoc->GetCheckStatus() == RCSHOW_SEEN && !mystery) { + search += Rando::StaticData::RetrieveItem(OTRGlobals::Instance->gRandoContext->overrides[check].LooksLike()) + .GetName() + .GetForLanguage(gSaveContext.language); + } + return (IsVisibleInCheckTracker(check) && + (checkSearch.Filters.Size == 0 || checkSearch.PassFilter(search.c_str()))); +} + +void LoadSettings() { + // If in randomzer, then get the setting and check if in general we should be showing the settings + // If in vanilla, _try_ to show items that at least are needed for 100% + + showShops = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) != RO_SHOPSANITY_OFF : false; + showBeans = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MERCHANTS) == + RO_SHUFFLE_MERCHANTS_BEANS_ONLY || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MERCHANTS) == + RO_SHUFFLE_MERCHANTS_ALL + : true; + showScrubs = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_SCRUBS) == RO_SCRUBS_ALL : false; + showMajorScrubs = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_SCRUBS) != RO_SCRUBS_OFF : false; + showMerchants = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MERCHANTS) == + RO_SHUFFLE_MERCHANTS_ALL_BUT_BEANS || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MERCHANTS) == + RO_SHUFFLE_MERCHANTS_ALL + : true; + showSongs = IS_RANDO + ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_SONGS) != RO_SONG_SHUFFLE_OFF + : false; + showBeehives = IS_RANDO + ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BEEHIVES) == RO_GENERIC_YES + : false; + showCows = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_COWS) == RO_GENERIC_YES : false; + showAdultTrade = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ADULT_TRADE) == RO_GENERIC_YES + : true; + showKokiriSword = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_KOKIRI_SWORD) == RO_GENERIC_YES + : true; + showMasterSword = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MASTER_SWORD) == RO_GENERIC_YES + : true; + showHyruleLoach = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY) == RO_FISHSANITY_HYRULE_LOACH + : false; + showWeirdEgg = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_WEIRD_EGG) == RO_GENERIC_YES + : true; + showGerudoCard = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue( + RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD) == RO_GENERIC_YES + : true; + showFrogSongRupees = + IS_RANDO + ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_FROG_SONG_RUPEES) == RO_GENERIC_YES + : false; + showFountainFairies = + IS_RANDO + ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_FOUNTAIN_FAIRIES) == RO_GENERIC_YES + : false; + showStoneFairies = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_STONE_FAIRIES) == RO_GENERIC_YES + : false; + showBeanFairies = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BEAN_FAIRIES) == RO_GENERIC_YES + : false; + showSongFairies = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_SONG_FAIRIES) == RO_GENERIC_YES + : false; + showStartingMapsCompasses = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue( + RSK_SHUFFLE_MAPANDCOMPASS) != RO_DUNGEON_ITEM_LOC_VANILLA + : false; + showKeysanity = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_KEYSANITY) != RO_DUNGEON_ITEM_LOC_VANILLA + : false; + showBossKeysanity = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_BOSS_KEYSANITY) != + RO_DUNGEON_ITEM_LOC_VANILLA + : false; + showGerudoFortressKeys = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GERUDO_KEYS) != RO_GERUDO_KEYS_VANILLA + : false; + showGanonBossKey = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY) != + RO_GANON_BOSS_KEY_VANILLA + : false; + showOcarinas = IS_RANDO + ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_OCARINA) == RO_GENERIC_YES + : false; + show100SkullReward = + IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_100_GS_REWARD) == RO_GENERIC_YES + : false; + showLinksPocket = + IS_RANDO ? // don't show Link's Pocket if not randomizer, or if rando and pocket is disabled + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING + : false; + + if (IS_RANDO) { + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS)) { + case RO_TOKENSANITY_ALL: + showOverworldTokens = true; + showDungeonTokens = true; + break; + case RO_TOKENSANITY_OVERWORLD: + showOverworldTokens = true; + showDungeonTokens = false; + break; + case RO_TOKENSANITY_DUNGEONS: + showOverworldTokens = false; + showDungeonTokens = true; + break; + default: + showOverworldTokens = false; + showDungeonTokens = false; + break; + } + + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_POTS)) { + case RO_SHUFFLE_POTS_ALL: + showOverworldPots = true; + showDungeonPots = true; + break; + case RO_SHUFFLE_POTS_OVERWORLD: + showOverworldPots = true; + showDungeonPots = false; + break; + case RO_SHUFFLE_POTS_DUNGEONS: + showOverworldPots = false; + showDungeonPots = true; + break; + default: + showOverworldPots = false; + showDungeonPots = false; + break; + } + + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_GRASS)) { + case RO_SHUFFLE_GRASS_ALL: + showOverworldGrass = true; + showDungeonGrass = true; + break; + case RO_SHUFFLE_GRASS_OVERWORLD: + showOverworldGrass = true; + showDungeonGrass = false; + break; + case RO_SHUFFLE_GRASS_DUNGEONS: + showOverworldGrass = false; + showDungeonGrass = true; + break; + default: + showOverworldGrass = false; + showDungeonGrass = false; + break; + } + + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_CRATES)) { + case RO_SHUFFLE_CRATES_ALL: + showOverworldCrates = true; + showDungeonCrates = true; + break; + case RO_SHUFFLE_CRATES_OVERWORLD: + showOverworldCrates = true; + showDungeonCrates = false; + break; + case RO_SHUFFLE_CRATES_DUNGEONS: + showOverworldCrates = false; + showDungeonCrates = true; + break; + default: + showOverworldCrates = false; + showDungeonCrates = false; + break; + } + showTrees = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TREES); + showBushes = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BUSHES); + } else { // Vanilla + showOverworldTokens = true; + showDungeonTokens = true; + showOverworldPots = false; + showDungeonPots = false; + showOverworldGrass = false; + showDungeonGrass = false; + showOverworldCrates = false; + showDungeonCrates = false; + showTrees = false; + showBushes = false; + } + + fortressFast = false; + fortressNormal = false; + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GERUDO_FORTRESS)) { + case RO_GF_CARPENTERS_FREE: + showGerudoFortressKeys = false; + showGerudoCard = false; + break; + case RO_GF_CARPENTERS_FAST: + fortressFast = true; + break; + case RO_GF_CARPENTERS_NORMAL: + fortressNormal = true; + break; + } + + fishsanityMode = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY); + fishsanityPondCount = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY_POND_COUNT); + fishsanityAgeSplit = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_FISHSANITY_AGE_SPLIT); + + if (IS_RANDO) { + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_FREESTANDING)) { + case RO_SHUFFLE_FREESTANDING_ALL: + showOverworldFreestanding = true; + showDungeonFreestanding = true; + break; + case RO_SHUFFLE_FREESTANDING_OVERWORLD: + showOverworldFreestanding = true; + showDungeonFreestanding = false; + break; + case RO_SHUFFLE_FREESTANDING_DUNGEONS: + showOverworldFreestanding = false; + showDungeonFreestanding = true; + break; + default: + showOverworldFreestanding = false; + showDungeonFreestanding = false; + break; + } + } else { // Vanilla + showOverworldFreestanding = false; + showDungeonFreestanding = true; + } + + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY)) { + case RO_GANON_BOSS_KEY_LACS_STONES: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_STONES); + break; + case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_MEDALLIONS); + break; + case RO_GANON_BOSS_KEY_LACS_REWARDS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_REWARDS); + break; + case RO_GANON_BOSS_KEY_LACS_DUNGEONS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_DUNGEONS); + break; + case RO_GANON_BOSS_KEY_LACS_TOKENS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_TOKENS); + break; + default: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_VANILLA); + break; + } +} + +bool IsCheckShuffled(RandomizerCheck rc) { + Rando::Location* loc = Rando::StaticData::GetLocation(rc); + if (loc->GetRCType() == RCTYPE_SHOP) { + auto identity = OTRGlobals::Instance->gRandomizer->IdentifyShopItem(loc->GetScene(), loc->GetActorParams() + 1); + } + if (IS_RANDO) { + return (loc->GetArea() != RCAREA_INVALID) && // don't show Invalid locations + (loc->GetRCType() != RCTYPE_GOSSIP_STONE) && // TODO: Don't show hints until tracker supports them + (loc->GetRCType() != RCTYPE_STATIC_HINT) && // TODO: Don't show hints until tracker supports them + (loc->GetRCType() != RCTYPE_CHEST_GAME) && // don't show non final reward chest game checks until we + // support shuffling them + (rc != RC_HC_ZELDAS_LETTER) && // don't show zeldas letter until we support shuffling it + (rc != RC_LINKS_POCKET || showLinksPocket) && + OTRGlobals::Instance->gRandoContext->IsQuestOfLocationActive(rc) && + (loc->GetRCType() != RCTYPE_SHOP || + (showShops && + OTRGlobals::Instance->gRandomizer->IdentifyShopItem(loc->GetScene(), loc->GetActorParams() + 1) + .enGirlAShopItem == 50)) && + (rc != RC_TRIFORCE_COMPLETED) && (rc != RC_GANON) && + (loc->GetRCType() != RCTYPE_SCRUB || showScrubs || + (showMajorScrubs && (rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized + rc == RC_HF_DEKU_SCRUB_GROTTO || rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT))) && + (loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) && + (loc->GetRCType() != RCTYPE_SONG_LOCATION || showSongs) && + (loc->GetRCType() != RCTYPE_BEEHIVE || showBeehives) && + (loc->GetRCType() != RCTYPE_OCARINA || showOcarinas) && + (loc->GetRCType() != RCTYPE_SKULL_TOKEN || alwaysShowGS || + (showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) || + (showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_POT || + (showOverworldPots && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) || + (showDungeonPots && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_GRASS || + (showOverworldGrass && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) || + (showDungeonGrass && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_CRATE || + (showOverworldCrates && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) || + (showDungeonCrates && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_NLCRATE || + (showOverworldCrates && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea()) && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LOGIC_RULES) == RO_LOGIC_NO_LOGIC) || + (showDungeonCrates && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_SMALL_CRATE || + (showOverworldCrates && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) || + (showDungeonCrates && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_TREE || showTrees) && + (loc->GetRCType() != RCTYPE_NLTREE || + (showTrees && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LOGIC_RULES) == RO_LOGIC_NO_LOGIC)) && + (loc->GetRCType() != RCTYPE_BUSH || showBushes) && (loc->GetRCType() != RCTYPE_COW || showCows) && + (loc->GetRCType() != RCTYPE_FISH || + OTRGlobals::Instance->gRandoContext->GetFishsanity()->GetFishLocationIncluded(loc)) && + (loc->GetRCType() != RCTYPE_FREESTANDING || + (showOverworldFreestanding && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) || + (showDungeonFreestanding && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))) && + (loc->GetRCType() != RCTYPE_ADULT_TRADE || showAdultTrade || + rc == RC_KAK_ANJU_AS_ADULT || // adult trade checks that are always shuffled + rc == RC_DMT_TRADE_CLAIM_CHECK // even when shuffle adult trade is off + ) && + (rc != RC_KF_KOKIRI_SWORD_CHEST || showKokiriSword) && (rc != RC_TOT_MASTER_SWORD || showMasterSword) && + (rc != RC_LH_HYRULE_LOACH || showHyruleLoach) && (rc != RC_ZR_MAGIC_BEAN_SALESMAN || showBeans) && + (rc != RC_HC_MALON_EGG || showWeirdEgg) && + (loc->GetRCType() != RCTYPE_FROG_SONG || showFrogSongRupees) && + ((loc->GetRCType() != RCTYPE_MAP && loc->GetRCType() != RCTYPE_COMPASS) || showStartingMapsCompasses) && + (loc->GetRCType() != RCTYPE_FOUNTAIN_FAIRY || showFountainFairies) && + (loc->GetRCType() != RCTYPE_STONE_FAIRY || showStoneFairies) && + (loc->GetRCType() != RCTYPE_BEAN_FAIRY || showBeanFairies) && + (loc->GetRCType() != RCTYPE_SONG_FAIRY || showSongFairies) && + (loc->GetRCType() != RCTYPE_SMALL_KEY || showKeysanity) && + (loc->GetRCType() != RCTYPE_BOSS_KEY || showBossKeysanity) && + (loc->GetRCType() != RCTYPE_GANON_BOSS_KEY || showGanonBossKey) && + (rc != RC_KAK_100_GOLD_SKULLTULA_REWARD || show100SkullReward) && + (loc->GetRCType() != RCTYPE_GF_KEY && rc != RC_TH_FREED_CARPENTERS || + (showGerudoCard && rc == RC_TH_FREED_CARPENTERS) || + (fortressNormal && showGerudoFortressKeys && loc->GetRCType() == RCTYPE_GF_KEY) || + (fortressFast && showGerudoFortressKeys && rc == RC_TH_1_TORCH_CARPENTER)); + } else if (loc->IsVanillaCompletion()) { + return (OTRGlobals::Instance->gRandoContext->IsQuestOfLocationActive(rc) || rc == RC_GIFT_FROM_RAURU) && + rc != RC_LINKS_POCKET; + } + return false; +} + +bool IsVisibleInCheckTracker(RandomizerCheck rc) { + auto loc = Rando::StaticData::GetLocation(rc); + if (IS_RANDO) { + return !Rando::Context::GetInstance()->GetItemLocation(rc)->IsExcluded() && + (IsCheckShuffled(rc) || + (alwaysShowGS && loc->GetRCType() == RCTYPE_SKULL_TOKEN && + OTRGlobals::Instance->gRandoContext->IsQuestOfLocationActive(rc)) || + (loc->GetRCType() == RCTYPE_SHOP && showShops && !hideShopUnshuffledChecks)); + } else { + return loc->IsVanillaCompletion() && + (!loc->IsDungeon() || (loc->IsDungeon() && loc->GetQuest() == gSaveContext.ship.quest.id)); + } +} + +void UpdateInventoryChecks() { + // For all the areas with maps, if you have one, spoil the area + for (auto [scene, area] : DungeonRCAreasBySceneID) { + if (CHECK_DUNGEON_ITEM(DUNGEON_MAP, scene)) { + SetAreaSpoiled(area); + } + } +} + +void UpdateAreaFullyChecked(RandomizerCheckArea area) { +} + +void UpdateAllAreas() { + // Sort the entire thing + for (int i = 0; i < RCAREA_INVALID; i++) { + UpdateAreas(static_cast(i)); + } +} + +void UpdateAreas(RandomizerCheckArea area) { + if (checksByArea.contains(area)) { + areasFullyChecked[area] = areaChecksGotten[area] == checksByArea.find(area)->second.size(); + } +} + +void UpdateAllOrdering() { + // Sort the entire thing + for (int i = 0; i < RCAREA_INVALID; i++) { + UpdateOrdering(static_cast(i)); + } +} + +void UpdateOrdering(RandomizerCheckArea rcArea) { + // Sort a single area + if (checksByArea.contains(rcArea)) { + std::sort(checksByArea.find(rcArea)->second.begin(), checksByArea.find(rcArea)->second.end(), CompareChecks); + } + RecalculateAllAreaTotals(); + CalculateTotals(); +} + +bool IsEoDCheck(RandomizerCheckType type) { + return type == RCTYPE_BOSS_HEART_OR_OTHER_REWARD || type == RCTYPE_DUNGEON_REWARD; +} + +bool CompareChecks(RandomizerCheck i, RandomizerCheck j) { + Rando::Location* x = Rando::StaticData::GetLocation(i); + Rando::Location* y = Rando::StaticData::GetLocation(j); + auto itemI = OTRGlobals::Instance->gRandoContext->GetItemLocation(i); + auto itemJ = OTRGlobals::Instance->gRandoContext->GetItemLocation(j); + bool iCollected = itemI->HasObtained(); + bool iSaved = itemI->GetCheckStatus() == RCSHOW_SAVED; + bool jCollected = itemJ->HasObtained(); + bool jSaved = itemJ->GetCheckStatus() == RCSHOW_SAVED; + + if (!iCollected && jCollected) { + return true; + } else if (iCollected && !jCollected) { + return false; + } + + if (!iSaved && jSaved) { + return true; + } else if (iSaved && !jSaved) { + return false; + } + + if (!itemI->GetIsSkipped() && itemJ->GetIsSkipped()) { + return true; + } else if (itemI->GetIsSkipped() && !itemJ->GetIsSkipped()) { + return false; + } + + if (!IsEoDCheck(x->GetRCType()) && IsEoDCheck(y->GetRCType())) { + return true; + } else if (IsEoDCheck(x->GetRCType()) && !IsEoDCheck(y->GetRCType())) { + return false; + } + + if (i < j) { + return true; + } else if (i > j) { + return false; + } + + return false; +} + +bool IsHeartPiece(GetItemID giid) { + return giid == GI_HEART_PIECE || giid == GI_HEART_PIECE_WIN; +} + +void DrawLocation(RandomizerCheck rc) { + Color_RGBA8 mainColor; + Color_RGBA8 extraColor; + std::string txt; + Rando::Location* loc = Rando::StaticData::GetLocation(rc); + Rando::ItemLocation* itemLoc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + RandomizerCheckStatus status = itemLoc->GetCheckStatus(); + bool skipped = itemLoc->GetIsSkipped(); + bool available = itemLoc->IsAvailable(); + + if (enableAvailableChecks && onlyShowAvailable && !available) { + return; + } + + if (status == RCSHOW_COLLECTED) { + if (!showHidden && hideCollected) { + return; + } + mainColor = + !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO + ? Color_Collected_Extra + : Color_Collected_Main; + extraColor = Color_Collected_Extra; + } else if (status == RCSHOW_SAVED) { + if (!showHidden && hideSaved) { + return; + } + mainColor = + !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO + ? Color_Saved_Extra + : Color_Saved_Main; + extraColor = Color_Saved_Extra; + } else if (skipped) { + if (!showHidden && hideSkipped) { + return; + } + mainColor = + !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO + ? Color_Skipped_Extra + : Color_Skipped_Main; + extraColor = Color_Skipped_Extra; + } else if (status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED) { + if (!showHidden && hideSeen) { + return; + } + mainColor = + !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO + ? Color_Seen_Extra + : Color_Seen_Main; + extraColor = Color_Seen_Extra; + } else if (status == RCSHOW_SCUMMED) { + if (!showHidden && hideScummed) { + return; + } + mainColor = + !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO + ? Color_Scummed_Extra + : Color_Scummed_Main; + extraColor = Color_Scummed_Extra; + } else if (status == RCSHOW_UNCHECKED) { + if (!showHidden && hideUnchecked) { + return; + } + mainColor = + !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO + ? Color_Unchecked_Extra + : Color_Unchecked_Main; + extraColor = Color_Unchecked_Extra; + } + + // Main Text + if (checkNameOverrides.contains(loc->GetRandomizerCheck())) { + txt = checkNameOverrides[loc->GetRandomizerCheck()]; + } else { + txt = loc->GetShortName(); + } + + if (lastLocationChecked == loc->GetRandomizerCheck()) { + txt = "* " + txt; + } + + // Draw button - for Skipped/Seen/Scummed/Unchecked only + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 4.0f, 3.0f }); + float sz = ImGui::GetFrameHeight(); + if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED || + status == RCSHOW_SCUMMED || skipped) { + if (UIWidgets::StateButton(std::to_string(rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES, ImVec2(sz, sz), + UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + if (skipped) { + OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(false); + areaChecksGotten[loc->GetArea()]--; + totalChecksGotten--; + if (available) { + areaChecksAvailable[loc->GetArea()]++; + totalChecksAvailable++; + } + } else { + OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(true); + areaChecksGotten[loc->GetArea()]++; + totalChecksGotten++; + if (available) { + areaChecksAvailable[loc->GetArea()]--; + totalChecksAvailable--; + } + } + UpdateOrdering(loc->GetArea()); + UpdateInventoryChecks(); + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + } + } else { + ImGui::Dummy(ImVec2(sz, sz)); + } + ImGui::PopStyleVar(); + + ImGui::SameLine(); + + // Draw + ImVec4 styleColor(mainColor.r / 255.0f, mainColor.g / 255.0f, mainColor.b / 255.0f, mainColor.a / 255.0f); + if (enableAvailableChecks) { + if (itemLoc->HasObtained()) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0, 0, 0, 0)); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, styleColor); + } + ImGui::Text("%s", available ? ICON_FA_UNLOCK : ICON_FA_LOCK); + ImGui::PopStyleColor(); + ImGui::SameLine(); + } + + ImGui::PushStyleColor(ImGuiCol_Text, styleColor); + ImGui::Text("%s", txt.c_str()); + ImGui::PopStyleColor(); + + // Draw the extra info + txt = ""; + + if (status != RCSHOW_UNCHECKED) { + switch (status) { + case RCSHOW_SAVED: + case RCSHOW_COLLECTED: + case RCSHOW_SCUMMED: + if (IS_RANDO) { + txt = itemLoc->GetPlacedItem().GetName().GetForLanguage(gSaveContext.language); + } else { + if (IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID())) { + if (gSaveContext.language == LANGUAGE_ENG || gSaveContext.language == LANGUAGE_GER || + gSaveContext.language == LANGUAGE_JPN) { + txt = Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetName().english; + } else if (gSaveContext.language == LANGUAGE_FRA) { + txt = Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetName().french; + } + } + } + break; + case RCSHOW_IDENTIFIED: + case RCSHOW_SEEN: + if (IS_RANDO) { + if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP && !mystery) { + if (status == RCSHOW_IDENTIFIED) { + txt = OTRGlobals::Instance->gRandoContext->overrides[rc].GetTrickName().GetForLanguage( + gSaveContext.language); + } else { + txt = Rando::StaticData::RetrieveItem( + OTRGlobals::Instance->gRandoContext->overrides[rc].LooksLike()) + .GetName() + .GetForLanguage(gSaveContext.language); + } + } else if (!mystery) { + txt = itemLoc->GetPlacedItem().GetName().GetForLanguage(gSaveContext.language); + } + if (IsVisibleInCheckTracker(rc) && status == RCSHOW_IDENTIFIED && !mystery) { + auto price = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetPrice(); + if (price) { + txt += fmt::format(" - {}", price); + } + } + } else { + if (IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID())) { + if (gSaveContext.language == LANGUAGE_ENG || gSaveContext.language == LANGUAGE_GER || + gSaveContext.language == LANGUAGE_JPN) { + txt = Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetName().english; + } else if (gSaveContext.language == LANGUAGE_FRA) { + txt = Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetName().french; + } + } + } + break; + } + } + if (txt == "" && skipped) { + txt = "Skipped"; // TODO language + } + + if (txt != "") { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, extraColor.b / 255.0f, + extraColor.a / 255.0f)); + ImGui::SameLine(); + ImGui::Text(" (%s)", txt.c_str()); + ImGui::PopStyleColor(); + } + + if (showLogicTooltip) { + for (auto& locationInRegion : areaTable[itemLoc->GetParentRegionKey()].locations) { + if (locationInRegion.GetLocation() == rc) { + std::string conditionStr = locationInRegion.GetConditionStr(); + if (conditionStr != "true") { + UIWidgets::Tooltip(conditionStr.c_str()); + } + break; + } + } + } +} + +static std::set rainbowCVars = { + CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), + CVAR_TRACKER_CHECK("AreaComplete.MainColor"), CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), + CVAR_TRACKER_CHECK("Unchecked.MainColor"), CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), + CVAR_TRACKER_CHECK("Skipped.MainColor"), CVAR_TRACKER_CHECK("Skipped.ExtraColor"), + CVAR_TRACKER_CHECK("Seen.MainColor"), CVAR_TRACKER_CHECK("Seen.ExtraColor"), + CVAR_TRACKER_CHECK("Hinted.MainColor"), CVAR_TRACKER_CHECK("Hinted.ExtraColor"), + CVAR_TRACKER_CHECK("Collected.MainColor"), CVAR_TRACKER_CHECK("Collected.ExtraColor"), + CVAR_TRACKER_CHECK("Scummed.MainColor"), CVAR_TRACKER_CHECK("Scummed.ExtraColor"), + CVAR_TRACKER_CHECK("Saved.MainColor"), CVAR_TRACKER_CHECK("Saved.ExtraColor"), +}; + +int hue = 0; +void RainbowTick() { + float freqHue = hue * 2 * M_PIf / (360 * CVarGetFloat(CVAR_COSMETIC("RainbowSpeed"), 0.6f)); + for (auto& cvar : rainbowCVars) { + if (CVarGetInteger((cvar + ".Rainbow").c_str(), 0) == 0) { + continue; + } + + Color_RGBA8 newColor; + newColor.r = static_cast(sin(freqHue + 0) * 127) + 128; + newColor.g = static_cast(sin(freqHue + (2 * M_PI / 3)) * 127) + 128; + newColor.b = static_cast(sin(freqHue + (4 * M_PI / 3)) * 127) + 128; + newColor.a = 255; + + CVarSetColor((cvar + ".Value").c_str(), newColor); + } + + hue++; + hue %= 360; +} + +void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, const char* cvarExtraName, + Color_RGBA8& main_color, Color_RGBA8& extra_color, + const Color_RGBA8& main_default_color, const Color_RGBA8& extra_default_color, + const char* cvarHideName, const char* tooltip, UIWidgets::Colors theme) { + Color_RGBA8 cvarMainColor = CVarGetColor(cvarMainName, main_default_color); + Color_RGBA8 cvarExtraColor = CVarGetColor(cvarExtraName, extra_default_color); + main_color = cvarMainColor; + extra_color = cvarExtraColor; + + UIWidgets::PushStyleCombobox(theme); + if (ImGui::CollapsingHeader(text)) { + if (*cvarHideName != '\0') { + std::string label = cvarHideName; + label += "##Hidden"; + ImGui::PushID(label.c_str()); + UIWidgets::CVarCheckbox( + "Hidden", cvarHideName, + UIWidgets::CheckboxOptions( + { { .tooltip = "When active, checks will hide by default when updated to this state. Can " + "be overridden with the \"Show Hidden Items\" option." } }) + .Color(theme)); + ImGui::PopID(); + } + std::string mainLabel = "Name##" + std::string(cvarMainName); + if (UIWidgets::CVarColorPicker(mainLabel.c_str(), cvarMainName, main_default_color, false, + UIWidgets::ColorPickerRandomButton | UIWidgets::ColorPickerResetButton | + UIWidgets::ColorPickerRainbowCheck, + theme)) { + main_color = CVarGetColor(cvarMainName, main_default_color); + } + + std::string extraLabel = "Details##" + std::string(cvarExtraName); + if (UIWidgets::CVarColorPicker(extraLabel.c_str(), cvarExtraName, extra_default_color, false, + UIWidgets::ColorPickerRandomButton | UIWidgets::ColorPickerResetButton | + UIWidgets::ColorPickerRainbowCheck, + theme)) { + extra_color = CVarGetColor(cvarExtraName, extra_default_color); + } + } + if (tooltip != NULL && strlen(tooltip) != 0) { + ImGui::SameLine(); + ImGui::Text(" ?"); + UIWidgets::Tooltip(tooltip); + } + UIWidgets::PopStyleCombobox(); +} + +void InternalRecalculateAvailableChecks(RandomizerRegion startingRegion, RandoAgeTime startingAgeTime) { + if (!enableAvailableChecks || !GameInteractor::IsSaveLoaded()) { + return; + } + + ResetPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS); + StartPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS); + + const auto& ctx = Rando::Context::GetInstance(); + logic = ctx->GetLogic(); + + int16_t entranceIndex = gPlayState->nextEntranceIndex; + if (startingRegion == RR_ROOT && entranceIndex >= 0 && entranceIndex < ENTR_MAX) { + // Try to find a mapped entrance + // e.g. ENTR_DEKU_TREE_0_1 (index 1) is not mapped, but ENTR_DEKU_TREE_ENTRANCE (index 0) is mapped + const int8_t scene = gEntranceTable[entranceIndex].scene; + for (; entranceIndex >= 0 && gEntranceTable[entranceIndex].scene == scene; entranceIndex--) { + const auto entrance = Rando::EntranceShuffler::GetEntranceByIndex(entranceIndex); + if (entrance != nullptr) { + startingRegion = entrance->GetOriginalConnectedRegionKey(); + break; + } + } + } + + if (startingAgeTime == RAT_NONE) { + if (LINK_IS_CHILD && IS_DAY) { + startingAgeTime = RAT_CHILD_DAY; + } else if (LINK_IS_CHILD && IS_NIGHT) { + startingAgeTime = RAT_CHILD_NIGHT; + } else if (LINK_IS_ADULT && IS_DAY) { + startingAgeTime = RAT_ADULT_DAY; + } else if (LINK_IS_ADULT && IS_NIGHT) { + startingAgeTime = RAT_ADULT_NIGHT; + } + } + + std::vector targetLocations; + targetLocations.reserve(RC_MAX); + for (auto& location : Rando::StaticData::GetLocationTable()) { + RandomizerCheck rc = location.GetRandomizerCheck(); + Rando::ItemLocation* itemLocation = ctx->GetItemLocation(rc); + itemLocation->SetAvailable(false); + if (!itemLocation->HasObtained()) { + targetLocations.emplace_back(rc); + } + } + + std::vector availableChecks = + ReachabilitySearch(targetLocations, RG_NONE, true, startingRegion, startingAgeTime); + for (auto& rc : availableChecks) { + const auto& itemLocation = ctx->GetItemLocation(rc); + itemLocation->SetAvailable(true); + } + + totalChecksAvailable = 0; + for (auto& [rcArea, vec] : checksByArea) { + areaChecksAvailable[rcArea] = 0; + for (auto& rc : vec) { + Rando::ItemLocation* itemLocation = ctx->GetItemLocation(rc); + if (itemLocation->IsAvailable() && IsVisibleInCheckTracker(rc) && !IsCheckHidden(rc)) { + areaChecksAvailable[rcArea]++; + } + } + totalChecksAvailable += areaChecksAvailable[rcArea]; + } + + StopPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS); + SPDLOG_INFO("Recalculate Available Checks Time: {}ms", + GetPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS).count()); +} + +void RecalculateAvailableChecks(RandomizerRegion startingRegion /* = RR_ROOT */, + RandoAgeTime startingAgeTime /* = RAT_NONE */) { + recalculateAvailable = true; + availableChecksStartingRegion = startingRegion; + availableChecksStartingAgeTime = startingAgeTime; +} + +void LoadFromPreset(nlohmann::json info) { + presetLoaded = true; + presetPos = { info["pos"]["x"], info["pos"]["y"] }; + presetSize = { info["size"]["width"], info["size"]["height"] }; +} + +void CheckTrackerWindow::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +void CheckTrackerSettingsWindow::DrawElement() { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); + if (ImGui::BeginTable("CheckTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("General settings").c_str(), ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Section settings").c_str(), ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + SohGui::GetSohMenu()->MenuDrawItem(backgroundColorWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + SohGui::GetSohMenu()->MenuDrawItem(windowTypeWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + UIWidgets::CVarSliderFloat("Font Size", CVAR_TRACKER_CHECK("FontSize"), + UIWidgets::FloatSliderOptions() + .Tooltip("Sets the font size used in the check tracker.") + .Format("%.1f") + .Step(0.1f) + .Min(0.3f) + .Max(2.0f) + .Color(THEME_COLOR) + .DefaultValue(1.0f)); + + if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { + UIWidgets::CVarCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Only Enable While Paused", CVAR_TRACKER_CHECK("ShowOnlyPaused"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCombobox("Display Mode", CVAR_TRACKER_CHECK("DisplayType"), showMode, + UIWidgets::ComboboxOptions() + .LabelPosition(UIWidgets::LabelPositions::Far) + .ComponentAlignment(UIWidgets::ComponentAlignments::Right) + .Color(THEME_COLOR) + .DefaultIndex(0)); + if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == + TRACKER_DISPLAY_COMBO_BUTTON) { + UIWidgets::CVarCombobox("Combo Button 1", CVAR_TRACKER_CHECK("ComboButton1"), buttonStrings, + UIWidgets::ComboboxOptions() + .LabelPosition(UIWidgets::LabelPositions::Far) + .ComponentAlignment(UIWidgets::ComponentAlignments::Right) + .Color(THEME_COLOR) + .DefaultIndex(TRACKER_COMBO_BUTTON_L)); + UIWidgets::CVarCombobox("Combo Button 2", CVAR_TRACKER_CHECK("ComboButton2"), buttonStrings, + UIWidgets::ComboboxOptions() + .LabelPosition(UIWidgets::LabelPositions::Far) + .ComponentAlignment(UIWidgets::ComponentAlignments::Right) + .Color(THEME_COLOR) + .DefaultIndex(TRACKER_COMBO_BUTTON_L)); + } + } + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); + SohGui::GetSohMenu()->MenuDrawItem(dungeonSpoilerWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + ImGui::EndDisabled(); + + SohGui::GetSohMenu()->MenuDrawItem(hideUnshuffledShopWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + SohGui::GetSohMenu()->MenuDrawItem(showGSWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + SohGui::GetSohMenu()->MenuDrawItem(showLogicWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); + SohGui::GetSohMenu()->MenuDrawItem(checkAvailabilityWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + ImGui::EndDisabled(); + + // Filtering settings + UIWidgets::CVarCheckbox( + "Filter Empty Areas", CVAR_TRACKER_CHECK("HideFilteredAreas"), + UIWidgets::CheckboxOptions() + .Tooltip("If enabled, will hide area headers that have no locations matching filter") + .Color(THEME_COLOR) + .DefaultValue(true)); + + ImGui::SeparatorText(LanguageManager::Instance().GetString("Tracker Header Visibility").c_str()); + UIWidgets::CVarCheckbox("Hidden Items Toggle", CVAR_TRACKER_CHECK("HiddenItemsToggleVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + UIWidgets::CVarCheckbox("Available Checks Toggle", CVAR_TRACKER_CHECK("AvailableChecksToggleVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + UIWidgets::CVarCheckbox("Expand/Collapse Buttons", CVAR_TRACKER_CHECK("ExpandCollapseButtonsVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(false)); + UIWidgets::CVarCheckbox("Search Input", CVAR_TRACKER_CHECK("SearchInputVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + UIWidgets::CVarCheckbox("Check Totals", CVAR_TRACKER_CHECK("CheckTotalsVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + + ImGui::TableNextColumn(); + + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), + CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), + Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, + Color_Main_Default, Color_Area_Incomplete_Extra_Default, + CVAR_TRACKER_CHECK("AreaIncomplete.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Complete", CVAR_TRACKER_CHECK("AreaComplete.MainColor"), + CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), + Color_Area_Complete_Main, Color_Area_Complete_Extra, + Color_Main_Default, Color_Area_Complete_Extra_Default, + CVAR_TRACKER_CHECK("AreaComplete.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection( + "Unchecked", CVAR_TRACKER_CHECK("Unchecked.MainColor"), CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), + Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, + CVAR_TRACKER_CHECK("Unchecked.Hide"), "Checks you have not interacted with at all.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection( + "Skipped", CVAR_TRACKER_CHECK("Skipped.MainColor"), CVAR_TRACKER_CHECK("Skipped.ExtraColor"), + Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, + CVAR_TRACKER_CHECK("Skipped.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection( + "Seen", CVAR_TRACKER_CHECK("Seen.MainColor"), CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Main, + Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, CVAR_TRACKER_CHECK("Seen.Hide"), + "Used for shops. Shows item names for shop slots when walking in, and prices when highlighting them in buy " + "mode.", + THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection( + "Scummed", CVAR_TRACKER_CHECK("Scummed.MainColor"), CVAR_TRACKER_CHECK("Scummed.ExtraColor"), + Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, + CVAR_TRACKER_CHECK("Scummed.Hide"), + "Checks you collect, but then reload before saving so you no longer have them.", THEME_COLOR); + // CheckTracker::ImGuiDrawTwoColorPickerSection("Hinted (WIP)", CVAR_TRACKER_CHECK("Hinted.MainColor"), + // CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Main, Color_Hinted_Extra, + // Color_Main_Default, Color_Hinted_Extra_Default, CVAR_TRACKER_CHECK("Hinted.Hide"), "", + // THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection( + "Collected", CVAR_TRACKER_CHECK("Collected.MainColor"), CVAR_TRACKER_CHECK("Collected.ExtraColor"), + Color_Collected_Main, Color_Collected_Extra, Color_Main_Default, Color_Collected_Extra_Default, + CVAR_TRACKER_CHECK("Collected.Hide"), "Checks you have collected without saving or reloading yet.", + THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection( + "Saved", CVAR_TRACKER_CHECK("Saved.MainColor"), CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Main, + Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, CVAR_TRACKER_CHECK("Saved.Hide"), + "Checks that you saved the game while having collected.", THEME_COLOR); + + ImGui::PopStyleVar(1); + ImGui::EndTable(); + } +} + +void CheckTrackerWindow::InitElement() { + SaveManager::Instance->AddInitFunction(InitTrackerData); + sectionId = SaveManager::Instance->AddSaveFunction("trackerData", 1, SaveFile, true, SECTION_PARENT_NONE); + SaveManager::Instance->AddLoadFunction("trackerData", 1, LoadFile); + GameInteractor::Instance->RegisterGameHook(CheckTrackerLoadGame); + GameInteractor::Instance->RegisterGameHook([](uint32_t fileNum) { Teardown(); }); + GameInteractor::Instance->RegisterGameHook(CheckTrackerItemReceive); + GameInteractor::Instance->RegisterGameHook(CheckTrackerTransition); + GameInteractor::Instance->RegisterGameHook(CheckTrackerShopSlotChange); + GameInteractor::Instance->RegisterGameHook(CheckTrackerSceneFlagSet); + GameInteractor::Instance->RegisterGameHook(CheckTrackerFlagSet); +} + +void CheckTrackerWindow::UpdateElement() { +} + +void RegisterCheckTrackerWidgets() { + backgroundColorWidget = { .name = "Background Color##CheckTracker", .type = WidgetType::WIDGET_CVAR_COLOR_PICKER }; + backgroundColorWidget.CVar(CVAR_TRACKER_CHECK("BgColor")) + .Options( + ColorPickerOptions().Color(THEME_COLOR).DefaultValue(Color_Bg_Default).UseAlpha().ShowReset().ShowRandom()); + SohGui::GetSohMenu()->AddSearchWidget({ backgroundColorWidget, "Randomizer", "Check Tracker", "General Settings" }); + + windowTypeWidget = { .name = "Window Type##CheckTracker", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + windowTypeWidget.CVar(CVAR_TRACKER_CHECK("WindowType")) + .Options(ComboboxOptions() + .DefaultIndex(TRACKER_WINDOW_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(windowType)); + SohGui::GetSohMenu()->AddSearchWidget({ windowTypeWidget, "Randomizer", "Check Tracker", "General Settings" }); + + dungeonSpoilerWidget = { .name = "Vanilla/MQ Dungeon Spoilers", .type = WidgetType::WIDGET_CVAR_CHECKBOX }; + dungeonSpoilerWidget.CVar(CVAR_TRACKER_CHECK("MQSpoilers")) + .Options(CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. " + "Otherwise, Vanilla/MQ dungeon locations must be unlocked.")); + SohGui::GetSohMenu()->AddSearchWidget({ dungeonSpoilerWidget, "Randomizer", "Check Tracker", "General Settings" }); + + hideUnshuffledShopWidget = { .name = "Hide Unshuffled Shop Item Checks", .type = WidgetType::WIDGET_CVAR_CHECKBOX }; + hideUnshuffledShopWidget.CVar(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks")) + .Options( + CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("If enabled, will prevent the tracker from displaying slots with non-shop-item shuffles.")) + .Callback([&](WidgetInfo& info) { + hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 0); + UpdateFilters(); + }); + SohGui::GetSohMenu()->AddSearchWidget( + { hideUnshuffledShopWidget, "Randomizer", "Check Tracker", "General Settings" }); + + showGSWidget = { .name = "Always Show Gold Skulltulas", .type = WidgetType::WIDGET_CVAR_CHECKBOX }; + showGSWidget.CVar(CVAR_TRACKER_CHECK("AlwaysShowGSLocs")) + .Options(CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings.")) + .Callback([&](WidgetInfo& info) { + alwaysShowGS = !alwaysShowGS; + UpdateFilters(); + }); + SohGui::GetSohMenu()->AddSearchWidget({ showGSWidget, "Randomizer", "Check Tracker", "General Settings" }); + + showLogicWidget = { .name = "Show Logic", .type = WidgetType::WIDGET_CVAR_CHECKBOX }; + showLogicWidget.CVar(CVAR_TRACKER_CHECK("ShowLogic")) + .Options(CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("If enabled, will show a check's logic when hovering over it.")); + SohGui::GetSohMenu()->AddSearchWidget({ showLogicWidget, "Randomizer", "Check Tracker", "General Settings" }); + + checkAvailabilityWidget = { .name = "Enable Available Checks", .type = WidgetType::WIDGET_CVAR_CHECKBOX }; + checkAvailabilityWidget.CVar(CVAR_TRACKER_CHECK("EnableAvailableChecks")) + .Options(CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("If enabled, will show the checks that are available to be collected " + "with your current progress.")) + .Callback([&](WidgetInfo& info) { + enableAvailableChecks = CVarGetInteger(CVAR_TRACKER_CHECK("EnableAvailableChecks"), 0); + RecalculateAvailableChecks(); + }); +} + +static RegisterMenuInitFunc menuInitFunc(RegisterCheckTrackerWidgets); +} // namespace CheckTracker diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp new file mode 100644 index 000000000..c692b7b21 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -0,0 +1,1146 @@ +#include "randomizer_entrance_tracker.h" +#include "soh/OTRGlobals.h" +#include "soh/cvar_prefixes.h" +#include "soh/SohGui/SohGui.hpp" +#include "soh/SohGui/LanguageManager.h" + +#include +#include +#include +#include + +extern "C" { +#include +#include "variables.h" +#include "functions.h" +#include "macros.h" +extern PlayState* gPlayState; + +#include "soh/Enhancements/randomizer/randomizer_entrance.h" +#include "soh/Enhancements/randomizer/randomizer_grotto.h" +#include "soh/Enhancements/randomizer/randomizerTypes.h" +} + +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "entrance.h" + +using namespace UIWidgets; +using namespace SohGui; + +#define COLOR_ORANGE IM_COL32(230, 159, 0, 255) +#define COLOR_GREEN IM_COL32(0, 158, 115, 255) +#define COLOR_GRAY IM_COL32(155, 155, 155, 255) + +namespace EntranceTracker { +EntranceOverride srcListSortedByArea[ENTRANCE_OVERRIDES_MAX_COUNT] = { 0 }; +EntranceOverride destListSortedByArea[ENTRANCE_OVERRIDES_MAX_COUNT] = { 0 }; +EntranceOverride srcListSortedByType[ENTRANCE_OVERRIDES_MAX_COUNT] = { 0 }; +EntranceOverride destListSortedByType[ENTRANCE_OVERRIDES_MAX_COUNT] = { 0 }; +EntranceTrackingData gEntranceTrackingData = { 0 }; + +static const EntranceOverride emptyOverride = { 0 }; + +static s16 lastEntranceIndex = -1; +static s16 currentGrottoId = -1; +static s16 lastSceneOrEntranceDetected = -1; + +Color_RGBA8 Color_Background = { 0, 0, 0, 255 }; +static WidgetInfo backgroundColorWidget; +static WidgetInfo windowTypeWidget; + +static bool presetLoaded = false; +static ImVec2 presetPos; +static ImVec2 presetSize; + +static std::string spoilerEntranceGroupNames[] = { + "Spawns/Warp Songs/Owls", + "Kokiri Forest", + "Lost Woods", + "Sacred Forest Meadow", + "Kakariko Village", + "Graveyard", + "Death Mountain Trail", + "Death Mountain Crater", + "Goron City", + "Zora's River", + "Zora's Domain", + "Zora's Fountain", + "Hyrule Field", + "Lon Lon Ranch", + "Lake Hylia", + "Gerudo Valley", + "Gerudo Fortress", + "Haunted Wasteland", + "Desert Colossus", + "Market", + "Hyrule Castle", +}; + +static std::string groupTypeNames[] = { + "One Way", "Overworld", "Interior", "Fortress", "Grotto", "Dungeon", +}; + +// Entrance data for the tracker taken from the 3ds rando entrance tracker, and supplemented with scene/spawn info and +// meta search tags ENTR_HYRULE_FIELD_10 and ENTR_POTION_SHOP_KAKARIKO_1 have been repurposed for entrance randomizer +const EntranceData entranceData[] = { + // clang-format off + //index, reverse, scenes (and spawns), source name, destination name, source group, destination group, type, metaTag, oneExit + { ENTR_LINKS_HOUSE_CHILD_SPAWN, -1, SINGLE_SCENE_INFO(SCENE_LINKS_HOUSE), "Child Spawn", "Link's House", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_HYRULE_FIELD_10, -1, SINGLE_SCENE_INFO(SCENE_TEMPLE_OF_TIME), "Adult Spawn", "Temple of Time", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + + { ENTR_SACRED_FOREST_MEADOW_WARP_PAD, -1, {{ -1 }}, "Minuet of Forest", "SFM Warp Pad", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD, -1, {{ -1 }}, "Bolero of Fire", "DMC Warp Pad", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_LAKE_HYLIA_WARP_PAD, -1, {{ -1 }}, "Serenade of Water", "Lake Hylia Warp Pad", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_DESERT_COLOSSUS_WARP_PAD, -1, {{ -1 }}, "Requiem of Spirit", "Desert Colossus Warp Pad", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_GRAVEYARD_WARP_PAD, -1, {{ -1 }}, "Nocturne of Shadow", "Graveyard Warp Pad", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_TEMPLE_OF_TIME_WARP_PAD, -1, {{ -1 }}, "Prelude of Light", "Temple of Time Warp Pad", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + + { ENTR_KAKARIKO_VILLAGE_OWL_DROP, -1, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "DMT Owl Flight", "Kakariko Village Owl Drop", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + { ENTR_HYRULE_FIELD_OWL_DROP, -1, SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "LH Owl Flight", "Hyrule Field Owl Drop", ENTRANCE_GROUP_ONE_WAY, ENTRANCE_GROUP_ONE_WAY, ENTRANCE_TYPE_ONE_WAY}, + + // Kokiri Forest + { ENTR_LOST_WOODS_BRIDGE_EAST_EXIT, ENTR_KOKIRI_FOREST_LOWER_EXIT, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "Kokiri Forest Lower Exit", "Lost Woods Bridge East Exit", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTR_LOST_WOODS_SOUTH_EXIT, ENTR_KOKIRI_FOREST_UPPER_EXIT, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "Kokiri Forest Upper Exit", "Lost Woods South Exit", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTR_LINKS_HOUSE_1, ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Link's House Entry", "Link's House", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_MIDOS_HOUSE_0, ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Mido's House Entry", "Mido's House", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_SARIAS_HOUSE_0, ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Saria's House Entry", "Saria's House", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_TWINS_HOUSE_0, ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF House of Twins Entry", "House of Twins", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_KNOW_IT_ALL_BROS_HOUSE_0, ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Know-It-All House Entry", "Know-It-All House", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_KOKIRI_SHOP_0, ENTR_KOKIRI_FOREST_OUTSIDE_SHOP, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Shop Entry", "Kokiri Shop", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_KF_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_KF_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Storms Grotto Entry", "KF Storms Grotto", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTR_DEKU_TREE_ENTRANCE, ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE, SINGLE_SCENE_INFO(SCENE_KOKIRI_FOREST), "KF Outside Deku Tree", "Deku Tree Entrance", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE, ENTR_LINKS_HOUSE_1, SINGLE_SCENE_INFO(SCENE_LINKS_HOUSE), "Link's House", "KF Link's House Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, ""}, + { ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE, ENTR_MIDOS_HOUSE_0, SINGLE_SCENE_INFO(SCENE_MIDOS_HOUSE), "Mido's House", "KF Mido's House Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, ""}, + { ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE, ENTR_SARIAS_HOUSE_0, SINGLE_SCENE_INFO(SCENE_SARIAS_HOUSE), "Saria's House", "KF Saria's House Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, ""}, + { ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE, ENTR_TWINS_HOUSE_0, SINGLE_SCENE_INFO(SCENE_TWINS_HOUSE), "House of Twins", "KF House of Twins Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, ""}, + { ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE, ENTR_KNOW_IT_ALL_BROS_HOUSE_0, SINGLE_SCENE_INFO(SCENE_KNOW_IT_ALL_BROS_HOUSE), "Know-It-All House", "KF Know-It-All House Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, ""}, + { ENTR_KOKIRI_FOREST_OUTSIDE_SHOP, ENTR_KOKIRI_SHOP_0, SINGLE_SCENE_INFO(SCENE_KOKIRI_SHOP), "Kokiri Shop", "KF Shop Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_INTERIOR, ""}, + { ENTRANCE_GROTTO_EXIT(GROTTO_KF_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_KF_STORMS_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "KF Storms Grotto", "KF Storms Grotto Entry", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_GROTTO, "chest"}, + { ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE, ENTR_DEKU_TREE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_DEKU_TREE), "Deku Tree Entrance", "KF Outside Deku Tree", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_DUNGEON, ""}, + { ENTR_DEKU_TREE_BOSS_ENTRANCE, ENTR_DEKU_TREE_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_DEKU_TREE), "Deku Tree Boss Door", "Gohma", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_DEKU_TREE_BOSS_DOOR, ENTR_DEKU_TREE_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_DEKU_TREE_BOSS), "Gohma", "Deku Tree Boss Door", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_DEKU_TREE_BOSS), "Gohma Blue Warp", "Deku Tree Blue Warp", ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_ONE_WAY, "bw", 1}, + + // Lost Woods + { ENTR_KOKIRI_FOREST_LOWER_EXIT, ENTR_LOST_WOODS_BRIDGE_EAST_EXIT, SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "Lost Woods Bridge East Exit", "Kokiri Forest Lower Exit", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTR_HYRULE_FIELD_WOODED_EXIT, ENTR_LOST_WOODS_BRIDGE_WEST_EXIT, SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "Lost Woods Bridge West Exit", "Hyrule Field Wooded Exit", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "lw,hf"}, + { ENTR_KOKIRI_FOREST_UPPER_EXIT, ENTR_LOST_WOODS_SOUTH_EXIT, SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "Lost Woods South Exit", "Kokiri Forest Upper Exit", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_KOKIRI_FOREST, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTR_GORON_CITY_TUNNEL_SHORTCUT, ENTR_LOST_WOODS_TUNNEL_SHORTCUT, SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "Lost Woods Tunnel Shortcut", "Goron City Tunnel Shortcut", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_OVERWORLD, "lw,gc"}, + { ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT, ENTR_LOST_WOODS_UNDERWATER_SHORTCUT, SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "Lost Woods Underwater Shortcut", "Zora's River Underwater Shortcut", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT, ENTR_LOST_WOODS_NORTH_EXIT, SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "Lost Woods North Exit", "Sacred Forest Meadow South Exit", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTRANCE_GROTTO_LOAD(GROTTO_LW_NEAR_SHORTCUTS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_LW_NEAR_SHORTCUTS_OFFSET), SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "LW Tunnel Grotto Entry", "LW Tunnel Grotto", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_GROTTO, "lw,chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_LW_SCRUBS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_LW_SCRUBS_OFFSET), SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "LW North Grotto Entry", "LW Deku Scrub Grotto", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_GROTTO, "lw,scrubs", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_LW_DEKU_THEATRE_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_LW_DEKU_THEATRE_OFFSET), SINGLE_SCENE_INFO(SCENE_LOST_WOODS), "LW Meadow Grotto Entry", "Deku Theater", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_GROTTO, "lw,mask,stage", 1}, + { ENTRANCE_GROTTO_EXIT(GROTTO_LW_NEAR_SHORTCUTS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_LW_NEAR_SHORTCUTS_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "LW Tunnel Grotto", "LW Tunnel Grotto Entry", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_GROTTO, "lw,chest"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_LW_SCRUBS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_LW_SCRUBS_OFFSET), {{ SCENE_GROTTOS, 0x07 }}, "LW Deku Scrub Grotto", "LW North Grotto Entry", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_GROTTO, "lw,scrubs"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_LW_DEKU_THEATRE_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_LW_DEKU_THEATRE_OFFSET), {{ SCENE_GROTTOS, 0x0C }}, "Deku Theater", "LW Meadow Grotto Entry", ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_GROTTO, "lw,mask,stage"}, + + // Sacred Forest Meadow + { ENTR_LOST_WOODS_NORTH_EXIT, ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT, SINGLE_SCENE_INFO(SCENE_SACRED_FOREST_MEADOW), "Sacred Forest Meadow South Exit", "Lost Woods North Exit", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTRANCE_GROTTO_LOAD(GROTTO_SFM_WOLFOS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_SFM_WOLFOS_OFFSET), SINGLE_SCENE_INFO(SCENE_SACRED_FOREST_MEADOW), "SFM Wolfos Grotto Entry", "SFM Wolfos Grotto", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_SFM_FAIRY_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_SFM_FAIRY_OFFSET), SINGLE_SCENE_INFO(SCENE_SACRED_FOREST_MEADOW), "SFM Fairy Grotto Entry", "SFM Fairy Grotto", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_SFM_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_SFM_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_SACRED_FOREST_MEADOW), "SFM Storms Grotto Entry", "SFM Deku Scrub Grotto", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTR_FOREST_TEMPLE_ENTRANCE, ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE, SINGLE_SCENE_INFO(SCENE_SACRED_FOREST_MEADOW), "Sacred Forest Meadow Outside Forest Temple", "Forest Temple Entrance", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTRANCE_GROTTO_EXIT(GROTTO_SFM_WOLFOS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_SFM_WOLFOS_OFFSET), {{ SCENE_GROTTOS, 0x08 }}, "SFM Wolfos Grotto", "SFM Wolfos Grotto Entry", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_GROTTO}, + { ENTRANCE_GROTTO_EXIT(GROTTO_SFM_FAIRY_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_SFM_FAIRY_OFFSET), {{ SCENE_FAIRYS_FOUNTAIN, 0x00 }}, "SFM Fairy Grotto", "SFM Fairy Grotto Entry", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_GROTTO}, + { ENTRANCE_GROTTO_EXIT(GROTTO_SFM_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_SFM_STORMS_OFFSET), {{ SCENE_GROTTOS, 0x0A }}, "SFM Deku Scrub Grotto", "SFM Storms Grotto Entry", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_GROTTO, "scrubs"}, + { ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE, ENTR_FOREST_TEMPLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_FOREST_TEMPLE), "Forest Temple Entrance", "Sacred Forest Meadow Outside Forest Temple", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_DUNGEON}, + { ENTR_FOREST_TEMPLE_BOSS_ENTRANCE, ENTR_FOREST_TEMPLE_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_FOREST_TEMPLE), "Forest Temple Boss Door", "Phantom Ganon", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_FOREST_TEMPLE_BOSS_DOOR, ENTR_FOREST_TEMPLE_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_FOREST_TEMPLE_BOSS), "Phantom Ganon", "Forest Temple Boss Door", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_FOREST_TEMPLE_BOSS), "Phantom Ganon Blue Warp", "Forest Temple Blue Warp", ENTRANCE_GROUP_SFM, ENTRANCE_GROUP_SFM, ENTRANCE_TYPE_ONE_WAY, "bw", 1}, + + // Kakariko Village + { ENTR_HYRULE_FIELD_STAIRS_EXIT, ENTR_KAKARIKO_VILLAGE_FRONT_GATE, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kakariko Front Gate", "Hyrule Field Stairs Exit", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_GRAVEYARD_ENTRANCE, ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kakariko Southeast Exit", "Graveyard Entrance", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT, ENTR_KAKARIKO_VILLAGE_GUARD_GATE, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kakariko Guard Gate Exit", "Death Mountain Trail Bottom Exit", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0, ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Boss House Entry", "Carpenter Boss House", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_HOUSE_OF_SKULLTULA_0, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Skulltula House Entry", "House of Skulltula", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_IMPAS_HOUSE_FRONT, ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Impa's House Front Entry", "Impa's House Front", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_IMPAS_HOUSE_BACK, ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Impa's House Back Entry", "Impa's House Back", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "cow", 1}, + { ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL, ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Windmill Entry", "Windmill", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_SHOOTING_GALLERY_0, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Shooting Gallery Entry", "Kak Shooting Gallery", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "adult", 1}, + { ENTR_POTION_SHOP_GRANNY_0, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Granny's Potion Shop Entry", "Granny's Potion Shop", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_BAZAAR_0, ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Bazaar Entry", "Kak Bazaar", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "shop", 1}, + { ENTR_POTION_SHOP_KAKARIKO_FRONT, ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Potion Shop Front Entry", "Kak Potion Shop Front", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_POTION_SHOP_KAKARIKO_BACK, ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Potion Shop Back Entry", "Kak Potion Shop Back", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_KAK_OPEN_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_KAK_OPEN_OFFSET), SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Open Grotto Entry", "Kak Open Grotto", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_KAK_REDEAD_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_KAK_REDEAD_OFFSET), SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kak Center Grotto Entry", "Kak Redead Grotto", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTR_BOTTOM_OF_THE_WELL_ENTRANCE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL, SINGLE_SCENE_INFO(SCENE_KAKARIKO_VILLAGE), "Kakariko Outside the Well", "Bottom of the Well Entrance", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_DUNGEON, "botw", 1}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE, ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0, SINGLE_SCENE_INFO(SCENE_KAKARIKO_CENTER_GUEST_HOUSE), "Carpenter Boss House", "Kak Boss House Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE, ENTR_HOUSE_OF_SKULLTULA_0, SINGLE_SCENE_INFO(SCENE_HOUSE_OF_SKULLTULA), "House of Skulltula", "Kak Skulltula House Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT, ENTR_IMPAS_HOUSE_FRONT, SINGLE_SCENE_INFO(SCENE_IMPAS_HOUSE), "Impa's House Front", "Kak Impa's House Front Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK, ENTR_IMPAS_HOUSE_BACK, SINGLE_SCENE_INFO(SCENE_IMPAS_HOUSE), "Impa's House Back", "Kak Impa's House Back Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "cow"}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL, ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL, SINGLE_SCENE_INFO(SCENE_WINDMILL_AND_DAMPES_GRAVE), "Windmill", "Kak Windmill Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY, ENTR_SHOOTING_GALLERY_0, {{ SCENE_SHOOTING_GALLERY, 0x00 }}, "Kak Shooting Gallery", "Kak Shooting Gallery Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY, ENTR_POTION_SHOP_GRANNY_0, SINGLE_SCENE_INFO(SCENE_POTION_SHOP_GRANNY), "Granny's Potion Shop", "Kak Granny's Potion Shop Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR, ENTR_BAZAAR_0, {{ SCENE_BAZAAR, 0x00 }}, "Kak Bazaar", "Kak Bazaar Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR, "shop"}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT, ENTR_POTION_SHOP_KAKARIKO_FRONT, SINGLE_SCENE_INFO(SCENE_POTION_SHOP_KAKARIKO), "Kak Potion Shop Front", "Kak Potion Shop Front Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK, ENTR_POTION_SHOP_KAKARIKO_BACK, SINGLE_SCENE_INFO(SCENE_POTION_SHOP_KAKARIKO), "Kak Potion Shop Back", "Kak Potion Shop Back Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_INTERIOR}, + { ENTRANCE_GROTTO_EXIT(GROTTO_KAK_OPEN_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_KAK_OPEN_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "Kak Open Grotto", "Kak Open Grotto Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_GROTTO, "chest"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_KAK_REDEAD_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_KAK_REDEAD_OFFSET), {{ SCENE_GROTTOS, 0x03 }}, "Kak Redead Grotto", "Kak Center Grotto Entry", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_GROTTO, "chest"}, + { ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL, ENTR_BOTTOM_OF_THE_WELL_ENTRANCE, SINGLE_SCENE_INFO(SCENE_BOTTOM_OF_THE_WELL), "Bottom of the Well Entrance", "Kakariko Outside the Well", ENTRANCE_GROUP_KAKARIKO, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_DUNGEON, "botw"}, + + // The Graveyard + { ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT, ENTR_GRAVEYARD_ENTRANCE, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "Graveyard Entrance", "Kakariko Southeast Exit", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_GRAVEKEEPERS_HUT_0, ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "GY Dampe's Hut Entry", "Dampe's Hut", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0, ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "GY Near-Hut Grave Entry", "Shield Grave", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTR_REDEAD_GRAVE_0, ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "GY Near-Tomb Grave Entry", "Heart Piece Grave", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTR_ROYAL_FAMILYS_TOMB_0, ENTR_GRAVEYARD_ROYAL_TOMB_EXIT, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "GY Royal Family's Tomb Entry", "Royal Family's Tomb", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE, ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "GY Near-Ledge Grave Entry", "Dampe's Grave", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO, "race", 1}, + { ENTR_SHADOW_TEMPLE_ENTRANCE, ENTR_GRAVEYARD_OUTSIDE_TEMPLE, SINGLE_SCENE_INFO(SCENE_GRAVEYARD), "Graveyard Outside Temple", "Shadow Temple Entrance", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT, ENTR_GRAVEKEEPERS_HUT_0, SINGLE_SCENE_INFO(SCENE_GRAVEKEEPERS_HUT), "Dampe's Hut", "GY Dampe's Hut Entry", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_INTERIOR}, + { ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT, ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0, SINGLE_SCENE_INFO(SCENE_GRAVE_WITH_FAIRYS_FOUNTAIN), "Shield Grave", "GY Near-Hut Grave Entry", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO}, + { ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT, ENTR_REDEAD_GRAVE_0, SINGLE_SCENE_INFO(SCENE_REDEAD_GRAVE), "Heart Piece Grave", "GY Near-Tomb Grave Entry", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO}, + { ENTR_GRAVEYARD_ROYAL_TOMB_EXIT, ENTR_ROYAL_FAMILYS_TOMB_0, SINGLE_SCENE_INFO(SCENE_ROYAL_FAMILYS_TOMB), "Royal Family's Tomb", "GY Royal Family's Tomb Entry", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO}, + { ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT, ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE, SINGLE_SCENE_INFO(SCENE_WINDMILL_AND_DAMPES_GRAVE), "Dampe's Grave", "GY Near-Ledge Grave Entry", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_GROTTO, "race"}, + { ENTR_GRAVEYARD_OUTSIDE_TEMPLE, ENTR_SHADOW_TEMPLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_SHADOW_TEMPLE), "Shadow Temple Entrance", "Graveyard Outside Temple", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_DUNGEON}, + { ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE, ENTR_SHADOW_TEMPLE_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_SHADOW_TEMPLE), "Shadow Temple Boss Door", "Bongo-Bongo", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_SHADOW_TEMPLE_BOSS_DOOR, ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_SHADOW_TEMPLE_BOSS), "Bongo-Bongo", "Shadow Temple Boss Door", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_SHADOW_TEMPLE_BOSS), "Bongo-Bongo Blue Warp", "Shadow Temple Blue Warp", ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_GROUP_GRAVEYARD, ENTRANCE_TYPE_ONE_WAY, "bw", 1}, + + // Death Mountain Trail + { ENTR_GORON_CITY_UPPER_EXIT, ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "Death Mountain Trail Middle Exit", "Goron City Upper Exit", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_OVERWORLD, "gc"}, + { ENTR_KAKARIKO_VILLAGE_GUARD_GATE, ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "Death Mountain Trail Bottom Exit", "Kakariko Guard Gate Exit", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT, ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "Death Mountain Trail Top Exit", "Death Mountain Crater Upper Exit", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT, ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "DMT Great Fairy Entry", "DMT Great Fairy Fountain", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_DMT_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_DMT_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "DMT Rock Circle Grotto Entry", "DMT Storms Grotto", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_DMT_COW_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_DMT_COW_OFFSET), SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "DMT Boulder Grotto Entry", "DMT Cow Grotto", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTR_DODONGOS_CAVERN_ENTRANCE, ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_TRAIL), "Death Mountain Trail Outside Dodongo's Cavern", "Dodongo's Cavern Entrance", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_DUNGEON, "dc", 1}, + { ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, 0x00 }}, "DMT Great Fairy Fountain", "DMT Great Fairy Entry", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_INTERIOR}, + { ENTRANCE_GROTTO_EXIT(GROTTO_DMT_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_DMT_STORMS_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "DMT Storms Grotto", "DMT Rock Circle Grotto Entry", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_GROTTO, "chest"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_DMT_COW_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_DMT_COW_OFFSET), {{ SCENE_GROTTOS, 0x0D }}, "DMT Cow Grotto", "DMT Boulder Grotto Entry", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_GROTTO}, + { ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, ENTR_DODONGOS_CAVERN_ENTRANCE, SINGLE_SCENE_INFO(SCENE_DODONGOS_CAVERN), "Dodongo's Cavern Entrance", "Death Mountain Trail Outside Dodongo's Cavern", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_DUNGEON, "dc"}, + { ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, ENTR_DODONGOS_CAVERN_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_DODONGOS_CAVERN), "Dodongo's Cavern Boss Door", "King Dodongo", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_DUNGEON, "dc", 1}, + { ENTR_DODONGOS_CAVERN_BOSS_DOOR, ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_DODONGOS_CAVERN_BOSS), "King Dodongo", "Dodongo's Cavern Boss Door", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_DUNGEON, "dc", 1}, + { ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_DODONGOS_CAVERN_BOSS), "King Dodongo Blue Warp", "Dodongo's Cavern Blue Warp", ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_ONE_WAY, "dc,bw", 1}, + + // Death Mountain Crater + { ENTR_GORON_CITY_DARUNIA_ROOM_EXIT, ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_CRATER), "Death Mountain Crater Bridge Exit", "Goron City Darunia's Room Backdoor", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_OVERWORLD, "gc"}, + { ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT, ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_CRATER), "Death Mountain Crater Upper Exit", "Death Mountain Trail Top Exit", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC, ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_CRATER), "DMC Great Fairy Entry", "DMC Great Fairy Fountain", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_DMC_UPPER_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_DMC_UPPER_OFFSET), SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_CRATER), "DMC Upper Grotto Entry", "DMC Upper Grotto", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_DMC_HAMMER_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_DMC_HAMMER_OFFSET), SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_CRATER), "DMC Hammer Grotto Entry", "DMC Deku Scrub Grotto", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTR_FIRE_TEMPLE_ENTRANCE, ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE, SINGLE_SCENE_INFO(SCENE_DEATH_MOUNTAIN_CRATER), "Death Mountain Crater Outside Temple", "Fire Temple Entrance", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, 0x01 }}, "DMC Great Fairy Fountain", "DMC Great Fairy Entry", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_INTERIOR}, + { ENTRANCE_GROTTO_EXIT(GROTTO_DMC_UPPER_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_DMC_UPPER_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "DMC Upper Grotto", "DMC Upper Grotto Entry", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_GROTTO, "chest"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_DMC_HAMMER_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_DMC_HAMMER_OFFSET), {{ SCENE_GROTTOS, 0x04 }}, "DMC Deku Scrub Grotto", "DMC Hammer Grotto Entry", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_GROTTO, "scrubs"}, + { ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE, ENTR_FIRE_TEMPLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_FIRE_TEMPLE), "Fire Temple Entrance", "Death Mountain Crater Outside Temple", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_DUNGEON}, + { ENTR_FIRE_TEMPLE_BOSS_ENTRANCE, ENTR_FIRE_TEMPLE_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_FIRE_TEMPLE), "Fire Temple Boss Door", "Volvagia", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_FIRE_TEMPLE_BOSS_DOOR, ENTR_FIRE_TEMPLE_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_FIRE_TEMPLE_BOSS), "Volvagia", "Fire Temple Boss Door", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_FIRE_TEMPLE_BOSS), "Volvagia Blue Warp", "Fire Temple Blue Warp", ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_ONE_WAY, "bw", 1}, + + // Goron City + { ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT, ENTR_GORON_CITY_UPPER_EXIT, SINGLE_SCENE_INFO(SCENE_GORON_CITY), "Goron City Upper Exit", "Death Mountain Trail Middle Exit", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL, ENTRANCE_TYPE_OVERWORLD, "gc"}, + { ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT, ENTR_GORON_CITY_DARUNIA_ROOM_EXIT, SINGLE_SCENE_INFO(SCENE_GORON_CITY), "Goron City Darunia's Room Backdoor", "Death Mountain Crater Bridge Exit", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER, ENTRANCE_TYPE_OVERWORLD, "gc"}, + { ENTR_LOST_WOODS_TUNNEL_SHORTCUT, ENTR_GORON_CITY_TUNNEL_SHORTCUT, SINGLE_SCENE_INFO(SCENE_GORON_CITY), "Goron City Tunnel Shortcut", "Lost Woods Tunnel Shortcut", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_OVERWORLD, "gc,lw"}, + { ENTR_GORON_SHOP_0, ENTR_GORON_CITY_OUTSIDE_SHOP, SINGLE_SCENE_INFO(SCENE_GORON_CITY), "GC Shop Entry", "Goron Shop", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_INTERIOR, "gc", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_GORON_CITY_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_GORON_CITY_OFFSET), SINGLE_SCENE_INFO(SCENE_GORON_CITY), "GC Lava Grotto Entry", "GC Deku Scrub Grotto", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_GROTTO, "gc,scrubs", 1}, + { ENTR_GORON_CITY_OUTSIDE_SHOP, ENTR_GORON_SHOP_0, SINGLE_SCENE_INFO(SCENE_GORON_SHOP), "Goron Shop", "GC Shop Entry", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_INTERIOR, "gc"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_GORON_CITY_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_GORON_CITY_OFFSET), {{ SCENE_GROTTOS, 0x04 }}, "GC Deku Scrub Grotto", "GC Lava Grotto Entry", ENTRANCE_GROUP_GORON_CITY, ENTRANCE_GROUP_GORON_CITY, ENTRANCE_TYPE_GROTTO, "gc,scrubs"}, + + // Zora's River + { ENTR_HYRULE_FIELD_RIVER_EXIT, ENTR_ZORAS_RIVER_WEST_EXIT, SINGLE_SCENE_INFO(SCENE_ZORAS_RIVER), "Zora's River Lower Exit", "Hyrule Field River Exit", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_LOST_WOODS_UNDERWATER_SHORTCUT, ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT, SINGLE_SCENE_INFO(SCENE_ZORAS_RIVER), "Zora's River Underwater Shortcut", "Lost Woods Underwater Shortcut", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_OVERWORLD, "lw"}, + { ENTR_ZORAS_DOMAIN_ENTRANCE, ENTR_ZORAS_RIVER_WATERFALL_EXIT, SINGLE_SCENE_INFO(SCENE_ZORAS_RIVER), "Zora's River Waterfall Exit", "Zora's Domain Entrance", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_OVERWORLD}, + { ENTRANCE_GROTTO_LOAD(GROTTO_ZR_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_ZR_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_ZORAS_RIVER), "ZR Rock Circle Grotto Entry", "ZR Deku Scrub Grotto", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_ZR_FAIRY_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_ZR_FAIRY_OFFSET), SINGLE_SCENE_INFO(SCENE_ZORAS_RIVER), "ZR Raised Boulder Grotto Entry", "ZR Fairy Grotto", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_ZR_OPEN_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_ZR_OPEN_OFFSET), SINGLE_SCENE_INFO(SCENE_ZORAS_RIVER), "ZR Raised Open Grotto Entry", "ZR Open Grotto", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_EXIT(GROTTO_ZR_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_ZR_STORMS_OFFSET), {{ SCENE_GROTTOS, 0x0A }}, "ZR Deku Scrub Grotto", "ZR Rock Circle Grotto Entry", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_GROTTO, "scrubs"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_ZR_FAIRY_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_ZR_FAIRY_OFFSET), {{ SCENE_FAIRYS_FOUNTAIN, 0x00 }}, "ZR Fairy Grotto", "ZR Raised Boulder Grotto Entry", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_GROTTO}, + { ENTRANCE_GROTTO_EXIT(GROTTO_ZR_OPEN_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_ZR_OPEN_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "ZR Open Grotto", "ZR Raised Open Grotto Entry", ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_GROTTO, "chest"}, + + // Zora's Domain + { ENTR_ZORAS_RIVER_WATERFALL_EXIT, ENTR_ZORAS_DOMAIN_ENTRANCE, SINGLE_SCENE_INFO(SCENE_ZORAS_DOMAIN), "Zora's Domain Entrance", "Zora's River Waterfall Exit", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT, ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT, SINGLE_SCENE_INFO(SCENE_ZORAS_DOMAIN), "Zora's Domain Underwater Shortcut", "Lake Hylia Underwater Shortcut", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_OVERWORLD, "lh"}, + { ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT, ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT, SINGLE_SCENE_INFO(SCENE_ZORAS_DOMAIN), "Zora's Domain Behind King Zora", "Zora's Fountain Tunnel Exit", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_ZORA_SHOP_0, ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP, SINGLE_SCENE_INFO(SCENE_ZORAS_DOMAIN), "ZD Shop Entry", "Zora Shop", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_ZD_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_ZD_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_ZORAS_DOMAIN), "ZD Island Grotto Entry", "ZD Fairy Grotto", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_GROTTO, "fairy", 1}, + { ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP, ENTR_ZORA_SHOP_0, SINGLE_SCENE_INFO(SCENE_ZORA_SHOP), "Zora Shop", "ZD Shop Entry", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_INTERIOR}, + { ENTRANCE_GROTTO_EXIT(GROTTO_ZD_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_ZD_STORMS_OFFSET), {{ SCENE_FAIRYS_FOUNTAIN, 0x00 }}, "ZD Fairy Grotto", "ZD Island Grotto Entry", ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_GROTTO, "fairy"}, + + // Zora's Fountain + { ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT, ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT, SINGLE_SCENE_INFO(SCENE_ZORAS_FOUNTAIN), "Zora's Fountain Tunnel Exit", "Zora's Domain Behind King Zora", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF, ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY, SINGLE_SCENE_INFO(SCENE_ZORAS_FOUNTAIN), "ZF Great Fairy Entry", "ZF Great Fairy Fountain", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_JABU_JABU_ENTRANCE, ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU, SINGLE_SCENE_INFO(SCENE_ZORAS_FOUNTAIN), "Zora's Fountain Outside Jabu Jabu", "Jabu Jabu's Belly Entrance", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_ICE_CAVERN_ENTRANCE, ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN, SINGLE_SCENE_INFO(SCENE_ZORAS_FOUNTAIN), "Zora's Fountain Outside Ice Cavern", "Ice Cavern Entrance", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS, 0x00 }}, "ZF Great Fairy Fountain", "ZF Great Fairy Entry", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_INTERIOR}, + { ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU, ENTR_JABU_JABU_ENTRANCE, SINGLE_SCENE_INFO(SCENE_JABU_JABU), "Jabu Jabu's Belly Entrance", "Zora's Fountain Outside Jabu Jabu", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_DUNGEON}, + { ENTR_JABU_JABU_BOSS_ENTRANCE, ENTR_JABU_JABU_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_JABU_JABU), "Jabu Jabu's Belly Boss Door", "Barinade", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_JABU_JABU_BOSS_DOOR, ENTR_JABU_JABU_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_JABU_JABU_BOSS), "Barinade", "Jabu Jabu's Belly Boss Door", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_JABU_JABU_BOSS), "Barinade Blue Warp", "Jabu Jabu's Belly Blue Warp", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_ONE_WAY, "bw", 1}, + { ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN, ENTR_ICE_CAVERN_ENTRANCE, SINGLE_SCENE_INFO(SCENE_ICE_CAVERN), "Ice Cavern Entrance", "Zora's Fountain Outside Ice Cavern", ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_GROUP_ZORAS_FOUNTAIN, ENTRANCE_TYPE_DUNGEON}, + + // Hyrule Field + { ENTR_LOST_WOODS_BRIDGE_WEST_EXIT, ENTR_HYRULE_FIELD_WOODED_EXIT, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field Wooded Exit", "Lost Woods Bridge West Exit", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_LOST_WOODS, ENTRANCE_TYPE_OVERWORLD, "hf,lw"}, + { ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT, ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field Drawbridge Exit", "Market Entrance South Exit", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_LON_LON_RANCH_ENTRANCE, ENTR_HYRULE_FIELD_CENTER_EXIT, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field Center Exit", "Lon Lon Ranch Entrance", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_OVERWORLD, "hf,llr"}, + { ENTR_KAKARIKO_VILLAGE_FRONT_GATE, ENTR_HYRULE_FIELD_STAIRS_EXIT, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field Stairs Exit", "Kakariko Front Gate", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_KAKARIKO, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_ZORAS_RIVER_WEST_EXIT, ENTR_HYRULE_FIELD_RIVER_EXIT, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field River Exit", "Zora's River Lower Exit", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_ZORAS_RIVER, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_LAKE_HYLIA_NORTH_EXIT, ENTR_HYRULE_FIELD_FENCE_EXIT, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field Fence Exit", "Lake Hylia North Exit", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_OVERWORLD, "hf,lh"}, + { ENTR_GERUDO_VALLEY_EAST_EXIT, ENTR_HYRULE_FIELD_ROCKY_PATH, SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "Hyrule Field Rocky Path", "Gerudo Valley East Exit", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_MARKET_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_MARKET_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF Near Market Boulder Grotto Entry", "HF Near Market Boulder Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_KAK_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_KAK_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF Stone Bridge Tree Grotto Entry", "HF Stone Bridge Tree Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "spider", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_TEKTITE_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_TEKTITE_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF Northwest Tree Grotto Entry", "HF Tektite Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "water", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_FAIRY_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_FAIRY_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF Northwest Boulder Grotto Entry", "HF Fairy Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_COW_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_COW_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF West Rock Circle Grotto Entry", "HF Cow Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "webbed", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_OPEN_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_OPEN_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF South Open Grotto Entry", "HF Open Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_INSIDE_FENCE_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_INSIDE_FENCE_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF Fenced Grotto Entry", "HF Fenced Deku Scrub Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HF_SOUTHEAST_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HF_SOUTHEAST_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_FIELD), "HF Southeast Boulder Grotto Entry", "HF Southeast Grotto", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "chest", 1}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_MARKET_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_MARKET_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "HF Near Market Boulder Grotto", "HF Near Market Boulder Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_KAK_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_KAK_OFFSET), {{ SCENE_GROTTOS, 0x01 }}, "HF Stone Bridge Tree Grotto", "HF Stone Bridge Tree Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "spider"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_TEKTITE_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_TEKTITE_OFFSET), {{ SCENE_GROTTOS, 0x0B }}, "HF Tektite Grotto", "HF Northwest Tree Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "water"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_FAIRY_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_FAIRY_OFFSET), {{ SCENE_FAIRYS_FOUNTAIN, 0x00 }}, "HF Fairy Grotto", "HF Northwest Boulder Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_COW_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_COW_OFFSET), {{ SCENE_GROTTOS, 0x05 }}, "HF Cow Grotto", "HF West Rock Circle Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "webbed"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_OPEN_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_OPEN_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "HF Open Grotto", "HF South Open Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "chest"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_INSIDE_FENCE_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_INSIDE_FENCE_OFFSET), {{ SCENE_GROTTOS, 0x02 }}, "HF Fenced Deku Scrub Grotto", "HF Fenced Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "scrubs"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HF_SOUTHEAST_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HF_SOUTHEAST_OFFSET), {{ SCENE_GROTTOS, 0x00 }}, "HF Southeast Grotto", "HF Southeast Boulder Grotto Entry", ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_GROTTO, "chest"}, + + // Lon Lon Ranch + { ENTR_HYRULE_FIELD_CENTER_EXIT, ENTR_LON_LON_RANCH_ENTRANCE, SINGLE_SCENE_INFO(SCENE_LON_LON_RANCH), "Lon Lon Ranch Entrance", "Hyrule Field Center Exit", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_LON_LON_BUILDINGS_TALONS_HOUSE, ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE, SINGLE_SCENE_INFO(SCENE_LON_LON_RANCH), "LLR Talon's House Entry", "Talon's House", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_INTERIOR, "llr", 1}, + { ENTR_STABLE_0, ENTR_LON_LON_RANCH_OUTSIDE_STABLES, SINGLE_SCENE_INFO(SCENE_LON_LON_RANCH), "LLR Stables Entry", "LLR Stables", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_INTERIOR, "cow", 1}, + { ENTR_LON_LON_BUILDINGS_TOWER, ENTR_LON_LON_RANCH_OUTSIDE_TOWER, SINGLE_SCENE_INFO(SCENE_LON_LON_RANCH), "LLR Tower Entry", "LLR Tower", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_INTERIOR, "cow", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_LLR_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_LLR_OFFSET), SINGLE_SCENE_INFO(SCENE_LON_LON_RANCH), "LLR Grotto Entry", "LLR Deku Scrub Grotto", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE, ENTR_LON_LON_BUILDINGS_TALONS_HOUSE, {{ SCENE_LON_LON_BUILDINGS, 0x00 }}, "Talon's House", "LLR Talon's House Entry", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_INTERIOR, "llr"}, + { ENTR_LON_LON_RANCH_OUTSIDE_STABLES, ENTR_STABLE_0, SINGLE_SCENE_INFO(SCENE_STABLE), "LLR Stables", "LLR Stables Entry", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_INTERIOR, "cow"}, + { ENTR_LON_LON_RANCH_OUTSIDE_TOWER, ENTR_LON_LON_BUILDINGS_TOWER, {{ SCENE_LON_LON_BUILDINGS, 0x01 }}, "LLR Tower", "LLR Tower Entry", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_INTERIOR, "cow"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_LLR_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_LLR_OFFSET), {{ SCENE_GROTTOS, 0x04 }}, "LLR Deku Scrub Grotto", "LLR Grotto Entry", ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_GROUP_LON_LON_RANCH, ENTRANCE_TYPE_GROTTO, "scrubs"}, + + // Lake Hylia + { ENTR_HYRULE_FIELD_FENCE_EXIT, ENTR_LAKE_HYLIA_NORTH_EXIT, SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "Lake Hylia North Exit", "Hyrule Field Fence Exit", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "lh"}, + { ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT, ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT, SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "Lake Hylia Underwater Shortcut", "Zora's Domain Underwater Shortcut", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_ZORAS_DOMAIN, ENTRANCE_TYPE_OVERWORLD, "lh"}, + { ENTR_LAKESIDE_LABORATORY_0, ENTR_LAKE_HYLIA_OUTSIDE_LAB, SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "LH Lab Entry", "LH Lab", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_INTERIOR, "lh", 1}, + { ENTR_FISHING_POND_0, ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND, SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "LH Fishing Pond Entry", "Fishing Pond", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_INTERIOR, "lh", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_LH_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_LH_OFFSET), SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "LH Grave Grotto Entry", "LH Deku Scrub Grotto", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTR_WATER_TEMPLE_ENTRANCE, ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE, SINGLE_SCENE_INFO(SCENE_LAKE_HYLIA), "Lake Hylia Outside Temple", "Water Temple Entrance", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_DUNGEON, "lh", 1}, + { ENTR_LAKE_HYLIA_OUTSIDE_LAB, ENTR_LAKESIDE_LABORATORY_0, SINGLE_SCENE_INFO(SCENE_LAKESIDE_LABORATORY), "LH Lab", "LH Lab Entry", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_INTERIOR, "lh"}, + { ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND, ENTR_FISHING_POND_0, SINGLE_SCENE_INFO(SCENE_FISHING_POND), "Fishing Pond", "LH Fishing Pond Entry", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_INTERIOR, "lh"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_LH_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_LH_OFFSET), {{ SCENE_GROTTOS, 0x04 }}, "LH Deku Scrub Grotto", "LH Grave Grotto Entry", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_GROTTO, "lh,scrubs"}, + { ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE, ENTR_WATER_TEMPLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_WATER_TEMPLE), "Water Temple Entrance", "Lake Hylia Outside Temple", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_DUNGEON, "lh"}, + { ENTR_WATER_TEMPLE_BOSS_ENTRANCE, ENTR_WATER_TEMPLE_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_WATER_TEMPLE), "Water Temple Boss Door", "Morpha", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_DUNGEON, "lh", 1}, + { ENTR_WATER_TEMPLE_BOSS_DOOR, ENTR_WATER_TEMPLE_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_WATER_TEMPLE_BOSS), "Morpha", "Water Temple Boss Door", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_DUNGEON, "lh", 1}, + { ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_WATER_TEMPLE_BOSS), "Morpha Blue Warp", "Water Temple Blue Warp", ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_ONE_WAY, "lh,bw", 1}, + + // Gerudo Area + { ENTR_HYRULE_FIELD_ROCKY_PATH, ENTR_GERUDO_VALLEY_EAST_EXIT, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "Gerudo Valley East Exit", "Hyrule Field Rocky Path", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_GERUDOS_FORTRESS_EAST_EXIT, ENTR_GERUDO_VALLEY_WEST_EXIT, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "Gerudo Valley West Exit", "Gerudo Fortress East Exit", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_OVERWORLD, ""}, + { ENTR_LAKE_HYLIA_RIVER_EXIT, -1, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "Gerudo Valley River Exit", "Lake Hylia River Exit", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_OVERWORLD, "lh"}, + { ENTR_CARPENTERS_TENT_0, ENTR_GERUDO_VALLEY_OUTSIDE_TENT, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV Carpenters' Tent Entry", "Carpenters' Tent", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_GV_OCTOROK_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_GV_OCTOROK_OFFSET), SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV Silver Rock Grotto Entry", "GV Octorok Grotto", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_GV_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_GV_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV Behind Tent Grotto Entry", "GV Deku Scrub Grotto", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_GROTTO, "scrubs", 1}, + { ENTR_GERUDO_VALLEY_OUTSIDE_TENT, ENTR_CARPENTERS_TENT_0, SINGLE_SCENE_INFO(SCENE_CARPENTERS_TENT), "Carpenters' Tent", "GV Carpenters' Tent Entry", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_INTERIOR}, + { ENTRANCE_GROTTO_EXIT(GROTTO_GV_OCTOROK_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_GV_OCTOROK_OFFSET), {{ SCENE_GROTTOS, 0x06 }}, "GV Octorok Grotto", "GV Silver Rock Grotto Entry", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_GROTTO}, + { ENTRANCE_GROTTO_EXIT(GROTTO_GV_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_GV_STORMS_OFFSET), {{ SCENE_GROTTOS, 0x0A }}, "GV Deku Scrub Grotto", "GV Behind Tent Grotto Entry", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_GROTTO, "scrubs"}, + { ENTR_GERUDO_VALLEY_WEST_EXIT, ENTR_GERUDOS_FORTRESS_EAST_EXIT, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "Gerudo Fortress East Exit", "Gerudo Valley West Exit", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_OVERWORLD, ""}, + { ENTR_HAUNTED_WASTELAND_EAST_EXIT, ENTR_GERUDOS_FORTRESS_GATE_EXIT, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "Gerudo Fortress Gate Exit", "Haunted Wasteland East Exit", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_OVERWORLD, ""}, + { ENTRANCE_GROTTO_LOAD(GROTTO_GF_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_GF_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Storms Grotto Entry", "GF Fairy Grotto", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_GROTTO, "", 1}, + { ENTR_GERUDO_TRAINING_GROUND_ENTRANCE, ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Outside Training Ground", "Gerudo Training Ground Entrance", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_DUNGEON, "gtg", 1}, + { ENTRANCE_GROTTO_EXIT(GROTTO_GF_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_GF_STORMS_OFFSET), {{ SCENE_FAIRYS_FOUNTAIN, 0x00 }}, "GF Fairy Grotto", "GF Storms Grotto Entry", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_GROTTO, ""}, + { ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND, ENTR_GERUDO_TRAINING_GROUND_ENTRANCE, SINGLE_SCENE_INFO(SCENE_GERUDO_TRAINING_GROUND), "Gerudo Training Ground Entrance", "GF Outside Training Ground", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_DUNGEON, "gtg"}, + { ENTR_GERUDOS_FORTRESS_1, ENTR_THIEVES_HIDEOUT_0, {{ SCENE_THIEVES_HIDEOUT, 2 }}, "TH 1 Torch Cell Turn", "GF Outskirts", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_2, ENTR_THIEVES_HIDEOUT_1, {{ SCENE_THIEVES_HIDEOUT, 2 }}, "TH 1 Torch Cell", "GF Near Grotto East", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_3, ENTR_THIEVES_HIDEOUT_2, {{ SCENE_THIEVES_HIDEOUT, 3 }}, "TH Kitchen Corridor Lower", "GF Near Grotto North", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_4, ENTR_THIEVES_HIDEOUT_3, {{ SCENE_THIEVES_HIDEOUT, 3 }}, "TH Kitchen Corridor Upper", "GF Above GTG", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_5, ENTR_THIEVES_HIDEOUT_4, {{ SCENE_THIEVES_HIDEOUT, 4 }}, "TH Steep Slope Cell", "GF Near Grotto", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_6, ENTR_THIEVES_HIDEOUT_5, {{ SCENE_THIEVES_HIDEOUT, 4 }}, "TH Steep Slope Cell Two Ramps", "GF Bottom of Lower Vines", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_7, ENTR_THIEVES_HIDEOUT_6, {{ SCENE_THIEVES_HIDEOUT, 5 }}, "TH Double Cell Lower", "GF Above GTG Directly", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_8, ENTR_THIEVES_HIDEOUT_7, {{ SCENE_THIEVES_HIDEOUT, 5 }}, "TH Double Cell Upper", "GF Top of Lower Vines Across", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_9, ENTR_THIEVES_HIDEOUT_8, {{ SCENE_THIEVES_HIDEOUT, 3 }}, "TH Kitchen By Corridor", "GF Top of Lower Vines Near", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_10, ENTR_THIEVES_HIDEOUT_9, {{ SCENE_THIEVES_HIDEOUT, 3 }}, "TH Kitchen Opposite Corridor", "GF Near GS", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_11, ENTR_THIEVES_HIDEOUT_10, {{ SCENE_THIEVES_HIDEOUT, 0 }}, "TH Break Room", "GF Below Chest", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_12, ENTR_THIEVES_HIDEOUT_11, {{ SCENE_THIEVES_HIDEOUT, 0 }}, "TH Break Room Corridor", "GF Above Jail", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_GERUDOS_FORTRESS_13, ENTR_THIEVES_HIDEOUT_12, {{ SCENE_THIEVES_HIDEOUT, 1 }}, "TH Dead End Cell", "GF Below GS", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_0, ENTR_GERUDOS_FORTRESS_1, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Outskirts", "TH 1 Torch Cell Turn", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_1, ENTR_GERUDOS_FORTRESS_2, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Near Grotto East", "TH 1 Torch Cell", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_2, ENTR_GERUDOS_FORTRESS_3, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Near Grotto North", "TH Kitchen Corridor Lower", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_3, ENTR_GERUDOS_FORTRESS_4, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Above GTG", "TH Kitchen Corridor Upper", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_4, ENTR_GERUDOS_FORTRESS_5, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Near Grotto", "TH Steep Slope Cell", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_5, ENTR_GERUDOS_FORTRESS_6, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Bottom of Lower Vines", "TH Steep Slope Cell Two Ramps", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_6, ENTR_GERUDOS_FORTRESS_7, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Above GTG Directly", "TH Double Cell Lower", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_7, ENTR_GERUDOS_FORTRESS_8, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Top of Lower Vines Across", "TH Double Cell Upper", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_8, ENTR_GERUDOS_FORTRESS_9, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Top of Lower Vines Near", "TH Kitchen By Corridor", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_9, ENTR_GERUDOS_FORTRESS_10, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Near GS", "TH Kitchen Opposite Corridor", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_10, ENTR_GERUDOS_FORTRESS_11, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Below Chest", "TH Break Room", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_11, ENTR_GERUDOS_FORTRESS_12, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Above Jail", "TH Break Room Corridor", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + { ENTR_THIEVES_HIDEOUT_12, ENTR_GERUDOS_FORTRESS_13, SINGLE_SCENE_INFO(SCENE_GERUDOS_FORTRESS), "GF Below GS", "TH Dead End Cell", ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_FORTRESS}, + + // The Wasteland + { ENTR_GERUDOS_FORTRESS_GATE_EXIT, ENTR_HAUNTED_WASTELAND_EAST_EXIT, SINGLE_SCENE_INFO(SCENE_HAUNTED_WASTELAND), "Haunted Wasteland East Exit", "Gerudo Fortress Gate Exit", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_GERUDO_FORTRESS, ENTRANCE_TYPE_OVERWORLD, "hw,gf"}, + { ENTR_DESERT_COLOSSUS_EAST_EXIT, ENTR_HAUNTED_WASTELAND_WEST_EXIT, SINGLE_SCENE_INFO(SCENE_HAUNTED_WASTELAND), "Haunted Wasteland West Exit", "Desert Colossus East Exit", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_OVERWORLD, "dc,hw"}, + { ENTR_HAUNTED_WASTELAND_WEST_EXIT, ENTR_DESERT_COLOSSUS_EAST_EXIT, SINGLE_SCENE_INFO(SCENE_DESERT_COLOSSUS), "Desert Colossus East Exit", "Haunted Wasteland West Exit", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_OVERWORLD, "dc,hw"}, + { ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS, ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT, SINGLE_SCENE_INFO(SCENE_DESERT_COLOSSUS), "Colossus Great Fairy Entry", "Colossus Great Fairy Fountain", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_INTERIOR, "dc", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_COLOSSUS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_COLOSSUS_OFFSET), SINGLE_SCENE_INFO(SCENE_DESERT_COLOSSUS), "Colossus Grotto Entry", "Colossus Deku Scrub Grotto", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_GROTTO, "dc,scrubs", 1}, + { ENTR_SPIRIT_TEMPLE_ENTRANCE, ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, SINGLE_SCENE_INFO(SCENE_DESERT_COLOSSUS), "Colossus Outside Temple", "Spirit Temple Entrance", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_DUNGEON, "dc", 1}, + { ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS, 0x02 }}, "Colossus Great Fairy Fountain", "Colossus Great Fairy Entry", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_INTERIOR, "dc"}, + { ENTRANCE_GROTTO_EXIT(GROTTO_COLOSSUS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_COLOSSUS_OFFSET), {{ SCENE_GROTTOS, 0x0A }}, "Colossus Deku Scrub Grotto", "Colossus Grotto Entry", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_GROTTO, "dc,scrubs"}, + { ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, ENTR_SPIRIT_TEMPLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_SPIRIT_TEMPLE), "Spirit Temple Entrance", "Colossus Outside Temple", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_DUNGEON, "dc"}, + { ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE, ENTR_SPIRIT_TEMPLE_BOSS_DOOR, SINGLE_SCENE_INFO(SCENE_SPIRIT_TEMPLE), "Spirit Temple Boss Door", "Twinrova", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_SPIRIT_TEMPLE_BOSS_DOOR, ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE, SINGLE_SCENE_INFO(SCENE_SPIRIT_TEMPLE_BOSS), "Twinrova", "Spirit Temple Boss Door", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_DUNGEON, "", 1}, + { ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP, -1, SINGLE_SCENE_INFO(SCENE_SPIRIT_TEMPLE_BOSS), "Twinrova Blue Warp", "Spirit Temple Blue Warp", ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_GROUP_DESERT_COLOSSUS, ENTRANCE_TYPE_ONE_WAY, "bw", 1}, + + // Market + { ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN, ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT, {SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_DAY), SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_RUINS)}, "Market Entrance South Exit", "Hyrule Field Drawbridge Exit", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "hf"}, + { ENTR_MARKET_SOUTH_EXIT, ENTR_MARKET_ENTRANCE_NORTH_EXIT, {SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_DAY), SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_RUINS)}, "Market Entrance North Exit", "Market South Exit", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_MARKET_GUARD_HOUSE_0, ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE, {SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_DAY), SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_ENTRANCE_RUINS)}, "MK Entrance Guard House Entry", "Guard House", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "pots,poe", 1}, + { ENTR_MARKET_ENTRANCE_NORTH_EXIT, ENTR_MARKET_SOUTH_EXIT, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "Market South Exit", "Market Entrance North Exit", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_CASTLE_GROUNDS_SOUTH_EXIT, ENTR_MARKET_DAY_CASTLE_EXIT, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "Market Castle Exit", "Castle Grounds South Exit", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_OVERWORLD, "outside ganon's castle"}, + { ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT, ENTR_MARKET_DAY_TEMPLE_EXIT, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "Market Temple Exit", "ToT Courtyard Gossip Stones Exit", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_OVERWORLD}, + { ENTR_SHOOTING_GALLERY_1, ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Shooting Gallery Entry", "MK Shooting Gallery", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "child", 1}, + { ENTR_BOMBCHU_BOWLING_ALLEY_0, ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Bombchu Bowling Entry", "Bombchu Bowling", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_TREASURE_BOX_SHOP_0, ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Treasure Chest Game Entry", "Treasure Chest Game", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE, ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Man-in-Green House Entry", "Man-in-Green's House", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_HAPPY_MASK_SHOP_0, ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Mask Shop Entry", "Mask Shop", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_BAZAAR_1, ENTR_MARKET_DAY_OUTSIDE_BAZAAR, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Bazaar Entry", "MK Bazaar", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "shop", 1}, + { ENTR_POTION_SHOP_MARKET_0, ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Potion Shop Entry", "MK Potion Shop", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_BOMBCHU_SHOP_1, ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP, {SCENE_NO_SPAWN(SCENE_MARKET_DAY), SCENE_NO_SPAWN(SCENE_MARKET_NIGHT), SCENE_NO_SPAWN(SCENE_MARKET_RUINS), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_DAY), SCENE_NO_SPAWN(SCENE_BACK_ALLEY_NIGHT)}, "MK Bombchu Shop Entry", "Bombchu Shop", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE, ENTR_MARKET_GUARD_HOUSE_0, {{ SCENE_MARKET_GUARD_HOUSE }}, "Guard House", "MK Entrance Guard House Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "pots,poe"}, + { ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY, ENTR_SHOOTING_GALLERY_1, {{ SCENE_SHOOTING_GALLERY, 0x01 }}, "MK Shooting Gallery", "MK Shooting Gallery Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING, ENTR_BOMBCHU_BOWLING_ALLEY_0, SINGLE_SCENE_INFO(SCENE_BOMBCHU_BOWLING_ALLEY), "Bombchu Bowling", "MK Bombchu Bowling Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP, ENTR_TREASURE_BOX_SHOP_0, SINGLE_SCENE_INFO(SCENE_TREASURE_BOX_SHOP), "Treasure Chest Game", "MK Treasure Chest Game Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE, ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE, SINGLE_SCENE_INFO(SCENE_BACK_ALLEY_HOUSE), "Man-in-Green's House", "MK Man-in-Green House Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP, ENTR_HAPPY_MASK_SHOP_0, SINGLE_SCENE_INFO(SCENE_HAPPY_MASK_SHOP), "Mask Shop", "MK Mask Shop Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_MARKET_DAY_OUTSIDE_BAZAAR, ENTR_BAZAAR_1, {{ SCENE_BAZAAR, 0x01 }}, "MK Bazaar", "MK Bazaar Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "shop"}, + { ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP, ENTR_POTION_SHOP_MARKET_0, SINGLE_SCENE_INFO(SCENE_POTION_SHOP_MARKET), "MK Potion Shop", "MK Potion Shop Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP, ENTR_BOMBCHU_SHOP_1, SINGLE_SCENE_INFO(SCENE_BOMBCHU_SHOP), "Bombchu Shop", "MK Bombchu Shop Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR}, + { ENTR_MARKET_DAY_TEMPLE_EXIT, ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT, {SCENE_NO_SPAWN(SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY), SCENE_NO_SPAWN(SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT), SCENE_NO_SPAWN(SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS)}, "ToT Courtyard Gossip Stones Exit", "Market Temple Exit", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_OVERWORLD, "tot"}, + { ENTR_TEMPLE_OF_TIME_ENTRANCE, ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE, {SCENE_NO_SPAWN(SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY), SCENE_NO_SPAWN(SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT), SCENE_NO_SPAWN(SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS)}, "ToT Courtyard Temple Entry", "Temple of Time Entrance", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "tot", 1}, + { ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE, ENTR_TEMPLE_OF_TIME_ENTRANCE, SINGLE_SCENE_INFO(SCENE_TEMPLE_OF_TIME), "Temple of Time Entrance", "ToT Courtyard Temple Entry", ENTRANCE_GROUP_MARKET, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_INTERIOR, "tot"}, + + // Hyrule Castle + { ENTR_MARKET_DAY_CASTLE_EXIT, ENTR_CASTLE_GROUNDS_SOUTH_EXIT, {SCENE_NO_SPAWN(SCENE_HYRULE_CASTLE), SCENE_NO_SPAWN(SCENE_OUTSIDE_GANONS_CASTLE)}, "Castle Grounds South Exit", "Market Castle Exit", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_MARKET, ENTRANCE_TYPE_OVERWORLD, "outside ganon's castle"}, + { ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC, ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT, SINGLE_SCENE_INFO(SCENE_HYRULE_CASTLE), "HC Boulder Crawlspace", "HC Great Fairy Fountain", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_INTERIOR, "", 1}, + { ENTRANCE_GROTTO_LOAD(GROTTO_HC_STORMS_OFFSET), ENTRANCE_GROTTO_EXIT(GROTTO_HC_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_HYRULE_CASTLE), "HC Storms Grotto Entry", "HC Storms Grotto", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_GROTTO, "bombable", 1}, + { ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS, 0x01 }}, "HC Great Fairy Fountain", "HC Boulder Crawlspace", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_INTERIOR}, + { ENTRANCE_GROTTO_EXIT(GROTTO_HC_STORMS_OFFSET), ENTRANCE_GROTTO_LOAD(GROTTO_HC_STORMS_OFFSET), {{ SCENE_GROTTOS, 0x09 }}, "HC Storms Grotto", "HC Storms Grotto Entry", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_GROTTO, "bombable"}, + { ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD, ENTR_POTION_SHOP_KAKARIKO_1, SINGLE_SCENE_INFO(SCENE_OUTSIDE_GANONS_CASTLE), "OGC Behind Pillar", "OGC Great Fairy Fountain", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_INTERIOR, "outside ganon's castle", 1}, + { ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT, SINGLE_SCENE_INFO(SCENE_OUTSIDE_GANONS_CASTLE), "OGC Rainbow Bridge Exit", "Inside Ganon's Castle Entrance", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "outside ganon's castle,gc", 1}, + { ENTR_POTION_SHOP_KAKARIKO_1, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, 0x02 }}, "OGC Great Fairy Fountain", "OGC Behind Pillar", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_INTERIOR, "outside ganon's castle"}, + { ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT, ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_INSIDE_GANONS_CASTLE), "Inside Ganon's Castle Entrance", "OGC Rainbow Bridge Exit", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "outside ganon's castle,gc"}, + { ENTR_INSIDE_GANONS_CASTLE_1, ENTR_GANONS_TOWER_0, SINGLE_SCENE_INFO(SCENE_GANONS_TOWER), "Ganon's Tower Entrance", "Inside Ganon's Castle", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "gc"}, + { ENTR_GANONS_TOWER_0, ENTR_INSIDE_GANONS_CASTLE_1, SINGLE_SCENE_INFO(SCENE_INSIDE_GANONS_CASTLE), "Inside Ganon's Castle", "Ganon's Tower Entrance", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "gc"}, + { ENTR_OUTSIDE_GANONS_CASTLE_1_2, -1, SINGLE_SCENE_INFO(SCENE_OUTSIDE_GANONS_CASTLE), "Ganon's Blue Warp", "Ganon's Castle Blue Warp", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_ONE_WAY, "gc,bw", 1}, + + // clang-format on +}; + +// Check if Link is in the area and return that scene/entrance for tracking +int16_t LinkIsInArea(const EntranceData* entrance) { + bool result = false; + + if (gPlayState == nullptr) { + return -1; + } + + // Handle detecting the current grotto + if ((gPlayState->sceneNum == SCENE_FAIRYS_FOUNTAIN || gPlayState->sceneNum == SCENE_GROTTOS) && + entrance->type == ENTRANCE_TYPE_GROTTO) { + if (entrance->index == (ENTRANCE_GROTTO_EXIT_START + currentGrottoId)) { + // Return the grotto entrance for tracking + return entrance->index; + } else { + return -1; + } + } + + // Otherwise check all scenes/spawns + // Not all areas require a spawn position to differeniate between another area + for (auto info : entrance->scenes) { + // only check current scene when spawn info missing + if (info.spawn == -1) { + result = gPlayState->sceneNum == info.scene; + } else if (gPlayState->sceneNum == SCENE_THIEVES_HIDEOUT) { // group by rooms, not spawn + result = info.scene == SCENE_THIEVES_HIDEOUT && gPlayState->roomCtx.curRoom.num == info.spawn; + } else { // Otherwise just check scene & spawn + result = Entrance_SceneAndSpawnAre(info.scene, info.spawn); + } + + // Return the scene for tracking + if (result) { + return info.scene; + } + } + + return -1; +} + +bool IsEntranceDiscovered(s16 index) { + bool isDiscovered = Entrance_GetIsEntranceDiscovered(index); + if (!isDiscovered) { + // If the pair included one of the hyrule field <-> zora's river entrances, + // the randomizer will have also overridden the water-based entrances, so check those too + if ((index == ENTR_ZORAS_RIVER_WEST_EXIT && Entrance_GetIsEntranceDiscovered(ENTR_ZORAS_RIVER_3)) || + (index == ENTR_ZORAS_RIVER_3 && Entrance_GetIsEntranceDiscovered(ENTR_ZORAS_RIVER_WEST_EXIT))) { + isDiscovered = true; + } else if ((index == ENTR_HYRULE_FIELD_RIVER_EXIT && Entrance_GetIsEntranceDiscovered(ENTR_HYRULE_FIELD_14)) || + (index == ENTR_HYRULE_FIELD_14 && Entrance_GetIsEntranceDiscovered(ENTR_HYRULE_FIELD_RIVER_EXIT))) { + isDiscovered = true; + } + } + return isDiscovered; +} + +const EntranceData* GetEntranceData(s16 index) { + for (size_t i = 0; i < ARRAY_COUNT(entranceData); i++) { + if (index == entranceData[i].index) { + return &entranceData[i]; + } + } + // Shouldn't be reached + return nullptr; +} + +void LoadFromPreset(nlohmann::json info) { + presetLoaded = true; + presetPos = { info["pos"]["x"], info["pos"]["y"] }; + presetSize = { info["size"]["width"], info["size"]["height"] }; +} + +// Used for verifying the names on both sides of entrance pairs match. Keeping for ease of use for further name changes +// later +// TODO: Figure out how to remove the need for duplicate entrance names so this is no longer necessary +void CheckEntranceNames() { + SPDLOG_ERROR("Checking entrance names:"); + for (size_t i = 0; i < ARRAY_COUNT(entranceData); i++) { + auto entrance = &entranceData[i]; + auto reverse = GetEntranceData(entrance->reverseIndex); + if (entrance != nullptr && reverse != nullptr) { + if (entrance->source != reverse->destination) { + SPDLOG_ERROR("{}({}) -> {}({})", entrance->source, entrance->index, reverse->destination, + reverse->reverseIndex); + } + } + } +} + +void SortEntranceListByType(EntranceOverride* entranceList, u8 byDest) { + EntranceOverride tempList[ENTRANCE_OVERRIDES_MAX_COUNT] = { 0 }; + + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + tempList[i] = entranceList[i]; + } + + size_t idx = 0; + + for (size_t k = 0; k < ENTRANCE_TYPE_COUNT; k++) { + for (size_t i = 0; i < ARRAY_COUNT(entranceData); i++) { + for (size_t j = 0; j < ENTRANCE_OVERRIDES_MAX_COUNT; j++) { + if (Entrance_EntranceIsNull(&tempList[j])) { + break; + } + + int16_t entranceIndex = byDest ? tempList[j].override : tempList[j].index; + + if (entranceData[i].type == k && entranceIndex == entranceData[i].index) { + entranceList[idx] = tempList[j]; + idx++; + break; + } + } + } + } +} + +void SortEntranceListByArea(EntranceOverride* entranceList, u8 byDest) { + auto entranceCtx = Rando::Context::GetInstance()->GetEntranceShuffler(); + EntranceOverride tempList[ENTRANCE_OVERRIDES_MAX_COUNT] = { 0 }; + + // Store to temp + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + tempList[i] = entranceList[i]; + // Don't include one-way indexes in the tempList if we're sorting by destination + // so that we keep them at the beginning. + if (byDest) { + if (GetEntranceData(tempList[i].index)->srcGroup == ENTRANCE_GROUP_ONE_WAY) { + tempList[i] = emptyOverride; + } + } + } + + size_t idx = 0; + // Sort Source List based on entranceData order + if (!byDest) { + for (size_t i = 0; i < ARRAY_COUNT(entranceData); i++) { + for (size_t j = 0; j < ENTRANCE_OVERRIDES_MAX_COUNT; j++) { + if (Entrance_EntranceIsNull(&tempList[j])) { + break; + } + if (tempList[j].index == entranceData[i].index) { + entranceList[idx] = tempList[j]; + idx++; + break; + } + } + } + + } else { + // Increment the idx by however many one-way entrances are shuffled since these + // will still be displayed at the beginning + idx += gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_AREA][ENTRANCE_GROUP_ONE_WAY]; + + // Sort the rest of the Destination List by matching destination strings with source strings when possible + // and otherwise by group + for (size_t group = ENTRANCE_GROUP_KOKIRI_FOREST; group < SPOILER_ENTRANCE_GROUP_COUNT; group++) { + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + if (Entrance_EntranceIsNull(&entranceCtx->entranceOverrides[i])) { + continue; + } + const EntranceData* curEntrance = GetEntranceData(entranceCtx->entranceOverrides[i].index); + if (curEntrance->srcGroup != group) { + continue; + } + // First, search the list for the matching reverse entrance if it exists + for (size_t j = 0; j < ENTRANCE_OVERRIDES_MAX_COUNT; j++) { + const EntranceData* curOverride = GetEntranceData(tempList[j].override); + if (Entrance_EntranceIsNull(&tempList[j]) || curOverride->dstGroup != group) { + continue; + } + + if (curEntrance->reverseIndex == curOverride->index) { + entranceList[idx] = tempList[j]; + // "Remove" this entrance from the tempList by setting it's values to zero + tempList[j] = emptyOverride; + idx++; + break; + } + } + } + // Then find any remaining entrances in the same group and add them to the end + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + if (Entrance_EntranceIsNull(&tempList[i])) { + continue; + } + const EntranceData* curOverride = GetEntranceData(tempList[i].override); + if (curOverride->dstGroup == group) { + entranceList[idx] = tempList[i]; + tempList[i] = emptyOverride; + idx++; + } + } + } + } +} + +s16 GetLastEntranceOverride() { + return lastEntranceIndex; +} + +s16 GetCurrentGrottoId() { + return currentGrottoId; +} + +void SetCurrentGrottoIDForTracker(s16 entranceIndex) { + currentGrottoId = entranceIndex; +} + +void SetLastEntranceOverrideForTracker(s16 entranceIndex) { + lastEntranceIndex = entranceIndex; +} + +void ClearEntranceTrackingData() { + currentGrottoId = -1; + lastEntranceIndex = -1; + lastSceneOrEntranceDetected = -1; + gEntranceTrackingData = { 0 }; +} + +void InitEntranceTrackingData() { + auto entranceCtx = Rando::Context::GetInstance()->GetEntranceShuffler(); + gEntranceTrackingData = { 0 }; + + // Check if entrance randomization is disabled + if (!OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES)) { + return; + } + + // Set total and group counts + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + if (Entrance_EntranceIsNull(&entranceCtx->entranceOverrides[i])) { + break; + } + const EntranceData* index = GetEntranceData(entranceCtx->entranceOverrides[i].index); + const EntranceData* override = GetEntranceData(entranceCtx->entranceOverrides[i].override); + + if (index->srcGroup == ENTRANCE_GROUP_ONE_WAY) { + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_AREA][ENTRANCE_GROUP_ONE_WAY]++; + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_DESTINATION_AREA][ENTRANCE_GROUP_ONE_WAY]++; + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_TYPE][ENTRANCE_TYPE_ONE_WAY]++; + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_DESTINATION_TYPE][ENTRANCE_TYPE_ONE_WAY]++; + } else { + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_AREA][index->srcGroup]++; + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_DESTINATION_AREA][override->dstGroup]++; + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_TYPE][index->type]++; + gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_DESTINATION_TYPE][override->type]++; + } + gEntranceTrackingData.EntranceCount++; + } + + // The entrance data is sorted and grouped in a one dimensional array, so we need to track offsets + // Set offsets for areas starting at 0 + u16 srcOffsetTotal = 0; + u16 dstOffsetTotal = 0; + for (size_t i = 0; i < SPOILER_ENTRANCE_GROUP_COUNT; i++) { + // Set the offset for the current group + gEntranceTrackingData.GroupOffsets[ENTRANCE_SOURCE_AREA][i] = srcOffsetTotal; + gEntranceTrackingData.GroupOffsets[ENTRANCE_DESTINATION_AREA][i] = dstOffsetTotal; + // Increment the offset by the areas entrance count + srcOffsetTotal += gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_AREA][i]; + dstOffsetTotal += gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_DESTINATION_AREA][i]; + } + // Set offsets for types starting at 0 + srcOffsetTotal = 0; + dstOffsetTotal = 0; + for (size_t i = 0; i < ENTRANCE_TYPE_COUNT; i++) { + // Set the offset for the current group + gEntranceTrackingData.GroupOffsets[ENTRANCE_SOURCE_TYPE][i] = srcOffsetTotal; + gEntranceTrackingData.GroupOffsets[ENTRANCE_DESTINATION_TYPE][i] = dstOffsetTotal; + // Increment the offset by the areas entrance count + srcOffsetTotal += gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_SOURCE_TYPE][i]; + dstOffsetTotal += gEntranceTrackingData.GroupEntranceCounts[ENTRANCE_DESTINATION_TYPE][i]; + } + + // Sort entrances by group and type in entranceData + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + srcListSortedByArea[i] = entranceCtx->entranceOverrides[i]; + destListSortedByArea[i] = entranceCtx->entranceOverrides[i]; + srcListSortedByType[i] = entranceCtx->entranceOverrides[i]; + destListSortedByType[i] = entranceCtx->entranceOverrides[i]; + } + SortEntranceListByArea(srcListSortedByArea, 0); + SortEntranceListByArea(destListSortedByArea, 1); + SortEntranceListByType(srcListSortedByType, 0); + SortEntranceListByType(destListSortedByType, 1); +} + +void EntranceTrackerSettingsWindow::DrawElement() { + + ImGui::TextWrapped("The entrance tracker will only track shuffled entrances"); + Spacer(0); + + ImGui::TableNextColumn(); + SohGui::GetSohMenu()->MenuDrawItem(backgroundColorWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + SohGui::GetSohMenu()->MenuDrawItem(windowTypeWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + + if (CVarGetInteger(CVAR_TRACKER_ENTRANCE("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { + CVarCheckbox("Enable Dragging", CVAR_TRACKER_ENTRANCE("Draggable"), CheckboxOptions().Color(THEME_COLOR)); + CVarCheckbox("Only Enable While Paused", CVAR_TRACKER_ENTRANCE("ShowOnlyPaused"), + CheckboxOptions().Color(THEME_COLOR)); + CVarCombobox("Display Mode", CVAR_TRACKER_ENTRANCE("DisplayType"), showMode, + ComboboxOptions() + .LabelPosition(LabelPositions::Far) + .ComponentAlignment(ComponentAlignments::Right) + .Color(THEME_COLOR) + .DefaultIndex(0)); + if (CVarGetInteger(CVAR_TRACKER_ENTRANCE("DisplayType"), TRACKER_DISPLAY_ALWAYS) == + TRACKER_DISPLAY_COMBO_BUTTON) { + CVarCombobox("Combo Button 1", CVAR_TRACKER_ENTRANCE("ComboButton1"), buttonStrings, + ComboboxOptions() + .LabelPosition(LabelPositions::Far) + .ComponentAlignment(ComponentAlignments::Right) + .Color(THEME_COLOR) + .DefaultIndex(TRACKER_COMBO_BUTTON_L)); + CVarCombobox("Combo Button 2", CVAR_TRACKER_ENTRANCE("ComboButton2"), buttonStrings, + ComboboxOptions() + .LabelPosition(LabelPositions::Far) + .ComponentAlignment(ComponentAlignments::Right) + .Color(THEME_COLOR) + .DefaultIndex(TRACKER_COMBO_BUTTON_L)); + } + } + + if (ImGui::BeginTable("entranceTrackerSubSettings", 2, + ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("column 1", ImGuiTableColumnFlags_WidthStretch, 150.0f); + ImGui::TableSetupColumn("column 2", ImGuiTableColumnFlags_WidthStretch, 150.0f); + + ImGui::TableNextColumn(); + + ImGui::Text(LanguageManager::Instance().GetString("Sort By").c_str()); + CVarRadioButton( + LanguageManager::Instance().GetString("To").c_str(), CVAR_TRACKER_ENTRANCE("SortBy"), 0, + RadioButtonsOptions().Color(THEME_COLOR).Tooltip(LanguageManager::Instance().GetString("Sort entrances by the original source entrance").c_str())); + CVarRadioButton( + LanguageManager::Instance().GetString("From").c_str(), CVAR_TRACKER_ENTRANCE("SortBy"), 1, + RadioButtonsOptions().Color(THEME_COLOR).Tooltip(LanguageManager::Instance().GetString("Sort entrances by the overrided destination").c_str())); + + ImGui::Text(LanguageManager::Instance().GetString("List Items").c_str()); + CVarCheckbox(LanguageManager::Instance().GetString("Auto scroll").c_str(), CVAR_TRACKER_ENTRANCE("AutoScroll"), + CheckboxOptions() + .Tooltip(LanguageManager::Instance().GetString("Automatically scroll to the first available entrance in the current scene").c_str()) + .Color(THEME_COLOR)); + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); + CVarCheckbox( + LanguageManager::Instance().GetString("Highlight previous").c_str(), CVAR_TRACKER_ENTRANCE("HighlightPrevious"), + CheckboxOptions().Tooltip(LanguageManager::Instance().GetString("Highlight the previous entrance that Link came from").c_str()).Color(THEME_COLOR)); + CVarCheckbox( + LanguageManager::Instance().GetString("Highlight available").c_str(), CVAR_TRACKER_ENTRANCE("HighlightAvailable"), + CheckboxOptions().Tooltip(LanguageManager::Instance().GetString("Highlight available entrances in the current scene").c_str()).Color(THEME_COLOR)); + ImGui::EndDisabled(); + CVarCheckbox(LanguageManager::Instance().GetString("Hide undiscovered").c_str(), CVAR_TRACKER_ENTRANCE("CollapseUndiscovered"), + CheckboxOptions() + .Tooltip(LanguageManager::Instance().GetString("Collapse undiscovered entrances towards the bottom of each group").c_str()) + .Color(THEME_COLOR)); + bool disableHideReverseEntrances = + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; + static const char* disableHideReverseEntrancesText = + LanguageManager::Instance().GetString("This option is disabled because \"Decouple Entrances\" is enabled.").c_str(); + CVarCheckbox(LanguageManager::Instance().GetString("Hide reverse").c_str(), CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), + CheckboxOptions({ { .disabled = disableHideReverseEntrances, + .disabledTooltip = disableHideReverseEntrancesText } }) + .Tooltip(LanguageManager::Instance().GetString("Hide reverse entrance transitions when Decouple Entrances is off").c_str()) + .DefaultValue(true) + .Color(THEME_COLOR)); + + ImGui::TableNextColumn(); + + ImGui::Text(LanguageManager::Instance().GetString("Group By").c_str()); + CVarRadioButton(LanguageManager::Instance().GetString("Area").c_str(), CVAR_TRACKER_ENTRANCE("GroupBy"), 0, + RadioButtonsOptions().Color(THEME_COLOR).Tooltip(LanguageManager::Instance().GetString("Group entrances by their area").c_str())); + CVarRadioButton(LanguageManager::Instance().GetString("Type").c_str(), CVAR_TRACKER_ENTRANCE("GroupBy"), 1, + RadioButtonsOptions().Color(THEME_COLOR).Tooltip(LanguageManager::Instance().GetString("Group entrances by their entrance type").c_str())); + + ImGui::Text(LanguageManager::Instance().GetString("Spoiler Reveal").c_str()); + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); + CVarCheckbox(LanguageManager::Instance().GetString("Show Source").c_str(), CVAR_TRACKER_ENTRANCE("ShowFrom"), + CheckboxOptions().Tooltip(LanguageManager::Instance().GetString("Reveal the source for undiscovered entrances").c_str()).Color(THEME_COLOR)); + CVarCheckbox(LanguageManager::Instance().GetString("Show Destination").c_str(), CVAR_TRACKER_ENTRANCE("ShowTo"), + CheckboxOptions().Tooltip(LanguageManager::Instance().GetString("Reveal the destination for undiscovered entrances").c_str()).Color(THEME_COLOR)); + ImGui::EndDisabled(); + ImGui::EndTable(); + } + + ImGui::SetNextItemOpen(false, ImGuiCond_Once); + if (ImGui::TreeNode(LanguageManager::Instance().GetString("Legend").c_str())) { + ImGui::TextColored(ImColor(COLOR_ORANGE), LanguageManager::Instance().GetString("Last Entrance").c_str()); + ImGui::TextColored(ImColor(COLOR_GREEN), LanguageManager::Instance().GetString("Available Entrances").c_str()); + ImGui::TextColored(ImColor(COLOR_GRAY), LanguageManager::Instance().GetString("Undiscovered Entrances").c_str()); + ImGui::TreePop(); + } +} + +void EntranceTrackerWindow::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +void EntranceTrackerWindow::DrawElement() { + Color_Background = CVarGetColor(CVAR_TRACKER_ENTRANCE("BgColor.Value"), Color_Bg_Default); + if (CVarGetInteger(CVAR_TRACKER_ENTRANCE("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { + if (CVarGetInteger(CVAR_TRACKER_ENTRANCE("ShowOnlyPaused"), 0) && + (gPlayState == nullptr || gPlayState->pauseCtx.state == 0)) { + return; + } + + if (CVarGetInteger(CVAR_TRACKER_ENTRANCE("DisplayType"), TRACKER_DISPLAY_ALWAYS) == + TRACKER_DISPLAY_COMBO_BUTTON) { + int comboButton1Mask = + buttons[CVarGetInteger(CVAR_TRACKER_ENTRANCE("ComboButton1"), TRACKER_COMBO_BUTTON_L)]; + int comboButton2Mask = + buttons[CVarGetInteger(CVAR_TRACKER_ENTRANCE("ComboButton2"), TRACKER_COMBO_BUTTON_R)]; + OSContPad* trackerButtonsPressed = + std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetControlDeck())->GetPads(); + bool comboButtonsHeld = trackerButtonsPressed != nullptr && + trackerButtonsPressed[0].button & comboButton1Mask && + trackerButtonsPressed[0].button & comboButton2Mask; + if (!comboButtonsHeld) { + return; + } + } + } + if (presetLoaded) { + ImGui::SetNextWindowSize(presetSize); + ImGui::SetNextWindowPos(presetPos); + presetLoaded = false; + } else { + ImGui::SetNextWindowSize(ImVec2(600, 375), ImGuiCond_FirstUseEver); + } + if (Trackers::BeginFloatWindows( + "Entrance Tracker", mIsVisible, Color_Background, + static_cast(CVarGetInteger(CVAR_TRACKER_ENTRANCE("WindowType"), TRACKER_WINDOW_WINDOW)), + CVarGetInteger(CVAR_TRACKER_ENTRANCE("Draggable"), 1), ImGuiWindowFlags_NoScrollbar)) { + if (!GameInteractor::IsSaveLoaded()) { + ImGui::Text(LanguageManager::Instance().GetString("Waiting for file load...").c_str()); // TODO Language + Trackers::EndFloatWindows(); + return; + } + + static ImGuiTextFilter locationSearch; + + uint8_t nextTreeState = 0; + if (Button("Collapse All", ButtonOptions({ { .tooltip = "Collapse all entrance groups" } }) + .Color(THEME_COLOR) + .Size(Sizes::Inline))) { + nextTreeState = 1; + } + ImGui::SameLine(); + if (Button("Expand All", ButtonOptions({ { .tooltip = "Expand all entrance groups" } }) + .Color(THEME_COLOR) + .Size(Sizes::Inline))) { + nextTreeState = 2; + } + ImGui::SameLine(); + if (Button("Clear", + ButtonOptions({ { .tooltip = "Clear the search field" } }).Color(THEME_COLOR).Size(Sizes::Inline))) { + locationSearch.Clear(); + } + + PushStyleCombobox(THEME_COLOR); + if (locationSearch.Draw()) { + nextTreeState = 2; + } + PopStyleCombobox(); + + uint8_t destToggle = CVarGetInteger(CVAR_TRACKER_ENTRANCE("SortBy"), 0); + uint8_t groupToggle = CVarGetInteger(CVAR_TRACKER_ENTRANCE("GroupBy"), 0); + + // Combine destToggle and groupToggle to get a range of 0-3 + uint8_t groupType = destToggle + (groupToggle * 2); + size_t groupCount = groupToggle ? (size_t)ENTRANCE_TYPE_COUNT : (size_t)SPOILER_ENTRANCE_GROUP_COUNT; + auto groupNames = groupToggle ? groupTypeNames : spoilerEntranceGroupNames; + + EntranceOverride* entranceList; + + switch (groupType) { + case ENTRANCE_SOURCE_AREA: + entranceList = srcListSortedByArea; + break; + case ENTRANCE_DESTINATION_AREA: + entranceList = destListSortedByArea; + break; + case ENTRANCE_SOURCE_TYPE: + entranceList = srcListSortedByType; + break; + case ENTRANCE_DESTINATION_TYPE: + entranceList = destListSortedByType; + break; + } + + // Begin tracker list + ImGui::BeginChild("ChildEntranceTrackerLocations", ImVec2(0, -8)); + bool showTo = CVarGetInteger(CVAR_TRACKER_ENTRANCE("ShowTo"), 0); + bool showFrom = CVarGetInteger(CVAR_TRACKER_ENTRANCE("ShowFrom"), 0); + bool collapseUndiscovered = CVarGetInteger(CVAR_TRACKER_ENTRANCE("CollapseUndiscovered"), 0); + bool highlightPrevious = CVarGetInteger(CVAR_TRACKER_ENTRANCE("HighlightPrevious"), 0); + bool highlightAvailable = CVarGetInteger(CVAR_TRACKER_ENTRANCE("HighlightAvailable"), 0); + bool hideReverse = CVarGetInteger(CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), 1); + bool autoScrollArea = CVarGetInteger(CVAR_TRACKER_ENTRANCE("AutoScroll"), 0); + for (size_t i = 0; i < groupCount; i++) { + std::string groupName = groupNames[i]; + + uint16_t entranceCount = gEntranceTrackingData.GroupEntranceCounts[groupType][i]; + uint16_t startIndex = gEntranceTrackingData.GroupOffsets[groupType][i]; + + bool doAreaScroll = false; + int undiscovered = 0; + std::vector displayEntrances = {}; + + // Loop over entrances first for filtering + for (size_t entranceIdx = 0; entranceIdx < entranceCount; entranceIdx++) { + size_t trueIdx = entranceIdx + startIndex; + + EntranceOverride entrance = entranceList[trueIdx]; + + const EntranceData* original = GetEntranceData(entrance.index); + const EntranceData* override = GetEntranceData(entrance.override); + + // If entrance is a dungeon, grotto, or interior entrance, the transition into that area has oneExit + // set, which means we can filter the return transitions as redundant if entrances are not decoupled, as + // this is redundant information. Also checks a setting, enabled by default, for hiding them. If all of + // these conditions are met, we skip adding this entrance to any lists. However, if entrances are + // decoupled, then all transitions need to be displayed, so we proceed with the filtering + if ((original->type == ENTRANCE_TYPE_DUNGEON || original->type == ENTRANCE_TYPE_GROTTO || + original->type == ENTRANCE_TYPE_INTERIOR) && + (original->oneExit != 1 && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue( + RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_OFF) && + hideReverse == 1) { + continue; + } + + // RANDOTODO: Only show blue warps if bluewarp shuffle is on + if (original->metaTag.ends_with("bw") || override->metaTag.ends_with("bw")) { + continue; + } + + bool isDiscovered = IsEntranceDiscovered(entrance.index); + + bool showOverride = (!destToggle ? showTo : showFrom) || isDiscovered; + bool showOriginal = (!destToggle ? showFrom : showTo) || isDiscovered; + + const char* origSrcAreaName = spoilerEntranceGroupNames[original->srcGroup].c_str(); + const char* origTypeName = groupTypeNames[original->type].c_str(); + const char* rplcSrcAreaName = spoilerEntranceGroupNames[override->srcGroup].c_str(); + const char* rplcTypeName = groupTypeNames[override->type].c_str(); + + const char* origSrcName = showOriginal ? original->source.c_str() : ""; + const char* rplcDstName = showOverride ? override->destination.c_str() : ""; + + // Filter for entrances by group name, type, source/destination names, and meta tags + if ((!locationSearch.IsActive() && (showOriginal || showOverride || !collapseUndiscovered)) || + ((showOriginal && + (locationSearch.PassFilter(origSrcName) || locationSearch.PassFilter(origSrcAreaName) || + locationSearch.PassFilter(origTypeName) || + locationSearch.PassFilter(original->metaTag.c_str()))) || + (showOverride && + (locationSearch.PassFilter(rplcDstName) || locationSearch.PassFilter(rplcSrcAreaName) || + locationSearch.PassFilter(rplcTypeName) || + locationSearch.PassFilter(override->metaTag.c_str()))))) { + + // Detect if a scroll should happen and remember the scene for that scroll + if (!doAreaScroll && + (lastSceneOrEntranceDetected != LinkIsInArea(original) && LinkIsInArea(original) != -1)) { + lastSceneOrEntranceDetected = LinkIsInArea(original); + doAreaScroll = true; + } + + displayEntrances.push_back(entrance); + } else if (!isDiscovered) { + undiscovered++; + } + } + + // Then display the entrances in groups + if (displayEntrances.size() != 0 || (!locationSearch.IsActive() && undiscovered > 0)) { + // Handle opening/closing trees based on auto scroll or collapse/expand buttons + if (nextTreeState == 1) { + ImGui::SetNextItemOpen(false, ImGuiCond_None); + } else { + ImGui::SetNextItemOpen(true, nextTreeState == 0 && !doAreaScroll ? ImGuiCond_Once : ImGuiCond_None); + } + + if (ImGui::TreeNode(groupName.c_str())) { + for (auto entrance : displayEntrances) { + const EntranceData* original = GetEntranceData(entrance.index); + const EntranceData* override = GetEntranceData(entrance.override); + + bool isDiscovered = IsEntranceDiscovered(entrance.index); + + bool showOverride = (!destToggle ? showTo : showFrom) || isDiscovered; + bool showOriginal = (!destToggle ? showFrom : showTo) || isDiscovered; + + const char* unknown = "???"; + + const char* origSrcName = showOriginal ? original->source.c_str() : unknown; + const char* rplcDstName = showOverride ? override->destination.c_str() : unknown; + + uint32_t color = isDiscovered ? IM_COL32_WHITE : COLOR_GRAY; + + // Handle highlighting and auto scroll + if ((original->index == lastEntranceIndex || + (override->reverseIndex == lastEntranceIndex && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == + RO_GENERIC_OFF)) && + highlightPrevious) { + color = COLOR_ORANGE; + } else if (LinkIsInArea(original) != -1) { + if (highlightAvailable) { + color = COLOR_GREEN; + } + + if (doAreaScroll) { + doAreaScroll = false; + if (autoScrollArea) { + ImGui::SetScrollHereY(0.0f); + } + } + } + + ImGui::PushStyleColor(ImGuiCol_Text, color); + + // Use a non-breaking space to keep the arrow from wrapping to a newline by itself + ImGui::TextWrapped("%s\u00A0-> %s", origSrcName, rplcDstName); + + ImGui::PopStyleColor(); + } + + // Write collapsed undiscovered info + if (!locationSearch.IsActive() && undiscovered > 0) { + Spacer(0); + ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY); + ImGui::TextWrapped("%d Undiscovered", undiscovered); + ImGui::PopStyleColor(); + } + + Spacer(0); + ImGui::TreePop(); + } + } + } + ImGui::EndChild(); + } + Trackers::EndFloatWindows(); +} + +void EntranceTrackerWindow::InitElement() { + // Setup hooks for loading and clearing the entrance tracker data + GameInteractor::Instance->RegisterGameHook( + [](int32_t fileNum) { InitEntranceTrackingData(); }); + GameInteractor::Instance->RegisterGameHook( + [](int32_t fileNum) { ClearEntranceTrackingData(); }); +} + +void RegisterCheckTrackerWidgets() { + backgroundColorWidget = { .name = "Background Color##EntranceTracker", + .type = WidgetType::WIDGET_CVAR_COLOR_PICKER }; + backgroundColorWidget.CVar(CVAR_TRACKER_ENTRANCE("BgColor")) + .Options( + ColorPickerOptions().Color(THEME_COLOR).DefaultValue(Color_Bg_Default).UseAlpha().ShowReset().ShowRandom()); + SohGui::GetSohMenu()->AddSearchWidget( + { backgroundColorWidget, "Randomizer", "Entrance Tracker", "General Settings" }); + + windowTypeWidget = { .name = "Window Type##EntranceTracker", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + windowTypeWidget.CVar(CVAR_TRACKER_ENTRANCE("WindowType")) + .Options(ComboboxOptions() + .DefaultIndex(TRACKER_WINDOW_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(windowType)); + SohGui::GetSohMenu()->AddSearchWidget({ windowTypeWidget, "Randomizer", "Entrance Tracker", "General Settings" }); +} + +static RegisterMenuInitFunc menuInitFunc(RegisterCheckTrackerWidgets); +} // namespace EntranceTracker + +namespace Trackers { +// Windowing stuff +bool BeginFloatWindows(std::string UniqueName, bool& open, Color_RGBA8& bgCol, TrackerWindowType windowType, + bool draggable, ImGuiWindowFlags flags) { + ImGuiWindowFlags windowFlags = flags; + + if (windowFlags == 0) { + windowFlags |= ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NoFocusOnAppearing; + } + + if (windowType == TRACKER_WINDOW_FLOATING) { + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + windowFlags |= ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar; + + if (!draggable) { + windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; + } + } + auto maybeParent = ImGui::GetCurrentWindow(); + ImGuiWindow* window = ImGui::FindWindowByName(UniqueName.c_str()); + ImVec4 bgColVec = VecFromRGBA8(bgCol); + if (window != NULL && window->DockTabIsVisible && window->ParentWindow != NULL && + std::string(window->ParentWindow->Name).compare(0, strlen("Main - Deck"), "Main - Deck") == 0) { + bgColVec.w = 1.0f; + } + ImGui::PushStyleColor(ImGuiCol_WindowBg, bgColVec); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + return ImGui::Begin(UniqueName.c_str(), &open, windowFlags); +} + +void EndFloatWindows() { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::End(); +} // namespace Trackers +} // namespace Trackers diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp new file mode 100644 index 000000000..7de6c552c --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -0,0 +1,2400 @@ +#include +#include +#include +#include + +#include +#include + +#include "randomizer_check_tracker.h" +#include "randomizer_item_tracker.h" +#include "randomizerTypes.h" +#include "soh/cvar_prefixes.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/OTRGlobals.h" +#include "soh/ResourceManagerHelpers.h" +#include "soh/SaveManager.h" +#include "soh/SohGui/SohGui.hpp" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/LanguageManager.h" +#include "soh/util.h" + +using namespace SohGui; + +extern "C" { +#include +#include "variables.h" +#include "functions.h" +#include "macros.h" +extern PlayState* gPlayState; +} + +void DrawEquip(ItemTrackerItem item); +void DrawItem(ItemTrackerItem item); +void DrawDungeonItem(ItemTrackerItem item); +void DrawBottle(ItemTrackerItem item); +void DrawQuest(ItemTrackerItem item); +void DrawSong(ItemTrackerItem item); + +int itemTrackerSectionId; + +using namespace UIWidgets; + +bool shouldUpdateVectors = true; + +std::vector mainWindowItems = {}; + +static WidgetInfo backgroundColor; +static WidgetInfo windowTypeWidget; +static WidgetInfo enableDraggingWidget; +static WidgetInfo onlyPausedWidget; +static WidgetInfo ammoTracking; +static WidgetInfo keyTracking; +static WidgetInfo triforcePieceCount; +static WidgetInfo dungeonItemTracking; +static WidgetInfo gregTracking; +static WidgetInfo triforcePieceTracking; +static WidgetInfo beanSoulsTracking; +static WidgetInfo bossSoulsTracking; +static WidgetInfo jabberNutsTracking; +static WidgetInfo ocarinaButtonTracking; +static WidgetInfo overworldKeysTracking; +static WidgetInfo fishingPoleTracking; +static WidgetInfo personalNotesWiget; +static WidgetInfo hookshotIdentWidget; + +namespace SohGui { +extern std::shared_ptr mSohMenu; +} + +std::vector inventoryItems = { + ITEM_TRACKER_ITEM(ITEM_STICK, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_NUT, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_BOMB, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_BOW, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_ARROW_FIRE, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_DINS_FIRE, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_SLINGSHOT, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_OCARINA_FAIRY, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_BOMBCHU, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_HOOKSHOT, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_ARROW_ICE, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_FARORES_WIND, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_BOOMERANG, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_LENS, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_BEAN, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_HAMMER, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_ARROW_LIGHT, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_NAYRUS_LOVE, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_BOTTLE, 0, DrawBottle), ITEM_TRACKER_ITEM(ITEM_BOTTLE, 1, DrawBottle), + ITEM_TRACKER_ITEM(ITEM_BOTTLE, 2, DrawBottle), ITEM_TRACKER_ITEM(ITEM_BOTTLE, 3, DrawBottle), + ITEM_TRACKER_ITEM(ITEM_POCKET_EGG, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_MASK_KEATON, 0, DrawItem), +}; + +std::vector equipmentItems = { + ITEM_TRACKER_ITEM(ITEM_SWORD_KOKIRI, 1 << 0, DrawEquip), ITEM_TRACKER_ITEM(ITEM_SWORD_MASTER, 1 << 1, DrawEquip), + ITEM_TRACKER_ITEM(ITEM_SWORD_BGS, 1 << 2, DrawEquip), ITEM_TRACKER_ITEM(ITEM_TUNIC_KOKIRI, 1 << 8, DrawEquip), + ITEM_TRACKER_ITEM(ITEM_TUNIC_GORON, 1 << 9, DrawEquip), ITEM_TRACKER_ITEM(ITEM_TUNIC_ZORA, 1 << 10, DrawEquip), + ITEM_TRACKER_ITEM(ITEM_SHIELD_DEKU, 1 << 4, DrawEquip), ITEM_TRACKER_ITEM(ITEM_SHIELD_HYLIAN, 1 << 5, DrawEquip), + ITEM_TRACKER_ITEM(ITEM_SHIELD_MIRROR, 1 << 6, DrawEquip), ITEM_TRACKER_ITEM(ITEM_BOOTS_KOKIRI, 1 << 12, DrawEquip), + ITEM_TRACKER_ITEM(ITEM_BOOTS_IRON, 1 << 13, DrawEquip), ITEM_TRACKER_ITEM(ITEM_BOOTS_HOVER, 1 << 14, DrawEquip), +}; + +std::vector miscItems = { + ITEM_TRACKER_ITEM(ITEM_BRACELET, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_SCALE_SILVER, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_WALLET_ADULT, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_HEART_CONTAINER, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_HEART_PIECE, 0, DrawItem), + ITEM_TRACKER_ITEM(ITEM_MAGIC_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM(QUEST_GERUDO_CARD, 1 << 22, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_SKULL_TOKEN, 1 << 23, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_STONE_OF_AGONY, 1 << 21, DrawQuest), +}; + +std::vector dungeonRewardStones = { + ITEM_TRACKER_ITEM(QUEST_KOKIRI_EMERALD, 1 << 18, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_GORON_RUBY, 1 << 19, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_ZORA_SAPPHIRE, 1 << 20, DrawQuest), +}; + +std::vector dungeonRewardMedallions = { + ITEM_TRACKER_ITEM(QUEST_MEDALLION_FOREST, 1 << 0, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_MEDALLION_FIRE, 1 << 1, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_MEDALLION_WATER, 1 << 2, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_MEDALLION_SPIRIT, 1 << 3, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_MEDALLION_SHADOW, 1 << 4, DrawQuest), + ITEM_TRACKER_ITEM(QUEST_MEDALLION_LIGHT, 1 << 5, DrawQuest), +}; + +std::vector dungeonRewards = {}; + +std::vector songItems = { + ITEM_TRACKER_ITEM(QUEST_SONG_LULLABY, 0, DrawSong), ITEM_TRACKER_ITEM(QUEST_SONG_EPONA, 0, DrawSong), + ITEM_TRACKER_ITEM(QUEST_SONG_SARIA, 0, DrawSong), ITEM_TRACKER_ITEM(QUEST_SONG_SUN, 0, DrawSong), + ITEM_TRACKER_ITEM(QUEST_SONG_TIME, 0, DrawSong), ITEM_TRACKER_ITEM(QUEST_SONG_STORMS, 0, DrawSong), + ITEM_TRACKER_ITEM(QUEST_SONG_MINUET, 0, DrawSong), ITEM_TRACKER_ITEM(QUEST_SONG_BOLERO, 0, DrawSong), + ITEM_TRACKER_ITEM(QUEST_SONG_SERENADE, 0, DrawSong), ITEM_TRACKER_ITEM(QUEST_SONG_REQUIEM, 0, DrawSong), + ITEM_TRACKER_ITEM(QUEST_SONG_NOCTURNE, 0, DrawSong), ITEM_TRACKER_ITEM(QUEST_SONG_PRELUDE, 0, DrawSong), +}; + +std::vector gregItems = { + ITEM_TRACKER_ITEM(ITEM_RUPEE_GREEN, 0, DrawItem), +}; + +std::vector triforcePieces = { + ITEM_TRACKER_ITEM(RG_TRIFORCE_PIECE, 0, DrawItem), +}; + +std::vector rocsFeather = { + ITEM_TRACKER_ITEM(RG_ROCS_FEATHER, 0, DrawItem), +}; + +std::vector swimItems = { + ITEM_TRACKER_ITEM_CUSTOM(RG_BRONZE_SCALE, ITEM_SCALE_SILVER, ITEM_SCALE_SILVER, 0, DrawItem), +}; + +std::vector crawlItems = { + ITEM_TRACKER_ITEM(RG_CRAWL, 0, DrawItem), +}; + +std::vector climbItems = { + ITEM_TRACKER_ITEM(RG_CLIMB, 0, DrawItem), +}; + +std::vector grabItems = { + ITEM_TRACKER_ITEM(RG_POWER_BRACELET, 0, DrawItem), +}; + +std::vector openChestItems = { + ITEM_TRACKER_ITEM(RG_OPEN_CHEST, 0, DrawItem), +}; + +std::vector beanSoulItems = { + ITEM_TRACKER_ITEM_CUSTOM(RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_DESERT_COLOSSUS_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_GERUDO_VALLEY_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_GRAVEYARD_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KOKIRI_FOREST_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_LAKE_HYLIA_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_LOST_WOODS_BRIDGE_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_LOST_WOODS_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_ZORAS_RIVER_BEAN_SOUL, ITEM_BEAN, ITEM_BEAN, 0, DrawItem), +}; + +std::vector bossSoulItems = { + ITEM_TRACKER_ITEM(RG_GOHMA_SOUL, 0, DrawItem), ITEM_TRACKER_ITEM(RG_KING_DODONGO_SOUL, 0, DrawItem), + ITEM_TRACKER_ITEM(RG_BARINADE_SOUL, 0, DrawItem), ITEM_TRACKER_ITEM(RG_PHANTOM_GANON_SOUL, 0, DrawItem), + ITEM_TRACKER_ITEM(RG_VOLVAGIA_SOUL, 0, DrawItem), ITEM_TRACKER_ITEM(RG_MORPHA_SOUL, 0, DrawItem), + ITEM_TRACKER_ITEM(RG_BONGO_BONGO_SOUL, 0, DrawItem), ITEM_TRACKER_ITEM(RG_TWINROVA_SOUL, 0, DrawItem), + ITEM_TRACKER_ITEM(RG_GANON_SOUL, 0, DrawItem), +}; + +std::vector jabbernutItems = { + ITEM_TRACKER_ITEM(RG_SPEAK_DEKU, 0, DrawItem), ITEM_TRACKER_ITEM(RG_SPEAK_GERUDO, 0, DrawItem), + ITEM_TRACKER_ITEM(RG_SPEAK_GORON, 0, DrawItem), ITEM_TRACKER_ITEM(RG_SPEAK_HYLIAN, 0, DrawItem), + ITEM_TRACKER_ITEM(RG_SPEAK_KOKIRI, 0, DrawItem), ITEM_TRACKER_ITEM(RG_SPEAK_ZORA, 0, DrawItem), +}; + +std::vector ocarinaButtonItems = { + // Hack for right now, just gonna draw ocarina buttons as ocarinas. + // Will replace with other macro once we have a custom texture + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_A_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_UP_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_DOWN_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_LEFT_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_RIGHT_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), +}; + +std::vector overworldKeyItems = { + // Hack for right now, just gonna overworld keys as dungeon keys. + // Will replace with other macro once we have a custom texture + ITEM_TRACKER_ITEM_CUSTOM(RG_GUARD_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MARKET_BAZAAR_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MARKET_POTION_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MASK_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MARKET_SHOOTING_GALLERY_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BOMBCHU_BOWLING_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_TREASURE_CHEST_GAME_BUILDING_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BOMBCHU_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_RICHARDS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_ALLEY_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KAK_BAZAAR_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KAK_POTION_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BOSS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_GRANNYS_POTION_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_SKULLTULA_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_IMPAS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_WINDMILL_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KAK_SHOOTING_GALLERY_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_DAMPES_HUT_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_TALONS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_STABLES_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BACK_TOWER_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_HYLIA_LAB_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_FISHING_HOLE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), +}; + +std::vector fishingPoleItems = { ITEM_TRACKER_ITEM(ITEM_FISHING_POLE, 0, DrawItem) }; + +std::vector itemTrackerDungeonsWithMapsHorizontal = { + { SCENE_DEKU_TREE, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_DODONGOS_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_JABU_JABU, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_ICE_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, +}; + +std::vector itemTrackerDungeonsHorizontal = { + { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL } }, + { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, +}; + +std::vector itemTrackerDungeonsWithMapsCompact = { + { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL, ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_DEKU_TREE, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_DODONGOS_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_JABU_JABU, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_ICE_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } }, + { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, +}; + +std::vector itemTrackerDungeonsCompact = { + { SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } }, + { SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL } }, + { SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } }, + { SCENE_THIEVES_HIDEOUT, { ITEM_KEY_SMALL } }, +}; + +std::map itemTrackerDungeonShortNames = { + { SCENE_FOREST_TEMPLE, "FRST" }, { SCENE_FIRE_TEMPLE, "FIRE" }, { SCENE_WATER_TEMPLE, "WATR" }, + { SCENE_SPIRIT_TEMPLE, "SPRT" }, { SCENE_SHADOW_TEMPLE, "SHDW" }, { SCENE_BOTTOM_OF_THE_WELL, "BOTW" }, + { SCENE_DEKU_TREE, "DEKU" }, { SCENE_DODONGOS_CAVERN, "DCVN" }, { SCENE_JABU_JABU, "JABU" }, + { SCENE_ICE_CAVERN, "ICE" }, { SCENE_INSIDE_GANONS_CASTLE, "GANON" }, { SCENE_GERUDO_TRAINING_GROUND, "GTG" }, + { SCENE_THIEVES_HIDEOUT, "HIDE" }, +}; + +std::map itemTrackerBeanShortNames = { + { RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, "DMC" }, + { RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, "DMT" }, + { RG_DESERT_COLOSSUS_BEAN_SOUL, "DC" }, + { RG_GERUDO_VALLEY_BEAN_SOUL, "GV" }, + { RG_GRAVEYARD_BEAN_SOUL, "GY" }, + { RG_KOKIRI_FOREST_BEAN_SOUL, "KF" }, + { RG_LAKE_HYLIA_BEAN_SOUL, "LH" }, + { RG_LOST_WOODS_BRIDGE_BEAN_SOUL, "LWB" }, + { RG_LOST_WOODS_BEAN_SOUL, "LWT" }, + { RG_ZORAS_RIVER_BEAN_SOUL, "ZR" }, +}; + +std::map itemTrackerBossShortNames = { + { RG_GOHMA_SOUL, "GOHMA" }, { RG_KING_DODONGO_SOUL, "KD" }, { RG_BARINADE_SOUL, "BARI" }, + { RG_PHANTOM_GANON_SOUL, "PG" }, { RG_VOLVAGIA_SOUL, "VOLV" }, { RG_MORPHA_SOUL, "MORPH" }, + { RG_BONGO_BONGO_SOUL, "BONGO" }, { RG_TWINROVA_SOUL, "TWIN" }, { RG_GANON_SOUL, "GANON" }, +}; + +std::map itemTrackerJabberNutShortNames = { + { RG_SPEAK_DEKU, "DEKU" }, { RG_SPEAK_GERUDO, "GERUDO" }, { RG_SPEAK_GORON, "GORON" }, + { RG_SPEAK_HYLIAN, "HYLIAN" }, { RG_SPEAK_KOKIRI, "KOKIRI" }, { RG_SPEAK_ZORA, "ZORA" }, +}; + +std::map itemTrackerOcarinaButtonShortNames = { + { RG_OCARINA_A_BUTTON, "A" }, { RG_OCARINA_C_UP_BUTTON, "C-U" }, { RG_OCARINA_C_DOWN_BUTTON, "C-D" }, + { RG_OCARINA_C_LEFT_BUTTON, "C-L" }, { RG_OCARINA_C_RIGHT_BUTTON, "C-R" }, +}; + +std::map itemTrackerOverworldKeyShortNames = { + { RG_GUARD_HOUSE_KEY, "GUARD" }, + { RG_MARKET_BAZAAR_KEY, "MKBAZ" }, + { RG_MARKET_POTION_SHOP_KEY, "MKPOT" }, + { RG_MASK_SHOP_KEY, "MASK" }, + { RG_MARKET_SHOOTING_GALLERY_KEY, "MKSHO" }, + { RG_BOMBCHU_BOWLING_KEY, "BOWL" }, + { RG_TREASURE_CHEST_GAME_BUILDING_KEY, "TREASU" }, + { RG_BOMBCHU_SHOP_KEY, "CHUSHO" }, + { RG_RICHARDS_HOUSE_KEY, "RICH" }, + { RG_ALLEY_HOUSE_KEY, "ALLEY" }, + { RG_KAK_BAZAAR_KEY, "KAKBAZ" }, + { RG_KAK_POTION_SHOP_KEY, "KAKPO" }, + { RG_BOSS_HOUSE_KEY, "BOSS" }, + { RG_GRANNYS_POTION_SHOP_KEY, "GRANNY" }, + { RG_SKULLTULA_HOUSE_KEY, "SKULL" }, + { RG_IMPAS_HOUSE_KEY, "IMPAS" }, + { RG_WINDMILL_KEY, "WIND" }, + { RG_KAK_SHOOTING_GALLERY_KEY, "KAKSHO" }, + { RG_DAMPES_HUT_KEY, "DAMPES" }, + { RG_TALONS_HOUSE_KEY, "TALONS" }, + { RG_STABLES_KEY, "STABLE" }, + { RG_BACK_TOWER_KEY, "TOWER" }, + { RG_HYLIA_LAB_KEY, "LAB" }, + { RG_FISHING_HOLE_KEY, "FISH" }, +}; + +std::vector dungeonItems = {}; + +std::unordered_map actualItemTrackerItemMap = { + { ITEM_BOTTLE, ITEM_TRACKER_ITEM(ITEM_BOTTLE, 0, DrawItem) }, + { ITEM_BIG_POE, ITEM_TRACKER_ITEM(ITEM_BIG_POE, 0, DrawItem) }, + { ITEM_BLUE_FIRE, ITEM_TRACKER_ITEM(ITEM_BLUE_FIRE, 0, DrawItem) }, + { ITEM_BUG, ITEM_TRACKER_ITEM(ITEM_BUG, 0, DrawItem) }, + { ITEM_FAIRY, ITEM_TRACKER_ITEM(ITEM_FAIRY, 0, DrawItem) }, + { ITEM_FISH, ITEM_TRACKER_ITEM(ITEM_FISH, 0, DrawItem) }, + { ITEM_POTION_GREEN, ITEM_TRACKER_ITEM(ITEM_POTION_GREEN, 0, DrawItem) }, + { ITEM_POE, ITEM_TRACKER_ITEM(ITEM_POE, 0, DrawItem) }, + { ITEM_POTION_RED, ITEM_TRACKER_ITEM(ITEM_POTION_RED, 0, DrawItem) }, + { ITEM_POTION_BLUE, ITEM_TRACKER_ITEM(ITEM_POTION_BLUE, 0, DrawItem) }, + { ITEM_MILK_BOTTLE, ITEM_TRACKER_ITEM(ITEM_MILK_BOTTLE, 0, DrawItem) }, + { ITEM_MILK_HALF, ITEM_TRACKER_ITEM(ITEM_MILK_HALF, 0, DrawItem) }, + { ITEM_LETTER_RUTO, ITEM_TRACKER_ITEM(ITEM_LETTER_RUTO, 0, DrawItem) }, + + { ITEM_HOOKSHOT, ITEM_TRACKER_ITEM(ITEM_HOOKSHOT, 0, DrawItem) }, + { ITEM_LONGSHOT, ITEM_TRACKER_ITEM(ITEM_LONGSHOT, 0, DrawItem) }, + + { ITEM_OCARINA_FAIRY, ITEM_TRACKER_ITEM(ITEM_OCARINA_FAIRY, 0, DrawItem) }, + { ITEM_OCARINA_TIME, ITEM_TRACKER_ITEM(ITEM_OCARINA_TIME, 0, DrawItem) }, + + { ITEM_MAGIC_SMALL, ITEM_TRACKER_ITEM(ITEM_MAGIC_SMALL, 0, DrawItem) }, + { ITEM_MAGIC_LARGE, ITEM_TRACKER_ITEM(ITEM_MAGIC_LARGE, 0, DrawItem) }, + + { ITEM_WALLET_ADULT, ITEM_TRACKER_ITEM(ITEM_WALLET_ADULT, 0, DrawItem) }, + { ITEM_WALLET_GIANT, ITEM_TRACKER_ITEM(ITEM_WALLET_GIANT, 0, DrawItem) }, + + { ITEM_BRACELET, ITEM_TRACKER_ITEM(ITEM_BRACELET, 0, DrawItem) }, + { ITEM_GAUNTLETS_SILVER, ITEM_TRACKER_ITEM(ITEM_GAUNTLETS_SILVER, 0, DrawItem) }, + { ITEM_GAUNTLETS_GOLD, ITEM_TRACKER_ITEM(ITEM_GAUNTLETS_GOLD, 0, DrawItem) }, + + { ITEM_SCALE_SILVER, ITEM_TRACKER_ITEM(ITEM_SCALE_SILVER, 0, DrawItem) }, + { ITEM_SCALE_GOLDEN, ITEM_TRACKER_ITEM(ITEM_SCALE_GOLDEN, 0, DrawItem) }, + + { ITEM_WEIRD_EGG, ITEM_TRACKER_ITEM(ITEM_WEIRD_EGG, 0, DrawItem) }, + { ITEM_CHICKEN, ITEM_TRACKER_ITEM(ITEM_CHICKEN, 0, DrawItem) }, + { ITEM_LETTER_ZELDA, ITEM_TRACKER_ITEM(ITEM_LETTER_ZELDA, 0, DrawItem) }, + { ITEM_MASK_KEATON, ITEM_TRACKER_ITEM(ITEM_MASK_KEATON, 0, DrawItem) }, + { ITEM_MASK_SKULL, ITEM_TRACKER_ITEM(ITEM_MASK_SKULL, 0, DrawItem) }, + { ITEM_MASK_SPOOKY, ITEM_TRACKER_ITEM(ITEM_MASK_SPOOKY, 0, DrawItem) }, + { ITEM_MASK_BUNNY, ITEM_TRACKER_ITEM(ITEM_MASK_BUNNY, 0, DrawItem) }, + { ITEM_MASK_GORON, ITEM_TRACKER_ITEM(ITEM_MASK_GORON, 0, DrawItem) }, + { ITEM_MASK_ZORA, ITEM_TRACKER_ITEM(ITEM_MASK_ZORA, 0, DrawItem) }, + { ITEM_MASK_GERUDO, ITEM_TRACKER_ITEM(ITEM_MASK_GERUDO, 0, DrawItem) }, + { ITEM_MASK_TRUTH, ITEM_TRACKER_ITEM(ITEM_MASK_TRUTH, 0, DrawItem) }, + { ITEM_SOLD_OUT, ITEM_TRACKER_ITEM(ITEM_SOLD_OUT, 0, DrawItem) }, + + { ITEM_POCKET_EGG, ITEM_TRACKER_ITEM(ITEM_POCKET_EGG, 0, DrawItem) }, + { ITEM_POCKET_CUCCO, ITEM_TRACKER_ITEM(ITEM_POCKET_CUCCO, 0, DrawItem) }, + { ITEM_COJIRO, ITEM_TRACKER_ITEM(ITEM_COJIRO, 0, DrawItem) }, + { ITEM_ODD_MUSHROOM, ITEM_TRACKER_ITEM(ITEM_ODD_MUSHROOM, 0, DrawItem) }, + { ITEM_ODD_POTION, ITEM_TRACKER_ITEM(ITEM_ODD_POTION, 0, DrawItem) }, + { ITEM_SAW, ITEM_TRACKER_ITEM(ITEM_SAW, 0, DrawItem) }, + { ITEM_SWORD_BROKEN, ITEM_TRACKER_ITEM(ITEM_SWORD_BROKEN, 0, DrawItem) }, + { ITEM_PRESCRIPTION, ITEM_TRACKER_ITEM(ITEM_PRESCRIPTION, 0, DrawItem) }, + { ITEM_FROG, ITEM_TRACKER_ITEM(ITEM_FROG, 0, DrawItem) }, + { ITEM_EYEDROPS, ITEM_TRACKER_ITEM(ITEM_EYEDROPS, 0, DrawItem) }, + { ITEM_CLAIM_CHECK, ITEM_TRACKER_ITEM(ITEM_CLAIM_CHECK, 0, DrawItem) }, +}; + +std::vector buttonMap = { + BTN_A, BTN_B, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_CRIGHT, BTN_L, + BTN_Z, BTN_R, BTN_START, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT, +}; + +typedef enum { + ITEM_TRACKER_NUMBER_NONE, + ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY, + ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY, + ITEM_TRACKER_NUMBER_CAPACITY, + ITEM_TRACKER_NUMBER_AMMO, +} ItemTrackerNumberOption; + +typedef enum { + KEYS_COLLECTED_MAX, + KEYS_CURRENT_COLLECTED_MAX, + KEYS_CURRENT_MAX, +} ItemTrackerKeysNumberOption; + +typedef enum { + TRIFORCE_PIECE_COLLECTED_REQUIRED, + TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX, +} ItemTrackerTriforcePieceNumberOption; + +typedef enum { + SECTION_DISPLAY_HIDDEN, + SECTION_DISPLAY_MAIN_WINDOW, + SECTION_DISPLAY_SEPARATE, +} ItemTrackerDisplayType; + +typedef enum { + SECTION_DISPLAY_EXTENDED_HIDDEN, + SECTION_DISPLAY_EXTENDED_MAIN_WINDOW, + SECTION_DISPLAY_EXTENDED_MISC_WINDOW, + SECTION_DISPLAY_EXTENDED_SEPARATE +} ItemTrackerExtendedDisplayType; + +typedef enum { + SECTION_DISPLAY_MINIMAL_HIDDEN, + SECTION_DISPLAY_MINIMAL_SEPARATE, +} ItemTrackerMinimalDisplayType; + +struct ItemTrackerNumbers { + int currentCapacity; + int maxCapacity; + int currentAmmo; +}; + +static ImVector itemTrackerNotes; +uint32_t notesIdleFrames = 0; +bool notesNeedSave = false; +const uint32_t notesMaxIdleFrames = 40; // two seconds of game time, since OnGameFrameUpdate is used to tick + +static bool presetLoaded = false; +static std::unordered_map presetPos; +static std::unordered_map presetSize; + +void ItemTrackerOnFrame() { + if (notesNeedSave && notesIdleFrames <= notesMaxIdleFrames) { + notesIdleFrames++; + } +} + +bool IsValidSaveFile() { + bool validSave = gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2; + return validSave; +} + +bool HasSong(ItemTrackerItem item) { + return GameInteractor::IsSaveLoaded() ? ((1 << item.id) & gSaveContext.inventory.questItems) : false; +} + +bool HasQuestItem(ItemTrackerItem item) { + return GameInteractor::IsSaveLoaded() ? (item.data & gSaveContext.inventory.questItems) : false; +} + +bool HasEquipment(ItemTrackerItem item) { + return GameInteractor::IsSaveLoaded() ? (item.data & gSaveContext.inventory.equipment) : false; +} + +void ItemTracker_LoadFromPreset(nlohmann::json trackerInfo) { + presetLoaded = true; + for (auto window : itemTrackerWindowIDs) { + if (trackerInfo.contains(window)) { + presetPos[window] = { trackerInfo[window]["pos"]["x"], trackerInfo[window]["pos"]["y"] }; + presetSize[window] = { trackerInfo[window]["size"]["width"], trackerInfo[window]["size"]["height"] }; + } + } +} + +ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) { + ItemTrackerNumbers result; + result.currentCapacity = 0; + result.maxCapacity = 0; + result.currentAmmo = 0; + + switch (item.id) { + case ITEM_STICK: + result.currentCapacity = CUR_CAPACITY(UPG_STICKS); + result.maxCapacity = 30; + result.currentAmmo = AMMO(ITEM_STICK); + break; + case ITEM_NUT: + result.currentCapacity = CUR_CAPACITY(UPG_NUTS); + result.maxCapacity = 40; + result.currentAmmo = AMMO(ITEM_NUT); + break; + case ITEM_BOMB: + result.currentCapacity = CUR_CAPACITY(UPG_BOMB_BAG); + result.maxCapacity = 40; + result.currentAmmo = AMMO(ITEM_BOMB); + break; + case ITEM_BOW: + result.currentCapacity = CUR_CAPACITY(UPG_QUIVER); + result.maxCapacity = 50; + result.currentAmmo = AMMO(ITEM_BOW); + break; + case ITEM_SLINGSHOT: + result.currentCapacity = CUR_CAPACITY(UPG_BULLET_BAG); + result.maxCapacity = 50; + result.currentAmmo = AMMO(ITEM_SLINGSHOT); + break; + case ITEM_WALLET_ADULT: + case ITEM_WALLET_GIANT: + result.currentCapacity = + IS_RANDO && !Flags_GetRandomizerInf(RAND_INF_HAS_WALLET) ? 0 : CUR_CAPACITY(UPG_WALLET); + result.maxCapacity = + IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_INCLUDE_TYCOON_WALLET) ? 999 + : 500; + result.currentAmmo = gSaveContext.rupees; + break; + case ITEM_BOMBCHU: + result.currentCapacity = INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU ? 50 : 0; + result.maxCapacity = 50; + result.currentAmmo = AMMO(ITEM_BOMBCHU); + break; + case ITEM_BEAN: + result.currentCapacity = INV_CONTENT(ITEM_BEAN) == ITEM_BEAN ? 10 : 0; + result.maxCapacity = 10; + result.currentAmmo = AMMO(ITEM_BEAN); + break; + case QUEST_SKULL_TOKEN: + result.maxCapacity = result.currentCapacity = 100; + result.currentAmmo = gSaveContext.inventory.gsTokens; + break; + case ITEM_HEART_CONTAINER: + result.maxCapacity = result.currentCapacity = 8; + result.currentAmmo = gSaveContext.ship.stats.heartContainers; + break; + case ITEM_HEART_PIECE: + result.maxCapacity = result.currentCapacity = 36; + result.currentAmmo = gSaveContext.ship.stats.heartPieces; + break; + case ITEM_KEY_SMALL: + // Though the ammo/capacity naming doesn't really make sense for keys, we are + // hijacking the same system to display key counts as there are enough similarities + result.currentAmmo = MAX(gSaveContext.inventory.dungeonKeys[item.data], 0); + result.currentCapacity = gSaveContext.ship.stats.dungeonKeys[item.data]; + switch (item.data) { + case SCENE_FOREST_TEMPLE: + result.maxCapacity = FOREST_TEMPLE_SMALL_KEY_MAX; + break; + case SCENE_FIRE_TEMPLE: + result.maxCapacity = FIRE_TEMPLE_SMALL_KEY_MAX; + break; + case SCENE_WATER_TEMPLE: + result.maxCapacity = WATER_TEMPLE_SMALL_KEY_MAX; + break; + case SCENE_SPIRIT_TEMPLE: + result.maxCapacity = SPIRIT_TEMPLE_SMALL_KEY_MAX; + break; + case SCENE_SHADOW_TEMPLE: + result.maxCapacity = SHADOW_TEMPLE_SMALL_KEY_MAX; + break; + case SCENE_BOTTOM_OF_THE_WELL: + result.maxCapacity = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; + break; + case SCENE_GERUDO_TRAINING_GROUND: + result.maxCapacity = GERUDO_TRAINING_GROUND_SMALL_KEY_MAX; + break; + case SCENE_THIEVES_HIDEOUT: + if (IS_RANDO) { + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GERUDO_FORTRESS)) { + case RO_GF_CARPENTERS_NORMAL: + result.maxCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX; + break; + case RO_GF_CARPENTERS_FAST: + result.maxCapacity = 1; + break; + case RO_GF_CARPENTERS_FREE: + result.maxCapacity = 0; + break; + default: + result.maxCapacity = 0; + SPDLOG_ERROR( + "Invalid value for RSK_GERUDO_FORTRESS: {}", + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GERUDO_FORTRESS)); + assert(false); + break; + } + } else { + result.maxCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX; + } + break; + case SCENE_INSIDE_GANONS_CASTLE: + result.maxCapacity = GANONS_CASTLE_SMALL_KEY_MAX; + break; + } + break; + } + + return result; +} + +#define IM_COL_WHITE IM_COL32(255, 255, 255, 255) +#define IM_COL_RED IM_COL32(255, 0, 0, 255) +#define IM_COL_GREEN IM_COL32(0, 255, 0, 255) +#define IM_COL_GRAY IM_COL32(155, 155, 155, 255) +#define IM_COL_PURPLE IM_COL32(180, 90, 200, 255) +#define IM_COL_LIGHT_YELLOW IM_COL32(255, 255, 130, 255) + +void DrawItemCount(ItemTrackerItem item, bool hideMax) { + if (!GameInteractor::IsSaveLoaded()) { + return; + } + int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); + int textSize = CVarGetInteger(CVAR_TRACKER_ITEM("TextSize"), 13); + ItemTrackerNumbers currentAndMax = GetItemCurrentAndMax(item); + ImVec2 p = ImGui::GetCursorScreenPos(); + int32_t trackerNumberDisplayMode = + CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY); + int32_t trackerKeyNumberDisplayMode = CVarGetInteger(CVAR_TRACKER_ITEM("KeyCounts"), KEYS_COLLECTED_MAX); + float textScalingFactor = static_cast(iconSize) / 36.0f; + uint32_t actualItemId = INV_CONTENT(item.id); + bool hasItem = actualItemId != ITEM_NONE; + + if (CVarGetInteger(CVAR_TRACKER_ITEM("HookshotIdentifier"), 0)) { + if ((actualItemId == ITEM_HOOKSHOT || actualItemId == ITEM_LONGSHOT) && hasItem) { + + // Calculate the scaled position for the text + ImVec2 textPos = + ImVec2(p.x + (iconSize / 2) - + (ImGui::CalcTextSize(item.id == ITEM_HOOKSHOT ? "H" : "L").x * textScalingFactor / 2) + + 8 * textScalingFactor, + p.y - 22 * textScalingFactor); + + ImGui::SetCursorScreenPos(textPos); + ImGui::SetWindowFontScale(textScalingFactor); + + ImGui::Text(item.id == ITEM_HOOKSHOT ? "H" : "L"); + ImGui::SetWindowFontScale(1.0f); // Reset font scale to the original state + } + } + + ImGui::SetWindowFontScale(textSize / 13.0f); + + if (item.id == ITEM_KEY_SMALL && IsValidSaveFile()) { + std::string currentString = ""; + std::string maxString = hideMax ? "???" : std::to_string(currentAndMax.maxCapacity); + ImU32 currentColor = IM_COL_WHITE; + ImU32 maxColor = IM_COL_GREEN; + // "Collected / Max", "Current / Collected / Max", "Current / Max" + if (trackerKeyNumberDisplayMode == KEYS_CURRENT_COLLECTED_MAX || + trackerKeyNumberDisplayMode == KEYS_CURRENT_MAX) { + currentString += std::to_string(currentAndMax.currentAmmo); + currentString += "/"; + } + if (trackerKeyNumberDisplayMode == KEYS_COLLECTED_MAX || + trackerKeyNumberDisplayMode == KEYS_CURRENT_COLLECTED_MAX) { + currentString += std::to_string(currentAndMax.currentCapacity); + currentString += "/"; + } + + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize((currentString + maxString).c_str()).x / 2), p.y - 14)); + ImGui::PushStyleColor(ImGuiCol_Text, currentColor); + ImGui::Text("%s", currentString.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(0, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, maxColor); + ImGui::Text("%s", maxString.c_str()); + ImGui::PopStyleColor(); + } else if (currentAndMax.currentCapacity > 0 && trackerNumberDisplayMode != ITEM_TRACKER_NUMBER_NONE && + IsValidSaveFile()) { + std::string currentString = ""; + std::string maxString = ""; + ImU32 currentColor = IM_COL_WHITE; + ImU32 maxColor = item.id == QUEST_SKULL_TOKEN ? IM_COL_RED : IM_COL_GREEN; + + bool shouldAlignToLeft = CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountAlignLeft"), 0) && + trackerNumberDisplayMode != ITEM_TRACKER_NUMBER_CAPACITY && + trackerNumberDisplayMode != ITEM_TRACKER_NUMBER_AMMO; + + bool shouldDisplayAmmo = trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_AMMO || + trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY || + // These items have a static capacity, so display ammo instead + item.id == ITEM_BOMBCHU || item.id == ITEM_BEAN || item.id == QUEST_SKULL_TOKEN || + item.id == ITEM_HEART_CONTAINER || item.id == ITEM_HEART_PIECE; + + bool shouldDisplayMax = !(trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || + trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY); + + if (shouldDisplayAmmo) { + currentString = std::to_string(currentAndMax.currentAmmo); + if (currentAndMax.currentAmmo >= currentAndMax.currentCapacity) { + if (item.id == QUEST_SKULL_TOKEN) { + currentColor = IM_COL_RED; + } else { + currentColor = IM_COL_GREEN; + } + } + if (shouldDisplayMax) { + currentString += "/"; + maxString = std::to_string(currentAndMax.currentCapacity); + } + if (currentAndMax.currentAmmo <= 0) { + currentColor = IM_COL_GRAY; + } + } else { + currentString = std::to_string(currentAndMax.currentCapacity); + if (currentAndMax.currentCapacity >= currentAndMax.maxCapacity) { + currentColor = IM_COL_GREEN; + } else if (shouldDisplayMax) { + currentString += "/"; + maxString = std::to_string(currentAndMax.maxCapacity); + } + } + + float x = shouldAlignToLeft + ? p.x + : p.x + (iconSize / 2) - (ImGui::CalcTextSize((currentString + maxString).c_str()).x / 2); + + ImGui::SetCursorScreenPos(ImVec2(x, p.y - 14)); + ImGui::PushStyleColor(ImGuiCol_Text, currentColor); + ImGui::Text("%s", currentString.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(0, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, maxColor); + ImGui::Text("%s", maxString.c_str()); + ImGui::PopStyleColor(); + } else if (item.id == RG_TRIFORCE_PIECE && IS_RANDO && + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) != RO_TRIFORCE_HUNT_OFF) && + IsValidSaveFile()) { + std::string currentString = ""; + std::string requiredString = ""; + std::string maxString = ""; + uint8_t piecesRequired = + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1); + uint8_t piecesTotal = + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_TOTAL) + 1); + ImU32 currentColor = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= piecesRequired + ? IM_COL_GREEN + : IM_COL_WHITE; + ImU32 maxColor = IM_COL_GREEN; + int32_t trackerTriforcePieceNumberDisplayMode = + CVarGetInteger(CVAR_TRACKER_ITEM("TriforcePieceCounts"), TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX); + + currentString += std::to_string(gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected); + currentString += "/"; + // gItemTrackerTriforcePieceTrack + if (trackerTriforcePieceNumberDisplayMode == TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX) { + currentString += std::to_string(piecesRequired); + currentString += "/"; + maxString += std::to_string(piecesTotal); + } else if (trackerTriforcePieceNumberDisplayMode == TRIFORCE_PIECE_COLLECTED_REQUIRED) { + maxString += std::to_string(piecesRequired); + } + + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize((currentString + maxString).c_str()).x / 2), p.y - 14)); + ImGui::PushStyleColor(ImGuiCol_Text, currentColor); + ImGui::Text("%s", currentString.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(0, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, maxColor); + ImGui::Text("%s", maxString.c_str()); + ImGui::PopStyleColor(); + } else { + ImGui::SetCursorScreenPos(ImVec2(p.x, p.y - 14)); + ImGui::Text(""); + } +} + +void DrawEquip(ItemTrackerItem item) { + bool hasEquip = HasEquipment(item); + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasEquip && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize, iconSize), ImVec2(0.0f, 0.0f), ImVec2(1, 1)); + + Tooltip(SohUtils::GetItemName(item.id).c_str()); +} + +void DrawQuest(ItemTrackerItem item) { + bool hasQuestItem = HasQuestItem(item); + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + ImGui::BeginGroup(); + ImGui::ImageWithBg(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasQuestItem && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); + + if (item.id == QUEST_SKULL_TOKEN) { + DrawItemCount(item, false); + } + + ImGui::EndGroup(); + + Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); +}; + +bool HasBossSoul(RandomizerInf bossSoul) { + uint8_t soulSetting = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BOSS_SOULS); + bool isSoulRandomized = IS_RANDO && (soulSetting == RO_BOSS_SOULS_ON_PLUS_GANON || + (soulSetting == RO_BOSS_SOULS_ON && bossSoul != RAND_INF_GANON_SOUL)); + + return isSoulRandomized ? Flags_GetRandomizerInf(bossSoul) : true; +} + +void DrawItem(ItemTrackerItem item) { + + uint32_t actualItemId = GameInteractor::IsSaveLoaded() ? INV_CONTENT(item.id) : ITEM_NONE; + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + bool hasItem = actualItemId != ITEM_NONE; + std::string itemName = ""; + + // Hack fix as RG_MARKET_SHOOTING_GALLERY_KEY is RandomizerGet #255 which collides + // with ITEM_NONE (ItemId #255) due to the lack of a modid to separate them + if (item.name != "ITEM_KEY_SMALL" && item.id == ITEM_NONE) { + return; + } + + switch (item.id) { + case ITEM_HEART_CONTAINER: + actualItemId = item.id; + hasItem = gSaveContext.ship.stats.heartContainers > 0; + break; + case ITEM_HEART_PIECE: + actualItemId = item.id; + hasItem = gSaveContext.ship.stats.heartPieces > 0; + break; + case ITEM_MAGIC_SMALL: + case ITEM_MAGIC_LARGE: + actualItemId = gSaveContext.magicLevel == 2 ? ITEM_MAGIC_LARGE : ITEM_MAGIC_SMALL; + hasItem = gSaveContext.magicLevel > 0; + break; + case ITEM_WALLET_ADULT: + case ITEM_WALLET_GIANT: + actualItemId = CUR_UPG_VALUE(UPG_WALLET) == 2 ? ITEM_WALLET_GIANT : ITEM_WALLET_ADULT; + hasItem = !IS_RANDO || Flags_GetRandomizerInf(RAND_INF_HAS_WALLET); + break; + case ITEM_BRACELET: + case ITEM_GAUNTLETS_SILVER: + case ITEM_GAUNTLETS_GOLD: + actualItemId = CUR_UPG_VALUE(UPG_STRENGTH) >= 3 ? ITEM_GAUNTLETS_GOLD + : CUR_UPG_VALUE(UPG_STRENGTH) == 2 ? ITEM_GAUNTLETS_SILVER + : ITEM_BRACELET; + hasItem = CUR_UPG_VALUE(UPG_STRENGTH) > 0; + break; + case ITEM_SCALE_SILVER: + case ITEM_SCALE_GOLDEN: + actualItemId = CUR_UPG_VALUE(UPG_SCALE) == 2 ? ITEM_SCALE_GOLDEN : ITEM_SCALE_SILVER; + hasItem = CUR_UPG_VALUE(UPG_SCALE) > 0; + break; + case ITEM_RUPEE_GREEN: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GREG_FOUND); + break; + case RG_TRIFORCE_PIECE: + actualItemId = item.id; + hasItem = IS_RANDO && (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) != + RO_TRIFORCE_HUNT_OFF); + itemName = "Triforce Piece"; + break; + case ITEM_NAYRUS_LOVE: + if (IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_ROCS_FEATHER)) { + hasItem = Flags_GetRandomizerInf(RAND_INF_OBTAINED_NAYRUS_LOVE); + } + break; + case RG_ROCS_FEATHER: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_OBTAINED_ROCS_FEATHER); + itemName = "Roc's Feather"; + break; + case RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL); + itemName = "Death Mountain Crater Bean Soul"; + break; + case RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL); + itemName = "Death Mountain Trail Bean Soul"; + break; + case RG_DESERT_COLOSSUS_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_DESERT_COLOSSUS_BEAN_SOUL); + itemName = "Desert Colossus Bean Soul"; + break; + case RG_GERUDO_VALLEY_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GERUDO_VALLEY_BEAN_SOUL); + itemName = "Gerudo Valley Bean Soul"; + break; + case RG_GRAVEYARD_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GRAVEYARD_BEAN_SOUL); + itemName = "Graveyard Bean Soul"; + break; + case RG_KOKIRI_FOREST_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KOKIRI_FOREST_BEAN_SOUL); + itemName = "Kokiri Forest Bean Soul"; + break; + case RG_LAKE_HYLIA_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_LAKE_HYLIA_BEAN_SOUL); + itemName = "Lake Hylia Bean Soul"; + break; + case RG_LOST_WOODS_BRIDGE_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_LOST_WOODS_BRIDGE_BEAN_SOUL); + itemName = "Lost Woods Bridge Bean Soul"; + break; + case RG_LOST_WOODS_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_LOST_WOODS_BEAN_SOUL); + itemName = "Lost Woods Theatre Bean Soul"; + break; + case RG_ZORAS_RIVER_BEAN_SOUL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_ZORAS_RIVER_BEAN_SOUL); + itemName = "Zora's River Bean Soul"; + break; + case RG_GOHMA_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_GOHMA_SOUL); + itemName = "Gohma's Soul"; + break; + case RG_KING_DODONGO_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_KING_DODONGO_SOUL); + itemName = "King Dodongo's Soul"; + break; + case RG_BARINADE_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_BARINADE_SOUL); + itemName = "Barinade's Soul"; + break; + case RG_PHANTOM_GANON_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_PHANTOM_GANON_SOUL); + itemName = "Phantom Ganon's Soul"; + break; + case RG_VOLVAGIA_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_VOLVAGIA_SOUL); + itemName = "Volvagia's Soul"; + break; + case RG_MORPHA_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_MORPHA_SOUL); + itemName = "Morpha's Soul"; + break; + case RG_BONGO_BONGO_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_BONGO_BONGO_SOUL); + itemName = "Bongo Bongo's Soul"; + break; + case RG_TWINROVA_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_TWINROVA_SOUL); + itemName = "Twinrova's Soul"; + break; + case RG_GANON_SOUL: + actualItemId = item.id; + hasItem = HasBossSoul(RAND_INF_GANON_SOUL); + itemName = "Ganon's Soul"; + break; + + case RG_SPEAK_DEKU: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_DEKU); + itemName = "Deku Jabber Nut"; + break; + case RG_SPEAK_GERUDO: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_GERUDO); + itemName = "Gerudo Jabber Nut"; + break; + case RG_SPEAK_GORON: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_GORON); + itemName = "Goron Jabber Nut"; + break; + case RG_SPEAK_HYLIAN: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_HYLIAN); + itemName = "Hylian Jabber Nut"; + break; + case RG_SPEAK_KOKIRI: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_KOKIRI); + itemName = "Kokiri Jabber Nut"; + break; + case RG_SPEAK_ZORA: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_ZORA); + itemName = "Zora Jabber Nut"; + break; + + case RG_OCARINA_A_BUTTON: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_A); + itemName = "Ocarina A Button"; + break; + case RG_OCARINA_C_UP_BUTTON: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_UP); + itemName = "Ocarina C Up Button"; + break; + case RG_OCARINA_C_DOWN_BUTTON: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_DOWN); + itemName = "Ocarina C Down Button"; + break; + case RG_OCARINA_C_LEFT_BUTTON: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_LEFT); + itemName = "Ocarina C Left Button"; + break; + case RG_OCARINA_C_RIGHT_BUTTON: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_RIGHT); + itemName = "Ocarina C Right Button"; + break; + case ITEM_FISHING_POLE: + actualItemId = item.id; + hasItem = IS_RANDO && Flags_GetRandomizerInf(RAND_INF_FISHING_POLE_FOUND); + itemName = "Fishing Pole"; + break; + + case RG_GUARD_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GUARD_HOUSE_KEY_OBTAINED); + itemName = "Guard House Key"; + break; + case RG_MARKET_BAZAAR_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MARKET_BAZAAR_KEY_OBTAINED); + itemName = "Market Bazaar Key"; + break; + case RG_MARKET_POTION_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MARKET_POTION_SHOP_KEY_OBTAINED); + itemName = "Market Potion Shop Key"; + break; + case RG_MASK_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MASK_SHOP_KEY_OBTAINED); + itemName = "Mask Shop Key"; + break; + case RG_MARKET_SHOOTING_GALLERY_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MARKET_SHOOTING_GALLERY_KEY_OBTAINED); + itemName = "Market Shooting Gallery Key"; + break; + case RG_BOMBCHU_BOWLING_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BOMBCHU_BOWLING_KEY_OBTAINED); + itemName = "Bombchu Bowling Key"; + break; + case RG_TREASURE_CHEST_GAME_BUILDING_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_TREASURE_CHEST_GAME_BUILDING_KEY_OBTAINED); + itemName = "Treasure Chest Game Building Key"; + break; + case RG_BOMBCHU_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BOMBCHU_SHOP_KEY_OBTAINED); + itemName = "Bombchu Shop Key"; + break; + case RG_RICHARDS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_RICHARDS_HOUSE_KEY_OBTAINED); + itemName = "Richards House Key"; + break; + case RG_ALLEY_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_ALLEY_HOUSE_KEY_OBTAINED); + itemName = "Alley House Key"; + break; + case RG_KAK_BAZAAR_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KAK_BAZAAR_KEY_OBTAINED); + itemName = "Kak Bazaar Key"; + break; + case RG_KAK_POTION_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KAK_POTION_SHOP_KEY_OBTAINED); + itemName = "Kak Potion Shop Key"; + break; + case RG_BOSS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BOSS_HOUSE_KEY_OBTAINED); + itemName = "Boss House Key"; + break; + case RG_GRANNYS_POTION_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GRANNYS_POTION_SHOP_KEY_OBTAINED); + itemName = "Granny's Potion Shop Key"; + break; + case RG_SKULLTULA_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_SKULLTULA_HOUSE_KEY_OBTAINED); + itemName = "Skulltula House Key"; + break; + case RG_IMPAS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_IMPAS_HOUSE_KEY_OBTAINED); + itemName = "Impa's House Key"; + break; + case RG_WINDMILL_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_WINDMILL_KEY_OBTAINED); + itemName = "Windmill Key"; + break; + case RG_KAK_SHOOTING_GALLERY_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KAK_SHOOTING_GALLERY_KEY_OBTAINED); + itemName = "Kak Shooting Gallery Key"; + break; + case RG_DAMPES_HUT_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_DAMPES_HUT_KEY_OBTAINED); + itemName = "Dampé's Hut Key"; + break; + case RG_TALONS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_TALONS_HOUSE_KEY_OBTAINED); + itemName = "Talon's House Key"; + break; + case RG_STABLES_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_STABLES_KEY_OBTAINED); + itemName = "Stables Key"; + break; + case RG_BACK_TOWER_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BACK_TOWER_KEY_OBTAINED); + itemName = "Back Tower Key"; + break; + case RG_HYLIA_LAB_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HYLIA_LAB_KEY_OBTAINED); + itemName = "Hylia Lab Key"; + break; + case RG_FISHING_HOLE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_FISHING_HOLE_KEY_OBTAINED); + itemName = "Fishing Hole Key"; + break; + case RG_BRONZE_SCALE: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_SWIM); + itemName = "Swim"; + break; + case RG_CRAWL: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_CRAWL); + itemName = "Crawl"; + break; + case RG_CLIMB: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_CLIMB); + itemName = "Climb"; + break; + case RG_POWER_BRACELET: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_GRAB); + itemName = "Grab"; + break; + case RG_OPEN_CHEST: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_CAN_OPEN_CHEST); + itemName = "Open"; + break; + } + + if (GameInteractor::IsSaveLoaded() && + (hasItem && item.id != actualItemId && + actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end())) { + item = actualItemTrackerItemMap[actualItemId]; + } + + ImGui::BeginGroup(); + + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasItem && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); + + DrawItemCount(item, false); + + if (item.id >= RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL && item.id <= RG_ZORAS_RIVER_BEAN_SOUL) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string beanName = itemTrackerBeanShortNames[item.id]; + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(beanName.c_str()).x / 2), p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", beanName.c_str()); + ImGui::PopStyleColor(); + } + + if (item.id >= RG_GOHMA_SOUL && item.id <= RG_GANON_SOUL) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string bossName = itemTrackerBossShortNames[item.id]; + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(bossName.c_str()).x / 2), p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", bossName.c_str()); + ImGui::PopStyleColor(); + } + + if (item.id >= RG_SPEAK_DEKU && item.id <= RG_SPEAK_ZORA) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string name = itemTrackerJabberNutShortNames[item.id]; + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(name.c_str()).x / 2), p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", name.c_str()); + ImGui::PopStyleColor(); + } + + if (item.id >= RG_OCARINA_A_BUTTON && item.id <= RG_OCARINA_C_RIGHT_BUTTON) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string ocarinaButtonName = itemTrackerOcarinaButtonShortNames[item.id]; + ImGui::SetCursorScreenPos(ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(ocarinaButtonName.c_str()).x / 2), + p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", ocarinaButtonName.c_str()); + ImGui::PopStyleColor(); + } + + if (item.id >= RG_GUARD_HOUSE_KEY && item.id <= RG_FISHING_HOLE_KEY) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string overworldKeyName = itemTrackerOverworldKeyShortNames[item.id]; + ImGui::SetCursorScreenPos(ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(overworldKeyName.c_str()).x / 2), + p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", overworldKeyName.c_str()); + ImGui::PopStyleColor(); + } + + if (item.id >= RG_BRONZE_SCALE && item.id <= RG_OPEN_CHEST) { + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(itemName.c_str()).x / 2), p.y - (iconSize + 2))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", itemName.c_str()); + ImGui::PopStyleColor(); + } + + ImGui::EndGroup(); + + if (itemName == "") { + itemName = SohUtils::GetItemName(item.id); + } + + Tooltip(itemName.c_str()); +} + +void DrawBottle(ItemTrackerItem item) { + uint32_t actualItemId = + GameInteractor::IsSaveLoaded() ? (gSaveContext.inventory.items[SLOT(item.id) + item.data]) : false; + bool hasItem = actualItemId != ITEM_NONE; + + if (GameInteractor::IsSaveLoaded() && + (hasItem && item.id != actualItemId && + actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end())) { + item = actualItemTrackerItemMap[actualItemId]; + } + + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasItem && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); + + Tooltip(SohUtils::GetItemName(item.id).c_str()); +}; + +void DrawDungeonItem(ItemTrackerItem item) { + uint32_t itemId = item.id; + ImU32 dungeonColor = IM_COL_WHITE; + uint32_t bitMask = 1 << (item.id - ITEM_KEY_BOSS); // Bitset starts at ITEM_KEY_BOSS == 0. the rest are sequential + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + bool hasItem = GameInteractor::IsSaveLoaded() ? (bitMask & gSaveContext.inventory.dungeonItems[item.data]) : false; + bool hasSmallKey = GameInteractor::IsSaveLoaded() ? ((gSaveContext.inventory.dungeonKeys[item.data]) >= 0) : false; + ImGui::BeginGroup(); + if (itemId == ITEM_KEY_SMALL) { + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasSmallKey && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); + } else { + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasItem && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); + } + + if (CheckTracker::IsAreaSpoiled(RandomizerCheckObjects::GetRCAreaBySceneID(static_cast(item.data))) && + GameInteractor::IsSaveLoaded()) { + dungeonColor = (ResourceMgr_IsSceneMasterQuest(item.data) ? IM_COL_PURPLE : IM_COL_LIGHT_YELLOW); + } + + if (itemId == ITEM_KEY_SMALL) { + DrawItemCount(item, !CheckTracker::IsAreaSpoiled( + RandomizerCheckObjects::GetRCAreaBySceneID(static_cast(item.data)))); + + ImVec2 p = ImGui::GetCursorScreenPos(); + // offset puts the text at the correct level. for some reason, if the save is loaded, the margin is 3 pixels + // higher only for small keys, so we use 16 then. Otherwise, 13 is where everything else is + int offset = GameInteractor::IsSaveLoaded() ? 16 : 13; + std::string dungeonName = itemTrackerDungeonShortNames[item.data]; + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(dungeonName.c_str()).x / 2), p.y - (iconSize + offset))); + ImGui::PushStyleColor(ImGuiCol_Text, dungeonColor); + ImGui::Text("%s", dungeonName.c_str()); + ImGui::PopStyleColor(); + } + + if (itemId == ITEM_DUNGEON_MAP && (item.data == SCENE_DEKU_TREE || item.data == SCENE_DODONGOS_CAVERN || + item.data == SCENE_JABU_JABU || item.data == SCENE_ICE_CAVERN)) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string dungeonName = itemTrackerDungeonShortNames[item.data]; + ImGui::SetCursorScreenPos( + ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(dungeonName.c_str()).x / 2), p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, dungeonColor); + ImGui::Text("%s", dungeonName.c_str()); + ImGui::PopStyleColor(); + } + ImGui::EndGroup(); + + Tooltip(SohUtils::GetItemName(item.id).c_str()); +} + +void DrawSong(ItemTrackerItem item) { + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + ImVec2 p = ImGui::GetCursorScreenPos(); + bool hasSong = HasSong(item); + ImGui::SetCursorScreenPos(ImVec2(p.x + 6, p.y)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + hasSong && IsValidSaveFile() ? item.name : item.nameFaded), + ImVec2(iconSize / 1.5f, iconSize), ImVec2(0, 0), ImVec2(1, 1)); + Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); +} + +void DrawNotes(bool resizeable = false) { + ImGui::BeginGroup(); + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + int iconSpacing = CVarGetInteger(CVAR_TRACKER_ITEM("IconSpacing"), 12); + + struct ItemTrackerNotes { + static int TrackerNotesResizeCallback(ImGuiInputTextCallbackData* data) { + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + ImVector* itemTrackerNotes = (ImVector*)data->UserData; + IM_ASSERT(itemTrackerNotes->begin() == data->Buf); + itemTrackerNotes->resize( + data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1 + data->Buf = itemTrackerNotes->begin(); + } + return 0; + } + static bool TrackerNotesInputTextMultiline(const char* label, ImVector* itemTrackerNotes, + const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + return ImGui::InputTextMultiline(label, itemTrackerNotes->begin(), (size_t)itemTrackerNotes->size(), size, + flags | ImGuiInputTextFlags_CallbackResize, + ItemTrackerNotes::TrackerNotesResizeCallback, (void*)itemTrackerNotes); + } + }; + ImVec2 size = resizeable ? ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y) + : ImVec2(((iconSize + iconSpacing) * 6) - 8.0f, 200.0f); + if (GameInteractor::IsSaveLoaded()) { + if (ItemTrackerNotes::TrackerNotesInputTextMultiline("##ItemTrackerNotes", &itemTrackerNotes, size, + ImGuiInputTextFlags_AllowTabInput)) { + notesNeedSave = true; + notesIdleFrames = 0; + } + if ((ImGui::IsItemDeactivatedAfterEdit() || (notesNeedSave && notesIdleFrames > notesMaxIdleFrames)) && + IsValidSaveFile()) { + notesNeedSave = false; + SaveManager::Instance->SaveSection(gSaveContext.fileNum, itemTrackerSectionId, true); + } + } + ImGui::EndGroup(); +} + +void DrawTotalChecks() { + uint16_t totalChecks = CheckTracker::GetTotalChecks(); + uint16_t totalChecksGotten = CheckTracker::GetTotalChecksGotten(); + + ImGui::BeginGroup(); + if (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_FLOATING) { + ImGui::SetWindowFontScale(2.5); + } else { + ImGui::SetWindowFontScale(1); + } + ImGui::Text(LanguageManager::Instance().GetString("Checks: %d/%d").c_str(), totalChecksGotten, totalChecks); + ImGui::EndGroup(); +} + +// Windowing stuff +void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { + ImGuiWindowFlags windowFlags = flags; + + if (windowFlags == 0) { + windowFlags |= + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize; + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_FLOATING) { + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + windowFlags |= ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar; + + if (!CVarGetInteger(CVAR_TRACKER_ITEM("Draggable"), 0)) { + windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; + } + } + auto color = VecFromRGBA8(CVarGetColor(CVAR_TRACKER_ITEM("BgColor.Value"), { 0, 0, 0, 0 })); + auto maybeParent = ImGui::GetCurrentWindow(); + ImGuiWindow* window = ImGui::FindWindowByName(UniqueName.c_str()); + if (window != NULL && window->DockTabIsVisible && window->ParentWindow != NULL && + std::string(window->ParentWindow->Name).compare(0, strlen("Main - Deck"), "Main - Deck") == 0) { + color.w = 1.0f; + } + ImGui::PushStyleColor(ImGuiCol_WindowBg, color); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + if (presetLoaded && presetPos.contains(UniqueName)) { + ImGui::SetNextWindowSize(presetSize[UniqueName]); + ImGui::SetNextWindowPos(presetPos[UniqueName]); + presetSize.erase(UniqueName); + presetPos.erase(UniqueName); + } + ImGui::Begin(UniqueName.c_str(), nullptr, windowFlags); +} +void EndFloatingWindows() { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::End(); +} + +/** + * DrawItemsInRows + * Takes in a vector of ItemTrackerItem and draws them in rows of N items + */ +void DrawItemsInRows(std::vector items, int columns = 6) { + float iconSize = static_cast(CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36)); + int iconSpacing = CVarGetInteger(CVAR_TRACKER_ITEM("IconSpacing"), 12); + int topPadding = + (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_WINDOW) ? 20 : 0; + + for (int i = 0; i < items.size(); i++) { + int row = i / columns; + int column = i % columns; + ImGui::SetCursorPos( + ImVec2((column * (iconSize + iconSpacing) + 8.0f), (row * (iconSize + iconSpacing)) + 8.0f + topPadding)); + items[i].drawFunc(items[i]); + } +} + +/** + * DrawItemsInACircle + * Takes in a vector of ItemTrackerItem and draws them evenly spread across a circle + */ +void DrawItemsInACircle(std::vector items) { + int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); + int iconSpacing = CVarGetInteger(CVAR_TRACKER_ITEM("IconSpacing"), 12); + + ImVec2 max = ImGui::GetWindowContentRegionMax(); + float radius = (iconSize + iconSpacing) * 2.0f; + + for (int i = 0; i < items.size(); i++) { + float angle = static_cast(i) / items.size() * 2.0f * M_PIf; + float x = (radius / 2.0f) * cos(angle) + max.x / 2.0f; + float y = (radius / 2.0f) * sin(angle) + max.y / 2.0f; + ImGui::SetCursorPos(ImVec2(x - (CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36) - 8) / 2.0f, y + 4)); + items[i].drawFunc(items[i]); + } +} + +/** + * GetDungeonItemsVector + * Loops over dungeons and creates vectors of items in the correct order + * to then call DrawItemsInRows + */ +std::vector GetDungeonItemsVector(std::vector dungeons, size_t columns = 6) { + std::vector dungeonItems = {}; + + size_t rowCount = 0; + for (size_t i = 0; i < dungeons.size(); i++) { + if (dungeons[i].items.size() > rowCount) + rowCount = static_cast(dungeons[i].items.size()); + } + + for (size_t i = 0; i < rowCount; i++) { + for (size_t j = 0; j < MIN(dungeons.size(), columns); j++) { + if (dungeons[j].items.size() > i) { + switch (dungeons[j].items[i]) { + case ITEM_KEY_SMALL: + dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, dungeons[j].id, DrawDungeonItem)); + break; + case ITEM_KEY_BOSS: + // Swap Ganon's Castle boss key to the right scene ID manually + if (dungeons[j].id == SCENE_INSIDE_GANONS_CASTLE) { + dungeonItems.push_back( + ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, SCENE_GANONS_TOWER, DrawDungeonItem)); + } else { + dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, dungeons[j].id, DrawDungeonItem)); + } + break; + case ITEM_DUNGEON_MAP: + dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_DUNGEON_MAP, dungeons[j].id, DrawDungeonItem)); + break; + case ITEM_COMPASS: + dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_COMPASS, dungeons[j].id, DrawDungeonItem)); + break; + } + } else { + dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + } + } + + if (dungeons.size() > columns) { + std::vector nextDungeonItems = + GetDungeonItemsVector(std::vector(dungeons.begin() + columns, dungeons.end()), columns); + dungeonItems.insert(dungeonItems.end(), nextDungeonItems.begin(), nextDungeonItems.end()); + } + + return dungeonItems; +} +/* ****************************************************** */ + +void UpdateVectors() { + if (!shouldUpdateVectors) { + return; + } + + dungeonRewards.clear(); + dungeonRewards.insert(dungeonRewards.end(), dungeonRewardStones.begin(), dungeonRewardStones.end()); + dungeonRewards.insert(dungeonRewards.end(), dungeonRewardMedallions.begin(), dungeonRewardMedallions.end()); + + dungeonItems.clear(); + if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.Layout"), 1) && + CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) { + dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsWithMapsHorizontal, 12); + // Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item + dungeonItems[23] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, SCENE_THIEVES_HIDEOUT, DrawDungeonItem); + } else { + // Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item + dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsHorizontal, 8); + dungeonItems[15] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, SCENE_THIEVES_HIDEOUT, DrawDungeonItem); + } + } else { + if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) { + dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsWithMapsCompact); + // Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item + dungeonItems[35] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, SCENE_THIEVES_HIDEOUT, DrawDungeonItem); + } else { + dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsCompact); + } + } + + mainWindowItems.clear(); + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Inventory"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) { + mainWindowItems.insert(mainWindowItems.end(), inventoryItems.begin(), inventoryItems.end()); + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Equipment"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) { + mainWindowItems.insert(mainWindowItems.end(), equipmentItems.begin(), equipmentItems.end()); + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Misc"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) { + mainWindowItems.insert(mainWindowItems.end(), miscItems.begin(), miscItems.end()); + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) { + mainWindowItems.insert(mainWindowItems.end(), dungeonRewardStones.begin(), dungeonRewardStones.end()); + mainWindowItems.insert(mainWindowItems.end(), dungeonRewardMedallions.begin(), dungeonRewardMedallions.end()); + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Songs"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) { + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Misc"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW && + CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) != + SECTION_DISPLAY_MAIN_WINDOW) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + mainWindowItems.insert(mainWindowItems.end(), songItems.begin(), songItems.end()); + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + mainWindowItems.insert(mainWindowItems.end(), dungeonItems.begin(), dungeonItems.end()); + } + if (IS_RANDO && RAND_GET_OPTION(RSK_ROCS_FEATHER)) { + mainWindowItems.insert(mainWindowItems.end(), rocsFeather.begin(), rocsFeather.end()); + } + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_SWIM)) { + mainWindowItems.insert(mainWindowItems.end(), swimItems.begin(), swimItems.end()); + } + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_GRAB)) { + mainWindowItems.insert(mainWindowItems.end(), grabItems.begin(), grabItems.end()); + } + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_CLIMB)) { + mainWindowItems.insert(mainWindowItems.end(), climbItems.begin(), climbItems.end()); + } + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_CRAWL)) { + mainWindowItems.insert(mainWindowItems.end(), crawlItems.begin(), crawlItems.end()); + } + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_OPEN_CHEST)) { + mainWindowItems.insert(mainWindowItems.end(), openChestItems.begin(), openChestItems.end()); + } + + // if we're adding greg to the misc window, + // and misc isn't on the main window, + // and it doesn't already have greg, add him + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Greg"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_MISC_WINDOW && + CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Misc"), SECTION_DISPLAY_MAIN_WINDOW) != + SECTION_DISPLAY_MAIN_WINDOW) { + if (std::none_of(miscItems.begin(), miscItems.end(), + [](ItemTrackerItem item) { return item.id == ITEM_RUPEE_GREEN; })) + miscItems.insert(miscItems.end(), gregItems.begin(), gregItems.end()); + } else { + miscItems.erase(std::remove_if(miscItems.begin(), miscItems.end(), + [](ItemTrackerItem i) { return i.id == ITEM_RUPEE_GREEN; }), + miscItems.end()); + } + + bool newRowAdded = false; + // if we're adding greg to the main window + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Greg"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_MAIN_WINDOW) { + if (!newRowAdded) { + // insert empty items until we're on a new row for greg + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + newRowAdded = true; + } + + // add greg + mainWindowItems.insert(mainWindowItems.end(), gregItems.begin(), gregItems.end()); + } + + // If we're adding triforce pieces to the main window + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + // If Greg isn't on the main window, add empty items to place the triforce pieces on a new row. + if (!newRowAdded) { + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + newRowAdded = true; + } + + // Add triforce pieces + mainWindowItems.insert(mainWindowItems.end(), triforcePieces.begin(), triforcePieces.end()); + } + + // if misc is separate and fishing pole isn't added, add fishing pole to misc + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.FishingPole"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_MISC_WINDOW && + CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Misc"), SECTION_DISPLAY_MAIN_WINDOW) != + SECTION_DISPLAY_MAIN_WINDOW) { + if (std::none_of(miscItems.begin(), miscItems.end(), + [](ItemTrackerItem item) { return item.id == ITEM_FISHING_POLE; })) + miscItems.insert(miscItems.end(), fishingPoleItems.begin(), fishingPoleItems.end()); + } else { + miscItems.erase(std::remove_if(miscItems.begin(), miscItems.end(), + [](ItemTrackerItem i) { return i.id == ITEM_FISHING_POLE; }), + miscItems.end()); + } + // add fishing pole to main window + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.FishingPole"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_MAIN_WINDOW) { + if (!newRowAdded) { + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + newRowAdded = true; + } + + mainWindowItems.insert(mainWindowItems.end(), fishingPoleItems.begin(), fishingPoleItems.end()); + } + + // If we're adding bean souls to the main window... + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.BeanSouls"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + //...add empty items on the main window to get the souls on their own row. (Too many to sit with Greg/Triforce + // pieces) + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + + // Add bean souls + mainWindowItems.insert(mainWindowItems.end(), beanSoulItems.begin(), beanSoulItems.end()); + } + + // If we're adding boss souls to the main window... + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.BossSouls"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + //...add empty items on the main window to get the souls on their own row + // (Too many to sit with Greg/Triforce pieces) + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + + // Add boss souls + mainWindowItems.insert(mainWindowItems.end(), bossSoulItems.begin(), bossSoulItems.end()); + } + + // If we're adding jabbernuts to the main window... + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.JabberNuts"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + // there are 6 jabbernuts, perfect for a row + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + + // Add jabbernuts + mainWindowItems.insert(mainWindowItems.end(), jabbernutItems.begin(), jabbernutItems.end()); + } + + // If we're adding ocarina buttons to the main window... + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + //...add empty items on the main window to get the buttons on their own row. + // (Too many to sit with Greg/Triforce pieces/boss souls) + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + + // Add ocarina buttons + mainWindowItems.insert(mainWindowItems.end(), ocarinaButtonItems.begin(), ocarinaButtonItems.end()); + } + + // If we're adding overworld keys to the main window... + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.OverworldKeys"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + //...add empty items on the main window to get the keys on their own row. + // (Too many to sit with Greg/Triforce pieces/boss souls/ocarina buttons) + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + + // Add overworld keys + mainWindowItems.insert(mainWindowItems.end(), overworldKeyItems.begin(), overworldKeyItems.end()); + } + + shouldUpdateVectors = false; +} + +void ItemTrackerInitFile(bool isDebug) { + itemTrackerNotes.clear(); + itemTrackerNotes.push_back(0); +} + +void ItemTrackerSaveFile(SaveContext* saveContext, int sectionID, bool fullSave) { + SaveManager::Instance->SaveData("personalNotes", + std::string(std::begin(itemTrackerNotes), std::end(itemTrackerNotes)).c_str()); +} + +void ItemTrackerLoadFile() { + std::string initialTrackerNotes = ""; + SaveManager::Instance->LoadData("personalNotes", initialTrackerNotes); + itemTrackerNotes.resize(static_cast(initialTrackerNotes.length() + 1)); + if (initialTrackerNotes != "") { + SohUtils::CopyStringToCharArray(itemTrackerNotes.Data, initialTrackerNotes.c_str(), itemTrackerNotes.size()); + } else { + itemTrackerNotes.push_back(0); + } +} + +void ItemTrackerWindow::Draw() { + if (!IsVisible()) { + return; + } + ImGui::PushFont(OTRGlobals::Instance->fontMono); + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); + ImGui::PopFont(); +} + +void ItemTrackerWindow::DrawElement() { + UpdateVectors(); + + int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); + int iconSpacing = CVarGetInteger(CVAR_TRACKER_ITEM("IconSpacing"), 12); + int comboButton1Mask = buttonMap[CVarGetInteger(CVAR_TRACKER_ITEM("ComboButton1"), TRACKER_COMBO_BUTTON_L)]; + int comboButton2Mask = buttonMap[CVarGetInteger(CVAR_TRACKER_ITEM("ComboButton2"), TRACKER_COMBO_BUTTON_R)]; + OSContPad* buttonsPressed = + std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetControlDeck())->GetPads(); + bool comboButtonsHeld = buttonsPressed != nullptr && buttonsPressed[0].button & comboButton1Mask && + buttonsPressed[0].button & comboButton2Mask; + bool isPaused = CVarGetInteger(CVAR_TRACKER_ITEM("ShowOnlyPaused"), 0) == 0 || + gPlayState != nullptr && gPlayState->pauseCtx.state > 0; + + if (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_WINDOW || + isPaused && + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_ALWAYS + ? CVarGetInteger(CVAR_WINDOW("ItemTracker"), 0) + : comboButtonsHeld)) { + if ((CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Inventory"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Equipment"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Misc"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Songs"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Greg"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.FishingPole"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_MAIN_WINDOW) || + (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Notes"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW)) { + BeginFloatingWindows("Item Tracker"); + DrawItemsInRows(mainWindowItems, 6); + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Notes"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_MAIN_WINDOW) { + DrawNotes(); + } + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Inventory"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Inventory Items Tracker"); + DrawItemsInRows(inventoryItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Equipment"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Equipment Items Tracker"); + DrawItemsInRows(equipmentItems, 3); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Misc"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Misc Items Tracker"); + DrawItemsInRows(miscItems, 4); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Dungeon Rewards Tracker"); + if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonRewardsLayout"), 0)) { + ImGui::BeginGroup(); + DrawItemsInACircle(dungeonRewardMedallions); + ImGui::EndGroup(); + ImGui::BeginGroup(); + DrawItemsInRows(dungeonRewardStones); + ImGui::EndGroup(); + } else { + DrawItemsInRows(dungeonRewards, 3); + } + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Songs"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Songs Tracker"); + DrawItemsInRows(songItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Dungeon Items Tracker"); + if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.Layout"), 1)) { + if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) { + DrawItemsInRows(dungeonItems, 12); + } else { + DrawItemsInRows(dungeonItems, 8); + } + } else { + DrawItemsInRows(dungeonItems); + } + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Greg"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_SEPARATE) { + BeginFloatingWindows("Greg Tracker"); + DrawItemsInRows(gregItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Triforce Piece Tracker"); + DrawItemsInRows(triforcePieces); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.BeanSouls"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Bean Soul Tracker"); + DrawItemsInRows(beanSoulItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.BossSouls"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Boss Soul Tracker"); + DrawItemsInRows(bossSoulItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.JabberNuts"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Jabber Nut Tracker"); + DrawItemsInRows(jabbernutItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Ocarina Button Tracker"); + DrawItemsInRows(ocarinaButtonItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.OverworldKeys"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Overworld Key Tracker"); + DrawItemsInRows(overworldKeyItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.FishingPole"), SECTION_DISPLAY_EXTENDED_HIDDEN) == + SECTION_DISPLAY_EXTENDED_SEPARATE) { + BeginFloatingWindows("Fishing Pole Tracker"); + DrawItemsInRows(fishingPoleItems); + EndFloatingWindows(); + } + + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Notes"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE && + (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_WINDOW || + (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_FLOATING && + CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) != + TRACKER_DISPLAY_COMBO_BUTTON))) { + ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); + BeginFloatingWindows("Personal Notes", ImGuiWindowFlags_NoFocusOnAppearing); + DrawNotes(true); + EndFloatingWindows(); + } + + if (CVarGetInteger("gTrackers.ItemTracker.TotalChecks.DisplayType", SECTION_DISPLAY_MINIMAL_HIDDEN) == + SECTION_DISPLAY_MINIMAL_SEPARATE) { + ImGui::SetNextWindowSize(ImVec2(450, 300), ImGuiCond_FirstUseEver); + BeginFloatingWindows("Total Checks"); + DrawTotalChecks(); + EndFloatingWindows(); + } + } + if (presetLoaded) { + shouldUpdateVectors = true; + presetLoaded = false; + } +} + +static std::map itemTrackerCapacityTrackOptions = { + { ITEM_TRACKER_NUMBER_NONE, "No Numbers" }, + { ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY, "Current Capacity" }, + { ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY, "Current Ammo" }, + { ITEM_TRACKER_NUMBER_CAPACITY, "Current Capacity / Max Capacity" }, + { ITEM_TRACKER_NUMBER_AMMO, "Current Ammo / Current Capacity" }, +}; +static std::map itemTrackerKeyTrackOptions = { + { KEYS_COLLECTED_MAX, "Collected / Max" }, + { KEYS_CURRENT_COLLECTED_MAX, "Current / Collected / Max" }, + { KEYS_CURRENT_MAX, "Current / Max" }, +}; +static std::map itemTrackerTriforcePieceTrackOptions = { + { TRIFORCE_PIECE_COLLECTED_REQUIRED, "Collected / Required" }, + { TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX, "Collected / Required / Max" }, +}; +static std::map displayTypes = { + { SECTION_DISPLAY_HIDDEN, "Hidden" }, + { SECTION_DISPLAY_MAIN_WINDOW, "Main Window" }, + { SECTION_DISPLAY_SEPARATE, "Separate" }, +}; +static std::map extendedDisplayTypes = { + { SECTION_DISPLAY_EXTENDED_HIDDEN, "Hidden" }, + { SECTION_DISPLAY_EXTENDED_MAIN_WINDOW, "Main Window" }, + { SECTION_DISPLAY_EXTENDED_MISC_WINDOW, "Misc Window" }, + { SECTION_DISPLAY_EXTENDED_SEPARATE, "Separate" }, +}; +static std::map minimalDisplayTypes = { { SECTION_DISPLAY_MINIMAL_HIDDEN, "Hidden" }, + { SECTION_DISPLAY_MINIMAL_SEPARATE, "Separate" } }; + +void ItemTrackerSettingsWindow::DrawElement() { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); + if (ImGui::BeginTable("itemTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("General settings").c_str(), ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableSetupColumn(LanguageManager::Instance().GetString("Section settings").c_str(), ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + SohGui::mSohMenu->MenuDrawItem(backgroundColor, 250, THEME_COLOR); + ImGui::PopItemWidth(); + SohGui::mSohMenu->MenuDrawItem(windowTypeWidget, 250, THEME_COLOR); + + if (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_FLOATING) { + if (CVarCheckbox("Enable Dragging", CVAR_TRACKER_ITEM("Draggable"), CheckboxOptions().Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCheckbox("Only Enable While Paused", CVAR_TRACKER_ITEM("ShowOnlyPaused"), + CheckboxOptions().Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCombobox("Display Mode", CVAR_TRACKER_ITEM("DisplayType.Main"), showMode, + ComboboxOptions() + .DefaultIndex(TRACKER_DISPLAY_ALWAYS) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == + TRACKER_DISPLAY_COMBO_BUTTON) { + if (CVarCombobox("Combo Button 1", CVAR_TRACKER_ITEM("ComboButton1"), buttonStrings, + ComboboxOptions() + .DefaultIndex(TRACKER_COMBO_BUTTON_L) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCombobox("Combo Button 2", CVAR_TRACKER_ITEM("ComboButton2"), buttonStrings, + ComboboxOptions() + .DefaultIndex(TRACKER_COMBO_BUTTON_R) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + } + } + ImGui::Separator(); + CVarSliderInt("Icon size : %dpx", CVAR_TRACKER_ITEM("IconSize"), + IntSliderOptions().Min(25).Max(128).DefaultValue(36).Color(THEME_COLOR)); + CVarSliderInt("Icon margins : %dpx", CVAR_TRACKER_ITEM("IconSpacing"), + IntSliderOptions().Min(-5).Max(50).DefaultValue(12).Color(THEME_COLOR)); + CVarSliderInt("Text size : %dpx", CVAR_TRACKER_ITEM("TextSize"), + IntSliderOptions().Min(1).Max(30).DefaultValue(13).Color(THEME_COLOR)); + + ImGui::NewLine(); + SohGui::mSohMenu->MenuDrawItem(ammoTracking, 250, THEME_COLOR); + if (CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == + ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || + CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == + ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY) { + if (CVarCheckbox("Align count to left side", CVAR_TRACKER_ITEM("ItemCountAlignLeft"), + CheckboxOptions().Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + } + + SohGui::mSohMenu->MenuDrawItem(keyTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(triforcePieceCount, 250, THEME_COLOR); + + ImGui::TableNextColumn(); + + if (CVarCombobox("Inventory", CVAR_TRACKER_ITEM("DisplayType.Inventory"), displayTypes, + ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCombobox("Equipment", CVAR_TRACKER_ITEM("DisplayType.Equipment"), displayTypes, + ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCombobox("Misc", CVAR_TRACKER_ITEM("DisplayType.Misc"), displayTypes, + ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCombobox("Dungeon Rewards", CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), displayTypes, + ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == + SECTION_DISPLAY_SEPARATE) { + if (CVarCheckbox("Circle display", CVAR_TRACKER_ITEM("DungeonRewardsLayout"), + CheckboxOptions().DefaultValue(false).Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + } + if (CVarCombobox("Songs", CVAR_TRACKER_ITEM("DisplayType.Songs"), displayTypes, + ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + SohGui::mSohMenu->MenuDrawItem(dungeonItemTracking, 250, THEME_COLOR); + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) != + SECTION_DISPLAY_HIDDEN) { + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == + SECTION_DISPLAY_SEPARATE) { + if (CVarCheckbox("Horizontal display", CVAR_TRACKER_ITEM("DungeonItems.Layout"), + CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + } + if (CVarCheckbox("Maps and compasses", CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), + CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + } + SohGui::mSohMenu->MenuDrawItem(gregTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(triforcePieceTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(beanSoulsTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(bossSoulsTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(jabberNutsTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(ocarinaButtonTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(overworldKeysTracking, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(fishingPoleTracking, 250, THEME_COLOR); + + if (CVarCombobox("Total Checks", CVAR_TRACKER_ITEM("TotalChecks.DisplayType"), minimalDisplayTypes, + ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MINIMAL_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + + SohGui::mSohMenu->MenuDrawItem(personalNotesWiget, 250, THEME_COLOR); + SohGui::mSohMenu->MenuDrawItem(hookshotIdentWidget, 250, THEME_COLOR); + + ImGui::PopStyleVar(1); + ImGui::EndTable(); + } +} + +void ItemTrackerWindow::InitElement() { + // Crashes when the itemTrackerNotes is empty, so add an empty character to it + if (itemTrackerNotes.empty()) { + itemTrackerNotes.push_back(0); + } + + SaveManager::Instance->AddInitFunction(ItemTrackerInitFile); + itemTrackerSectionId = SaveManager::Instance->AddSaveFunction("itemTrackerData", 1, ItemTrackerSaveFile, true, -1); + SaveManager::Instance->AddLoadFunction("itemTrackerData", 1, ItemTrackerLoadFile); + + GameInteractor::Instance->RegisterGameHook(ItemTrackerOnFrame); +} + +void RegisterItemTrackerWidgets() { + backgroundColor = { .name = "Background Color##ItemTracker", .type = WidgetType::WIDGET_CVAR_COLOR_PICKER }; + backgroundColor.CVar(CVAR_TRACKER_ITEM("BgColor")) + .Options( + ColorPickerOptions().Color(THEME_COLOR).DefaultValue({ 0, 0, 0, 0 }).UseAlpha().ShowReset().ShowRandom()); + SohGui::mSohMenu->AddSearchWidget({ backgroundColor, "Randomizer", "Item Tracker", "General Settings" }); + + windowTypeWidget = { .name = "Window Type##ItemTracker", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + windowTypeWidget.CVar(CVAR_TRACKER_ITEM("WindowType")) + .Options(ComboboxOptions() + .DefaultIndex(TRACKER_WINDOW_FLOATING) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(windowType)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + SohGui::mSohMenu->AddSearchWidget({ windowTypeWidget, "Randomizer", "Item Tracker", "General Settings" }); + enableDraggingWidget; + onlyPausedWidget; + + ammoTracking = { .name = "Ammo/Capacity Tracking", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + ammoTracking.CVar(CVAR_TRACKER_ITEM("ItemCountType")) + .Options(ComboboxOptions() + .DefaultIndex(ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(itemTrackerCapacityTrackOptions) + .Tooltip("Customize what the numbers under each item are tracking." + "\n\nNote: items without capacity upgrades will track ammo even in capacity mode")); + SohGui::mSohMenu->AddSearchWidget({ ammoTracking, "Randomizer", "Item Tracker", "General Settings" }); + + keyTracking = { .name = "Key Count Tracking", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + keyTracking.CVar(CVAR_TRACKER_ITEM("KeyCounts")) + .Options(ComboboxOptions() + .DefaultIndex(KEYS_COLLECTED_MAX) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(itemTrackerKeyTrackOptions) + .Tooltip("Customize what numbers are shown for key tracking.")); + SohGui::mSohMenu->AddSearchWidget({ keyTracking, "Randomizer", "Item Tracker", "General Settings" }); + + triforcePieceTracking = { .name = "Triforce Pieces", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + triforcePieceTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.TriforcePieces")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + SohGui::mSohMenu->AddSearchWidget({ triforcePieceTracking, "Randomizer", "Item Tracker", "General Settings" }); + + dungeonItemTracking = { .name = "Dungeon Items", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + dungeonItemTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.DungeonItems")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget( + { dungeonItemTracking, "Randomizer", "Item Tracker", "General Settings", "keys maps compasses icon" }); + + gregTracking = { .name = "Greg", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + gregTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.Greg")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(extendedDisplayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget({ gregTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + beanSoulsTracking = { .name = "Bean Souls", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + beanSoulsTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.BeanSouls")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget({ beanSoulsTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + bossSoulsTracking = { .name = "Boss Souls", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + bossSoulsTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.BossSouls")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget({ bossSoulsTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + jabberNutsTracking = { .name = "Jabber Nuts", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + jabberNutsTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.JabberNuts")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget({ jabberNutsTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + triforcePieceCount = { .name = "Triforce Piece Count Tracking", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + triforcePieceCount.CVar(CVAR_TRACKER_ITEM("TriforcePieceCounts")) + .Options(ComboboxOptions() + .DefaultIndex(TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(itemTrackerTriforcePieceTrackOptions) + .Tooltip("Customize what numbers are shown for triforce piece tracking.")); + SohGui::mSohMenu->AddSearchWidget({ triforcePieceCount, "Randomizer", "Item Tracker", "General Settings" }); + + ocarinaButtonTracking = { .name = "Ocarina Buttons", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + ocarinaButtonTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget( + { ocarinaButtonTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + overworldKeysTracking = { .name = "Overworld Keys", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + overworldKeysTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.OverworldKeys")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget( + { overworldKeysTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + fishingPoleTracking = { .name = "Fishing Pole", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + fishingPoleTracking.CVar(CVAR_TRACKER_ITEM("DisplayType.FishingPole")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(extendedDisplayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget( + { fishingPoleTracking, "Randomizer", "Item Tracker", "General Settings", "icon" }); + + personalNotesWiget = { .name = "Personal notes", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; + static const char* notesDisabledTooltip = + "Disabled because tracker is set to floating and display combo is enabled."; + personalNotesWiget.CVar(CVAR_TRACKER_ITEM("DisplayType.Notes")) + .Options(ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(displayTypes)) + .Callback([](WidgetInfo& info) { shouldUpdateVectors = true; }); + ; + SohGui::mSohMenu->AddSearchWidget({ personalNotesWiget, "Randomizer", "Item Tracker", "General Settings" }); + + hookshotIdentWidget = { .name = "Show Hookshot Identifiers", .type = WidgetType::WIDGET_CVAR_CHECKBOX }; + hookshotIdentWidget.CVar(CVAR_TRACKER_ITEM("HookshotIdentifier")) + .Options(CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Shows an 'H' or an 'L' to more easily distinguish between Hookshot and Longshot.")); + SohGui::mSohMenu->AddSearchWidget({ hookshotIdentWidget, "Randomizer", "Item Tracker", "General Settings" }); +} + +void RegisterItemTracker() { + COND_HOOK(OnLoadFile, true, [](int32_t fileNum) { shouldUpdateVectors = true; }); +} + +static RegisterShipInitFunc registerItemTracker(RegisterItemTracker); +static RegisterMenuInitFunc menuInitFunc(RegisterItemTrackerWidgets);