From 72bed7a1ba9afd4f7d8ac0af4d7a6fec2a906362 Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Wed, 8 Apr 2026 00:35:02 -0700 Subject: [PATCH] initial rando generator hookup --- CMakeLists.txt | 5 +- src/dusk/randomizer/CMakeLists.txt | 77 + .../data/entrance_shuffle_data.yaml | 2582 ++++++++++ src/dusk/randomizer/data/items.yaml | 791 +++ src/dusk/randomizer/data/locations.yaml | 4431 +++++++++++++++++ src/dusk/randomizer/data/macros.yaml | 262 + src/dusk/randomizer/data/settings_list.yaml | 660 +++ .../data/tests/logic/all random/settings.yaml | 66 + .../logic/any dungeon items/settings.yaml | 4 + .../anywhere dungeon items/settings.yaml | 4 + .../anywhere dungeon rewards/settings.yaml | 2 + .../data/tests/logic/bonko/settings.yaml | 5 + .../cleared eldin twilight/settings.yaml | 2 + .../cleared lanayru twilight/settings.yaml | 2 + .../data/tests/logic/default/settings.yaml | 1 + .../faron twilight cleared/settings.yaml | 2 + .../tests/logic/gifts from npcs/settings.yaml | 2 + .../tests/logic/golden bugs/settings.yaml | 2 + .../tests/logic/hidden skills/settings.yaml | 2 + .../hyrule castle all dungeons/settings.yaml | 2 + .../hyrule castle fused shadows/settings.yaml | 2 + .../hyrule castle mirror shards/settings.yaml | 2 + .../logic/hyrule castle open/settings.yaml | 2 + .../data/tests/logic/keysy/settings.yaml | 4 + .../logic/max entrance rando/settings.yaml | 16 + .../logic/mixed entrance pools/settings.yaml | 15 + .../tests/logic/open forest/settings.yaml | 2 + .../data/tests/logic/open start/settings.yaml | 7 + .../tests/logic/overworld items/settings.yaml | 4 + .../logic/own dungeon items/settings.yaml | 4 + .../logic/palace fused shadows/settings.yaml | 2 + .../logic/palace mirror shards/settings.yaml | 2 + .../tests/logic/palace open/settings.yaml | 2 + .../tests/logic/poe souls all/settings.yaml | 2 + .../logic/poe souls dungeon/settings.yaml | 2 + .../logic/poe souls overworld/settings.yaml | 2 + .../logic/random boss entrances/settings.yaml | 2 + .../logic/random cave entrances/settings.yaml | 8 + .../random dungeon entrances/settings.yaml | 2 + .../random grotto entrances/settings.yaml | 2 + .../random interior entrances/settings.yaml | 8 + .../random overworld entrances/settings.yaml | 8 + .../randomize starting spawn/settings.yaml | 8 + .../logic/scarcity minimal/settings.yaml | 2 + .../logic/scarcity plentiful/settings.yaml | 2 + .../data/tests/logic/shop items/settings.yaml | 2 + .../settings.yaml | 8 + .../data/tests/logic/skip mdh/settings.yaml | 2 + .../tests/logic/skip prologue/settings.yaml | 2 + .../tests/logic/sky characters/settings.yaml | 2 + .../settings.yaml | 3 + .../data/tests/logic/wolf start/settings.yaml | 4 + src/dusk/randomizer/data/world/Root.yaml | 163 + .../data/world/dungeons/Arbiters Grounds.yaml | 347 ++ .../data/world/dungeons/City in the Sky.yaml | 404 ++ .../data/world/dungeons/Forest Temple.yaml | 277 ++ .../data/world/dungeons/Goron Mines.yaml | 336 ++ .../data/world/dungeons/Hyrule Castle.yaml | 214 + .../data/world/dungeons/Lakebed Temple.yaml | 438 ++ .../world/dungeons/Palace of Twilight.yaml | 194 + .../data/world/dungeons/Snowpeak Ruins.yaml | 346 ++ .../data/world/dungeons/Temple of Time.yaml | 205 + .../data/world/overworld/Eldin Province.yaml | 594 +++ .../data/world/overworld/Faron Province.yaml | 385 ++ .../data/world/overworld/Gerudo Desert.yaml | 188 + .../world/overworld/Lanayru Province.yaml | 728 +++ .../data/world/overworld/Ordona Province.yaml | 165 + .../world/overworld/Snowpeak Province.yaml | 90 + src/dusk/randomizer/logic/area.cpp | 273 + src/dusk/randomizer/logic/area.hpp | 116 + src/dusk/randomizer/logic/dungeon.cpp | 127 + src/dusk/randomizer/logic/dungeon.hpp | 76 + src/dusk/randomizer/logic/entrance.cpp | 337 ++ src/dusk/randomizer/logic/entrance.hpp | 204 + .../randomizer/logic/entrance_shuffle.cpp | 780 +++ .../randomizer/logic/entrance_shuffle.hpp | 54 + src/dusk/randomizer/logic/fill.cpp | 529 ++ src/dusk/randomizer/logic/fill.hpp | 67 + src/dusk/randomizer/logic/flatten/bits.cpp | 275 + src/dusk/randomizer/logic/flatten/bits.hpp | 71 + src/dusk/randomizer/logic/flatten/flatten.cpp | 505 ++ src/dusk/randomizer/logic/flatten/flatten.hpp | 94 + .../logic/flatten/simplify_algebraic.cpp | 424 ++ .../logic/flatten/simplify_algebraic.hpp | 186 + src/dusk/randomizer/logic/generate.cpp | 114 + src/dusk/randomizer/logic/generate.hpp | 24 + src/dusk/randomizer/logic/item.cpp | 157 + src/dusk/randomizer/logic/item.hpp | 78 + src/dusk/randomizer/logic/item_pool.cpp | 348 ++ src/dusk/randomizer/logic/item_pool.hpp | 42 + src/dusk/randomizer/logic/location.cpp | 144 + src/dusk/randomizer/logic/location.hpp | 109 + src/dusk/randomizer/logic/plandomizer.cpp | 113 + src/dusk/randomizer/logic/plandomizer.hpp | 19 + src/dusk/randomizer/logic/requirement.cpp | 622 +++ src/dusk/randomizer/logic/requirement.hpp | 115 + src/dusk/randomizer/logic/search.cpp | 650 +++ src/dusk/randomizer/logic/search.hpp | 161 + src/dusk/randomizer/logic/spoiler_log.cpp | 247 + src/dusk/randomizer/logic/spoiler_log.hpp | 10 + src/dusk/randomizer/logic/world.cpp | 1174 +++++ src/dusk/randomizer/logic/world.hpp | 172 + src/dusk/randomizer/randomizer.cpp | 29 + src/dusk/randomizer/randomizer.hpp | 3 + src/dusk/randomizer/seedgen/config.cpp | 467 ++ src/dusk/randomizer/seedgen/config.hpp | 104 + src/dusk/randomizer/seedgen/packed_bits.hpp | 105 + src/dusk/randomizer/seedgen/seed.cpp | 113 + src/dusk/randomizer/seedgen/seed.hpp | 22 + src/dusk/randomizer/seedgen/settings.cpp | 328 ++ src/dusk/randomizer/seedgen/settings.hpp | 213 + src/dusk/randomizer/test/test.cpp | 42 + src/dusk/randomizer/test/test.hpp | 6 + src/dusk/randomizer/utility/color.cpp | 277 ++ src/dusk/randomizer/utility/color.hpp | 110 + src/dusk/randomizer/utility/common.cpp | 47 + src/dusk/randomizer/utility/common.hpp | 199 + src/dusk/randomizer/utility/container.hpp | 71 + src/dusk/randomizer/utility/endian.cpp | 103 + src/dusk/randomizer/utility/endian.hpp | 78 + src/dusk/randomizer/utility/exception.hpp | 7 + src/dusk/randomizer/utility/file.cpp | 210 + src/dusk/randomizer/utility/file.hpp | 120 + src/dusk/randomizer/utility/general.hpp | 10 + src/dusk/randomizer/utility/log.cpp | 101 + src/dusk/randomizer/utility/log.hpp | 119 + src/dusk/randomizer/utility/math.hpp | 10 + src/dusk/randomizer/utility/path.cpp | 71 + src/dusk/randomizer/utility/path.hpp | 32 + src/dusk/randomizer/utility/platform.cpp | 263 + src/dusk/randomizer/utility/platform.hpp | 34 + src/dusk/randomizer/utility/random.cpp | 40 + src/dusk/randomizer/utility/random.hpp | 60 + src/dusk/randomizer/utility/string.cpp | 52 + src/dusk/randomizer/utility/string.hpp | 116 + src/dusk/randomizer/utility/text.cpp | 178 + src/dusk/randomizer/utility/text.hpp | 59 + src/dusk/randomizer/utility/thread_local.hpp | 41 + src/dusk/randomizer/utility/time.cpp | 79 + src/dusk/randomizer/utility/time.hpp | 92 + src/dusk/randomizer/utility/yaml.hpp | 29 + src/m_Do/m_Do_main.cpp | 10 + 142 files changed, 26578 insertions(+), 2 deletions(-) create mode 100644 src/dusk/randomizer/CMakeLists.txt create mode 100644 src/dusk/randomizer/data/entrance_shuffle_data.yaml create mode 100644 src/dusk/randomizer/data/items.yaml create mode 100644 src/dusk/randomizer/data/locations.yaml create mode 100644 src/dusk/randomizer/data/macros.yaml create mode 100644 src/dusk/randomizer/data/settings_list.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/all random/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/any dungeon items/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/anywhere dungeon items/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/anywhere dungeon rewards/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/bonko/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/cleared eldin twilight/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/cleared lanayru twilight/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/default/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/faron twilight cleared/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/gifts from npcs/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/golden bugs/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/hidden skills/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/keysy/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/max entrance rando/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/mixed entrance pools/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/open forest/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/open start/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/overworld items/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/own dungeon items/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/palace fused shadows/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/palace mirror shards/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/palace open/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/poe souls all/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/poe souls dungeon/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/poe souls overworld/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/random boss entrances/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/random cave entrances/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/random dungeon entrances/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/random grotto entrances/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/random interior entrances/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/random overworld entrances/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/randomize starting spawn/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/scarcity minimal/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/scarcity plentiful/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/shop items/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/skip dungeon entrance requirements/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/skip mdh/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/skip prologue/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/sky characters/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml create mode 100644 src/dusk/randomizer/data/tests/logic/wolf start/settings.yaml create mode 100644 src/dusk/randomizer/data/world/Root.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Arbiters Grounds.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/City in the Sky.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Forest Temple.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Goron Mines.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Lakebed Temple.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Palace of Twilight.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Snowpeak Ruins.yaml create mode 100644 src/dusk/randomizer/data/world/dungeons/Temple of Time.yaml create mode 100644 src/dusk/randomizer/data/world/overworld/Eldin Province.yaml create mode 100644 src/dusk/randomizer/data/world/overworld/Faron Province.yaml create mode 100644 src/dusk/randomizer/data/world/overworld/Gerudo Desert.yaml create mode 100644 src/dusk/randomizer/data/world/overworld/Lanayru Province.yaml create mode 100644 src/dusk/randomizer/data/world/overworld/Ordona Province.yaml create mode 100644 src/dusk/randomizer/data/world/overworld/Snowpeak Province.yaml create mode 100644 src/dusk/randomizer/logic/area.cpp create mode 100644 src/dusk/randomizer/logic/area.hpp create mode 100644 src/dusk/randomizer/logic/dungeon.cpp create mode 100644 src/dusk/randomizer/logic/dungeon.hpp create mode 100644 src/dusk/randomizer/logic/entrance.cpp create mode 100644 src/dusk/randomizer/logic/entrance.hpp create mode 100644 src/dusk/randomizer/logic/entrance_shuffle.cpp create mode 100644 src/dusk/randomizer/logic/entrance_shuffle.hpp create mode 100644 src/dusk/randomizer/logic/fill.cpp create mode 100644 src/dusk/randomizer/logic/fill.hpp create mode 100644 src/dusk/randomizer/logic/flatten/bits.cpp create mode 100644 src/dusk/randomizer/logic/flatten/bits.hpp create mode 100644 src/dusk/randomizer/logic/flatten/flatten.cpp create mode 100644 src/dusk/randomizer/logic/flatten/flatten.hpp create mode 100644 src/dusk/randomizer/logic/flatten/simplify_algebraic.cpp create mode 100644 src/dusk/randomizer/logic/flatten/simplify_algebraic.hpp create mode 100644 src/dusk/randomizer/logic/generate.cpp create mode 100644 src/dusk/randomizer/logic/generate.hpp create mode 100644 src/dusk/randomizer/logic/item.cpp create mode 100644 src/dusk/randomizer/logic/item.hpp create mode 100644 src/dusk/randomizer/logic/item_pool.cpp create mode 100644 src/dusk/randomizer/logic/item_pool.hpp create mode 100644 src/dusk/randomizer/logic/location.cpp create mode 100644 src/dusk/randomizer/logic/location.hpp create mode 100644 src/dusk/randomizer/logic/plandomizer.cpp create mode 100644 src/dusk/randomizer/logic/plandomizer.hpp create mode 100644 src/dusk/randomizer/logic/requirement.cpp create mode 100644 src/dusk/randomizer/logic/requirement.hpp create mode 100644 src/dusk/randomizer/logic/search.cpp create mode 100644 src/dusk/randomizer/logic/search.hpp create mode 100644 src/dusk/randomizer/logic/spoiler_log.cpp create mode 100644 src/dusk/randomizer/logic/spoiler_log.hpp create mode 100644 src/dusk/randomizer/logic/world.cpp create mode 100644 src/dusk/randomizer/logic/world.hpp create mode 100644 src/dusk/randomizer/randomizer.cpp create mode 100644 src/dusk/randomizer/randomizer.hpp create mode 100644 src/dusk/randomizer/seedgen/config.cpp create mode 100644 src/dusk/randomizer/seedgen/config.hpp create mode 100644 src/dusk/randomizer/seedgen/packed_bits.hpp create mode 100644 src/dusk/randomizer/seedgen/seed.cpp create mode 100644 src/dusk/randomizer/seedgen/seed.hpp create mode 100644 src/dusk/randomizer/seedgen/settings.cpp create mode 100644 src/dusk/randomizer/seedgen/settings.hpp create mode 100644 src/dusk/randomizer/test/test.cpp create mode 100644 src/dusk/randomizer/test/test.hpp create mode 100644 src/dusk/randomizer/utility/color.cpp create mode 100644 src/dusk/randomizer/utility/color.hpp create mode 100644 src/dusk/randomizer/utility/common.cpp create mode 100644 src/dusk/randomizer/utility/common.hpp create mode 100644 src/dusk/randomizer/utility/container.hpp create mode 100644 src/dusk/randomizer/utility/endian.cpp create mode 100644 src/dusk/randomizer/utility/endian.hpp create mode 100644 src/dusk/randomizer/utility/exception.hpp create mode 100644 src/dusk/randomizer/utility/file.cpp create mode 100644 src/dusk/randomizer/utility/file.hpp create mode 100644 src/dusk/randomizer/utility/general.hpp create mode 100644 src/dusk/randomizer/utility/log.cpp create mode 100644 src/dusk/randomizer/utility/log.hpp create mode 100644 src/dusk/randomizer/utility/math.hpp create mode 100644 src/dusk/randomizer/utility/path.cpp create mode 100644 src/dusk/randomizer/utility/path.hpp create mode 100644 src/dusk/randomizer/utility/platform.cpp create mode 100644 src/dusk/randomizer/utility/platform.hpp create mode 100644 src/dusk/randomizer/utility/random.cpp create mode 100644 src/dusk/randomizer/utility/random.hpp create mode 100644 src/dusk/randomizer/utility/string.cpp create mode 100644 src/dusk/randomizer/utility/string.hpp create mode 100644 src/dusk/randomizer/utility/text.cpp create mode 100644 src/dusk/randomizer/utility/text.hpp create mode 100644 src/dusk/randomizer/utility/thread_local.hpp create mode 100644 src/dusk/randomizer/utility/time.cpp create mode 100644 src/dusk/randomizer/utility/time.hpp create mode 100644 src/dusk/randomizer/utility/yaml.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 66c86bb5f4..b6e92c17f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) add_subdirectory(libs/freeverb) +add_subdirectory(src/dusk/randomizer) option(DUSK_BUILD_WARNINGS "If off, compiler warnings will be suppressed") option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.") @@ -94,7 +95,7 @@ message(STATUS "dusk: TP Version: ${DUSK_TP_VERSION}") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${JSYSTEM_DEBUG_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) -set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 +set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 RANDOMIZER_ONLY=${RANDOMIZER_ONLY} DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") set(GAME_INCLUDE_DIRS @@ -160,7 +161,7 @@ target_link_libraries(game PUBLIC ${GAME_LIBS}) add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) -target_link_libraries(dusk PRIVATE game aurora::main freeverb) +target_link_libraries(dusk PRIVATE game aurora::main freeverb randomizer) add_custom_command(TARGET dusk POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory diff --git a/src/dusk/randomizer/CMakeLists.txt b/src/dusk/randomizer/CMakeLists.txt new file mode 100644 index 0000000000..45106f8ae3 --- /dev/null +++ b/src/dusk/randomizer/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.16) + +set(RANDOMIZER_ONLY "0" CACHE STRING "Runs only the randomizer generator") +set(RANDO_SAVE_PATH "${CMAKE_BINARY_DIR}/randomizer/") + +add_compile_definitions(RANDO_SAVE_PATH="${RANDO_SAVE_PATH}" + LOGS_PATH="${RANDO_SAVE_PATH}logs/" + DATA_PATH="${RANDO_SAVE_PATH}data/") + +if(WRITE_ERROR_LOG) + message("Error Log will be saved") + add_compile_definitions(WRITE_ERROR_LOG) +endif() + +if(ENABLE_TIMING) + message("Some events will be timed") + + add_compile_definitions(ENABLE_TIMING) +endif() + +if(DRY_RUN) + message("Game patching will be skipped") + + add_compile_definitions(DRY_RUN) +endif() + +if(RANDO_DEBUG) + add_compile_definitions(RANDO_DEBUG) +endif() + +if(LOGIC_TESTS) + message("Configuring for Logic Tests") + + add_compile_definitions(LOGIC_TESTS) + + if(TEST_COUNT) + message("Test Count: " ${TEST_COUNT}) + add_compile_definitions(TEST_COUNT=${TEST_COUNT}) + endif() + add_compile_definitions(SETTINGS_PATH="${RANDO_SAVE_PATH}randomizer_settings.yaml.test" PREFERENCES_PATH="${RANDO_SAVE_PATH}randomizer_preferences.yaml.test") +else() + add_compile_definitions(SETTINGS_PATH="${RANDO_SAVE_PATH}randomizer_settings.yaml" PREFERENCES_PATH="${RANDO_SAVE_PATH}randomizer_preferences.yaml") +endif() + +string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE) +add_compile_definitions(SOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}) + +# Put data files together for easier manipulation +file(COPY "./data/" DESTINATION "${CMAKE_BINARY_DIR}/randomizer/data/" REGEX "^.*example.*$" EXCLUDE) # World, macros, and location info + +message(STATUS "randomizer: Fetching yaml-cpp") +FetchContent_Declare( + yaml-cpp + GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git + GIT_TAG yaml-cpp-0.9.0 +) +FetchContent_MakeAvailable(yaml-cpp) + +message(STATUS "randomizer: Fetching base64pp") +FetchContent_Declare( + base64pp + GIT_REPOSITORY https://github.com/matheusgomes28/base64pp.git + GIT_TAG v0.2.0-rc0 +) +FetchContent_MakeAvailable(base64pp) + +message(STATUS "randomizer: Fetching zlib-ng") +FetchContent_Declare( + zlib-ng + GIT_REPOSITORY https://github.com/zlib-ng/zlib-ng.git + GIT_TAG 2.3.3 +) +FetchContent_MakeAvailable(zlib-ng) + +file(GLOB_RECURSE CODE_SOURCES CONFIGURE_DEPENDS "*.cpp" "*.hpp") +add_library(randomizer OBJECT ${CODE_SOURCES}) +target_link_libraries(randomizer yaml-cpp::yaml-cpp zlib base64pp) \ No newline at end of file diff --git a/src/dusk/randomizer/data/entrance_shuffle_data.yaml b/src/dusk/randomizer/data/entrance_shuffle_data.yaml new file mode 100644 index 0000000000..7f7e486b6c --- /dev/null +++ b/src/dusk/randomizer/data/entrance_shuffle_data.yaml @@ -0,0 +1,2582 @@ +############################### +# SPAWN # +############################### + +- Type: Spawn + Forward: + Connection: Links Spawn -> Outside Links House + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +############################### +# WARP PORTALS # +############################### + +- Type: Warp Portal + Forward: + Connection: Ordon Spring Warp Portal -> Ordon Spring + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: South Faron Woods Warp Portal -> South Faron Woods + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: North Faron Woods Warp Portal -> North Faron Woods + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Sacred Grove Warp Portal -> Sacred Grove Lower + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Kakariko Gorge Warp Portal -> Kakariko Gorge + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Kakariko Village Warp Portal -> Lower Kakariko Village + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Death Mountain Warp Portal -> Death Mountain Volcano + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Bridge of Eldin Warp Portal -> Eldin Field North of Bridge + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Castle Town Warp Portal -> Outside Castle Town West + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Lake Hylia Warp Portal -> Lake Hylia + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Upper Zoras River Warp Portal -> Upper Zoras River + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Zoras Domain Warp Portal -> Zoras Throne Room + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Snowpeak Warp Portal -> Snowpeak Summit Upper + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Gerudo Desert Warp Portal -> Gerudo Desert Cave of Ordeals Plateau + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Warp Portal + Forward: + Connection: Mirror Chamber Warp Portal -> Mirror Chamber Upper + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +############################### +# DUNGEONS # +############################### + +- Type: Dungeon + Forward: + Connection: North Faron Woods -> Forest Temple Entrance + Stage: 6 + Room: 22 + Spawn: "00" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + Return: + Connection: Forest Temple Entrance -> North Faron Woods + Stage: 45 + Room: 6 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Death Mountain Sumo Hall Goron Mines Tunnel -> Goron Mines Entrance + Stage: 3 + Room: 1 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Goron Mines Entrance -> Death Mountain Sumo Hall Goron Mines Tunnel + Stage: 69 + Room: 0 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Lake Hylia Lakebed Temple Entrance -> Lakebed Temple Entrance + Stage: 0 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + Return: + Connection: Lakebed Temple Entrance -> Lake Hylia Lakebed Temple Entrance + Stage: 52 + Room: 0 + Spawn: "0B" + Spawn Type: "D0" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Outside Arbiters Grounds -> Arbiters Grounds Entrance + Stage: 24 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Arbiters Grounds Entrance -> Outside Arbiters Grounds + Stage: 55 + Room: 3 + Spawn: "03" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Door Couple Tag: Snowpeak Ruins Entrance + Forward: + Connection: Snowpeak Ruins East Door Exterior -> Snowpeak Ruins East Door Interior + Stage: 27 + Room: 0 + Spawn: "02" + Spawn Type: "A0" + Parameters: "F01F" + State: "FF" + Return: + Connection: Snowpeak Ruins East Door Interior -> Snowpeak Ruins East Door Exterior + Stage: 51 + Room: 1 + Spawn: "0A" + Spawn Type: "0B" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Door Couple Tag: Snowpeak Ruins Entrance + Forward: + Connection: Snowpeak Ruins West Door Exterior -> Snowpeak Ruins West Door Interior + Stage: 27 + Room: 0 + Spawn: "01" + Spawn Type: "A0" + Parameters: "F01F" + State: "FF" + Return: + Connection: Snowpeak Ruins West Door Interior -> Snowpeak Ruins West Door Exterior + Stage: 51 + Room: 1 + Spawn: "09" + Spawn Type: "0B" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Sacred Grove Past Behind Window -> Temple of Time Entrance + Stage: 9 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Temple of Time Entrance -> Sacred Grove Past Behind Window + Stage: 54 + Room: 2 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Lake Hylia -> City in the Sky Entrance + Stage: 12 + Room: 0 + Spawn: "02" + Spawn Type: "00" + Parameters: "C00F" + State: "FF" + Return: + Connection: City in the Sky Entrance -> Lake Hylia + Stage: 52 + Room: 0 + Spawn: "4D" + Spawn Type: "40" + Parameters: "C00F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Mirror Chamber Portal -> Palace of Twilight Entrance + Stage: 15 + Room: 0 + Spawn: "0A" + Spawn Type: "00" + Parameters: "F01F" + State: "E" + Return: + Connection: Palace of Twilight Entrance -> Mirror Chamber Portal + Stage: 60 + Room: 4 + Spawn: "04" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Dungeon + Forward: + Connection: Castle Town North Inside Barrier -> Hyrule Castle Entrance + Stage: 20 + Room: 11 + Spawn: "00" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Hyrule Castle Entrance -> Castle Town North Inside Barrier + Stage: 53 + Room: 1 + Spawn: "32" + Spawn Type: "10" + Parameters: "F01F" + State: "FF" + +############################### +# BOSSES # +############################### + +- Type: Boss + Forward: + Connection: Forest Temple Boss Door Room North Side -> Forest Temple Boss Room + Stage: 7 + Room: 50 + Spawn: "01" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: Forest Temple Boss Room -> South Faron Woods + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: Goron Mines Boss Door Room Near Boss Door -> Goron Mines Boss Room + Stage: 4 + Room: 50 + Spawn: "01" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: Goron Mines Boss Room -> Lower Kakariko Village + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: Lakebed Temple Central Room Past Boss Door -> Lakebed Temple Boss Room + Stage: 1 + Room: 50 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Lakebed Temple Boss Room -> Lake Hylia Lanayru Spring + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: Arbiters Grounds Socket Room Near Boss Door -> Arbiters Grounds Boss Room + Stage: 25 + Room: 50 + Spawn: "00" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: Arbiters Grounds Boss Room -> Mirror Chamber Lower + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: Snowpeak Ruins West Courtyard North Balcony -> Snowpeak Ruins Boss Room + Stage: 27 + Room: 4 + Spawn: "01" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: Snowpeak Ruins Boss Room -> Snowpeak Summit Lower + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: Temple of Time Crumbling Corridor Near Boss Door -> Temple of Time Boss Room + Stage: 10 + Room: 50 + Spawn: "00" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: Temple of Time Boss Room -> Sacred Grove Past Behind Window + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: City in the Sky North Tower Top -> City in the Sky Boss Room + Stage: 13 + Room: 50 + Spawn: "01" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: City in the Sky Boss Room -> City in the Sky Entrance + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Boss + Forward: + Connection: Palace of Twilight Boss Door Room -> Palace of Twilight Boss Room + Stage: 16 + Room: 10 + Spawn: "00" + Spawn Type: "60" + Parameters: "F01F" + State: "FF" + Return: + Connection: Palace of Twilight Boss Room -> Palace of Twilight Entrance + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +############################### +# GROTTOS # +############################### + +- Type: Grotto + Forward: + Connection: Ordon Ranch -> Ordon Ranch Grotto + Stage: 35 + Room: 0 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "2" + Return: + Connection: Ordon Ranch Grotto -> Ordon Ranch + Stage: 41 + Room: 0 + Spawn: "05" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lost Woods Lower Battle Arena -> Lost Woods Baba Serpent Grotto + Stage: 36 + Room: 1 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F01F" + State: "2" + Return: + Connection: Lost Woods Baba Serpent Grotto -> Lost Woods Lower Battle Arena + Stage: 54 + Room: 3 + Spawn: "05" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Faron Field -> Faron Field Corner Grotto + Stage: 36 + Room: 1 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F01F" + State: "1" + Return: + Connection: Faron Field Corner Grotto -> Faron Field + Stage: 56 + Room: 6 + Spawn: "02" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Faron Field -> Faron Field Fishing Grotto + Stage: 39 + Room: 4 + Spawn: "00" + Spawn Type: "00" + Parameters: "F010" + State: "1" + Return: + Connection: Faron Field Fishing Grotto -> Faron Field + Stage: 56 + Room: 6 + Spawn: "03" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Kakariko Gorge -> Kakariko Gorge Keese Grotto + Stage: 36 + Room: 1 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F01F" + State: "3" + Return: + Connection: Kakariko Gorge Keese Grotto -> Kakariko Gorge + Stage: 56 + Room: 3 + Spawn: "04" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Eldin Field -> Eldin Field Bomskit Grotto + Stage: 35 + Room: 0 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "1" + Return: + Connection: Eldin Field Bomskit Grotto -> Eldin Field + Stage: 56 + Room: 0 + Spawn: "0C" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Eldin Field -> Eldin Field Water Bomb Fish Grotto + Stage: 39 + Room: 4 + Spawn: "00" + Spawn Type: "00" + Parameters: "F010" + State: "3" + Return: + Connection: Eldin Field Water Bomb Fish Grotto -> Eldin Field + Stage: 56 + Room: 0 + Spawn: "0D" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Eldin Field Grotto Platform -> Eldin Field Stalfos Grotto + Stage: 36 + Room: 1 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F01F" + State: "0" + Return: + Connection: Eldin Field Stalfos Grotto -> Eldin Field Grotto Platform + Stage: 56 + Room: 7 + Spawn: "02" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lanayru Field -> Lanayru Field Chu Grotto + Stage: 37 + Room: 2 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "3" + Return: + Connection: Lanayru Field Chu Grotto -> Lanayru Field + Stage: 56 + Room: 10 + Spawn: "08" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lanayru Field -> Lanayru Field Skulltula Grotto + Stage: 38 + Room: 3 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "3" + Return: + Connection: Lanayru Field Skulltula Grotto -> Lanayru Field + Stage: 56 + Room: 10 + Spawn: "06" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lanayru Field -> Lanayru Field Poe Grotto + Stage: 35 + Room: 0 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "3" + Return: + Connection: Lanayru Field Poe Grotto -> Lanayru Field + Stage: 56 + Room: 10 + Spawn: 07"" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Outside Castle Town West Grotto Ledge -> Outside Castle Town West Helmasaur Grotto + Stage: 35 + Room: 0 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "0" + Return: + Connection: Outside Castle Town West Helmasaur Grotto -> Outside Castle Town West Grotto Exit + Stage: 57 + Room: 8 + Spawn: "05" + Spawn Type: "90" + Parameters: F01F"" + State: "FF" + +- Type: Grotto + Forward: + Connection: Outside Castle Town South Tektite Grotto Platform -> Outside Castle Town South Tektite Grotto + Stage: 39 + Room: 4 + Spawn: "00" + Spawn Type: "00" + Parameters: "F010" + State: "0" + Return: + Connection: Outside Castle Town South Tektite Grotto -> Outside Castle Town South Tektite Grotto Platform + Stage: 57 + Room: 16 + Spawn: "03" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lake Hylia Bridge Grotto Ledge -> Lake Hylia Bridge Bubble Grotto + Stage: 38 + Room: 3 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "0" + Return: + Connection: Lake Hylia Bridge Bubble Grotto -> Lake Hylia Bridge Grotto Ledge + Stage: 56 + Room: 13 + Spawn: "03" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lake Hylia Upper Area -> Lake Hylia Water Toadpoli Grotto + Stage: 39 + Room: 4 + Spawn: "00" + Spawn Type: "00" + Parameters: "F010" + State: "2" + Return: + Connection: Lake Hylia Water Toadpoli Grotto -> Lake Hylia Upper Area + Stage: 52 + Room: 0 + Spawn: "04" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Lake Hylia Shell Blade Grotto Ledge -> Lake Hylia Shell Blade Grotto + Stage: 39 + Room: 4 + Spawn: "00" + Spawn Type: "00" + Parameters: "F010" + State: "4" + Return: + Connection: Lake Hylia Shell Blade Grotto -> Lake Hylia Shell Blade Grotto Ledge + Stage: 52 + Room: 0 + Spawn: "63" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Gerudo Desert -> Gerudo Desert Skulltula Grotto + Stage: 38 + Room: 3 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "1" + Return: + Connection: Gerudo Desert Skulltula Grotto -> Gerudo Desert + Stage: 59 + Room: 0 + Spawn: "08" + Spawn Type: "90" + Parameters: "F01F" + State: "0" + +- Type: Grotto + Forward: + Connection: Gerudo Desert Basin -> Gerudo Desert Chu Grotto + Stage: 37 + Room: 2 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "1" + Return: + Connection: Gerudo Desert Chu Grotto -> Gerudo Desert Basin + Stage: 59 + Room: 0 + Spawn: "0A" + Spawn Type: "90" + Parameters: "F01F" + State: "0" + +- Type: Grotto + Forward: + Connection: Gerudo Desert North East Ledge -> Gerudo Desert Rock Grotto + Stage: 37 + Room: 2 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "0" + Return: + Connection: Gerudo Desert Rock Grotto -> Gerudo Desert North East Ledge + Stage: 59 + Room: 0 + Spawn: "09" + Spawn Type: "90" + Parameters: "F01F" + State: "0" + +- Type: Grotto + Forward: + Connection: Snowpeak Climb Upper -> Snowpeak Ice Keese Grotto + Stage: 37 + Room: 2 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "2" + Return: + Connection: Snowpeak Ice Keese Grotto -> Snowpeak Climb Upper + Stage: 51 + Room: 0 + Spawn: "02" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +- Type: Grotto + Forward: + Connection: Snowpeak Climb Upper -> Snowpeak Freezard Grotto + Stage: 38 + Room: 3 + Spawn: "00" + Spawn Type: "C0" + Parameters: "F013" + State: "2" + Return: + Connection: Snowpeak Freezard Grotto -> Snowpeak Climb Upper + Stage: 51 + Room: 0 + Spawn: "01" + Spawn Type: "90" + Parameters: "F01F" + State: "FF" + +############################### +# INTERIORS # +############################### + +- Type: Interior + Forward: + Connection: Outside Links House -> Ordon Links House + Stage: 65 + Room: 4 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Links House -> Outside Links House + Stage: 43 + Room: 1 + Spawn: "03" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Ordon Village -> Ordon Seras Shop + Stage: 65 + Room: 1 + Spawn: "00" + Spawn Type: "10" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Seras Shop -> Ordon Village + Stage: 43 + Room: 0 + Spawn: "05" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Ordon Village -> Ordon Sword House + Stage: 65 + Room: 5 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Sword House -> Ordon Village + Stage: 43 + Room: 0 + Spawn: "09" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Ordon Village -> Ordon Shield House + Stage: 65 + Room: 2 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Shield House -> Ordon Village + Stage: 43 + Room: 0 + Spawn: "06" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Ordon Village -> Ordon Shield House Upper Ledge + Stage: + Room: + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + Return: + Connection: Ordon Shield House Upper Ledge -> Ordon Village + Stage: + Room: + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Interior + Forward: + Connection: Ordon Village -> Ordon Fados House + Stage: + Room: + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + Return: + Connection: Ordon Fados House -> Ordon Village + Stage: + Room: + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Interior + Door Couple Tag: Ordon Bos House + Forward: + Connection: Ordon Bos House Left Door Exterior -> Ordon Bos House Left Door Interior + Stage: 65 + Room: 0 + Spawn: "01" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Bos House Left Door Interior -> Ordon Bos House Left Door Exterior + Stage: 43 + Room: 0 + Spawn: "0B" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Ordon Bos House + Forward: + Connection: Ordon Bos House Right Door Exterior -> Ordon Bos House Right Door Interior + Stage: 65 + Room: 0 + Spawn: "00" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Bos House Right Door Interior -> Ordon Bos House Right Door Exterior + Stage: 43 + Room: 0 + Spawn: "04" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: South Faron Woods -> Faron Woods Coros House Lower + Stage: 67 + Room: 0 + Spawn: "01" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Faron Woods Coros House Lower -> South Faron Woods + Stage: 45 + Room: 4 + Spawn: "01" + Spawn Type: "A0" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: South Faron Woods Coros Ledge -> Faron Woods Coros House Upper + Stage: 67 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F05F" + State: "FF" + Return: + Connection: Faron Woods Coros House Upper -> South Faron Woods Coros Ledge + Stage: 45 + Room: 4 + Spawn: "09" + Spawn Type: "00" + Parameters: "F05F" + State: "FF" + +- Type: Interior + Door Couple Tag: Renados Sanctuary Front Door + Forward: + Connection: Renados Sanctuary Front East Door Exterior -> Renados Sanctuary Front East Door Interior + Stage: 68 + Room: 0 + Spawn: "06" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Renados Sanctuary Front East Door Interior -> Renados Sanctuary Front East Door Exterior + Stage: 46 + Room: 0 + Spawn: "30" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Renados Sanctuary Front Door + Forward: + Connection: Renados Sanctuary Front West Door Exterior -> Renados Sanctuary Front West Door Interior + Stage: 68 + Room: 0 + Spawn: "05" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Renados Sanctuary Front West Door Interior -> Renados Sanctuary Front West Door Exterior + Stage: 46 + Room: 0 + Spawn: "2E" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Renados Sanctuary Back Door + Forward: + Connection: Renados Sanctuary Back East Door Exterior -> Renados Sanctuary Back East Door Interior + Stage: 68 + Room: 0 + Spawn: "08" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Renados Sanctuary Back East Door Interior -> Renados Sanctuary Back East Door Exterior + Stage: 46 + Room: 0 + Spawn: "33" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Renados Sanctuary Back Door + Forward: + Connection: Renados Sanctuary Back West Door Exterior -> Renados Sanctuary Back West Door Interior + Stage: 68 + Room: 0 + Spawn: "07" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Renados Sanctuary Back West Door Interior -> Renados Sanctuary Back West Door Exterior + Stage: 46 + Room: 0 + Spawn: "32" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Kakariko Renados Sanctuary -> Kakariko Renados Sanctuary Basement + Stage: 75 + Room: 5 + Spawn: "00" + Spawn Type: "FF" + Parameters: "FFFF" + State: "FF" + Return: + Connection: Kakariko Renados Sanctuary Basement -> Kakariko Renados Sanctuary + Stage: 68 + Room: 0 + Spawn: "02" + Spawn Type: "FF" + Parameters: "FFFF" + State: "FF" + +- Type: Interior + Forward: + Connection: Lower Kakariko Village -> Kakariko Malo Mart + Stage: 68 + Room: 3 + Spawn: "00" + Spawn Type: "10" + Parameters: "f09f" + State: "FF" + Return: + Connection: Kakariko Malo Mart -> Lower Kakariko Village + Stage: 46 + Room: 0 + Spawn: "28" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Elde Inn + Forward: + Connection: Elde Inn North Door Exterior -> Elde Inn North Door Interior + Stage: 68 + Room: 2 + Spawn: "02" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Elde Inn North Door Interior -> Elde Inn North Door Exterior + Stage: 46 + Room: 0 + Spawn: "31" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Elde Inn + Forward: + Connection: Elde Inn South Door Exterior -> Elde Inn South Door Interior + Stage: 68 + Room: 2 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Elde Inn South Door Interior -> Elde Inn South Door Exterior + Stage: 46 + Room: 0 + Spawn: "2A" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Kakariko Bug House Door -> Kakariko Bug House + Stage: 68 + Room: 6 + Spawn: "05" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Kakariko Bug House -> Kakariko Bug House Door + Stage: 46 + Room: 0 + Spawn: "29" + Spawn Type: "90" + Parameters: "F03F" + State: "FF" + +- Type: Interior + Forward: + Connection: Kakariko Bug House Ceiling Hole -> Kakariko Bug House + Stage: 68 + Room: 6 + Spawn: "00" + Spawn Type: "00" + Parameters: "F03F" + State: "FF" + Return: + Connection: Kakariko Bug House -> Kakariko Bug House Ceiling Hole + Stage: 46 + Room: 0 + Spawn: "04" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Lower Kakariko Village -> Kakariko Barnes Bomb Shop Lower + Stage: 68 + Room: 1 + Spawn: "02" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Kakariko Barnes Bomb Shop Lower -> Lower Kakariko Village + Stage: 46 + Room: 0 + Spawn: "2C" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Upper Kakariko Village -> Kakariko Barnes Bomb Shop Upper + Stage: 68 + Room: 1 + Spawn: "00" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + Return: + Connection: Kakariko Barnes Bomb Shop Upper -> Upper Kakariko Village + Stage: 46 + Room: 0 + Spawn: "0A" + Spawn Type: "10" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Kakariko Watchtower Lower Door -> Kakariko Watchtower Lower Interior + Stage: 68 + Room: 4 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Kakariko Watchtower Lower Interior -> Kakariko Watchtower Lower Door + Stage: 46 + Room: 0 + Spawn: "2D" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Kakariko Watchtower Dig Spot -> Kakariko Watchtower Lower Interior + Stage: 68 + Room: 4 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Kakariko Watchtower Lower Interior -> Kakariko Watchtower Dig Spot + Stage: 46 + Room: 0 + Spawn: "03" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Kakariko Top of Watchtower -> Kakariko Watchtower Upper Interior + Stage: 68 + Room: 4 + Spawn: "02" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Kakariko Watchtower Upper Interior -> Kakariko Top of Watchtower + Stage: 46 + Room: 0 + Spawn: "2F" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Death Mountain Outside Sumo Hall -> Death Mountain Sumo Hall + Stage: 69 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Death Mountain Sumo Hall -> Death Mountain Outside Sumo Hall + Stage: 47 + Room: 3 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Death Mountain Lower Elevator -> Death Mountain Sumo Hall Elevator + Stage: 69 + Room: 0 + Spawn: "03" + Spawn Type: "E0" + Parameters: "F01F" + State: "FF" + Return: + Connection: Death Mountain Sumo Hall Elevator -> Death Mountain Lower Elevator + Stage: 47 + Room: 3 + Spawn: "03" + Spawn Type: "E0" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Hidden Village -> Hidden Village Impaz House + Stage: 72 + Room: 0 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Hidden Village Impaz House -> Hidden Village + Stage: 63 + Room: 0 + Spawn: "01" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Upper Zoras River -> Upper Zoras River Izas House + Stage: 49 + Room: 1 + Spawn: "01" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Upper Zoras River Izas House -> Upper Zoras River + Stage: 61 + Room: 0 + Spawn: "04" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Fishing Hole -> Fishing Hole House + Stage: 71 + Room: 0 + Spawn: "00" + Spawn Type: "A0" + Parameters: "F01F" + State: "FF" + Return: + Connection: Fishing Hole House -> Fishing Hole + Stage: 62 + Room: 0 + Spawn: "03" + Spawn Type: "B0" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town West -> Castle Town STAR Game + Stage: 74 + Room: 7 + Spawn: "03" + Spawn Type: "10" + Parameters: "F090" + State: "FF" + Return: + Connection: Castle Town STAR Game -> Castle Town West + Stage: 53 + Room: 2 + Spawn: "01" + Spawn Type: "10" + Parameters: "F01F" + State: "FF" + + +- Type: Interior + Door Couple Tag: Castle Town Goron House + Forward: + Connection: Castle Town Goron House West Door Exterior -> Castle Town Goron House West Door Interior + Stage: 73 + Room: 4 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Goron House West Door Interior -> Castle Town Goron House West Door Exterior + Stage: 53 + Room: 0 + Spawn: "0E" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Door Couple Tag: Castle Town Goron House + Forward: + Connection: Castle Town Goron House East Door Exterior -> Castle Town Goron House East Door Interior + Stage: 73 + Room: 4 + Spawn: "02" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Goron House East Door Interior -> Castle Town Goron House East Door Exterior + Stage: 53 + Room: 0 + Spawn: "10" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town Center -> Castle Town Malo Mart + Stage: 73 + Room: 0 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Malo Mart -> Castle Town Center + Stage: 53 + Room: 0 + Spawn: "0C" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Castle Town Doctors Office + Forward: + Connection: Castle Town Doctors Office West Door Exterior -> Castle Town Doctors Office West Door Interior + Stage: 73 + Room: 2 + Spawn: "01" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Doctors Office West Door Interior -> Castle Town Doctors Office West Door Exterior + Stage: 53 + Room: 4 + Spawn: "04" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Door Couple Tag: Castle Town Doctors Office + Forward: + Connection: Castle Town Doctors Office East Door Exterior -> Castle Town Doctors Office East Door Interior + Stage: 73 + Room: 2 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F01F" + State: "FF" + Return: + Connection: Castle Town Doctors Office East Door Interior -> Castle Town Doctors Office East Door Exterior + Stage: 53 + Room: 4 + Spawn: "03" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town Doctors Office Balcony -> Castle Town Doctors Office Upper + Stage: 73 + Room: 2 + Spawn: "02" + Spawn Type: "10" + Parameters: "F01F" + State: "FF" + Return: + Connection: Castle Town Doctors Office Upper -> Castle Town Doctors Office Balcony + Stage: 53 + Room: 4 + Spawn: "05" + Spawn Type: "00" + Parameters: "F05F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town South -> Castle Town Agithas House + Stage: 73 + Room: 3 + Spawn: "01" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Agithas House -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "05" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town South -> Castle Town Seer House + Stage: 73 + Room: 1 + Spawn: "00" + Spawn Type: "10" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Seer House -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "04" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town South -> Castle Town Jovanis House + Stage: 73 + Room: 5 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Castle Town Jovanis House -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "0A" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Interior + Forward: + Connection: Castle Town South -> Castle Town Telmas Bar + Stage: 70 + Room: 5 + Spawn: "00" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Telmas Bar -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +############################### +# CAVES # +############################### + +- Type: Cave + Forward: + Connection: South Faron Woods Behind Gate -> Faron Woods Cave South + Stage: 40 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Faron Woods Cave South -> South Faron Woods Behind Gate + Stage: 45 + Room: 3 + Spawn: "63" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Cave + Forward: + Connection: Mist Area Near Faron Woods Cave -> Faron Woods Cave North + Stage: 40 + Room: 0 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Faron Woods Cave North -> Mist Area Near Faron Woods Cave + Stage: 45 + Room: 5 + Spawn: "04" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Cave + Forward: + Connection: Mist Area Outside Faron Mist Cave -> Mist Area Faron Mist Cave + Stage: 45 + Room: 14 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Mist Area Faron Mist Cave -> Mist Area Outside Faron Mist Cave + Stage: 45 + Room: 5 + Spawn: "06" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Cave + Forward: + Connection: Kakariko Gorge Cave Entrance -> Eldin Lantern Cave + Stage: 32 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Eldin Lantern Cave -> Kakariko Gorge Cave Entrance + Stage: 56 + Room: 3 + Spawn: "0F" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Cave + Forward: + Connection: Eldin Field Lava Cave Upper Ledge -> Eldin Field Lava Cave Upper + Stage: 34 + Room: 10 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Eldin Field Lava Cave Upper -> Eldin Field Lava Cave Upper Ledge + Stage: 56 + Room: 0 + Spawn: "14" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Cave + Forward: + Connection: Eldin Field Lava Cave Lower -> Eldin Field Lava Cave Lower Ledge + Stage: 56 + Room: 0 + Spawn: "15" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Eldin Field Lava Cave Lower Ledge -> Eldin Field Lava Cave Lower + Stage: 34 + Room: 10 + Spawn: "01" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + +- Type: Cave + Forward: + Connection: Lanayru Field Cave Entrance -> Lanayru Ice Puzzle Cave + Stage: 30 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + Return: + Connection: Lanayru Ice Puzzle Cave -> Lanayru Field Cave Entrance + Stage: 56 + Room: 10 + Spawn: "0F" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Cave + Forward: + Connection: Lake Hylia -> Lake Hylia Lanayru Spring + Stage: 52 + Room: 1 + Spawn: "00" + Spawn Type: "50" + Parameters: "C000" + State: "FF" + Return: + Connection: Lake Hylia Lanayru Spring -> Lake Hylia + Stage: 52 + Room: 0 + Spawn: "07" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Cave + Forward: + Connection: Lake Hylia Cave Entrance -> Lake Hylia Long Cave + Stage: 33 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Lake Hylia Long Cave -> Lake Hylia Cave Entrance + Stage: 52 + Room: 0 + Spawn: "1D" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Cave + Forward: + Connection: Gerudo Desert Cave of Ordeals Plateau -> Cave of Ordeals + Stage: 31 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Cave of Ordeals -> Gerudo Desert Cave of Ordeals Plateau + Stage: 59 + Room: 0 + Spawn: "06" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +############################### +# OVERWORLD # +############################### + +- Type: Overworld + Forward: + Connection: Outside Links House -> Ordon Village + Stage: 43 + Room: 0 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Ordon Village -> Outside Links House + Stage: 43 + Room: 1 + Spawn: "00" + Spawn Type: "10" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Outside Links House -> Ordon Spring + Stage: 44 + Room: 1 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Ordon Spring -> Outside Links House + Stage: 43 + Room: 1 + Spawn: "02" + Spawn Type: "50" + Parameters: "3091" + State: "FF" + +- Type: Overworld + Forward: + Connection: Ordon Village -> Ordon Ranch Village Pathway + Stage: 41 + Room: 0 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Ordon Ranch Village Pathway -> Ordon Village + Stage: 43 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Ordon Bridge -> South Faron Woods + Stage: 45 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: South Faron Woods -> Ordon Bridge + Stage: 44 + Room: 1 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: South Faron Woods Above Owl Statue -> Mist Area Near Owl Statue Chest + Stage: 45 + Room: 5 + Spawn: "62" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Mist Area Near Owl Statue Chest -> South Faron Woods Above Owl Statue + Stage: 45 + Room: 8 + Spawn: "02" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Mist Area Near North Faron Woods -> North Faron Woods + Stage: 45 + Room: 6 + Spawn: "02" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + Return: + Connection: North Faron Woods -> Mist Area Near North Faron Woods + Stage: 45 + Room: 11 + Spawn: "00" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: North Faron Lost Woods Ledge -> Lost Woods + Stage: 54 + Room: 3 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Lost Woods -> North Faron Lost Woods Ledge + Stage: 45 + Room: 6 + Spawn: "03" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lost Woods Lower Battle Arena -> Sacred Grove Lower + Stage: 54 + Room: 1 + Spawn: "03" + Spawn Type: "50" + Parameters: "" + State: "FF" + Return: + Connection: Sacred Grove Lower -> Lost Woods Lower Battle Arena + Stage: 54 + Room: 1 + Spawn: "01" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lost Woods Upper Battle Arena -> Sacred Grove Before Block + Stage: 54 + Room: 1 + Spawn: "06" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Sacred Grove Before Block -> Lost Woods Upper Battle Arena + Stage: 54 + Room: 3 + Spawn: "02" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Sacred Grove Upper -> Sacred Grove Past + Stage: 54 + Room: 2 + Spawn: "00" + Spawn Type: "00" + Parameters: "F012" + State: "FF" + Return: + Connection: Sacred Grove Past -> Sacred Grove Upper + Stage: 54 + Room: 1 + Spawn: "05" + Spawn Type: "10" + Parameters: "F032" + State: "FF" + +- Type: Overworld + Forward: + Connection: South Faron Woods -> Faron Field + Stage: 56 + Room: 6 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Faron Field -> South Faron Woods + Stage: 45 + Room: 4 + Spawn: "08" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Faron Field Behind Boulder -> Outside Castle Town South Inside Boulder + Stage: 57 + Room: 16 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Outside Castle Town South -> Faron Field Behind Boulder + Stage: 56 + Room: 6 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Kakariko Gorge Behind Gate -> Lower Kakariko Village + Stage: 46 + Room: 0 + Spawn: "3C" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Lower Kakariko Village -> Kakariko Gorge Behind Gate + Stage: 56 + Room: 3 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lower Kakariko Village -> Kakariko Graveyard + Stage: 48 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Kakariko Graveyard -> Lower Kakariko Village + Stage: 46 + Room: 0 + Spawn: "06" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lower Kakariko Village -> Death Mountain Near Kakariko + Stage: 47 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Death Mountain Near Kakariko -> Lower Kakariko Village + Stage: 46 + Room: 0 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Kakariko Graveyard Pond -> Lake Hylia + Stage: 52 + Room: 0 + Spawn: "19" + Spawn Type: "D0" + Parameters: "00FF" + State: "FF" + Return: + Connection: Lake Hylia -> Kakariko Graveyard Pond + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Overworld + Forward: + Connection: Kakariko Village Behind Gate -> Eldin Field + Stage: 56 + Room: 0 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Eldin Field -> Kakariko Village Behind Gate + Stage: 46 + Room: 0 + Spawn: "08" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Eldin Field Near Castle Town -> Outside Castle Town East + Stage: 57 + Room: 17 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Outside Castle Town East -> Eldin Field Near Castle Town + Stage: 56 + Room: 0 + Spawn: "07" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Eldin Field Outside Hidden Village -> Hidden Village + Stage: 63 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Hidden Village -> Eldin Field Outside Hidden Village + Stage: 56 + Room: 7 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lanayru Field Near Zoras Domain -> Zoras Domain West Ledge + Stage: 50 + Room: 1 + Spawn: "0F" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Zoras Domain West Ledge -> Lanayru Field Near Zoras Domain + Stage: 56 + Room: 10 + Spawn: "03" + Spawn Type: "00" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Upper Zoras River -> Fishing Hole + Stage: 62 + Room: 0 + Spawn: "02" + Spawn Type: "B0" + Parameters: "F01F" + State: "FF" + Return: + Connection: Fishing Hole -> Upper Zoras River + Stage: 61 + Room: 0 + Spawn: "0A" + Spawn Type: "A0" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Upper Zoras River -> Lanayru Field + Stage: 56 + Room: 10 + Spawn: "02" + Spawn Type: D0"" + Parameters: "00FF" + State: "FF" + Return: + Connection: Lanayru Field -> Upper Zoras River + Stage: + Room: + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Overworld + Forward: + Connection: Upper Zoras River -> Zoras Domain + Stage: 50 + Room: 1 + Spawn: "0A" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Zoras Domain -> Upper Zoras River + Stage: 61 + Room: 0 + Spawn: "07" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Zoras Domain Top of Waterfall -> Zoras Throne Room + Stage: 50 + Room: 0 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Zoras Throne Room -> Zoras Domain Top of Waterfall + Stage: 50 + Room: 1 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Zoras Domain -> Snowpeak Climb Lower + Stage: 51 + Room: 0 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Snowpeak Climb Lower -> Zoras Domain + Stage: 50 + Room: 1 + Spawn: "06" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Snowpeak Climb Upper -> Snowpeak Summit Cave + Stage: 51 + Room: 2 + Spawn: "08" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Snowpeak Summit Cave -> Snowpeak Climb Upper + Stage: 51 + Room: 0 + Spawn: "0F" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lanayru Field -> Outside Castle Town West + Stage: 57 + Room: 8 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Outside Castle Town West -> Lanayru Field + Stage: 56 + Room: 10 + Spawn: "04" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Outside Castle Town West -> Lake Hylia Bridge + Stage: 56 + Room: 12 + Spawn: "01" + Spawn Type: "00" + Parameters: "F01F" + State: "FF" + Return: + Connection: Lake Hylia Bridge -> Outside Castle Town West + Stage: 57 + Room: 8 + Spawn: "02" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Outside Castle Town West -> Castle Town West + Stage: 53 + Room: 2 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town West -> Outside Castle Town West + Stage: 57 + Room: 8 + Spawn: "01" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town West -> Castle Town Center + Stage: 53 + Room: 0 + Spawn: "04" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town Center -> Castle Town West + Stage: 53 + Room: 2 + Spawn: "02" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town West -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "08" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town South -> Castle Town West + Stage: 53 + Room: 2 + Spawn: "03" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town Center -> Castle Town North + Stage: 53 + Room: 1 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Castle Town North -> Castle Town Center + Stage: 53 + Room: 0 + Spawn: "03" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town Center -> Castle Town East + Stage: 53 + Room: 4 + Spawn: "02" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Castle Town East -> Castle Town Center + Stage: 53 + Room: 0 + Spawn: "06" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town Center -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "02" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Castle Town South -> Castle Town Center + Stage: 53 + Room: 0 + Spawn: "05" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town East -> Outside Castle Town East + Stage: 57 + Room: 17 + Spawn: "00" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Outside Castle Town East -> Castle Town East + Stage: 53 + Room: 4 + Spawn: "00" + Spawn Type: "10" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town East -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "09" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + Return: + Connection: Castle Town South -> Castle Town East + Stage: 53 + Room: 4 + Spawn: "06" + Spawn Type: "50" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Castle Town South -> Outside Castle Town South + Stage: 57 + Room: 16 + Spawn: "01" + Spawn Type: "05" + Parameters: "F09F" + State: "FF" + Return: + Connection: Outside Castle Town South -> Castle Town South + Stage: 53 + Room: 3 + Spawn: "00" + Spawn Type: "10" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Outside Castle Town South -> Lake Hylia + Stage: 57 + Room: 16 + Spawn: "01" + Spawn Type: "05" + Parameters: "F09F" + State: "FF" + Return: + Connection: Lake Hylia -> Outside Castle Town South + Stage: -1 + Room: -1 + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lake Hylia Bridge -> Flight by Fowl + Stage: 52 + Room: 0 + Spawn: "08" + Spawn Type: "B0" + Parameters: "F09F" + State: "FF" + Return: + Connection: Flight by Fowl -> Lake Hylia Bridge + Stage: 56 + Room: 13 + Spawn: "01" + Spawn Type: "A0" + Parameters: "F09F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Lake Hylia -> Gerudo Desert + Stage: 59 + Room: 0 + Spawn: "00" + Spawn Type: "00" + Parameters: "F018" + State: "FF" + Return: + Connection: Gerudo Desert -> Lake Hylia + Stage: + Room: + Spawn: "" + Spawn Type: "" + Parameters: "" + State: "FF" + +- Type: Overworld + Forward: + Connection: Gerudo Desert Outside Bulblin Camp -> Bulblin Camp + Stage: 55 + Room: 1 + Spawn: "00" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Bulblin Camp -> Gerudo Desert Outside Bulblin Camp + Stage: 59 + Room: 0 + Spawn: "02" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + +- Type: Overworld + Forward: + Connection: Bulblin Camp -> Outside Arbiters Grounds + Stage: 55 + Room: 3 + Spawn: "07" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" + Return: + Connection: Outside Arbiters Grounds -> Bulblin Camp + Stage: 55 + Room: 1 + Spawn: "02" + Spawn Type: "50" + Parameters: "F01F" + State: "FF" diff --git a/src/dusk/randomizer/data/items.yaml b/src/dusk/randomizer/data/items.yaml new file mode 100644 index 0000000000..c1d17dbf52 --- /dev/null +++ b/src/dusk/randomizer/data/items.yaml @@ -0,0 +1,791 @@ +# TODO: Add relevant item data + +# Item Importance: +# 1. Major - Item can potentially unlock locations. Will be placed in a non-excluded location +# 2. Minor - Item does not unlock locations, but has gameplay utility. Will be placed +# in a non-excluded location if any are empty. +# 3. Junk - Item is expendible. Will be placed completely randomly. + +# WARNING: ITEM IDS ARE CURRENTLY NOT CORRECT. FIX BEFORE USING FOR ACTUAL GAME PATCHING +- Name: Green Rupee + Importance: Junk + Id: 0x00 + +- Name: Blue Rupee + Importance: Junk + Id: 0x01 + +- Name: Yellow Rupee + Importance: Junk + Id: 0x02 + +- Name: Red Rupee + Importance: Junk + Id: 0x03 + +- Name: Purple Rupee + Importance: Junk + Id: 0x04 + +- Name: Orange Rupee + Importance: Junk + Id: 0x05 + +- Name: Silver Rupee + Importance: Junk + Id: 0x06 + +- Name: Purple Rupee Links House + Importance: Junk + Id: 0x07 + +- Name: Bombs 5 + Importance: Junk + Id: 0x08 + +- Name: Bombs 10 + Importance: Junk + Id: 0x09 + +- Name: Bombs 20 + Importance: Junk + Id: 0x0A + +- Name: Bombs 30 + Importance: Junk + Id: 0x0B + +- Name: Arrows 10 + Importance: Junk + Id: 0x0C + +- Name: Arrows 20 + Importance: Junk + Id: 0x0D + +- Name: Arrows 30 + Importance: Junk + Id: 0x0E + +- Name: Seeds 50 + Importance: Junk + Id: 0x0F + +- Name: Water Bombs 5 + Importance: Junk + Id: 0x10 + +- Name: Water Bombs 10 + Importance: Junk + Id: 0x11 + +- Name: Water Bombs 15 + Importance: Junk + Id: 0xAE + +- Name: Water Bombs 20 + Importance: Junk + Id: 0x12 + +- Name: Water Bombs 30 + Importance: Junk + Id: 0x13 + +- Name: Bomblings 5 + Importance: Junk + Id: 0x14 + +- Name: Bomblings 10 + Importance: Junk + Id: 0x15 + +- Name: Shadow Crystal + Importance: Major + Id: 0x16 + +- Name: Progressive Sword + Importance: Major + Id: 0x17 + +- Name: Asheis Sketch + Importance: Major + Id: 0x18 + +- Name: Aurus Memo + Importance: Major + Id: 0x19 + +- Name: Ball and Chain + Importance: Major + Id: 0x1A + +- Name: Gale Boomerang + Importance: Major + Id: 0x1B + +- Name: Bomb Bag + Importance: Major + Id: 0x1C + +- Name: Iron Boots + Importance: Major + Id: 0x1D + +- Name: Lantern + Importance: Major + Id: 0x1E + +- Name: Progressive Bow + Importance: Major + Id: 0x1F + +- Name: Progressive Clawshot + Importance: Major + Id: 0x20 + +- Name: Progressive Dominion Rod + Importance: Major + Id: 0x21 + +- Name: Progressive Fishing Rod + Importance: Major + Id: 0x22 + +- Name: Progressive Sky Book + Importance: Major + Id: 0x23 + +- Name: Slingshot + Importance: Major + Id: 0x24 + +- Name: Spinner + Importance: Major + Id: 0x25 + +- Name: Zora Armor + Importance: Major + Id: 0x26 + +- Name: Progressive Fused Shadow + Importance: Major + Id: 0x27 + +- Name: Progressive Mirror Shard + Importance: Major + Id: 0x28 + +- Name: Forest Temple Small Key + Importance: Major + Id: 0x29 + Dungeon Small Key: Forest Temple + +- Name: Goron Mines Small Key + Importance: Major + Id: 0x2A + Dungeon Small Key: Goron Mines + +- Name: Lakebed Temple Small Key + Importance: Major + Id: 0x2B + Dungeon Small Key: Lakebed Temple + +- Name: Arbiters Grounds Small Key + Importance: Major + Id: 0x2C + Dungeon Small Key: Arbiters Grounds + +- Name: Snowpeak Ruins Small Key + Importance: Major + Id: 0x2D + Dungeon Small Key: Snowpeak Ruins + +- Name: Ordon Pumpkin + Importance: Major + Id: 0x2E + +- Name: Ordon Cheese + Importance: Major + Id: 0x2F + +- Name: Temple of Time Small Key + Importance: Major + Id: 0x30 + Dungeon Small Key: Temple of Time + +- Name: City in the Sky Small Key + Importance: Major + Id: 0x31 + Dungeon Small Key: City in the Sky + +- Name: Palace of Twilight Small Key + Importance: Major + Id: 0x32 + Dungeon Small Key: Palace of Twilight + +- Name: Hyrule Castle Small Key + Importance: Major + Id: 0x33 + Dungeon Small Key: Hyrule Castle + +- Name: North Faron Woods Gate Key + Importance: Major + Id: 0x34 + +- Name: Gate Keys + Importance: Major + Id: 0x35 + +- Name: Gerudo Desert Bulblin Camp Key + Importance: Major + Id: 0x36 + +- Name: Forest Temple Big Key + Importance: Major + Id: 0x37 + Dungeon Big Key: Forest Temple + +- Name: Goron Mines Key Shard + Importance: Major + Id: 0x38 + Dungeon Big Key: Goron Mines + +- Name: Lakebed Temple Big Key + Importance: Major + Id: 0x39 + Dungeon Big Key: Lakebed Temple + +- Name: Arbiters Grounds Big Key + Importance: Major + Id: 0x3A + Dungeon Big Key: Arbiters Grounds + +- Name: Snowpeak Ruins Bedroom Key + Importance: Major + Id: 0x3B + Dungeon Big Key: Snowpeak Ruins + +- Name: Temple of Time Big Key + Importance: Major + Id: 0x3C + Dungeon Big Key: Temple of Time + +- Name: City in the Sky Big Key + Importance: Major + Id: 0x3D + Dungeon Big Key: City in the Sky + +- Name: Palace of Twilight Big Key + Importance: Major + Id: 0xA9 + Dungeon Big Key: Palace of Twilight + +- Name: Hyrule Castle Big Key + Importance: Major + Id: 0x3E + Dungeon Big Key: Hyrule Castle + +- Name: Forest Temple Compass + Importance: Junk + Id: 0x3F + Dungeon Compass: Forest Temple + +- Name: Goron Mines Compass + Importance: Junk + Id: 0x40 + Dungeon Compass: Goron Mines + +- Name: Lakebed Temple Compass + Importance: Junk + Id: 0x41 + Dungeon Compass: Lakebed Temple + +- Name: Arbiters Grounds Compass + Importance: Junk + Id: 0x42 + Dungeon Compass: Arbiters Grounds + +- Name: Snowpeak Ruins Compass + Importance: Junk + Id: 0x43 + Dungeon Compass: Snowpeak Ruins + +- Name: Temple of Time Compass + Importance: Junk + Id: 0x44 + Dungeon Compass: Temple of Time + +- Name: City in the Sky Compass + Importance: Junk + Id: 0x45 + Dungeon Compass: City in the Sky + +- Name: Palace of Twilight Compass + Importance: Junk + Id: 0xAA + Dungeon Compass: Palace of Twilight + +- Name: Hyrule Castle Compass + Importance: Junk + Id: 0x46 + Dungeon Compass: Hyrule Castle + +- Name: Forest Temple Dungeon Map + Importance: Junk + Id: 0x47 + Dungeon Map: Forest Temple + +- Name: Goron Mines Dungeon Map + Importance: Junk + Id: 0x48 + Dungeon Map: Goron Mines + +- Name: Lakebed Temple Dungeon Map + Importance: Junk + Id: 0x49 + Dungeon Map: Lakebed Temple + +- Name: Arbiters Grounds Dungeon Map + Importance: Junk + Id: 0x4A + Dungeon Map: Arbiters Grounds + +- Name: Snowpeak Ruins Dungeon Map + Importance: Junk + Id: 0x4B + Dungeon Map: Snowpeak Ruins + +- Name: Temple of Time Dungeon Map + Importance: Junk + Id: 0x4C + Dungeon Map: Temple of Time + +- Name: City in the Sky Dungeon Map + Importance: Junk + Id: 0x4D + Dungeon Map: City in the Sky + +- Name: Palace of Twilight Dungeon Map + Importance: Junk + Id: 0xAB + Dungeon Map: Palace of Twilight + +- Name: Hyrule Castle Dungeon Map + Importance: Junk + Id: 0x4E + Dungeon Compass: Hyrule Castle + +- Name: Male Beetle + Importance: Major + Id: 0x4F + +- Name: Female Beetle + Importance: Major + Id: 0x50 + +- Name: Male Butterfly + Importance: Major + Id: 0x51 + +- Name: Female Butterfly + Importance: Major + Id: 0x52 + +- Name: Male Stag Beetle + Importance: Major + Id: 0x53 + +- Name: Female Stag Beetle + Importance: Major + Id: 0x54 + +- Name: Male Grasshopper + Importance: Major + Id: 0x55 + +- Name: Female Grasshopper + Importance: Major + Id: 0x56 + +- Name: Male Phasmid + Importance: Major + Id: 0x57 + +- Name: Female Phasmid + Importance: Major + Id: 0x58 + +- Name: Male Pill Bug + Importance: Major + Id: 0x59 + +- Name: Female Pill Bug + Importance: Major + Id: 0x5A + +- Name: Male Mantis + Importance: Major + Id: 0x5B + +- Name: Female Mantis + Importance: Major + Id: 0x5C + +- Name: Male Ladybug + Importance: Major + Id: 0x5D + +- Name: Female Ladybug + Importance: Major + Id: 0x5E + +- Name: Male Snail + Importance: Major + Id: 0x5F + +- Name: Female Snail + Importance: Major + Id: 0x60 + +- Name: Male Dragonfly + Importance: Major + Id: 0x61 + +- Name: Female Dragonfly + Importance: Major + Id: 0x62 + +- Name: Male Ant + Importance: Major + Id: 0x63 + +- Name: Female Ant + Importance: Major + Id: 0x64 + +- Name: Male Dayfly + Importance: Major + Id: 0x65 + +- Name: Female Dayfly + Importance: Major + Id: 0x66 + +- Name: Ordon Shield + Importance: Major + Id: 0x67 + +- Name: Wooden Shield + Importance: Junk + Id: 0x68 + +- Name: Hylian Shield + Importance: Major + Id: 0x69 + +- Name: Magic Armor + Importance: Major + Id: 0x6A + +- Name: Poe Soul + Importance: Major + Id: 0x6B + +- Name: Piece of Heart + Importance: Junk + Id: 0x6C + +- Name: Heart Container + Importance: Junk + Id: 0x6D + +- Name: Progressive Hidden Skill + Importance: Major + Id: 0x6E + +- Name: Progressive Wallet + Importance: Major + Id: 0x6F + +- Name: Giant Bomb Bag + Importance: Minor + Id: 0x70 + +- Name: Empty Bottle + Importance: Major + Id: 0x71 + +- Name: Bottle with Lantern Oil + Importance: Major + Id: 0x72 + +- Name: Bottle with Half Milk + Importance: Major + Id: 0x73 + +- Name: Bottle with Great Fairies Tears + Importance: Major + Id: 0x74 + +- Name: Hawkeye + Importance: Minor + Id: 0x75 + +- Name: Horse Call + Importance: Minor + Id: 0x76 + +# - Name: Ghost Lantern +# Importance: Minor +# Id: 0xAF + +- Name: Foolish Item + Importance: Junk + Id: 0x77 + +# - Name: Stamp (A) +# Importance: Junk +# Id: 0x78 + +# - Name: Stamp (B) +# Importance: Junk +# Id: 0x79 + +# - Name: Stamp (C) +# Importance: Junk +# Id: 0x7A + +# - Name: Stamp (D) +# Importance: Junk +# Id: 0x7B + +# - Name: Stamp (E) +# Importance: Junk +# Id: 0x7C + +# - Name: Stamp (F) +# Importance: Junk +# Id: 0x7D + +# - Name: Stamp (G) +# Importance: Junk +# Id: 0x7E + +# - Name: Stamp (H) +# Importance: Junk +# Id: 0x7F + +# - Name: Stamp (I) +# Importance: Junk +# Id: 0x80 + +# - Name: Stamp (J) +# Importance: Junk +# Id: 0x81 + +# - Name: Stamp (K) +# Importance: Junk +# Id: 0x82 + +# - Name: Stamp (L) +# Importance: Junk +# Id: 0x83 + +# - Name: Stamp (M) +# Importance: Junk +# Id: 0x84 + +# - Name: Stamp (N) +# Importance: Junk +# Id: 0x85 + +# - Name: Stamp (O) +# Importance: Junk +# Id: 0x86 + +# - Name: Stamp (P) +# Importance: Junk +# Id: 0x87 + +# - Name: Stamp (Q) +# Importance: Junk +# Id: 0x88 + +# - Name: Stamp (R) +# Importance: Junk +# Id: 0x89 + +# - Name: Stamp (S) +# Importance: Junk +# Id: 0x8A + +# - Name: Stamp (T) +# Importance: Junk +# Id: 0x8B + +# - Name: Stamp (U) +# Importance: Junk +# Id: 0x8C + +# - Name: Stamp (V) +# Importance: Junk +# Id: 0x8D + +# - Name: Stamp (W) +# Importance: Junk +# Id: 0x8E + +# - Name: Stamp (X) +# Importance: Junk +# Id: 0x8F + +# - Name: Stamp (Y) +# Importance: Junk +# Id: 0x90 + +# - Name: Stamp (Z) +# Importance: Junk +# Id: 0x91 + +# - Name: Stamp (Rupee) +# Importance: Junk +# Id: 0x92 + +# - Name: Stamp (Treasure Chest) +# Importance: Junk +# Id: 0x93 + +# - Name: Stamp (Piece of Heart) +# Importance: Junk +# Id: 0x94 + +# - Name: Stamp (Heart Container) +# Importance: Junk +# Id: 0x95 + +# - Name: Stamp (Happy Link) +# Importance: Junk +# Id: 0x96 + +# - Name: Stamp (Angry Link) +# Importance: Junk +# Id: 0x97 + +# - Name: Stamp (Sad Link) +# Importance: Junk +# Id: 0x98 + +# - Name: Stamp (Surprised Link) +# Importance: Junk +# Id: 0x99 + +# - Name: Stamp (Wolf Link) +# Importance: Junk +# Id: 0x9A + +# - Name: Stamp (Happy Midna) +# Importance: Junk +# Id: 0x9B + +# - Name: Stamp (Angry Midna) +# Importance: Junk +# Id: 0x9C + +# - Name: Stamp (Sad Midna) +# Importance: Junk +# Id: 0xAD + +# - Name: Stamp (Surprised Midna) +# Importance: Junk +# Id: 0x9D + +# - Name: Stamp (Ooccoo) +# Importance: Junk +# Id: 0x9E + +# - Name: Stamp (Happy Zelda) +# Importance: Junk +# Id: 0x9F + +# - Name: Stamp (Angry Zelda) +# Importance: Junk +# Id: 0xA0 + +# - Name: Stamp (Sad Zelda) +# Importance: Junk +# Id: 0xA1 + +# - Name: Stamp (Surprised Zelda) +# Importance: Junk +# Id: 0xA2 + +# - Name: Stamp (Zant) +# Importance: Junk +# Id: 0xA3 + +# - Name: Stamp (Agitha) +# Importance: Junk +# Id: 0xA4 + +# - Name: Stamp (Malo Mart) +# Importance: Junk +# Id: 0xA5 + +# - Name: Stamp (Cucco) +# Importance: Junk +# Id: 0xA6 + +# - Name: Stamp (Fairy) +# Importance: Junk +# Id: 0xA7 + +# - Name: Stamp (True Midna) +# Importance: Junk +# Id: 0xA8 + +- Name: Renados Letter + Importance: Major + Id: 0xA9 + +- Name: Ilias Charm + Importance: Major + Id: 0xAA + +- Name: Invoice + Importance: Major + Id: 0xAB + +- Name: Wooden Statue + Importance: Major + Id: 0xAC + +# Dummy Items that are used to represent other logical states (for now) + +- Name: Game Beatable + Importance: Major + Id: 0x101 + Game Winning Item: True + +- Name: Hint + Importance: Junk + Id: 0x102 + +- Name: Faron Twilight Tear + Importance: Major + Id: 0x103 + +- Name: Eldin Twilight Tear + Importance: Major + Id: 0x104 + +- Name: Lanayru Twilight Tear + Importance: Major + Id: 0x105 + +- Name: Red Potion Shop + Importance: Junk + Id: 0x106 + +- Name: Fairy Tears + Importance: Junk + Id: 0x107 diff --git a/src/dusk/randomizer/data/locations.yaml b/src/dusk/randomizer/data/locations.yaml new file mode 100644 index 0000000000..a50036d6d0 --- /dev/null +++ b/src/dusk/randomizer/data/locations.yaml @@ -0,0 +1,4431 @@ +# ORDONA PROVINCE + +- Name: Wooden Sword Chest + Original Item: Progressive Sword + Categories: + - Overworld + - Chest + - Ordona Province + - ARC + +- Name: Links Basement Chest + Original Item: Purple Rupee Links House + Categories: + - Overworld + - Chest + - Ordona Province + - ARC + +- Name: Uli Cradle Delivery + Original Item: Progressive Fishing Rod + Categories: + - Overworld + - Ordona Province + - ARC + +- Name: Ordon Cat Rescue + Original Item: Bottle with Half Milk + Categories: + - Overworld + - Npc + - Ordona Province + - ARC + +- Name: Sera Shop Slingshot + Original Item: Slingshot + Categories: + - Overworld + - Shop + - Ordona Province + - ARC + +- Name: Ordon Shield + Original Item: Ordon Shield + Categories: + - Overworld + - Ordona Province + - REL + +- Name: Ordon Sword + Original Item: Progressive Sword + Categories: + - Overworld + - Ordona Province + - REL + +- Name: Wrestling With Bo + Original Item: Iron Boots + Categories: + - Overworld + - Chest + - Ordona Province + - ARC + +- Name: Herding Goats Reward + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Ordona Province + - ARC + +- Name: Ordon Ranch Grotto Lantern Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (Happy Link) + Categories: + - Overworld + - Chest + - Ordona Province + - DZX + +- Name: Ordon Spring Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Ordona Province + +# FARON PROVINCE + +- Name: South Faron Woods Twilit Insect in Tunnel 1 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: South Faron Woods Twilit Insect in Tunnel 2 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Woods Coros House Twilit Insect 1 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Woods Coros House Twilit Insect 2 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: South Faron Woods Twilit Insect Behind Gate + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Mist Twilit Insect on Wall + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Mist Twilit Insect on Center Stump 1 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Mist Twilit Insect on Center Stump 2 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Mist Burrowing Twilit Insect 1 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Faron Mist Burrowing Twilit Insect 2 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: North Faron Woods Twilit Insect 1 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: North Faron Woods Twilit Insect 2 + Original Item: Faron Twilight Tear + Categories: + - Overworld + - Faron Woods + - Twilit Insect + +- Name: Coro Bottle + Original Item: Bottle with Lantern Oil + Categories: + - Overworld + - Npc + - Faron Woods + - ARC + +- Name: South Faron Cave Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Faron Woods + - ARC + +- Name: Faron Mist South Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Faron Woods + - DZX + +- Name: Faron Mist Stump Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Faron Woods + - DZX + +- Name: Faron Mist North Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Faron Woods + - DZX + +- Name: Faron Mist Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Faron Woods + +- Name: Faron Mist Cave Open Chest + Original Item: North Faron Woods Gate Key + Categories: + - Overworld + - Chest + - Faron Woods + - Small Key + - ARC + +- Name: Faron Mist Cave Lantern Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Faron Woods + - ARC + +- Name: Faron Woods Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Faron Woods + +- Name: North Faron Woods Deku Baba Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Faron Woods + - ARC + +- Name: Faron Woods Owl Statue Sky Character + Original Item: Progressive Sky Book + Categories: + - Overworld + - Faron Woods + - Sky Book + +- Name: Faron Woods Owl Statue Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Faron Woods + - ARC + +- Name: Faron Field Bridge Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Faron Province + - ARC + +# HD Only +# - Name: Faron Field Corner Grotto Main Chest +# Original Item: Stamp (Wolf Link) +# Categories: +# - Overworld +# - Chest +# - Hyrule Field - Faron Province + +- Name: Faron Field Corner Grotto Left Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Faron Province + - DZX + +- Name: Faron Field Corner Grotto Rear Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Faron Province + - DZX + +- Name: Faron Field Corner Grotto Right Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Faron Province + - DZX + +- Name: Faron Field Female Beetle + Original Item: Female Beetle + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Faron Province + - DZX + +- Name: Faron Field Male Beetle + Original Item: Male Beetle + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Faron Province + - DZX + +- Name: Faron Field Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Faron Province + +- Name: Faron Field Tree Heart Piece + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Faron Province + - ARC + +- Name: Lost Woods Boulder Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Sacred Grove + +- Name: Lost Woods Lantern Chest + Original Item: Bombs 30 + Categories: + - Overworld + - Chest + - Sacred Grove + - ARC + +- Name: Lost Woods Waterfall Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Sacred Grove + +- Name: Sacred Grove Baba Serpent Grotto Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Sacred Grove + - DZX + +- Name: Sacred Grove Spinner Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (X) + Categories: + - Overworld + - Chest + - Sacred Grove + - ARC + +- Name: Sacred Grove Pedestal Master Sword + Original Item: Progressive Sword + Categories: + - Overworld + - Sacred Grove + - Event + +- Name: Sacred Grove Pedestal Shadow Crystal + Original Item: Shadow Crystal + Categories: + - Overworld + - Sacred Grove + - Event + +- Name: Sacred Grove Master Sword Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Sacred Grove + +- Name: Sacred Grove Female Snail + Original Item: Female Snail + Categories: + - Overworld + - Golden Bug + - Sacred Grove + - DZX + +- Name: Sacred Grove Male Snail + Original Item: Male Snail + Categories: + - Overworld + - Golden Bug + - Sacred Grove + - DZX + +- Name: Sacred Grove Past Owl Statue Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Sacred Grove + - ARC + +- Name: Sacred Grove Temple of Time Owl Statue Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Sacred Grove + +# ELDIN PROVINCE + +- Name: Sanctuary Basement Twilit Insect 1 + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Sanctuary Basement Twilit Insect 2 + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Sanctuary Basement Twilit Insect 3 + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Kakariko Inn Twilit Insect + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Kakariko Bug House Twilit Insect + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Barnes Bomb Shop Twilit Insect + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Kakariko Destroyed Building Twilit Insect 1 + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Kakariko Destroyed Building Twilit Insect 2 + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Kakariko Destroyed Building Twilit Insect 3 + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Death Mountain Trail Twilit Insect Near Howling Stone + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Death Mountain Trail Twilit Insect on Wall + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Death Mountain Trail Twilit Insect in Hot Spring + Original Item: Eldin Twilight Tear + Categories: + - Overworld + - Eldin Province + - Twilit Insect + +- Name: Kakariko Gorge Female Pill Bug + Original Item: Female Pill Bug + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Eldin Province + - DZX + +- Name: Kakariko Gorge Male Pill Bug + Original Item: Male Pill Bug + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Eldin Province + - DZX + +- Name: Kakariko Gorge Owl Statue Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (E) + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - ARC + +- Name: Kakariko Gorge Owl Statue Sky Character + Original Item: Progressive Sky Book + Categories: + - Overworld + - Hyrule Field - Eldin Province + - Sky Book + +- Name: Kakariko Gorge Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Eldin Province + +- Name: Kakariko Gorge Spire Heart Piece + Original Item: Piece of Heart + Categories: + - Overworld + - Hyrule Field - Eldin Province + - ARC + +- Name: Kakariko Gorge Double Clawshot Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - ARC + +- Name: Eldin Lantern Cave First Chest + Original Item: Red Rupee + # HD Original Item: Stamp (O) + Categories: + - Overworld + - Chest + - Eldin Lantern Cave + - ARC + +- Name: Eldin Lantern Cave Lantern Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Eldin Lantern Cave + - ARC + +- Name: Eldin Lantern Cave Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Eldin Lantern Cave + +- Name: Eldin Lantern Cave Second Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Eldin Lantern Cave + - ARC + +- Name: Eldin Spring Underwater Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Kakariko Village + - ARC + +- Name: Kakariko Village Bomb Rock Spire Heart Piece + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Kakariko Village + - DZX + +- Name: Kakariko Village Malo Mart Hawkeye + Original Item: Hawkeye + Categories: + - Overworld + - Kakariko Village + - ARC + - Shop + +- Name: Kakariko Village Malo Mart Hylian Shield + Original Item: Hylian Shield + Categories: + - Overworld + - Kakariko Village + - ARC + - Shop + +- Name: Kakariko Village Malo Mart Red Potion + Original Item: Red Potion Shop + Categories: + - Overworld + - Kakariko Village + - ARC + - Shop + +- Name: Kakariko Village Malo Mart Wooden Shield + Original Item: Wooden Shield + Categories: + - Overworld + - Kakariko Village + - ARC + - Shop + +- Name: Kakariko Inn Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Kakariko Village + - ARC + +- Name: Kakariko Village Female Ant + Original Item: Female Ant + Categories: + - Overworld + - Golden Bug + - Kakariko Village + - DZX + +- Name: Barnes Bomb Bag + Original Item: Bomb Bag + Categories: + - Overworld + - Npc + - Shop + - Kakariko Village + - ARC + +- Name: Kakariko Village Bomb Shop Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Kakariko Village + +- Name: Kakariko Village Watchtower Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Kakariko Village + +- Name: Kakariko Watchtower Alcove Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Kakariko Village + - ARC + +- Name: Kakariko Watchtower Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Kakariko Village + - ARC + +- Name: Talo Sharpshooting + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Kakariko Village + - ARC + +- Name: Renados Letter + Original Item: Renados Letter + Categories: + - Overworld + - Npc + - Kakariko Village + - ARC + +- Name: Ilia Memory Reward + Original Item: Horse Call + Categories: + - Overworld + - Npc + - Kakariko Village + +- Name: Rutelas Blessing + Original Item: Zora Armor + Categories: + - Overworld + - Npc + - Kakariko Graveyard + - ARC + +- Name: Gift From Ralis + Original Item: Progressive Fishing Rod + Categories: + - Overworld + - Npc + - Kakariko Graveyard + - ARC + +- Name: Kakariko Graveyard Lantern Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Kakariko Graveyard + - ARC + +- Name: Kakariko Graveyard Male Ant + Original Item: Male Ant + Categories: + - Overworld + - Golden Bug + - Kakariko Graveyard + - DZX + +- Name: Kakariko Graveyard Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Kakariko Graveyard + +- Name: Kakariko Graveyard Open Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Kakariko Graveyard + +- Name: Kakariko Graveyard Grave Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Kakariko Graveyard + +- Name: Death Mountain Alcove Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Death Mountain + - ARC + +- Name: Death Mountain Trail Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Death Mountain + +- Name: Eldin Field Bomb Rock Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - ARC + +- Name: Eldin Field Bomskit Grotto Lantern Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (C) + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - DZX + +- Name: Eldin Field Bomskit Grotto Left Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - DZX + +- Name: Eldin Field Female Grasshopper + Original Item: Female Grasshopper + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Eldin Province + - DZX + +- Name: Eldin Field Male Grasshopper + Original Item: Male Grasshopper + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Eldin Province + - DZX + +- Name: Goron Springwater Rush + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Hyrule Field - Eldin Province + - Boss + +- Name: Eldin Field Water Bomb Fish Grotto Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (Sad Link) + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - DZX + +- Name: Bridge of Eldin Female Phasmid + Original Item: Female Phasmid + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Eldin Province + - DZX + +- Name: Bridge of Eldin Male Phasmid + Original Item: Male Phasmid + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Eldin Province + - DZX + +- Name: Bridge of Eldin Owl Statue Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Eldin Province + - ARC + +- Name: Bridge of Eldin Owl Statue Sky Character + Original Item: Progressive Sky Book + Categories: + - Overworld + - Hyrule Field - Eldin Province + - Sky Book + +- Name: Eldin Stockcave Upper Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Eldin Stockcave + - ARC + +- Name: Eldin Stockcave Lantern Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (Piece of Heart) + Categories: + - Overworld + - Chest + - Eldin Stockcave + - ARC + +- Name: Eldin Stockcave Lowest Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Eldin Stockcave + - ARC + +- Name: Eldin Field Stalfos Grotto Left Small Chest + Original Item: Bombs 5 + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: Eldin Field Stalfos Grotto Right Small Chest + Original Item: Bombs 5 + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: Eldin Field Stalfos Grotto Stalfos Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: Skybook From Impaz + Original Item: Progressive Sky Book + Categories: + - Overworld + - Npc + - Hidden Village + - ARC + +- Name: Cats Hide and Seek Minigame + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Hidden Village + - DZX + +- Name: Hidden Village Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hidden Village + +- Name: Ilia Charm + Original Item: Ilias Charm + Categories: + - Overworld + - Npc + - Hidden Village + - ARC + +# LANAYRU PROVINCE + +- Name: Castle Town Twilit Insect + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Lake Hylia Twilit Insect Between Bridges + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Lake Hylia Burrowing Twilit Insect + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Lake Hylia Twilit Insect on Docks + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Lake Hylia Twilit Bloat + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Zora's River Twilit Insect 1 + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Zora's River Twilit Insect 2 + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Zora's River Twilit Insect 3 + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Upper Zoras River Twilit Insect + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Zoras Domain Twilit Insect near Lilypads + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Zoras Domain Burrowing Twilit Insect + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Zoras Domain Throne Room Twilit Insect + Original Item: Lanayru Twilight Tear + Categories: + - Overworld + - Lanayru Province + - Twilit Insect + +- Name: Lanayru Field Behind Gate Underwater Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (F) + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Lanayru Field Bridge Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Lanayru Field Female Stag Beetle + Original Item: Female Stag Beetle + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: Lanayru Field Male Stag Beetle + Original Item: Male Stag Beetle + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +# HD Only location +# - Name: Lanayru Field Chu Grotto Chest +# Original Item: Stamp (I) +# Categories: +# - Overworld +# - Chest +# - Hyrule Field - Lanayru Province +# - DZX + +- Name: Lanayru Field Poe Grotto Left Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Lanayru Field Poe Grotto Right Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Lanayru Field Skulltula Grotto Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (P) + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: Lanayru Ice Block Puzzle Cave Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Lanayru Field Spinner Track Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: West Hyrule Field Female Butterfly + Original Item: Female Butterfly + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: West Hyrule Field Male Butterfly + Original Item: Male Butterfly + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: West Hyrule Field Helmasaur Grotto Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (Angry Link) + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: West Hyrule Field Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Hyrule Field - Lanayru Province + +- Name: Hyrule Field Amphitheater Owl Statue Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Hyrule Field Amphitheater Owl Statue Sky Character + Original Item: Progressive Sky Book + Categories: + - Overworld + - Hyrule Field - Lanayru Province + - Sky Book + +- Name: Hyrule Field Amphitheater Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Charlo Donation Blessing + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Castle Town + - ObjectARC + - Boss + +- Name: STAR Prize 1 + Original Item: Progressive Bow + Categories: + - Overworld + - Npc + - Castle Town + - ARC + +- Name: STAR Prize 2 + Original Item: Progressive Bow + Categories: + - Overworld + - Npc + - Castle Town + - ARC + +- Name: Castle Town Malo Mart Magic Armor + Original Item: Magic Armor + Categories: + - Overworld + - Castle Town + - ARC + - Shop + +# HD Only location +# - Name: Castle Town Malo Mart Stamp +# Original Item: Stamp (Malo Mart) +# Categories: +# - Overworld +# - Castle Town +# - ARC +# - Shop + +- Name: North Castle Town Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Castle Town + +- Name: Doctors Office Balcony Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Castle Town + - ARC + +- Name: East Castle Town Bridge Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Jovani 20 Poe Soul Reward + Original Item: Bottle with Great Fairies Tears + # HD Original Item: Ghost Lantern + Categories: + - Overworld + - Npc + - Castle Town + - ARC + +- Name: Jovani 60 Poe Soul Reward + Original Item: Silver Rupee + #HD Original Item: Bottle with Great Fairies Tears + Categories: + - Overworld + - Npc + - Castle Town + - ARC + +# HD Only Location +# - Name: Gengle 60 Poe Soul Reward +# Original Item: Stamp (Rupee) +# Categories: +# - Overworld +# - Npc +# - Castle Town + +- Name: Jovani House Poe + Original Item: Poe Soul + Categories: + - Overworld + - Castle Town + - Poe + +- Name: Telma Invoice + Original Item: Invoice + Categories: + - Overworld + - Npc + - Castle Town + - ARC + +- Name: Agitha Female Ant Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Beetle Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Butterfly Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Dayfly Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Dragonfly Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Grasshopper Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Ladybug Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Mantis Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Phasmid Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Pill Bug Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Snail Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Female Stag Beetle Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Ant Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Beetle Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Butterfly Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Dayfly Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Dragonfly Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Grasshopper Reward + Original Item: Progressive Wallet + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Ladybug Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Mantis Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Phasmid Reward + Original Item: Progressive Wallet + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Pill Bug Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Snail Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +- Name: Agitha Male Stag Beetle Reward + Original Item: Purple Rupee + Categories: + - Overworld + - Npc + - Castle Town + - Bug Reward + +# HD Only Location +# - Name: Agitha 12 Golden Bugs Reward +# Original Item: Stamp (Agitha) +# Categories: +# - Overworld +# - Npc +# - Castle Town +# - Bug Reward + +- Name: Outside South Castle Town Tightrope Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Outside South Castle Town Fountain Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (Surprised Zelda) + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Outside South Castle Town Male Ladybug + Original Item: Male Ladybug + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: Outside South Castle Town Female Ladybug + Original Item: Female Ladybug + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: Outside South Castle Town Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Outside South Castle Town Tektite Grotto Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: Outside South Castle Town Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Hyrule Field - Lanayru Province + +- Name: Outside South Castle Town Double Clawshot Chasm Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (V) + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Wooden Statue + Original Item: Wooden Statue + Categories: + - Overworld + - Npc + - Hyrule Field - Lanayru Province + - REL + +- Name: Lake Hylia Bridge Owl Statue Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Lake Hylia Bridge Owl Statue Sky Character + Original Item: Progressive Sky Book + Categories: + - Overworld + - Hyrule Field - Lanayru Province + - Sky Book + +- Name: Lake Hylia Bridge Vines Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Lake Hylia Bridge Female Mantis + Original Item: Female Mantis + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: Lake Hylia Bridge Male Mantis + Original Item: Male Mantis + Categories: + - Overworld + - Golden Bug + - Hyrule Field - Lanayru Province + - DZX + +- Name: Lake Hylia Bridge Cliff Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - ARC + +- Name: Lake Hylia Bridge Cliff Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Hyrule Field - Lanayru Province + +- Name: Lake Hylia Bridge Bubble Grotto Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (M) + Categories: + - Overworld + - Chest + - Hyrule Field - Lanayru Province + - DZX + +- Name: Auru Gift To Fyer + Original Item: Aurus Memo + Categories: + - Overworld + - Npc + - Lake Hylia + - ARC + +- Name: Lake Hylia Tower Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Hylia + +- Name: Lake Hylia Water Toadpoli Grotto Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (S) + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Lake Lantern Cave First Chest + Original Item: Bomblings 5 + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Second Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Third Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Fourth Chest + Original Item: Arrows 10 + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Fifth Chest + Original Item: Red Rupee + # HD Original Item: Stamp (J) + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Sixth Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Seventh Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Eighth Chest + Original Item: Bomblings 5 + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Ninth Chest + Original Item: Arrows 10 + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Tenth Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Eleventh Chest + Original Item: Bomblings 10 + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Twelfth Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Thirteenth Chest + Original Item: Seeds 50 + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave Fourteenth Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (Treasure Chest) + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave End Lantern Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Lake Lantern Cave + - ARC + +- Name: Lake Lantern Cave First Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Lantern Cave + +- Name: Lake Lantern Cave Second Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Lantern Cave + +- Name: Lake Lantern Cave Final Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Lantern Cave + +- Name: Lake Hylia Underwater Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (W) + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Lake Hylia Alcove Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Hylia + +- Name: Flight By Fowl Top Platform Reward + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Flight By Fowl Second Platform Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Flight By Fowl Third Platform Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (Cucco) + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Flight By Fowl Fourth Platform Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Flight By Fowl Fifth Platform Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + + +- Name: Isle of Riches Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Hylia + +- Name: Flight By Fowl Ledge Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Hylia + +- Name: Lake Hylia Shell Blade Grotto Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Lake Hylia Dock Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Lake Hylia + +- Name: Outside Lanayru Spring Left Statue Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Outside Lanayru Spring Right Statue Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - DZX + +- Name: Lanayru Spring Back Room Lantern Chest + Original Item: Piece of Heart + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Lanayru Spring Back Room Left Chest + Original Item: Bombs 5 + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Lanayru Spring Back Room Right Chest + Original Item: Blue Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Lanayru Spring East Double Clawshot Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Lanayru Spring West Double Clawshot Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Lanayru Spring Underwater Left Chest + Original Item: Blue Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Lanayru Spring Underwater Right Chest + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Lake Hylia + - ARC + +- Name: Plumm Fruit Balloon Minigame + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Lake Hylia + - ARC + +- Name: Upper Zoras River Female Dragonfly + Original Item: Female Dragonfly + Categories: + - Overworld + - Golden Bug + - Upper Zoras River + - DZX + +- Name: Upper Zoras River Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Upper Zoras River + +- Name: Iza Helping Hand + Original Item: Bomb Bag + Categories: + - Overworld + - Npc + - Upper Zoras River + - ARC + +- Name: Iza Raging Rapids Minigame + Original Item: Giant Bomb Bag + Categories: + - Overworld + - Npc + - Upper Zoras River + - ARC + +- Name: Fishing Hole Bottle + Original Item: Empty Bottle + Categories: + - Overworld + - Fishing Hole + - ARC + - REL + +- Name: Fishing Hole Heart Piece + Original Item: Piece of Heart + Categories: + - Overworld + - Fishing Hole + - ARC + - REL + +- Name: Zoras Domain Male Dragonfly + Original Item: Male Dragonfly + Categories: + - Overworld + - Golden Bug + - Zoras Domain + - DZX + +- Name: Zoras Domain Mother and Child Isle Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Zoras Domain + +- Name: Zoras Domain Chest Behind Waterfall + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Zoras Domain + - ARC + +- Name: Zoras Domain Chest By Mother and Child Isles + Original Item: Yellow Rupee + Categories: + - Overworld + - Chest + - Zoras Domain + - ARC + +- Name: Zoras Domain Waterfall Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Zoras Domain + +- Name: Zoras Domain Extinguish All Torches Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Zoras Domain + - ARC + +- Name: Zoras Domain Light All Torches Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Zoras Domain + - ARC + +- Name: Zoras Domain Underwater Goron + Original Item: Bomb Bag + Categories: + - Overworld + - Npc + - Zoras Domain + - ARC + +# SNOWPEAK PROVINCE + +- Name: Ashei Sketch + Original Item: Asheis Sketch + Categories: + - Overworld + - Npc + - Snowpeak Province + - ARC + +- Name: Snowpeak Blizzard Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Snowpeak Province + +- Name: Snowpeak Above Freezard Grotto Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Snowpeak Province + +- Name: Snowpeak Poe Among Trees + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Snowpeak Province + +- Name: Snowpeak Freezard Grotto Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Snowpeak Province + - DZX + +- Name: Snowpeak Cave Ice Lantern Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Snowpeak Province + - ARC + +- Name: Snowpeak Cave Ice Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Snowpeak Province + +- Name: Snowpeak Icy Summit Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Snowpeak Province + +- Name: Snowboard Racing Prize + Original Item: Piece of Heart + Categories: + - Overworld + - Npc + - Snowpeak Province + - ARC + +# DESERT PROVINCE + +- Name: Gerudo Desert East Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Gerudo Desert + +- Name: Gerudo Desert Skulltula Grotto Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - DZX + +- Name: Gerudo Desert Peahat Ledge Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert East Canyon Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert South Chest Behind Wooden Gates + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Lone Small Chest + Original Item: Arrows 10 + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Male Dayfly + Original Item: Male Dayfly + Categories: + - Overworld + - Golden Bug + - Gerudo Desert + - DZX + +- Name: Gerudo Desert Female Dayfly + Original Item: Female Dayfly + Categories: + - Overworld + - Golden Bug + - Gerudo Desert + - DZX + +- Name: Gerudo Desert Owl Statue Chest + Original Item: Orange Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Owl Statue Sky Character + Original Item: Progressive Sky Book + Categories: + - Overworld + - Gerudo Desert + - Sky Book + +- Name: Gerudo Desert Poe Above Cave of Ordeals + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Gerudo Desert + +- Name: Gerudo Desert West Canyon Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (R) + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert North Peahat Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Gerudo Desert + +- Name: Gerudo Desert Rock Grotto First Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Gerudo Desert + +- Name: Gerudo Desert Rock Grotto Second Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Gerudo Desert + +- Name: Gerudo Desert Rock Grotto Lantern Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (Sad Midna) + Categories: + - Overworld + - Chest + - Gerudo Desert + - DZX + +- Name: Gerudo Desert Campfire East Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Campfire North Chest + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Campfire West Chest + Original Item: Arrows 10 + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Northeast Chest Behind Gates + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Northwest Chest Behind Gates + Original Item: Red Rupee + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert North Small Chest Before Bulblin Camp + Original Item: Arrows 10 + Categories: + - Overworld + - Chest + - Gerudo Desert + - ARC + +- Name: Gerudo Desert Golden Wolf + Original Item: Progressive Hidden Skill + Categories: + - Overworld + - Hidden Skill + - Gerudo Desert + +- Name: Outside Bulblin Camp Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Gerudo Desert + +- Name: Bulblin Camp First Chest Under Tower At Entrance + Original Item: Arrows 20 + Categories: + - Overworld + - Chest + - Bulblin Camp + - DZX + +- Name: Bulblin Camp Small Chest in Back of Camp + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Bulblin Camp + - DZX + +- Name: Bulblin Camp Roasted Boar + Original Item: Piece of Heart + Categories: + - Overworld + - Bulblin Camp + - Boss + +- Name: Bulblin Guard Key + Original Item: Gerudo Desert Bulblin Camp Key + Categories: + - Overworld + - Bulblin Camp + - Small Key + - DZX + +- Name: Bulblin Camp Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Bulblin Camp + +- Name: Outside Arbiters Grounds Lantern Chest + Original Item: Purple Rupee + Categories: + - Overworld + - Chest + - Bulblin Camp + - ARC + +- Name: Outside Arbiters Grounds Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Bulblin Camp + +# HD Only Location +# - Name: Cave of Ordeals Floor 10 Chest +# Original Item: Stamp (Surprised Link) +# Categories: +# - Overworld +# - Chest +# - Cave of Ordeals + +# HD Only Location +# - Name: Cave of Ordeals Floor 20 Chest +# Original Item: Stamp (Angry Zelda) +# Categories: +# - Overworld +# - Chest +# - Cave of Ordeals + +# HD Only Location +# - Name: Cave of Ordeals Floor 30 Chest +# Original Item: Stamp (Happy Midna) +# Categories: +# - Overworld +# - Chest +# - Cave of Ordeals + +# HD Only Location +# - Name: Cave of Ordeals Floor 40 Chest +# Original Item: Stamp (Heart Container) +# Categories: +# - Overworld +# - Chest +# - Cave of Ordeals + +# HD Only Location +# - Name: Cave of Ordeals Floor 50 Chest +# Original Item: Stamp (Fairy) +# Categories: +# - Overworld +# - Chest +# - Cave of Ordeals + +- Name: Cave of Ordeals Floor 17 Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Cave of Ordeals + +- Name: Cave of Ordeals Floor 33 Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Cave of Ordeals + +- Name: Cave of Ordeals Floor 44 Poe + Original Item: Poe Soul + Categories: + - Overworld + - Poe + - Cave of Ordeals + +- Name: Cave of Ordeals Great Fairy Reward + Original Item: Fairy Tears + Categories: + - Overworld + - Npc + - Cave of Ordeals + - ARC + +# FOREST TEMPLE + +- Name: Forest Temple Entrance Vines Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple Central Chest Hanging From Web + Original Item: Forest Temple Compass + Categories: + - Chest + - Dungeon + - Forest Temple + - Dungeon Items + - Compass + - ARC + +- Name: Forest Temple Central Chest Behind Stairs + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple Central North Chest + Original Item: Forest Temple Dungeon Map + Categories: + - Chest + - Dungeon + - Forest Temple + - Dungeon Items + - Dungeon Map + - ARC + +- Name: Forest Temple West Deku Like Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple Big Baba Key + Original Item: Forest Temple Small Key + Categories: + - Small Key + - Dungeon + - Forest Temple + - REL + +- Name: Forest Temple Totem Pole Chest + Original Item: Forest Temple Small Key + Categories: + - Chest + - Dungeon + - Forest Temple + - Small Key + - ARC + +- Name: Forest Temple West Tile Worm Chest Behind Stairs + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple West Tile Worm Room Vines Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple Gale Boomerang + Original Item: Gale Boomerang + Categories: + - Forest Temple + - Dungeon + - Boss + +- Name: Forest Temple Big Key Chest + Original Item: Forest Temple Big Key + Categories: + - Chest + - Dungeon + - Forest Temple + - Big Key + - ARC + +- Name: Forest Temple East Water Cave Chest + Original Item: Yellow Rupee + # HD Original Item: Stamp (A) + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple Second Monkey Under Bridge Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple Windless Bridge Chest + Original Item: Forest Temple Small Key + Categories: + - Chest + - Dungeon + - Forest Temple + - Small Key + - ARC + +- Name: Forest Temple East Tile Worm Chest + Original Item: Red Rupee + # HD Original Item: Stamp (N) + Categories: + - Chest + - Dungeon + - Forest Temple + - ARC + +- Name: Forest Temple North Deku Like Chest + Original Item: Forest Temple Small Key + Categories: + - Chest + - Dungeon + - Forest Temple + - Small Key + - ARC + +- Name: Forest Temple Diababa Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Dungeon + - Forest Temple + - Boss + +- Name: Forest Temple Dungeon Reward + Original Item: Progressive Fused Shadow + Categories: + - Dungeon + - Forest Temple + - Dungeon Reward + - REL + - ARC + Goal Location: True + +# GORON MINES + +- Name: Goron Mines Entrance Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Main Magnet Room Bottom Chest + Original Item: Goron Mines Small Key + Categories: + - Chest + - Dungeon + - Goron Mines + - Small Key + - ARC + +- Name: Goron Mines Gor Amato Key Shard + Original Item: Goron Mines Key Shard + Categories: + - Npc + - Dungeon + - Goron Mines + - Dungeon Items + - Big Key + - ARC + +- Name: Goron Mines Gor Amato Chest + Original Item: Goron Mines Dungeon Map + Categories: + - Chest + - Dungeon + - Goron Mines + - Dungeon Items + - Dungeon Map + - ARC + +- Name: Goron Mines Gor Amato Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Magnet Maze Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Crystal Switch Room Underwater Chest + Original Item: Goron Mines Small Key + Categories: + - Chest + - Dungeon + - Goron Mines + - Small Key + - ARC + +- Name: Goron Mines Crystal Switch Room Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines After Crystal Switch Room Magnet Wall Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Outside Beamos Chest + Original Item: Goron Mines Small Key + Categories: + - Chest + - Dungeon + - Goron Mines + - Small Key + - ARC + +- Name: Goron Mines Outside Underwater Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (H) + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Outside Clawshot Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (U) + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Gor Ebizo Key Shard + Original Item: Goron Mines Key Shard + Categories: + - Npc + - Dungeon + - Goron Mines + - Dungeon Items + - Big Key + - ARC + +- Name: Goron Mines Gor Ebizo Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Chest Before Dangoro + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Dangoro Chest + Original Item: Progressive Bow + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Beamos Room Chest + Original Item: Goron Mines Compass + Categories: + - Chest + - Dungeon + - Goron Mines + - Dungeon Items + - Compass + - ARC + +- Name: Goron Mines Gor Liggs Key Shard + Original Item: Goron Mines Key Shard + Categories: + - Npc + - Dungeon + - Goron Mines + - Dungeon Items + - Big Key + - ARC + +- Name: Goron Mines Gor Liggs Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Main Magnet Room Top Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Goron Mines + - ARC + +- Name: Goron Mines Fyrus Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Dungeon + - Goron Mines + - Boss + +- Name: Goron Mines Dungeon Reward + Original Item: Progressive Fused Shadow + Categories: + - Dungeon + - Goron Mines + - Dungeon Reward + - REL + - ARC + Goal Location: True + +# LAKEBED TEMPLE + +- Name: Lakebed Temple Lobby Left Chest + Original Item: Arrows 20 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Lobby Rear Chest + Original Item: Water Bombs 10 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Stalactite Room Chest + Original Item: Water Bombs 10 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Chandelier Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Central Room Spire Chest + Original Item: Red Rupee + # HD Original Item: Stamp (K) + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Central Room Chest + Original Item: Lakebed Temple Dungeon Map + Categories: + - Chest + - Dungeon + - Lakebed Temple + - Dungeon Items + - Dungeon Map + - ARC + +- Name: Lakebed Temple Central Room Small Chest + Original Item: Arrows 20 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple East Lower Waterwheel Stalactite Chest + Original Item: Lakebed Temple Small Key + Categories: + - Chest + - Dungeon + - Lakebed Temple + - Small Key + - ARC + +- Name: Lakebed Temple East Lower Waterwheel Bridge Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple East Second Floor Southeast Chest + Original Item: Lakebed Temple Small Key + Categories: + - Chest + - Dungeon + - Lakebed Temple + - Small Key + - ARC + +- Name: Lakebed Temple East Second Floor Southwest Chest + Original Item: Water Bombs 5 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple East Water Supply Small Chest + Original Item: Water Bombs 10 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple East Water Supply Clawshot Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (Y) + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Before Deku Toad Alcove Chest + Original Item: Lakebed Temple Small Key + Categories: + - Chest + - Dungeon + - Lakebed Temple + - Small Key + - ARC + +- Name: Lakebed Temple Before Deku Toad Underwater Left Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Before Deku Toad Underwater Right Chest + Original Item: Water Bombs 5 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Deku Toad Chest + Original Item: Progressive Clawshot + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Lower Small Chest + Original Item: Water Bombs 10 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Second Floor Central Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Second Floor Northeast Chest + Original Item: Water Bombs 15 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Second Floor Southeast Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Second Floor Southwest Underwater Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Water Supply Small Chest + Original Item: Water Bombs 10 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple West Water Supply Chest + Original Item: Lakebed Temple Compass + Categories: + - Chest + - Dungeon + - Lakebed Temple + - Dungeon Items + - Compass + - ARC + +- Name: Lakebed Temple Underwater Maze Small Chest + Original Item: Water Bombs 5 + Categories: + - Chest + - Dungeon + - Lakebed Temple + - ARC + +- Name: Lakebed Temple Big Key Chest + Original Item: Lakebed Temple Big Key + Categories: + - Chest + - Dungeon + - Lakebed Temple + - Big Key + - ARC + +- Name: Lakebed Temple Morpheel Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Dungeon + - Lakebed Temple + - Boss + +- Name: Lakebed Temple Dungeon Reward + Original Item: Progressive Fused Shadow + Categories: + - Dungeon + - Lakebed Temple + - Dungeon Reward + - REL + - ARC + Goal Location: True + +# ARBITERS GROUNDS + +- Name: Arbiters Grounds Entrance Chest + Original Item: Arbiters Grounds Small Key + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Small Key + +- Name: Arbiters Grounds Torch Room Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Arbiters Grounds + +- Name: Arbiters Grounds Torch Room East Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Torch Room West Chest + Original Item: Arbiters Grounds Dungeon Map + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Dungeon Items + - Dungeon Map + +- Name: Arbiters Grounds West Small Chest Behind Block + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds East Lower Turnable Redead Chest + Original Item: Arbiters Grounds Small Key + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Small Key + +- Name: Arbiters Grounds East Turning Room Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Arbiters Grounds + +- Name: Arbiters Grounds East Upper Turnable Chest + Original Item: Arbiters Grounds Compass + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Dungeon Items + - Compass + +- Name: Arbiters Grounds East Upper Turnable Redead Chest + Original Item: Arbiters Grounds Small Key + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Small Key + +- Name: Arbiters Grounds Hidden Wall Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Arbiters Grounds + +- Name: Arbiters Grounds Ghoul Rat Room Chest + Original Item: Arbiters Grounds Small Key + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Small Key + +- Name: Arbiters Grounds West Chandelier Chest + Original Item: Red Rupee + # HD Original Item: Stamp (Surprised Midna) + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds West Stalfos Northeast Chest + Original Item: Bombs 5 + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds West Stalfos West Chest + Original Item: Bombs 5 + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds West Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Arbiters Grounds + +- Name: Arbiters Grounds North Turning Room Chest + Original Item: Arbiters Grounds Small Key + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Small Key + +- Name: Arbiters Grounds Death Sword Chest + Original Item: Spinner + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Spinner Room First Small Chest + Original Item: Bombs 10 + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Spinner Room Second Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Spinner Room Lower Central Small Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Spinner Room Stalfos Alcove Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Spinner Room Lower North Chest + Original Item: Yellow Rupee + # HD Original Item: Stamp (D) + Categories: + - Chest + - Dungeon + - Arbiters Grounds + +- Name: Arbiters Grounds Big Key Chest + Original Item: Arbiters Grounds Big Key + Categories: + - Chest + - Dungeon + - Arbiters Grounds + - Big Key + +- Name: Arbiters Grounds Stallord Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Arbiters Grounds + - Dungeon + - Boss + +- Name: Arbiters Grounds Dungeon Reward + Original Item: Progressive Mirror Shard + Categories: + - Dungeon + - Arbiters Grounds + - Dungeon Reward + Goal Location: True + +# SNOWPEAK RUINS + +- Name: Snowpeak Ruins Lobby West Armor Chest + Original Item: Red Rupee + # HD Original Item: Stamp (Q) + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Lobby East Armor Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Lobby Armor Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Snowpeak Ruins + +- Name: Snowpeak Ruins Lobby Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Snowpeak Ruins + +- Name: Snowpeak Ruins Lobby Chandelier Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Mansion Map + Original Item: Snowpeak Ruins Dungeon Map + Categories: + - Npc + - Dungeon + - Snowpeak Ruins + - Dungeon Items + - Dungeon Map + - ARC + +- Name: Snowpeak Ruins East Courtyard Chest + Original Item: Snowpeak Ruins Small Key + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Small Key + - ARC + +- Name: Snowpeak Ruins East Courtyard Buried Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Ordon Pumpkin Chest + Original Item: Ordon Pumpkin + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Dungeon Items + - Ordon Pumpkin + - Small Key + - ARC + +- Name: Snowpeak Ruins Courtyard Central Chest + Original Item: Bombs 5 + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins West Courtyard Buried Chest + Original Item: Snowpeak Ruins Small Key + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Small Key + - ARC + +- Name: Snowpeak Ruins West Cannon Room Central Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins West Cannon Room Corner Chest + Original Item: Bombs 5 + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Wooden Beam Central Chest + Original Item: Red Rupee + # HD Original Item: Stamp (B) + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Wooden Beam Chandelier Chest + Original Item: Snowpeak Ruins Small Key + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Small Key + - ARC + +- Name: Snowpeak Ruins Wooden Beam Northwest Chest + Original Item: Snowpeak Ruins Compass + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Dungeon Items + - Compass + - ARC + +- Name: Snowpeak Ruins Ball and Chain + Original Item: Ball and Chain + Categories: + - Dungeon + - Snowpeak Ruins + - REL + +- Name: Snowpeak Ruins Chest After Darkhammer + Original Item: Ordon Cheese + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Small Key + - ARC + +- Name: Snowpeak Ruins Broken Floor Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - ARC + +- Name: Snowpeak Ruins Ice Room Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Snowpeak Ruins + +- Name: Snowpeak Ruins Northeast Chandelier Chest + Original Item: Snowpeak Ruins Small Key + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Small Key + - ARC + +- Name: Snowpeak Ruins Chapel Chest + Original Item: Snowpeak Ruins Bedroom Key + Categories: + - Chest + - Dungeon + - Snowpeak Ruins + - Big Key + - ARC + +- Name: Snowpeak Ruins Blizzeta Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Dungeon + - Snowpeak Ruins + - Boss + +- Name: Snowpeak Ruins Dungeon Reward + Original Item: Progressive Mirror Shard + Categories: + - Dungeon + - Snowpeak Ruins + - Dungeon Reward + - REL + - ARC + Goal Location: True + +- Name: Temple of Time Lobby Lantern Chest + Original Item: Temple of Time Small Key + Categories: + - Chest + - Dungeon + - Temple of Time + - Small Key + - ARC + +- Name: Temple of Time First Staircase Gohma Gate Chest + Original Item: Arrows 30 + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time First Staircase Window Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time First Staircase Armos Chest + Original Item: Temple of Time Dungeon Map + Categories: + - Chest + - Dungeon + - Temple of Time + - Dungeon Items + - Dungeon Map + - ARC + +- Name: Temple of Time Poe Behind Gate + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Temple of Time + +- Name: Temple of Time Armos Antechamber East Chest + Original Item: Temple of Time Small Key + Categories: + - Chest + - Dungeon + - Temple of Time + - Small Key + - ARC + +- Name: Temple of Time Armos Antechamber North Chest + Original Item: Red Rupee + # HD Original Item: Stamp (L) + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Armos Antechamber Statue Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Moving Wall Beamos Room Chest + Original Item: Temple of Time Compass + Categories: + - Chest + - Dungeon + - Temple of Time + - Dungeon Items + - Compass + - ARC + +- Name: Temple of Time Moving Wall Dinalfos Room Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Scales Gohma Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Scales Upper Chest + Original Item: Red Rupee + # HD Original Item: Stamp (T) + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Poe Above Scales + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - Temple of Time + +- Name: Temple of Time Floor Switch Puzzle Room Upper Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Big Key Chest + Original Item: Temple of Time Big Key + Categories: + - Chest + - Dungeon + - Temple of Time + - Big Key + - ARC + +- Name: Temple of Time Gilloutine Chest + Original Item: Temple of Time Small Key + Categories: + - Chest + - Dungeon + - Temple of Time + - Small Key + - ARC + +- Name: Temple of Time Chest Before Darknut + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Darknut Chest + Original Item: Progressive Dominion Rod + Categories: + - Chest + - Dungeon + - Temple of Time + - ARC + +- Name: Temple of Time Armogohma Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Dungeon + - Temple of Time + - Boss + +- Name: Temple of Time Dungeon Reward + Original Item: Progressive Mirror Shard + Categories: + - Dungeon + - Temple of Time + - Dungeon Reward + - REL + - ARC + Goal Location: True + +# CITY IN THE SKY + +- Name: City in the Sky Underwater East Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Underwater West Chest + Original Item: Water Bombs 15 + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky West Wing First Chest + Original Item: City in the Sky Small Key + Categories: + - Chest + - Dungeon + - City in the Sky + - Small Key + - ARC + +- Name: City in the Sky West Wing Baba Balcony Chest + Original Item: Arrows 20 + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky West Wing Narrow Ledge Chest + Original Item: Red Rupee + # HD Original Item: Stamp (Ooccoo) + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky West Wing Tile Worm Chest + Original Item: Bombs 10 + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Baba Tower Top Small Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Baba Tower Narrow Ledge Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Baba Tower Alcove Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky West Garden Corner Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky West Garden Lone Island Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (G) + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Garden Island Poe + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - City in the Sky + +- Name: City in the Sky West Garden Lower Chest + Original Item: Bombs 5 + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky West Garden Ledge Chest + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Central Outside Ledge Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Central Outside Poe Island Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (Z) + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Poe Above Central Fan + Original Item: Poe Soul + Categories: + - Dungeon + - Poe + - City in the Sky + +- Name: City in the Sky Big Key Chest + Original Item: City in the Sky Big Key + Categories: + - Chest + - Dungeon + - City in the Sky + - Big Key + - ARC + +- Name: City in the Sky Chest Below Big Key Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky East First Wing Chest After Fans + Original Item: City in the Sky Dungeon Map + Categories: + - Chest + - Dungeon + - City in the Sky + - Dungeon Items + - Dungeon Map + - ARC + +- Name: City in the Sky East Tile Worm Small Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky East Wing After Dinalfos Alcove Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky East Wing After Dinalfos Ledge Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky East Wing Lower Level Chest + Original Item: City in the Sky Compass + Categories: + - Chest + - Dungeon + - City in the Sky + - Dungeon Items + - Compass + - ARC + +- Name: City in the Sky Aeralfos Chest + Original Item: Progressive Clawshot + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Chest Behind North Fan + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - City in the Sky + - ARC + +- Name: City in the Sky Argorok Heart Container + Original Item: Heart Container + Categories: + - Heart Container + - Dungeon + - City in the Sky + - Boss + +- Name: City in the Sky Dungeon Reward + Original Item: Progressive Mirror Shard + Categories: + - Dungeon + - City in the Sky + - Dungeon Reward + - REL + - ARC + Goal Location: True + +# PALACE OF TWILIGHT + +- Name: Palace of Twilight West Wing First Room Central Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight West Wing Chest Behind Wall of Darkness + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight West Wing Second Room Central Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight West Wing Second Room Lower South Chest + Original Item: Palace of Twilight Compass + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Dungeon Items + - Compass + +- Name: Palace of Twilight West Wing Second Room Southeast Chest + Original Item: Orange Rupee + # HD Original Item: Stamp (Angry Midna) + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight East Wing First Room Zant Head Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight East Wing First Room East Alcove + Original Item: Piece of Heart + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight East Wing First Room North Small Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight East Wing First Room West Alcove + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight East Wing Second Room Northeast Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight East Wing Second Room Northwest Chest + Original Item: Purple Rupee + # HD Original Item: Stamp (Zant) + Categories: + - Chest + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight East Wing Second Room Southeast Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight East Wing Second Room Southwest Chest + Original Item: Palace of Twilight Dungeon Map + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Dungeon Items + - Dungeon Map + +- Name: Palace of Twilight Collect Both Sols + Original Item: Progressive Sword + Categories: + - Cutscene + - Dungeon + - Palace of Twilight + +- Name: Palace of Twilight Central First Room Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight Central Outdoor Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight Big Key Chest + Original Item: Palace of Twilight Big Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Big Key + +- Name: Palace of Twilight Central Tower Chest + Original Item: Palace of Twilight Small Key + Categories: + - Chest + - Dungeon + - Palace of Twilight + - Small Key + +- Name: Palace of Twilight Zant Heart Container + Original Item: Heart Container + Categories: + - Dungeon + - Palace of Twilight + - Heart Container + - Dungeon Reward + Goal Location: True + +# HYRULE CASTLE + +- Name: Hyrule Castle West Courtyard Central Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle West Courtyard North Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle King Bulblin Key + Original Item: Hyrule Castle Small Key + Categories: + - Npc + - Small Key + - Dungeon + - Hyrule Castle + - REL + +- Name: Hyrule Castle East Wing Boomerang Puzzle Chest + Original Item: Hyrule Castle Dungeon Map + Categories: + - Chest + - Dungeon + - Hyrule Castle + - Dungeon Items + - Dungeon Map + - ARC + +- Name: Hyrule Castle East Wing Balcony Chest + Original Item: Yellow Rupee + # HD Original Item: Stamp (Sad Zelda) + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Graveyard Grave Switch Room Back Left Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Graveyard Grave Switch Room Front Left Chest + Original Item: Green Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Graveyard Grave Switch Room Right Chest + Original Item: Orange Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Graveyard Owl Statue Chest + Original Item: Hyrule Castle Small Key + Categories: + - Chest + - Dungeon + - Hyrule Castle + - Small Key + - ARC + +- Name: Hyrule Castle Main Hall Northeast Chest + Original Item: Hyrule Castle Compass + Categories: + - Chest + - Dungeon + - Hyrule Castle + - Dungeon Items + - Compass + - ARC + +- Name: Hyrule Castle Main Hall Northwest Chest + Original Item: Silver Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Main Hall Southwest Chest + Original Item: Orange Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Lantern Staircase Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Southeast Balcony Tower Chest + Original Item: Hyrule Castle Small Key + Categories: + - Chest + - Dungeon + - Hyrule Castle + - Small Key + - ARC + +- Name: Hyrule Castle Big Key Chest + Original Item: Hyrule Castle Big Key + Categories: + - Chest + - Dungeon + - Hyrule Castle + - Big Key + - ARC + +- Name: Hyrule Castle Treasure Room First Chest + Original Item: Orange Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Second Chest + Original Item: Seeds 50 + # HD Original Item: Stamp (Happy Zelda) + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Third Chest + Original Item: Silver Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Fourth Chest + Original Item: Bomblings 10 + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Fifth Chest + Original Item: Purple Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room First Small Chest + Original Item: Arrows 30 + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Second Small Chest + Original Item: Green Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Third Small Chest + Original Item: Bombs 20 + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Fourth Small Chest + Original Item: Arrows 20 + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Fifth Small Chest + Original Item: Water Bombs 15 + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Sixth Small Chest + Original Item: Red Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Seventh Small Chest + Original Item: Yellow Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Hyrule Castle Treasure Room Eighth Small Chest + Original Item: Blue Rupee + Categories: + - Chest + - Dungeon + - Hyrule Castle + - ARC + +- Name: Defeat Ganondorf + Original Item: Game Beatable + Categories: + - Placeholder + +# HINT SIGNS + +- Name: Ordon Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: South Faron Woods Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Sacred Grove Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Faron Field Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Kakariko Gorge Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Kakariko Village Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Kakariko Graveyard Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Eldin Field Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: North Eldin Field Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Hidden Village Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Lanayru Field Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Beside Castle Town Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Castle Town Center Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Outside South Castle Town Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Lake Hylia Bridge Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Lake Hylia Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Lanayru Spring Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Lake Lantern Cave Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Fishing Hole Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Zoras Domain Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Snowpeak Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Gerudo Desert Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Bulblin Camp Hint Sign + Categories: + - Hint Sign + - Overworld + - Non-Item Location + +- Name: Forest Temple Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Goron Mines Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Lakebed Temple Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Arbiters Grounds Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Snowpeak Ruins Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Temple of Time First Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Temple of Time Second Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: City in the Sky Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Palace of Twilight Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location + +- Name: Hyrule Castle Hint Sign + Categories: + - Hint Sign + - Dungeon + - Non-Item Location diff --git a/src/dusk/randomizer/data/macros.yaml b/src/dusk/randomizer/data/macros.yaml new file mode 100644 index 0000000000..2b326279d6 --- /dev/null +++ b/src/dusk/randomizer/data/macros.yaml @@ -0,0 +1,262 @@ +# Macros are a way to shorten or make more explicit the logical requirements +# for certain things. Macros can be used on logic statements like any item and do +# not need any code modifications to run. Simply add them here and they can be used +# in the world graph files. + +Can Open Doors: Human_Link +Can Climb Ladders: Human_Link +Can Climb Vines: Human_Link +Can Talk to Humans: Human_Link +Can Swing on Monkeys: Human_Link +Can Pickup Bomblings: Human_Link +Can Pull Lakebed Levers: Human_Link +Can Pull Blocks: Human_Link +Can Ride Boars: Human_Link +Can Talk to Animals: Wolf_Link +Can Use Senses: Wolf_Link +Can Dig: Wolf_Link +Can Howl: Wolf_Link +Can Sniff: Wolf_Link +Can Midna Jump: Wolf_Link +Can Use Tightrope: Wolf_Link +Not Twilight: Human_Link or Wolf_Link +# You can only use warp portals from certain stages. +# 'Can_Warp' is an event added to any logical area +# which is part of a stage that players can warp from. +Can Use Warp Portals: Wolf_Link and 'Can_Warp' + +Slingshot: Slingshot and 'Can_Refill_Slingshot_Seeds' and Human_Link +Lantern: Lantern and 'Can_Refill_Lantern_Oil' and Human_Link +Gale Boomerang: Gale_Boomerang and Human_Link +Iron Boots: Iron_Boots and Human_Link +Bow: Progressive_Bow and 'Can_Refill_Arrows' and Human_Link +Regular Bombs: Bomb_Bag and 'Can_Refill_Regular_Bombs' and Human_Link +Water Bombs: Bomb_Bag and 'Can_Refill_Water_Bombs' and Human_Link +Bombs: Regular_Bombs or Water_Bombs +Bomb Arrows: Bow and Bombs +Spinner: Spinner and Human_Link +Ball and Chain: Ball_and_Chain and Human_Link +Zora Armor: Zora_Armor and Human_Link +Magic Armor: Magic_Armor and Human_Link +Shield: Hylian_Shield or 'Can_Buy_Wooden_Shield' +Bottle: Empty_Bottle and Lantern # Can't empty lantern oil from a bottle until you have the lantern +Asheis Sketch: Asheis_Sketch and Human_Link +Aurus Memo: Aurus_Memo and Human_Link +Renados Letter: Renados_Letter and Human_Link +Invoice: Invoice and Human_Link +Wooden Statue: Wooden_Statue and Human_Link +Ilias Charm: Ilias_Charm and Human_Link + +Fishing Rod: Progressive_Fishing_Rod and Human_Link +Coral Earring: count(Progressive_Fishing_Rod, 2) and Human_Link +Sword: Progressive_Sword and Human_Link +Ordon Sword: count(Progressive_Sword, 2) and Human_Link +Master Sword: count(Progressive_Sword, 3) and Human_Link +Light Sword: count(Progressive_Sword, 4) and Human_Link +Big Quiver: count(Progressive_Bow, 2) and 'Can_Refill_Arrows' and Human_Link +Giant Quiver: count(Progressive_Bow, 3) and 'Can_Refill_Arrows' and Human_Link +Clawshot: Progressive_Clawshot and Human_Link +Double Clawshots: count(Progressive_Clawshot, 2) and Human_Link +Dominion Rod: Progressive_Dominion_Rod and Human_Link +Restored Dominion Rod: count(Progressive_Dominion_Rod, 2) and Human_Link +Big Wallet: Progressive_Wallet +Giant Wallet: count(Progressive_Wallet, 2) +Collosal Wallet: count(Progressive_Wallet, 3) + +Ending Blow: Sword and Progressive_Hidden_Skill +Shield Attack: Shield and count(Progressive_Hidden_Skill, 2) +Back Slice: Sword and count(Progressive_Hidden_Skill, 3) +Helm Splitter: Sword and Shield_Attack and count(Progressive_Hidden_Skill, 4) +Mortal Draw: Sword and count(Progressive_Hidden_Skill, 5) +Jump Strike: Sword and count(Progressive_Hidden_Skill, 6) +Great Spin: Sword and count(Progressive_Hidden_Skill, 7) + +Can Use Back Slice as Sword: Back_Slice_as_Sword == On and count(Progressive_Hidden_Skill, 3) + +Can Do Niche Stuff: Impossible # TODO: Make settings for all of these +Can Do Difficult Combat: Impossible # TODO: Make settings for all of these + +Can Use Hot Spring Water: Bottle and 'Can_Buy_Hot_Spring_Water' +Can Use Bottled Fairy: Bottle and 'Fairy_Access' +# This will have to change if we allow players to start with less than 3 hearts +Can Survive Damage: Damage_Multiplier != OHKO or Can_Use_Bottled_Fairy +Can Survive One Bonk: Bonks_Do_Damage == Off or Can_Survive_Damage +Can Survive Two Bonks: Bonks_Do_Damage == Off or Damage_Multiplier != OHKO or + (Can_Use_Bottled_Fairy and count(Empty_Bottle, 2)) +Can Survive Three Bonks: Bonks_Do_Damage == Off or Damage_Multiplier != OHKO or + (Can_Use_Bottled_Fairy and count(Empty_Bottle, 3)) +Can Smash: Bombs or Ball_and_Chain +Can Break Webs: Lantern or Bombs or (Ball_and_Chain and Ball_and_Chain_Webs == On) +Can Light Torches: Lantern +Can Extinguish Torches: Gale_Boomerang +Can Launch Bombs: Bombs and (Gale_Boomerang or Bow) +Can Break Monkey Cage: Sword or Iron_Boots or Spinner or Ball_and_Chain or Wolf_Link or Bombs or + Bow or Clawshot or (Can_Do_Niche_Stuff and Shield_Attack) +Can Cut Hanging Web: Clawshot or Bow or Gale_Boomerang or Ball_and_Chain +Can Break Wooden Barrier: Sword or Can_Smash or Wolf_Link or Can_Use_Back_Slice_as_Sword +Can Refill Air: Zora_Armor # or glitched logic water bombs +Can Cross Quicksand: Wolf_Link or Spinner +Can Break Armor: Ball_and_Chain +Can Break Ice: Ball_and_Chain +Can Launch Canonball: Bombs +Can Knock Down Hyrule Castle Painting: Bow or (Can_Do_Niche_Stuff and (Bombs or Jump_Strike)) +Can Knock Down Hanging Baba: Bow or Clawshot or Gale_Boomerang or Slingshot + +Has Damaging Item: Sword or Ball_and_Chain or Bow or Bombs or Iron_Boots or Wolf_Link or Spinner +Can Hit Crystal Switch: Clawshot or Has_Damaging_Item +Can Hit Crystal Switch at Range: Clawshot or Bow + +Can Complete MDH: Skip_Midna's_Desparate_Hour == On or ('Can_Access_Castle_Town_South' and 'Can_Complete_Lakebed_Temple') + + + +# REGULAR ENEMIES + +# A lot of enemies use this identical logical requirement +Can Defeat Generic Enemy: Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) + +Can Defeat Shadow Beast: Sword or (Wolf_Link and Can_Complete_MDH) +Can Defeat Keese: Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link or Slingshot or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Fire Keese: Can_Defeat_Keese +Can Defeat Ice Keese: Can_Defeat_Keese +Can Defeat Shadow Keese: Can_Defeat_Keese +Can Defeat Rat: Slingshot or Can_Defeat_Generic_Enemy +Can Defeat Ghoul Rat: Can_Use_Senses +Can Defeat Bokoblin: Can_Defeat_Generic_Enemy or Slingshot +Can Defeat Red Bokoblin: Sword or Ball_and_Chain or Giant_Quiver or Wolf_Link or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Difficult_Combat and (Iron_Boots or Spinner)) +Can Defeat Bulblin: Can_Defeat_Generic_Enemy +Can Defeat Deku Baba: Sword or Ball_and_Chain or Bow or Spinner or Shield_Attack or Slingshot or Clawshot or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Big Baba: Can_Defeat_Generic_Enemy +Can Defeat Baba Serpent: Can_Defeat_Generic_Enemy +Can Defeat Walltula: Ball_and_Chain or Slingshot or Bow or Gale_Boomerang or Clawshot +Can Defeat Skulltula: Can_Defeat_Generic_Enemy +Can Defeat Deku Like: Bombs +Can Launch Tileworm: Gale_Boomerang +Can Defeat Tileworm: Gale_Boomerang and Can_Defeat_Generic_Enemy +Can Defeat Stalhound: Can_Defeat_Generic_Enemy +Can Defeat Kargarok: Can_Defeat_Generic_Enemy +Can Defeat Goron: Sword or Ball_and_Chain or Bow or Spinner or Shield_Attack or Slingshot or Clawshot or Bombs or + (Can_Do_Niche_Stuff and Iron_Boots) or (Can_Do_Difficult_Combat and Lantern) or Can_Use_Back_Slice_as_Sword +Can Defeat Torch Slug: Sword or Ball_and_Chain or Bow or Wolf_Link or Bombs +Can Defeat Dodongo: Can_Defeat_Generic_Enemy +Can Defeat Beamos: Ball_and_Chain or Bow or Bombs +Can Defeat Leever: Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link or Bombs or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Helmasaur: Can_Defeat_Generic_Enemy +Can Defeat Tektite: Can_Defeat_Generic_Enemy +Can Defeat Toado: Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link +Can Defeat Water Toadpoli: Sword or Ball_and_Chain or Bow or Shield_Attack or (Can_Do_Difficult_Combat and Wolf_Link) +Can Defeat Shell Blade: Water_Bombs or (Sword and (Iron_Boots or (Can_Do_Niche_Stuff and Magic_Armor))) +Can Defeat Lizalfos: Sword or Ball_and_Chain or Bow or Wolf_Link or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Dinalfos: Sword or Ball_and_Chain or Wolf_Link +Can Defeat Chu: Can_Defeat_Generic_Enemy or Clawshot +Can Defeat Chu Worm: (Bombs or Clawshot) and (Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link or Can_Use_Back_Slice_as_Sword) +Can Defeat Bubble: Can_Defeat_Generic_Enemy +Can Defeat Fire Bubble: Can_Defeat_Bubble +Can Defeat Ice Bubble: Can_Defeat_Bubble +Can Defeat Skull Kid: Bow +Can Defeat Poe: Can_Use_Senses +Can Defeat Stalchild: Can_Defeat_Generic_Enemy +Can Defeat Stalfos: Can_Smash +Can Defeat Redead Knight: Sword or Ball_and_Chain or Bow or Wolf_Link or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat White Wolfos: Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link or Bombs or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Chilfos: Can_Defeat_Generic_Enemy +Can Defeat Mini Freezard: Can_Defeat_Generic_Enemy +Can Defeat Freezard: Ball_and_Chain +Can Defeat Young Gohma: Sword or Ball_and_Chain or Bow or Spinner or Wolf_Link or Bombs or + (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Baby Gohma: Can_Defeat_Generic_Enemy or Slingshot or Clawshot +Can Defeat Armos: Can_Defeat_Generic_Enemy or Clawshot +Can Defeat Zant Head: Sword or Wolf_Link or Can_Use_Back_Slice_as_Sword + +# MINIBOSSES + +Can Defeat Ook: Sword or Ball_and_Chain or Bow or Wolf_Link or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Dangoro: Iron_Boots and (Sword or Wolf_Link or Bomb_Arrows or (Can_Do_Niche_Stuff and Ball_and_Chain)) +Can Defeat Deku Toad: Sword or Ball_and_Chain or Bow or Wolf_Link or Bombs or + Can_Use_Back_Slice_as_Sword or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat King Bulblin Desert: Sword or Ball_and_Chain or Wolf_Link or Giant_Quiver or Can_Use_Back_Slice_as_Sword or + (Can_Do_Difficult_Combat and (Spinner or Iron_Boots or Bombs or Big_Quiver)) +Can Defeat Deathsword: Can_Use_Senses and Sword and (Clawshot or Bow or Gale_Boomerang) +Can Defeat Darkhammer: Sword or Ball_and_Chain or Bow or Wolf_Link or Bombs or + (Can_Use_Back_Slice_as_Sword and Can_Do_Difficult_Combat) or (Can_Do_Niche_Stuff and Iron_Boots) +Can Defeat Darknut: Sword or (Can_Do_Difficult_Combat and (Bombs or Ball_and_Chain)) +Can Defeat Aerolfos: Clawshot and (Sword or Ball_and_Chain or Wolf_Link or (Can_Do_Niche_Stuff and Iron_Boots)) +Can Defeat Phantom Zant: Sword or Wolf_Link +Can Defeat King Bulblin Castle: Sword or Ball_and_Chain or Wolf_Link or Big_Quiver or (Can_Do_Difficult_Combat and + (Spinner or Iron_Boots or Bombs or Can_Use_Back_Slice_as_Sword)) + +# BOSSES + +Can Defeat Diababa: Can_Launch_Bombs or (Gale_Boomerang and + (Sword or Ball_and_Chain or Wolf_Link or Bombs or + (Can_Do_Difficult_Combat and Can_Use_Back_Slice_as_Sword) or (Can_Do_Niche_Stuff and Iron_Boots))) +Can Defeat Fyrus: Bow and Iron_Boots and (Sword or (Can_Do_Difficult_Combat and Can_Use_Back_Slice_as_Sword)) +Can Defeat Morpheel: Iron_Boots and Clawshot and Sword and Can_Refill_Air +Can Defeat Stallord: Spinner and (Sword or Can_Do_Difficult_Combat) +Can Defeat Blizzeta: Ball_and_Chain +Can Defeat Armogohma: Bow and Dominion_Rod +Can Defeat Argorok: Double_Clawshots and Ordon_Sword and (Iron_Boots or (Can_Do_Niche_Stuff and Magic_Armor)) +Can Defeat Zant: Master_Sword and Gale_Boomerang and (Iron_Boots or (Can_Do_Niche_Stuff and Magic_Armor)) and + Can_Refill_Air and Clawshot and Ball_and_Chain + +Can Open North Faron Woods Gate: North_Faron_Woods_Gate_Key or Small_Keys == Keysy +Can Complete Prologue: Skip_Prologue == On or (Sword and Slingshot and Can_Open_North_Faron_Woods_Gate) + +Can Free All Monkeys in Forest Temple: "'Can_Free_Monkey_in_Entrance_Room' and 'Can_Free_Monkey_on_Totem' and + 'Can_Free_Monkey_in_Big_Baba_Room' and 'Can_Free_Monkey_in_West_Tileworm_Room' and + 'Can_Free_Monkey_in_East_Tileworm_Room' and 'Can_Free_Monkey_in_Dark_Spider_Room' and + 'Can_Free_Monkey_in_North_Deku_Like_Room'" + +Has Sword For Temple of Time: Temple_of_Time_Sword_Requirement == None or + (Temple_of_Time_Sword_Requirement == Wooden_Sword and Sword) or + (Temple_of_Time_Sword_Requirement == Ordon_Sword and Ordon_Sword) or + (Temple_of_Time_Sword_Requirement == Master_Sword and Master_Sword) or + (Temple_of_Time_Sword_Requirement == Light_Sword and Light_Sword) + +Can Complete Faron Twilight: Faron_Twilight_Cleared == On or count(Faron_Twilight_Tear, 12) +Can Complete Eldin Twilight: Eldin_Twilight_Cleared == On or count(Eldin_Twilight_Tear, 12) +Can Complete Lanayru Twilight: Lanayru_Twilight_Cleared == On or count(Lanayru_Twilight_Tear, 12) +Can Complete All Twilight: Can_Complete_Faron_Twilight and Can_Complete_Eldin_Twilight and Can_Complete_Lanayru_Twilight + +Can Defeat Faron Twilit Insect: Twilight +Can Defeat Eldin Twilit Insect: Twilight +Can Defeat Lanayru Twilit Insect: Twilight and Can_Complete_Eldin_Twilight + +Can Clear Forest: Can_Complete_Faron_Twilight and ('Can_Complete_Forest_Temple' or Faron_Woods_Logic == Open) + +Can Complete All Dungeons: "'Can_Complete_Forest_Temple' and 'Can_Complete_Goron_Mines' and 'Can_Complete_Lakebed_Temple' and + 'Can_Complete_Arbiters_Grounds' and 'Can_Complete_Snowpeak_Ruins' and 'Can_Complete_Temple_of_Time' and + 'Can_Complete_City_in_the_Sky' and 'Can_Complete_Palace_of_Twilight'" + +Can Break Hyrule Castle Barrier: Hyrule_Castle_Requirements == Open or + (Hyrule_Castle_Requirements == Vanilla and 'Can_Complete_Palace_of_Twilight') or + (Hyrule_Castle_Requirements == Fused_Shadows and count(Progressive_Fused_Shadow, 3)) or + (Hyrule_Castle_Requirements == Mirror_Shards and count(Progressive_Mirror_Shard, 4)) or + (Hyrule_Castle_Requirements == All_Dungeons and Can_Complete_All_Dungeons) + +# WARP PORTALS + +Ordon Spring Warp Portal: Can_Complete_Prologue +South Faron Woods Warp Portal: Can_Complete_Faron_Twilight +North Faron Woods Warp Portal: Can_Complete_Faron_Twilight +Sacred Grove Warp Portal: Sacred_Grove_Does_Not_Require_Skull_Kid == On or (Has_Sword_For_Temple_of_Time and 'Can_Access_Sacred_Grove_Lower' and Can_Defeat_Shadow_Beast) +Kakariko Gorge Warp Portal: Can_Complete_Eldin_Twilight +Kakariko Village Warp Portal: Can_Complete_Eldin_Twilight +Death Mountain Warp Portal: Can_Complete_Eldin_Twilight +Bridge of Eldin Warp Portal: Can_Defeat_Shadow_Beast and 'Can_Access_Eldin_Field_North_of_Bridge' +Castle Town Warp Portal: Can_Complete_Lanayru_Twilight +Lake Hylia Warp Portal: Can_Complete_Lanayru_Twilight +Upper Zoras River Warp Portal: (Sword or (Can_Defeat_Shadow_Beast and Transform_Anywhere == On)) and 'Can_Access_Upper_Zoras_River' +Zoras Domain Warp Portal: Can_Complete_Lanayru_Twilight +Snowpeak Warp Portal: Snowpeak_Does_Not_Require_Reekfish_Scent == On or (Can_Defeat_Shadow_Beast and 'Can_Access_Snowpeak_Summit_Upper') +Gerudo Desert Warp Portal: Can_Defeat_Shadow_Beast and 'Can_Access_Gerudo_Desert_Cave_of_Ordeals_Plateau' +Mirror Chamber Warp Portal: Can_Defeat_Shadow_Beast and 'Can_Access_Mirror_Chamber_Upper' + +Can Talk to Springwater Goron: Nothing # Change later \ No newline at end of file diff --git a/src/dusk/randomizer/data/settings_list.yaml b/src/dusk/randomizer/data/settings_list.yaml new file mode 100644 index 0000000000..c0c7cb60a9 --- /dev/null +++ b/src/dusk/randomizer/data/settings_list.yaml @@ -0,0 +1,660 @@ +# NOTE: Initially copied from SSHDR. Some commented out settings may reference skyward sword + +###################### +## Logic Settings ## +###################### + +- Name: Logic Rules + Default Option: All Locations Reachable + Options: + - All Locations Reachable: "Logic is considered when placing items. Tricks and Glitches can be enabled for consideration below." + - Beatable Only: "Logic is considered when placing items only until the world is beatable. Remaining items are placed without logic consideration. Tricks and Glitches can be enabled for consideration below." + - No Logic: "Maximize randomization, logic is not considered when placing items. MAY BE IMPOSSIBLE TO BEAT." + # - Vanilla: "Items are placed in their vanilla locations." + +###################### +## Access Options ## +###################### + +- Name: Hyrule Castle Requirements + Default Option: Vanilla + Options: + - Open: "The barrier around Hyrule Castle is dispelled from the beginning." + - Fused Shadows: "The player must collect all 3 Fused Shadows." + - Mirror Shards: "The player must collect all 4 Mirror Shards." + - All Dungeons: "The player must complete all dungeons." + - Vanilla: "The player must complete Palace of Twilight." + +- Name: Palace of Twilight Requirements + Default Option: Vanilla + Options: + - Open: "The barrier around Hyrule Castle is dispelled from the beginning." + - Fused Shadows: "The player must collect all 3 Fused Shadows." + - Mirror Shards: "The player must collect all 4 Mirror Shards." + - Vanilla: "The player must complete City in the Sky." + +- Name: Faron Woods Logic + Default Option: Closed + Options: + - Closed: "Midna will block the player from leaving Faron Woods until Forest Temple is completed." + - Open: "Midna will not prevent the player from leaving Faron Woods." + +###################### +## Item Pool ## +###################### + +- Name: Golden Bugs + Default Option: "Off" + Options: + - "Off": "The Golden Bug locations across Hyrule will not be randomized." + - "On": "The Golden Bug locations across Hyrule will be randomized." + +- Name: Sky Characters + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Gifts From NPCs + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Shop Items + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Hidden Skills + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Poe Souls + Default Option: Vanilla + Options: + - Vanilla: description + - Overworld: description + - Dungeon: description + - All: description + +- Name: Item Scarcity + Default Option: Vanilla + Options: + - Vanilla: "No changes to the item pool." + - Minimal: "Removes unrequired items such as Heart Containers and Pieces, Hawkeye, etc. Has as few items as possible for Bomb Bags, Bows, Hidden Skills, and Wallets. No Magic Armor and 1 Hidden Skill if Glitchless logic." + - Plentiful: "One extra copy of major items. There are 17 Heart Containers but no Pieces of Heart. Extra keys if `Keysanity` or `Any Dungeon`." + +###################### +## Dungeon Items ## +###################### + +- Name: Small Keys + Tracker Important: True + Default Option: Vanilla + Random Low: Own Dungeon + Random High: Anywhere + Options: + - Vanilla: "Small Keys will appear in their vanilla locations." + - Own Dungeon: "Small Keys will appear inside their respective dungeon." + - Any Dungeon: "Small Keys can appear inside any dungeon." + # - Own Region: "Small Keys will appear in their dungeon's region." + - Overworld: "Small Keys will only appear in the overworld." + - Anywhere: "Small Keys can appear anywhere." + - Keysy: "Small Keys will not appear anywhere in the world and their locks will start opened." + +- Name: Big Keys + Tracker Important: True + Default Option: Vanilla + Options: + - Vanilla: "Big Keys will appear in their vanilla locations." + - Own Dungeon: "Big Keys will appear inside their respective dungeon." + - Any Dungeon: "Big Keys can appear inside any dungeon." + # - Own Region: "Big Keys appear in their dungeon's region." + - Overworld: "Big Keys will only appear in the overworld." + - Anywhere: "Big Keys can appear anywhere." + - Keysy: "Big Keys will not appear anywhere in the world and boss doors will start opened." + +- Name: Maps and Compasses + Default Option: Vanilla + Options: + - Vanilla: "Maps and Compasses will appear in their vanilla locations." + - Own Dungeon: "Maps and Compasses can appear anywhere inside their respective dungeon." + - Any Dungeon: "Maps and Compasses can appear inside any dungeon." + # - Own Region: "Maps and Compasses will appear in their dungeon's region." + - Overworld: "Maps and Compasses will only appear in the overworld." + - Anywhere: "Maps and Compasses can appear anywhere." + - Start With: "The player starts with all Maps and Compasses." + +- Name: Dungeon Rewards Can Be Anywhere + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: No Small Keys on Bosses + Default Option: "Off" + Options: + - "Off": description + - "On": description + +# - Name: required_dungeons +# Default Option: 2 +# Pretty Name: Required Dungeons +# Pretty Options: +# - 0-7 +# Options: +# - 0-7: "Choose how many dungeons will have required items on their dungeon completion checks. Triforces, then swords, then other progress items are chosen, in this order." + +- Name: Unrequired Dungeons Are Barren + Tracker Important: True + Default Option: "On" + Options: + - "Off": "Unrequired dungeons may contain items needed to beat the game." + - "On": "Unrequired dungeons will be barren." + +###################### +## Timesavers ## +###################### + +- Name: Skip Prologue + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Faron Twilight Cleared + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Eldin Twilight Cleared + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Lanayru Twilight Cleared + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Skip Midna's Desparate Hour + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Skip Minor Cutscenes + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Skip Major Cutscenes + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Fast Iron Boots + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Quick Transform + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Instant Message Text + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Unlock Map Regions + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Increase Spinner Speed + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Open Door of Time + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +###################### +# Additional Settings# +###################### + +- Name: Starting Form + Tracker Important: True + Default Option: Human + Options: + - Human: Start as Human Link + - Wolf: Start as Wolf Link + +- Name: Transform Anywhere + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Increase Wallet Capacity + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Shops Display The Replaced Item + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Bonks Do Damage + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Trap Item Frequency + Default Option: None + Options: + - None: "All items in the game will be genuine." + - Few: "Approximately 12.5% of non-major items will be replaced with 'traps' that don't give the item they appear to be." + - Many: "Approximately 39.1% of non-major items will be replaced with 'traps' that don't give the item they appear to be." + - Mayhem: "Approximately 62.7% of non-major items will be replaced with 'traps' that don't give the item they appear to be." + - Nightmare: "All of the non-major items will be replaced with 'traps' that don't give the item they appear to be." + +- Name: Damage Multiplier + Default Option: Vanilla + Options: + - Vanilla: description + - Double: description + - Triple: description + - Quadruple: description + - OHKO: Any damage taken will kill link + +- Name: Starting Time of Day + Default Option: Noon + Options: + - Morning: description + - Noon: description + - Evening: description + - Night: description + +############################# +# Dungeon Entrance Settings # +############################# + +- Name: Lakebed Does Not Require Water Bombs + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Arbiters Does Not Require Bulblin Camp + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Snowpeak Does Not Require Reekfish Scent + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Sacred Grove Does Not Require Skull Kid + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: City Does Not Require Filled Skybook + Tracker Important: True + Default Option: "Off" + Options: + - "Off": description + - "On": description + +- Name: Goron Mines Entrance + Tracker Important: True + Default Option: Closed + Options: + - Closed: description + - No Wrestling: description + - Open: description + +- Name: Temple of Time Sword Requirement + Tracker Important: True + Default Option: None + Options: + - None: description + - Wooden Sword: description + - Ordon Sword: description + - Master Sword: description + - Light Sword: description + +# - Name: chest_type_matches_contents +# Default Option: "Off" +# Pretty Name: Chest Type Matches Contents +# Pretty Options: +# - "Off" +# - "Only Dungeon Items" +# - "All Contents" +# Options: +# - "Off": "Each chest will have the same type as it would in the vanilla game." +# - "only_dungeon_items": "Each chest will have the same type as it would in the vanilla game, unless it contains a dungeon item. If a chest contains a Boss Key, it will appear as a fancy chest. If it contains a Small Key, it will appear as a big blue chest. If it contains a Map, it will appear as a brown, wooden chest." +# - "all_contents": "Each chest will have a different type based on the item inside. Important items will appear in big blue chests. Big Keys will appear in fancy chests. Other items will appear in brown, wooden chests." + +# - Name: small_keys_in_fancy_chests +# Default Option: "Off" +# Pretty Name: Small Keys in Fancy Chests +# Pretty Options: +# - "Off" +# - "On" +# Options: +# - "Off": "When Chest Type Matches Contents is not off, Small Keys will appear in big blue chests." +# - "On": "When Chest Type Matches Contents is not off, Small Keys will appear in fancy chests." + +# - Name: Path Hints +# Default Option: 3 +# Options: +# - 0-7: "Select the number of path hints you would like to generate." + +# - Name: barren_hints +# Default Option: 2 +# Pretty Name: Barren Hints +# Pretty Options: +# - 0-7 +# Options: +# - 0-7: "Select the number of barren hints you would like to generate." + +# - Name: location_hints +# Default Option: 7 # assumes always_hints is on +# Pretty Name: Location Hints +# Pretty Options: +# - 0-7 +# Options: +# - 0-7: "Select the number of location hints you would like to generate." + +# - Name: item_hints +# Default Option: 3 +# Pretty Name: Item Hints +# Pretty Options: +# - 0-7 +# Options: +# - 0-7: "Select the number of item hints you would like to generate." + +# - Name: gossip_stone_hints +# Default Option: "Off" +# Pretty Name: Gossip Stone Hints +# Pretty Options: +# - "Off" +# - "On" +# Options: +# - "Off": "Hints will not be placed on Gossip Stones." +# - "On": "Hints will be placed on Gossip Stones and can be seen by talking to them." + +# - Name: cryptic_hint_text +# Default Option: "Off" +# Pretty Name: Cryptic Hint Text +# Pretty Options: +# - "Off" +# - "On" +# Options: +# - "Off": "Item and location hints will use the normal, clear Names for their items and locations." +# - "On": "Item and location hints will use cryptic text for their items and locations." + +# - Name: always_hints +# Default Option: "On" +# Pretty Name: Prioritize Remote Location Hints +# Pretty Options: +# - "Off" +# - "On" +# Options: +# - "Off": "All locations will be treated equally for location hints." +# - "On": "Certain locations which are time-consuming to check will be given priority for location hints." + +- Name: Randomize Starting Spawn + Default Option: "Off" + Options: + - "Off": "Link will start outside his house." + - "On": "Link will start in a random area." + +- Name: Randomize Dungeon Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a dungeon will lead to the vanilla dungeon." + - "On": "Entering a dungeon will lead to a random dungeon." + - "On + Hyrule Castle": "Entering a dungeon will lead to a random dungeon. Hyrule Castle's entrance will be shuffled as well." + +- Name: Randomize Boss Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a boss door will lead to the intended boss." + - "On": "Entering a boss door will lead to a random boss." + +- Name: Randomize Grotto Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a grotto will lead to the intended grotto." + - "On": "Entering a grotto will lead to a random grotto." + +- Name: Randomize Cave Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a door or loadzone that leads to a cave area will lead to the intended cave." + - "On": "Entering a door or loadzone that leads to a cave area will lead to a random cave." + +- Name: Randomize Interior Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a door or loadzone that leads to an interior area will lead to the intended area." + - "On": "Entering a door or loadzone that leads to an interior area will lead to a random intended area." + +- Name: Randomize Overworld Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a loadzone that leads to an overworld area will lead to the intended area." + - "On": "Entering a loadzone that leads to an overworld area will lead to a random area." + +- Name: Decouple Double Door Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a left door will lead to the same location as the corresponding right door." + - "On": "Entering a left door may lead somewhere different than the corresponding right door (only if the door's type is randomized)." + +- Name: Decouple Entrances + Tracker Important: True + Default Option: "Off" + Options: + - "Off": "Entering a location and taking the entrance behind you will take you back to where you came from." + - "On": "Entering a location and taking the entrance behind you will take you to a random location." + + + +# - Name: random_bottle_contents +# Default Option: "Off" +# Pretty Name: Random Bottle Contents +# Pretty Options: +# - "Off" +# - "On" +# Options: +# - "Off": "There will be 3 Empty Bottles, 1 Revitalizing Potion, and 1 Bottle of Mushroom Spores in the item pool." +# - "On": "There will be 5 Bottles in the item pool - each with random contents (e.g. Heart Potion, Hot Pumpkin Soup, Sacred Water, etc...)." + + + +# - Name: trappable_items +# Default Option: "major_items" +# Pretty Name: Trappable Items +# Pretty Options: +# - "Major Items" +# - "Non Major Items" +# - "Any Items" +# Options: +# - "major_items": "Traps can mimic any major item that could be necessary to beat the game." +# - "non_major_items": "Traps can only mimic non-major items that cannot be necessary to beat the game." +# - "any_items": "Traps can mimic any item." + + + +######################## +## Preference Options ## +######################## + +# - Name: language +# type: Preference +# Default Option: english_us +# Pretty Name: In-Game Language +# Pretty Options: +# # - Chinese +# - Dutch +# - English (GB) +# - English (US) +# - French (FR) +# - French (US) +# - German +# - Italian +# # - Japanese +# # - Korean +# # - Russian +# - Spanish (ES) +# - Spanish (US) +# # - Taiwanese +# Options: +# # - chinese: "Status: Broken. The randomized game will use the Chinese language files." +# - dutch: "Status: Incomplete. The randomized game will use the Dutch language files." +# - english_gb: "Status: Complete. The randomized game will use the British English language files." +# - english_us: "Status: Complete. The randomized game will use the American English language files." +# - french_fr: "Status: Incomplete. The randomized game will use the French language files." +# - french_us: "Status: Incomplete. The randomized game will use the French language files." +# - german: "Status: Incomplete. The randomized game will use the German language files." +# - italian: "Status: Incomplete. The randomized game will use the Italian language files." +# # - japanese: "Status: Broken. The randomized game will use the Japanese language files." +# # - korean: "Status: Broken. The randomized game will use the Korean language files." +# # - russian: "Status: Broken. The randomized game will use the Russian language files." +# - spanish_es: "Status: Incomplete. The randomized game will use the Spanish language files." +# - spanish_us: "Status: Incomplete. The randomized game will use the Spanish language files." +# # - taiwanese: "Status: Broken. The randomized game will use the Taiwanese language files." + +# - Name: tunic_swap +# type: Preference +# Default Option: "Off" +# Pretty Name: Tunic Swap +# Pretty Options: +# - "Off" +# - "On" +# Options: +# - "Off": "Link will be dressed in his green knight's uniform." +# - "On": "Link will be dressed in his Skyloft outfit seen at the start of the vanilla game." + +- Name: Remove Enemy Music + Type: Preference + Default Option: "Off" + Options: + - "Off": "The background music will be interrupted by enemy drums when you get near to an enemy. Additionally, the intense music when Scaldera and Tentalus are vulnerable will play as normal." + - "On": "The background music will continue to play when you get near to enemies. Additionally, the intense music when Scaldera and Tentalus are vulnerable will not play and the background music will continue uninterrupted." + +# - Name: low_health_beeping_speed +# type: Preference +# Default Option: normal +# Pretty Name: Low Health Beeping Speed +# Pretty Options: +# - Very Fast +# - Fast +# - Normal +# - Slow +# - Very Slow +# - No Beeping +# - No Beeping or Flashing +# Options: +# - very_fast: "The game will play the low health beep sound every time Link flashes red." +# - fast: "The game will play the low health beep sound every other time Link flashes red." +# - normal: "The game will play the low health beep sound every 3rd time Link flashes red." +# - slow: "The game will play the low health beep sound every 6th time Link flashes red." +# - very_slow: "The game will play the low health beep sound every 12th time Link flashes red." +# - no_beeping: "The game will not play the low health beep sound. Link will still flash red." +# - no_beeping_or_flashing: "The game will not play the low health beep sound. Link will not flash red either." + +############### +## Shortcuts ## +############### + + + +############################# +## Logic settings (tricks) ## +############################# + +- Name: Back Slice as Sword + Default Option: "Off" + Options: + - "Off": "Back Slicing without a sword can be logically required to do damage." + - "On": "Back Slicing without a sword can be logically required to do damage." + +- Name: Ball and Chain Webs + Default Option: "Off" + Options: + - "Off": "Ball and Chain will not be required to break webs." + - "On": "Ball and Chain may be required to break webs." + +####################################### +## Extra Starting Inventory Settings ## +####################################### + +# - Name: Random Starting Item Count +# Default Option: 0 +# Options: +# - 0-7: "Select the number of randomly chosen items you would like to start with. Only a selection of the most useful items are able to be chosen to maximize the benefit of this setting." + +# - Name: Starting Hearts +# Default Option: 3 +# Options: +# - 3-20: "Select the number of hearts you want to start with." diff --git a/src/dusk/randomizer/data/tests/logic/all random/settings.yaml b/src/dusk/randomizer/data/tests/logic/all random/settings.yaml new file mode 100644 index 0000000000..cd3035b8e2 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/all random/settings.yaml @@ -0,0 +1,66 @@ +Seed: TESTTESTTEST +Plandomizer: false +Generate Spoiler Log: true +Arbiters Does Not Require Bulblin Camp: Random +Back Slice as Sword: Random +Ball and Chain Webs: Random +Big Keys: Random +Bonks Do Damage: Random +City Does Not Require Filled Skybook: Random +Damage Multiplier: Random +Dungeon Rewards Can Be Anywhere: Random +Eldin Twilight Cleared: Random +Faron Twilight Cleared: Random +Faron Woods Logic: Random +Fast Iron Boots: Random +Gifts From NPCs: Random +Golden Bugs: Random +Goron Mines Entrance: Random +Hidden Skills: Random +Hyrule Castle Requirements: Random +Increase Spinner Speed: Random +Increase Wallet Capacity: Random +Instant Message Text: Random +Item Scarcity: Random +Lakebed Does Not Require Water Bombs: Random +Lanayru Twilight Cleared: Random +Logic Rules: Random +Maps and Compasses: Random +No Small Keys on Bosses: Random +Open Door of Time: Random +Palace of Twilight Requirements: Random +Poe Souls: Random +Quick Transform: Random +Random Starting Item Count: 0 +Randomize Starting Spawn: Random +Randomize Dungeon Entrances: Random +Randomize Boss Entrances: Random +Randomize Grotto Entrances: Random +Randomize Cave Entrances: Random +Randomize Interior Entrances: Random +Randomize Overworld Entrances: Random +Decouple Double Door Entrances: Random +Decouple Entrances: Random +Sacred Grove Does Not Require Skull Kid: Random +Shop Items: Random +Shops Display The Replaced Item: Random +Skip Major Cutscenes: Random +Skip Midna's Desparate Hour: Random +Skip Minor Cutscenes: Random +Skip Prologue: Random +Sky Characters: Random +Small Keys: Random +Snowpeak Does Not Require Reekfish Scent: Random +Starting Form: Random +Starting Hearts: 3 +Starting Time of Day: Random +Temple of Time Sword Requirement: Random +Transform Anywhere: Random +Trap Item Frequency: Random +Unlock Map Regions: Random +Unrequired Dungeons Are Barren: Random +trappable_items: major_items +Starting Inventory: + {} +Excluded Locations: + [] \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/any dungeon items/settings.yaml b/src/dusk/randomizer/data/tests/logic/any dungeon items/settings.yaml new file mode 100644 index 0000000000..aec28c687e --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/any dungeon items/settings.yaml @@ -0,0 +1,4 @@ +Seed: TESTTESTTEST +Small Keys: Any Dungeon +Big Keys: Any Dungeon +Maps and Compasses: Any Dungeon \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/anywhere dungeon items/settings.yaml b/src/dusk/randomizer/data/tests/logic/anywhere dungeon items/settings.yaml new file mode 100644 index 0000000000..7a0c1f658b --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/anywhere dungeon items/settings.yaml @@ -0,0 +1,4 @@ +Seed: TESTTESTTEST +Small Keys: Anywhere +Big Keys: Anywhere +Maps and Compasses: Anywhere \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/anywhere dungeon rewards/settings.yaml b/src/dusk/randomizer/data/tests/logic/anywhere dungeon rewards/settings.yaml new file mode 100644 index 0000000000..847ce9dd1d --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/anywhere dungeon rewards/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Dungeon Rewards Can Be Anywhere: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/bonko/settings.yaml b/src/dusk/randomizer/data/tests/logic/bonko/settings.yaml new file mode 100644 index 0000000000..173cd5e7f4 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/bonko/settings.yaml @@ -0,0 +1,5 @@ +Seed: TESTTESTTEST +Bonks Do Damage: On +Damage Multiplier: OHKO +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/cleared eldin twilight/settings.yaml b/src/dusk/randomizer/data/tests/logic/cleared eldin twilight/settings.yaml new file mode 100644 index 0000000000..e7210fc32c --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/cleared eldin twilight/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Eldin Twilight Cleared: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/cleared lanayru twilight/settings.yaml b/src/dusk/randomizer/data/tests/logic/cleared lanayru twilight/settings.yaml new file mode 100644 index 0000000000..c7df62930d --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/cleared lanayru twilight/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Lanayru Twilight Cleared: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/default/settings.yaml b/src/dusk/randomizer/data/tests/logic/default/settings.yaml new file mode 100644 index 0000000000..c94c60212a --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/default/settings.yaml @@ -0,0 +1 @@ +Seed: TESTTESTTEST \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/faron twilight cleared/settings.yaml b/src/dusk/randomizer/data/tests/logic/faron twilight cleared/settings.yaml new file mode 100644 index 0000000000..9a59c656ea --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/faron twilight cleared/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Faron Twilight Cleared: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/gifts from npcs/settings.yaml b/src/dusk/randomizer/data/tests/logic/gifts from npcs/settings.yaml new file mode 100644 index 0000000000..d09610d619 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/gifts from npcs/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Gifts From NPCs: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/golden bugs/settings.yaml b/src/dusk/randomizer/data/tests/logic/golden bugs/settings.yaml new file mode 100644 index 0000000000..9b8cac5311 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/golden bugs/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Golden Bugs: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hidden skills/settings.yaml b/src/dusk/randomizer/data/tests/logic/hidden skills/settings.yaml new file mode 100644 index 0000000000..1b8d67719e --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hidden skills/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hidden Skills: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml new file mode 100644 index 0000000000..f49d10949c --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Castle Requirements: All Dungeons \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml new file mode 100644 index 0000000000..fa53bccd12 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Castle Requirements: Fused Shadows \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml new file mode 100644 index 0000000000..983a271e6b --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Castle Requirements: Mirror Shards \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml new file mode 100644 index 0000000000..c5c79d9e92 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Castle Requirements: Open \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/keysy/settings.yaml b/src/dusk/randomizer/data/tests/logic/keysy/settings.yaml new file mode 100644 index 0000000000..5ccf688d7b --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/keysy/settings.yaml @@ -0,0 +1,4 @@ +Seed: TESTTESTTEST +Small Keys: Keysy +Big Keys: Keysy +Maps and Compasses: Start With \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/max entrance rando/settings.yaml b/src/dusk/randomizer/data/tests/logic/max entrance rando/settings.yaml new file mode 100644 index 0000000000..c95a0803c5 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/max entrance rando/settings.yaml @@ -0,0 +1,16 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open +Skip Prologue: On +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Skip Midna's Desparate Hour: On +Randomize Starting Spawn: On +Randomize Dungeon Entrances: On +Randomize Boss Entrances: On +Randomize Grotto Entrances: On +Randomize Cave Entrances: On +Randomize Interior Entrances: On +Randomize Overworld Entrances: On +Decouple Double Door Entrances: On +Decouple Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/mixed entrance pools/settings.yaml b/src/dusk/randomizer/data/tests/logic/mixed entrance pools/settings.yaml new file mode 100644 index 0000000000..77babdaf99 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/mixed entrance pools/settings.yaml @@ -0,0 +1,15 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open +Skip Prologue: On +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Skip Midna's Desparate Hour: On +Randomize Starting Spawn: On +Randomize Dungeon Entrances: On +Randomize Grotto Entrances: On +Randomize Cave Entrances: On +Randomize Interior Entrances: On +Randomize Overworld Entrances: On +Mixed Entrance Pools: + [["Dungeon", "Grotto"], ["Overworld", "Cave", "Interior"]] \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/open forest/settings.yaml b/src/dusk/randomizer/data/tests/logic/open forest/settings.yaml new file mode 100644 index 0000000000..9684708bbe --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/open forest/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/open start/settings.yaml b/src/dusk/randomizer/data/tests/logic/open start/settings.yaml new file mode 100644 index 0000000000..1c93c6e244 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/open start/settings.yaml @@ -0,0 +1,7 @@ +Seed: TESTTESTTEST +Skip Prologue: On +Faron Woods Logic: Open +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Unlock Map Regions: On diff --git a/src/dusk/randomizer/data/tests/logic/overworld items/settings.yaml b/src/dusk/randomizer/data/tests/logic/overworld items/settings.yaml new file mode 100644 index 0000000000..25f6cb1269 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/overworld items/settings.yaml @@ -0,0 +1,4 @@ +Seed: TESTTESTTEST +Small Keys: Overworld +Big Keys: Overworld +Maps and Compasses: Overworld \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/own dungeon items/settings.yaml b/src/dusk/randomizer/data/tests/logic/own dungeon items/settings.yaml new file mode 100644 index 0000000000..5446c402b4 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/own dungeon items/settings.yaml @@ -0,0 +1,4 @@ +Seed: TESTTESTTEST +Small Keys: Own Dungeon +Big Keys: Own Dungeon +Maps and Compasses: Own Dungeon \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/palace fused shadows/settings.yaml b/src/dusk/randomizer/data/tests/logic/palace fused shadows/settings.yaml new file mode 100644 index 0000000000..43c7ddcaa8 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/palace fused shadows/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Palace of Twilight Requirements: Fused Shadows \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/palace mirror shards/settings.yaml b/src/dusk/randomizer/data/tests/logic/palace mirror shards/settings.yaml new file mode 100644 index 0000000000..5bfe56e251 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/palace mirror shards/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Palace of Twilight Requirements: Mirror Shards \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/palace open/settings.yaml b/src/dusk/randomizer/data/tests/logic/palace open/settings.yaml new file mode 100644 index 0000000000..4a2f81e0dd --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/palace open/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Palace of Twilight Requirements: Open \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/poe souls all/settings.yaml b/src/dusk/randomizer/data/tests/logic/poe souls all/settings.yaml new file mode 100644 index 0000000000..47999cb3a1 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/poe souls all/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Poe Souls: All \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/poe souls dungeon/settings.yaml b/src/dusk/randomizer/data/tests/logic/poe souls dungeon/settings.yaml new file mode 100644 index 0000000000..8ca225b4b2 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/poe souls dungeon/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Poe Souls: Dungeon \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/poe souls overworld/settings.yaml b/src/dusk/randomizer/data/tests/logic/poe souls overworld/settings.yaml new file mode 100644 index 0000000000..27b7142f46 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/poe souls overworld/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Poe Souls: Overworld \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/random boss entrances/settings.yaml b/src/dusk/randomizer/data/tests/logic/random boss entrances/settings.yaml new file mode 100644 index 0000000000..d16615de50 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/random boss entrances/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Randomize Boss Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/random cave entrances/settings.yaml b/src/dusk/randomizer/data/tests/logic/random cave entrances/settings.yaml new file mode 100644 index 0000000000..5f51f783c7 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/random cave entrances/settings.yaml @@ -0,0 +1,8 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open +Skip Prologue: On +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Skip Midna's Desparate Hour: On +Randomize Cave Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/random dungeon entrances/settings.yaml b/src/dusk/randomizer/data/tests/logic/random dungeon entrances/settings.yaml new file mode 100644 index 0000000000..32fabb63a9 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/random dungeon entrances/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Randomize Dungeon Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/random grotto entrances/settings.yaml b/src/dusk/randomizer/data/tests/logic/random grotto entrances/settings.yaml new file mode 100644 index 0000000000..792f886bef --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/random grotto entrances/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Randomize Grotto Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/random interior entrances/settings.yaml b/src/dusk/randomizer/data/tests/logic/random interior entrances/settings.yaml new file mode 100644 index 0000000000..6510d279bf --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/random interior entrances/settings.yaml @@ -0,0 +1,8 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open +Skip Prologue: On +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Skip Midna's Desparate Hour: On +Randomize Interior Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/random overworld entrances/settings.yaml b/src/dusk/randomizer/data/tests/logic/random overworld entrances/settings.yaml new file mode 100644 index 0000000000..a018903bf5 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/random overworld entrances/settings.yaml @@ -0,0 +1,8 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open +Skip Prologue: On +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Skip Midna's Desparate Hour: On +Randomize Overworld Entrances: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/randomize starting spawn/settings.yaml b/src/dusk/randomizer/data/tests/logic/randomize starting spawn/settings.yaml new file mode 100644 index 0000000000..27784d7b97 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/randomize starting spawn/settings.yaml @@ -0,0 +1,8 @@ +Seed: TESTTESTTEST +Faron Woods Logic: Open +Skip Prologue: On +Faron Twilight Cleared: On +Eldin Twilight Cleared: On +Lanayru Twilight Cleared: On +Skip Midna's Desparate Hour: On +Randomize Starting Spawn: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/scarcity minimal/settings.yaml b/src/dusk/randomizer/data/tests/logic/scarcity minimal/settings.yaml new file mode 100644 index 0000000000..c5de27cabd --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/scarcity minimal/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Item Scarcity: Minimal \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/scarcity plentiful/settings.yaml b/src/dusk/randomizer/data/tests/logic/scarcity plentiful/settings.yaml new file mode 100644 index 0000000000..96e1309d08 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/scarcity plentiful/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Item Scarcity: Plentiful \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/shop items/settings.yaml b/src/dusk/randomizer/data/tests/logic/shop items/settings.yaml new file mode 100644 index 0000000000..bfc793d443 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/shop items/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Shop Items: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/skip dungeon entrance requirements/settings.yaml b/src/dusk/randomizer/data/tests/logic/skip dungeon entrance requirements/settings.yaml new file mode 100644 index 0000000000..3ec52cb217 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/skip dungeon entrance requirements/settings.yaml @@ -0,0 +1,8 @@ +Seed: TESTTESTTEST +Lakebed Does Not Require Water Bombs: On +Arbiters Does Not Require Bulblin Camp: On +Snowpeak Does Not Require Reekfish Scent: On +Sacred Grove Does Not Require Skull Kid: On +City Does Not Require Filled Skybook: On +Goron Mines Entrance: On +Temple of Time Sword Requirement: None \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/skip mdh/settings.yaml b/src/dusk/randomizer/data/tests/logic/skip mdh/settings.yaml new file mode 100644 index 0000000000..d08be69c12 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/skip mdh/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Skip Midna's Desparate Hour: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/skip prologue/settings.yaml b/src/dusk/randomizer/data/tests/logic/skip prologue/settings.yaml new file mode 100644 index 0000000000..4600a7d811 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/skip prologue/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Skip Prologue: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/sky characters/settings.yaml b/src/dusk/randomizer/data/tests/logic/sky characters/settings.yaml new file mode 100644 index 0000000000..e840960681 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/sky characters/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Sky Characters: On \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml b/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml new file mode 100644 index 0000000000..a0980349e1 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml @@ -0,0 +1,3 @@ +Seed: TESTTESTTEST +Unrequired Dungeons Are Barren: On +Hyrule Castle Requirements: Mirror Shards \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/wolf start/settings.yaml b/src/dusk/randomizer/data/tests/logic/wolf start/settings.yaml new file mode 100644 index 0000000000..c509bd8e43 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/wolf start/settings.yaml @@ -0,0 +1,4 @@ +Seed: TESTTESTTEST +Starting Form: Wolf +Skip Prologue: On +Faron Woods Logic: Open \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/Root.yaml b/src/dusk/randomizer/data/world/Root.yaml new file mode 100644 index 0000000000..9854d72d00 --- /dev/null +++ b/src/dusk/randomizer/data/world/Root.yaml @@ -0,0 +1,163 @@ +# These logic files contain the listings for each predefined logical area. Areas can contain +# events, locations, and exits. +# +# EVENTS are logic variables which become true when their requirement evaluates +# to true and are surrounded with single quotes when used in other logic statements +# (unlike macros which are defined in macros.yaml). It's possible to add new events +# to the logic simply by defining them in an area and then using them in whichever +# logic statements you wish. No modifications to the code are necessary. Every logical +# area that gets defined will also come with an automatically generated 'Can_Access_' +# event. This event is added to the area and has no requirements. If you start a logic statement +# with an event, then the entire statement must be surrounded in double quotes due to how yaml +# is parsed. +# +# LOCATIONS are places which can contain randomized items. Adding new locations +# here will require adding them in locations.yaml as well. +# +# EXITS define how the areas of the world graph connect to each other. Each exit +# is a one way connection, so when making new areas remember to define the exit +# connections both ways if applicable. No modifications to the code are necessary +# for adding new exits. +# +# Each dungeon in the world graph is at least defined on a room by room basis +# (although in many cases rooms are split up into multiple parts). This is to +# help simplify glitched logic (eventually). +# +# NOTE: When writing direct logic requirements for exits, do not write a logic +# statement that requires both human link *and* wolf link to evaluate to true. +# The search and flatten algorithms need to keep track of human-wolf/day-night +# combinations separately and test them one at a time to know which forms +# are allowed through any single exit. This doesn't work if both forms are +# directly required. If you need to write a logic statement for an exit that +# absolutely requires both human and wolf link, then hide away one (or both) +# of the forms behind an event in the area. +# +# See the "Lost Woods Lower Battle Arena -> Lost Woods Baba Serpent Grotto" exit +# for an example of the above. + +# The following logical operators/functions are available for logic statements: +# - and +# - or +# - +# - Human_Link +# - Wolf_Link +# - Day +# - Night +# - count(, ) +# - setting_name [<=, ==, >=, !=] option +# - golden_bugs(count) + +- Name: Root + Exits: + Root Human Day: (Human_Link and (Starting_Form == Human or Shadow_Crystal)) and (Starting_Time_of_Day == Morning or Starting_Time_of_Day == Noon) + Root Human Night: (Human_Link and (Starting_Form == Human or Shadow_Crystal)) and (Starting_Time_of_Day == Evening or Starting_Time_of_Day == Night) + Root Wolf Day: (Wolf_Link and (Starting_Form == Wolf or Shadow_Crystal)) and (Starting_Time_of_Day == Morning or Starting_Time_of_Day == Noon) + Root Wolf Night: (Wolf_Link and (Starting_Form == Wolf or Shadow_Crystal)) and (Starting_Time_of_Day == Evening or Starting_Time_of_Day == Night) + +- Name: Root Human Day + Can Transform: Never + Exits: + Root Exits: Nothing + +- Name: Root Human Night + Can Transform: Never + Exits: + Root Exits: Nothing + +- Name: Root Wolf Day + Can Transform: Never + Exits: + Root Exits: Nothing + +- Name: Root Wolf Night + Can Transform: Never + Exits: + Root Exits: Nothing + +- Name: Root Exits + Can Transform: Never + Exits: + Links Spawn: Nothing + Warp Portals: Can_Use_Warp_Portals + +- Name: Links Spawn + Exits: + Outside Links House: Nothing + +- Name: Warp Portals + Exits: + Ordon Spring Warp Portal: Ordon_Spring_Warp_Portal and (Unlock_Map_Regions == On or 'Ordona_Province_Map_Sector') + South Faron Woods Warp Portal: South_Faron_Woods_Warp_Portal and (Unlock_Map_Regions == On or 'Faron_Province_Map_Sector') + North Faron Woods Warp Portal: North_Faron_Woods_Warp_Portal and (Unlock_Map_Regions == On or 'Faron_Province_Map_Sector') + Sacred Grove Warp Portal: Sacred_Grove_Warp_Portal and (Unlock_Map_Regions == On or 'Faron_Province_Map_Sector') + Kakariko Gorge Warp Portal: Kakariko_Gorge_Warp_Portal and (Unlock_Map_Regions == On or 'Eldin_Province_Map_Sector') + Kakariko Village Warp Portal: Kakariko_Village_Warp_Portal and (Unlock_Map_Regions == On or 'Eldin_Province_Map_Sector') + Death Mountain Warp Portal: Death_Mountain_Warp_Portal and (Unlock_Map_Regions == On or 'Eldin_Province_Map_Sector') + Bridge of Eldin Warp Portal: Bridge_of_Eldin_Warp_Portal and (Unlock_Map_Regions == On or 'Eldin_Province_Map_Sector') + Castle Town Warp Portal: Castle_Town_Warp_Portal and (Unlock_Map_Regions == On or 'Lanayru_Province_Map_Sector') + Lake Hylia Warp Portal: Lake_Hylia_Warp_Portal and (Unlock_Map_Regions == On or 'Lanayru_Province_Map_Sector') + Upper Zoras River Warp Portal: Upper_Zoras_River_Warp_Portal and (Unlock_Map_Regions == On or 'Lanayru_Province_Map_Sector') + Zoras Domain Warp Portal: Zoras_Domain_Warp_Portal and (Unlock_Map_Regions == On or 'Lanayru_Province_Map_Sector') + Snowpeak Warp Portal: Snowpeak_Warp_Portal and (Unlock_Map_Regions == On or 'Snowpeak_Province_Map_Sector') + Gerudo Desert Warp Portal: Gerudo_Desert_Warp_Portal and 'Desert_Province_Map_Sector' + Mirror Chamber Warp Portal: Mirror_Chamber_Warp_Portal and 'Desert_Province_Map_Sector' + +- Name: Ordon Spring Warp Portal + Exits: + Ordon Spring: Nothing + +- Name: South Faron Woods Warp Portal + Exits: + South Faron Woods: Nothing + +- Name: North Faron Woods Warp Portal + Exits: + North Faron Woods: Nothing + +- Name: Sacred Grove Warp Portal + Exits: + Sacred Grove Lower: Nothing + +- Name: Kakariko Gorge Warp Portal + Exits: + Kakariko Gorge: Nothing + +- Name: Kakariko Village Warp Portal + Exits: + Lower Kakariko Village: Nothing + +- Name: Death Mountain Warp Portal + Exits: + Death Mountain Volcano: Nothing + +- Name: Bridge of Eldin Warp Portal + Exits: + Eldin Field North of Bridge: Nothing + +- Name: Castle Town Warp Portal + Exits: + Outside Castle Town West: Nothing + +- Name: Lake Hylia Warp Portal + Exits: + Lake Hylia: Nothing + +- Name: Upper Zoras River Warp Portal + Exits: + Upper Zoras River: Nothing + +- Name: Zoras Domain Warp Portal + Exits: + Zoras Throne Room: Nothing + +- Name: Snowpeak Warp Portal + Exits: + Snowpeak Summit Upper: Nothing + +- Name: Gerudo Desert Warp Portal + Exits: + Gerudo Desert Cave of Ordeals Plateau: Nothing + +- Name: Mirror Chamber Warp Portal + Exits: + Mirror Chamber Upper: Nothing \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Arbiters Grounds.yaml b/src/dusk/randomizer/data/world/dungeons/Arbiters Grounds.yaml new file mode 100644 index 0000000000..553287c81a --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Arbiters Grounds.yaml @@ -0,0 +1,347 @@ + +# ARBITERS GROUNDS ENTRANCE ROOM + +- Name: Arbiters Grounds Entrance + Region: Arbiters Grounds + Dungeon Start Area: True + Events: + Can Pull Arbiters Entrance Chain: Clawshot or Can_Cross_Quicksand + Exits: + Arbiters Grounds Entrance Past Gate: "'Can_Pull_Arbiters_Entrance_Chain'" + Outside Arbiters Grounds: Nothing + +- Name: Arbiters Grounds Entrance Past Gate + Region: Arbiters Grounds + Events: + Can Refill Lantern Oil: Nothing + Locations: + Arbiters Grounds Entrance Chest: Can_Break_Wooden_Barrier + Exits: + Arbiters Grounds Dark Lantern Room: Arbiters_Grounds_Small_Key or Small_Keys == Keysy + Arbiters Grounds Entrance: "'Can_Pull_Arbiters_Entrance_Chain'" + +# ARBITERS GROUNDS DARK LANTERN ROOM + +- Name: Arbiters Grounds Dark Lantern Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Torch Room: Lantern + Arbiters Grounds Entrance Past Gate: Nothing + +# ARBITERS GROUNDS TORCH ROOM + +- Name: Arbiters Grounds Torch Room + Region: Arbiters Grounds + Events: + Poe Scent: Can_Use_Senses and Can_Sniff + Can Pull Torch Room Chain: "'Poe_Scent'" + Arbiters Grounds Poe 1: Can_Use_Senses + Locations: + Arbiters Grounds Torch Room East Chest: Nothing + Arbiters Grounds Torch Room West Chest: Nothing + Arbiters Grounds Torch Room Poe: Can_Use_Senses + Arbiters Grounds Hint Sign: Nothing + Exits: + Arbiters Grounds East Turnable Room Middle: Nothing + Arbiters Grounds Torch Room Near East Turnable Room Lower: "'Can_Pull_Torch_Room_Chain'" + Arbiters Grounds West Chandelier Room Near Lower Torch Room: Nothing + Arbiters Grounds Socket Room Near Torch Room: "'Arbiters_Grounds_Poe_1' and 'Arbiters_Grounds_Poe_2' and 'Arbiters_Grounds_Poe_3' and 'Arbiters_Grounds_Poe_4'" + +- Name: Arbiters Grounds Torch Room Near East Turnable Room Lower + Region: Arbiters Grounds + Exits: + Arbiters Grounds East Turnable Room Lower: Nothing + Arbiters Grounds Torch Room: "'Can_Pull_Torch_Room_Chain'" + +- Name: Arbiters Grounds Torch Room Chandelier + Region: Arbiters Grounds + Exits: + Arbiters Grounds Torch Room: Nothing + Arbiters Grounds Ghoul Rat Room: Nothing + Arbiters Grounds West Chandelier Room: Nothing + +# ARBITERS GROUNDS EAST TURNABLE ROOM + +- Name: Arbiters Grounds East Turnable Room Middle + Region: Arbiters Grounds + Exits: + Arbiters Grounds East Chandelier Room Near Turnable Room: count(Arbiters_Grounds_Small_Key, 2) or Small_Keys == Keysy + Arbiters Grounds Torch Room: Nothing + +- Name: Arbiters Grounds East Turnable Room Lower + Region: Arbiters Grounds + Locations: + Arbiters Grounds East Lower Turnable Redead Chest: Nothing + Exits: + Arbiters Grounds Poe 2 Room: Can_Defeat_Redead_Knight and Clawshot + Arbiters Grounds Torch Room Near East Turnable Room Lower: Nothing + +# ARBITERS GROUNDS POE 2 ROOM + +- Name: Arbiters Grounds Poe 2 Room + Region: Arbiters Grounds + Events: + Arbiters Grounds Poe 2: Can_Use_Senses + Locations: + Arbiters Grounds East Turning Room Poe: Can_Use_Senses + +# ARBITERS GROUNDS EAST CHANDELIER ROOM + +- Name: Arbiters Grounds East Chandelier Room Near Turnable Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds East Chandelier Room Past Chandelier: Can_Pull_Blocks + Arbiters Grounds East Turnable Room Middle: Nothing + +- Name: Arbiters Grounds East Chandelier Room Past Chandelier + Region: Arbiters Grounds + Locations: + Arbiters Grounds East Upper Turnable Chest: Nothing + Arbiters Grounds East Upper Turnable Redead Chest: Can_Break_Wooden_Barrier + Exits: + Arbiters Grounds Poe 3 Room Near East Chandelier Room: count(Arbiters_Grounds_Small_Key, 3) or Small_Keys == Keysy + Arbiters Grounds East Chandelier Room Near Turnable Room: Nothing + +# ARBITERS GROUNDS POE 3 ROOM + +- Name: Arbiters Grounds Poe 3 Room Near East Chandelier Room + Region: Arbiters Grounds + Events: + Arbiters Grounds Poe 3: Can_Use_Senses and Can_Defeat_Stalchild and Can_Defeat_Redead_Knight and 'Poe_Scent' + Locations: + Arbiters Grounds Hidden Wall Poe: Can_Use_Senses and Can_Defeat_Stalchild and Can_Defeat_Redead_Knight and 'Poe_Scent' + Exits: + Arbiters Grounds Poe 3 Room Near Ghoul Rat Room: Can_Defeat_Stalchild and Can_Defeat_Redead_Knight + Arbiters Grounds East Chandelier Room Past Chandelier: Impossible # Wall + +- Name: Arbiters Grounds Poe 3 Room Near Ghoul Rat Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Ghoul Rat Room: Nothing + Arbiters Grounds Poe 3 Room Near East Chandelier Room: Can_Defeat_Stalchild and Can_Defeat_Redead_Knight + +# ARBITERS GROUNDS GOUL RAT ROOM + +- Name: Arbiters Grounds Ghoul Rat Room + Region: Arbiters Grounds + Locations: + Arbiters Grounds Ghoul Rat Room Chest: Nothing + Exits: + Arbiters Grounds Torch Room Chandelier: count(Arbiters_Grounds_Small_Key, 4) or Small_Keys == Keysy + Arbiters Grounds Poe 3 Room Near Ghoul Rat Room: Nothing + +# ARBITERS GROUNDS WEST CHANDELIER ROOM + +- Name: Arbiters Grounds West Chandelier Room Near Lower Torch Room + Region: Arbiters Grounds + Locations: + Arbiters Grounds West Small Chest Behind Block: Nothing + Exits: + Arbiters Grounds West Chandelier Room: "'Can_Push_West_Chandelier_Room_Block'" + +- Name: Arbiters Grounds West Chandelier Room + Region: Arbiters Grounds + Events: + Can Push West Chandelier Room Block: Nothing + Locations: + Arbiters Grounds West Chandelier Chest: Nothing + Exits: + Arbiters Grounds West Chandelier Room Near Lower Torch Room: Nothing + Arbiters Grounds Single Stalfos Room Near West Chandelier Room: Nothing + +- Name: Arbiters Grounds West Chandelier Room High Platform + Region: Arbiters Grounds + Exits: + Arbiters Grounds Poe 4 Room: Nothing + Arbiters Grounds West Chandelier Room: Nothing + +# ARBITERS GROUNDS SINGLE STALFOS ROOM + +- Name: Arbiters Grounds Single Stalfos Room Near West Chandelier Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Single Stalfos Room Near Lantern Puzzle Room: Can_Break_Wooden_Barrier + Arbiters Grounds West Chandelier Room: Nothing + +- Name: Arbiters Grounds Single Stalfos Room Near Lantern Puzzle Room + Region: Arbiters Grounds + Locations: + Arbiters Grounds West Stalfos West Chest: Nothing + Arbiters Grounds West Stalfos Northeast Chest: Can_Break_Wooden_Barrier + Exits: + Arbiters Grounds Lantern Puzzle Room Near Single Stalfos Room: Can_Defeat_Stalfos + Arbiters Grounds Single Stalfos Room Near West Chandelier Room: Can_Break_Wooden_Barrier + +# ARBITERS GROUNDS LANTERN PUZZLE ROOM + +- Name: Arbiters Grounds Lantern Puzzle Room Near Single Stalfos Room + Region: Arbiters Grounds + Events: + Can Light Arbiters Grounds Lantern Puzzle: Lantern + Exits: + Arbiters Grounds Lantern Puzzle Room Near Poe 4 Room: "'Can_Light_Arbiters_Grounds_Lantern_Puzzle'" + Arbiters Grounds Single Stalfos Room Near Lantern Puzzle Room: Nothing + +- Name: Arbiters Grounds Lantern Puzzle Room Near Poe 4 Room + Exits: + Arbiters Grounds Poe 4 Room: Nothing + Arbiters Grounds Lantern Puzzle Room Near Single Stalfos Room: "'Can_Light_Arbiters_Grounds_Lantern_Puzzle'" + +# Arbiters GROUNDS POE 4 ROOM + +- Name: Arbiters Grounds Poe 4 Room + Region: Arbiters Grounds + Events: + Arbiters Grounds Poe 4: Can_Use_Senses + Locations: + Arbiters Grounds West Poe: Can_Use_Senses + Exits: + Arbiters Grounds West Chandelier Room High Platform: Nothing + Arbiters Grounds Lantern Puzzle Room Near Poe 4 Room: Nothing + +# ARBITERS GROUNDS SOCKET ROOM + +- Name: Arbiters Grounds Socket Room Near Torch Room + Region: Arbiters Grounds + Events: + Can Spin Turning Wall Socket: Spinner + Exits: + Arbiters Grounds Socket Room Near North Turning Room: Clawshot # If the wall is already turned + Arbiters Grounds Socket Room Bottom Tower: "'Can_Spin_Turning_Wall_Socket'" + Arbiters Grounds Socket Room Near Torch Room: Nothing + +- Name: Arbiters Grounds Socket Room Near North Turning Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds North Turning Room Near Socket Room: Nothing + # Can't assume access the other way because of the potential wall + +- Name: Arbiters Grounds Socket Room Near Spinner Room + Region: Arbiters Grounds + Locations: + Arbiters Grounds Big Key Chest: Nothing + Exits: + Arbiters Grounds Socket Room Near Torch Room: Spinner + Arbiters Grounds Spinner Room Near Socket Room: Nothing + +- Name: Arbiters Grounds Socket Room Bottom Tower + Region: Arbiters Grounds + Exits: + Arbiters Grounds Socket Room Near Boss Door: Spinner + Arbiters Grounds Socket Room Near Torch Room: "'Can_Spin_Turning_Wall_Socket'" + +- Name: Arbiters Grounds Socket Room Near Boss Door + Region: Arbiters Grounds + Exits: + Arbiters Grounds Boss Room: Arbiters_Grounds_Big_Key or Big_Keys == Keysy + Arbiters Grounds Socket Room Bottom Tower: Nothing + +# ARBITERS GROUNDS NORTH TURNING ROOM + +- Name: Arbiters Grounds North Turning Room Near Socket Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds North Turning Room Central Column: Nothing + Arbiters Grounds Socket Room Near North Turning Room: Nothing + +- Name: Arbiters Grounds North Turning Room Central Column + Region: Arbiters Grounds + Locations: + Arbiters Grounds North Turning Room Chest: Nothing + Exits: + Arbiters Grounds North Turning Room Near Basement Spike Room: Nothing + Arbiters Grounds North Turning Room Near Socket Room: Clawshot + +- Name: Arbiters Grounds North Turning Room Near Basement Spike Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Basement Spike Room Near North Turning Room: count(Arbiters_Grounds_Small_Key, 5) or Small_Keys == Keysy + +# ARBITERS GROUNDS BASEMENT SPIKE ROOM + +- Name: Arbiters Grounds Basement Spike Room Near North Turning Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Basement Spike Room Near Spinner Traps: Can_Defeat_Ghoul_Rat + Arbiters Grounds North Turning Room Near Basement Spike Room: count(Arbiters_Grounds_Small_Key, 5) or Small_Keys == Keysy + +- Name: Arbiters Grounds Basement Spike Room Near Spinner Traps + Region: Arbiters Grounds + Exits: + Arbiters Grounds Triple Stalfos Room Center: Nothing + +# ARBITERS GROUNDS TRIPLE STALFOS ROOM + +- Name: Arbiters Grounds Triple Stalfos Room Center + Region: Arbiters Grounds + Exits: + Arbiters Grounds Triple Stalfos Room Near Miniboss Room: Can_Defeat_Stalfos + Arbiters Grounds Triple Stalfos Room Near Spinner Room: Spinner + +- Name: Arbiters Grounds Triple Stalfos Room Near Miniboss Room + Region: Arbiters Grounds + Exits: + Deathsword Miniboss Room: Nothing + Arbiters Grounds Triple Stalfos Room Near Spinner Room: Spinner + Arbiters Grounds Triple Stalfos Room Center: Nothing + +- Name: Arbiters Grounds Triple Stalfos Room Near Spinner Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Spinner Room Bottom: Nothing + Arbiters Grounds Triple Stalfos Room Center: Spinner + Arbiters Grounds Triple Stalfos Room Near Miniboss Room: Spinner + +# DEATHSWORD MINIBOSS ROOM + +- Name: Deathsword Miniboss Room + Locations: + Arbiters Grounds Death Sword Chest: Can_Defeat_Deathsword + Exits: + Arbiters Grounds Triple Stalfos Room Near Miniboss Room: Can_Defeat_Deathsword + +# ARBITERS GROUNDS SPINNER ROOM + +- Name: Arbiters Grounds Spinner Room Bottom + Region: Arbiters Grounds + Locations: + Arbiters Grounds Spinner Room First Small Chest: Can_Cross_Quicksand + Arbiters Grounds Spinner Room Second Small Chest: Can_Cross_Quicksand + Arbiters Grounds Spinner Room Lower Central Small Chest: Can_Cross_Quicksand + Exits: + Arbiters Grounds Spinner Room Near Single Stalfos: Spinner + Arbiters Grounds Spinner Room Near Double Stalfos: Spinner + Arbiters Grounds Triple Stalfos Room Near Spinner Room: Can_Cross_Quicksand + +- Name: Arbiters Grounds Spinner Room Near Single Stalfos + Region: Arbiters Grounds + Locations: + Arbiters Grounds Spinner Room Stalfos Alcove Chest: Nothing + Exits: + Arbiters Grounds Spinner Room Bottom: Nothing + +- Name: Arbiters Grounds Spinner Room Near Double Stalfos + Region: Arbiters Grounds + Locations: + Arbiters Grounds Spinner Room Lower North Chest: Nothing + Exits: + Arbiters Grounds Spinner Room Near Socket Room: Spinner + Arbiters Grounds Spinner Room Near Single Stalfos: Nothing + Arbiters Grounds Spinner Room Bottom: Nothing + +- Name: Arbiters Grounds Spinner Room Near Socket Room + Region: Arbiters Grounds + Exits: + Arbiters Grounds Socket Room Near Spinner Room: Nothing + Arbiters Grounds Spinner Room Near Double Stalfos: Nothing + +# ARBITERS GROUNDS BOSS ROOM + +- Name: Arbiters Grounds Boss Room + Events: + Can Complete Arbiters Grounds: Can_Defeat_Stallord + Locations: + Arbiters Grounds Stallord Heart Container: Can_Defeat_Stallord + Arbiters Grounds Dungeon Reward: Can_Defeat_Stallord + Exits: + Mirror Chamber Lower: Can_Defeat_Stallord \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/City in the Sky.yaml b/src/dusk/randomizer/data/world/dungeons/City in the Sky.yaml new file mode 100644 index 0000000000..2d5350ce99 --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/City in the Sky.yaml @@ -0,0 +1,404 @@ +# CITY IN THE SKY ENTRANCE + +- Name: City in the Sky Entrance + Region: City in the Sky + Dungeon Start Area: True + Locations: + City in the Sky Underwater West Chest: Iron_Boots + City in the Sky Underwater East Chest: Iron_Boots + Exits: + Lake Hylia: Clawshot # Or Nothing after fall entrance is set + City in the Sky Oocca Hallway Near Entrance: Can_Hit_Crystal_Switch_at_Range + City in the Sky Shop: Nothing + +# CITY IN THE SKY SHOP + +- Name: City in the Sky Shop + Region: City in the Sky + Events: + Can Refill Bombs: "'Can_Farm_Rupees'" + Can Refill Arrows: "'Can_Farm_Rupees'" + Can Refill Lantern Oil: "'Can_Farm_Rupees'" + Exits: + City in the Sky Entrance: Nothing + +# CITY IN THE SKY OOCCA HALLWAY + +- Name: City in the Sky Oocca Hallway Near Entrance + Region: City in the Sky + Exits: + City in the Sky Oocca Hallway Near Lobby: Clawshot + City in the Sky Entrance: Clawshot + +- Name: City in the Sky Oocca Hallway Near Lobby + Region: City in the Sky + Exits: + City in the Sky Lobby Floor: Nothing + City in the Sky Oocca Hallway Near Entrance: Clawshot + +# CITY IN THE SKY LOBBY + +- Name: City in the Sky Lobby Floor + Region: City in the Sky + Locations: + City in the Sky Hint Sign: Nothing + Exits: + City in the Sky Lobby Floor Near West Bridge: Double_Clawshots + City in the Sky East Bridge Near Lobby: Nothing + City in the Sky North Fan Passageway Near Lobby: Nothing + City in the Sky Lobby Floor Upper West Ledge: Clawshot + City in the Sky Lobby Above Ceiling Fan: Double_Clawshots and 'Can_Disable_City_in_the_Sky_Lobby_Ceiling_Fan' + +- Name: City in the Sky Lobby Floor Near West Bridge + Region: City in the Sky + Exits: + City in the Sky West Bridge Near Lobby: Nothing + City in the Sky Lobby Floor: Clawshot + +- Name: City in the Sky Lobby Floor Upper West Ledge + Region: City in the Sky + Exits: + City in the Sky West Bridge Upper Ledge: Nothing + City in the Sky Lobby Floor: Nothing + +- Name: City in the Sky Lobby Above Ceiling Fan + Region: City in the Sky + Events: + Can Turn on City in the Sky North Fan: Double_Clawshots and 'Can_Disable_City_in_the_Sky_Lobby_Ceiling_Fan' + Locations: + City in the Sky Chest Below Big Key Chest: Nothing + Exits: + City in the Sky Outside Central Tower Ground: Nothing + City in the Sky Lobby Floor: Can_Survive_Damage and 'Can_Disable_City_in_the_Sky_Lobby_Ceiling_Fan' + +- Name: City in the Sky Lobby Above Highest Grating + Region: City in the Sky + Events: + Can Disable City in the Sky Lobby Ceiling Fan: Iron_Boots and Clawshot + Locations: + City in the Sky Big Key Chest: Iron_Boots + Exits: + City in the Sky Lobby Above Ceiling Fan: Nothing + City in the Sky Outside Central Tower Ropes: Nothing + +# CITY IN THE SKY WEST BRIDGE + +- Name: City in the Sky West Bridge Upper Ledge + Region: City in the Sky + Exits: + City in the Sky West Bridge Near Lobby: Clawshot + City in the Sky Lobby Floor Upper West Ledge: Nothing + +- Name: City in the Sky West Bridge Near Lobby + Region: City in the Sky + Exits: + City in the Sky West Bridge Near Double Clawshot Maze Room: Double_Clawshots + City in the Sky West Bridge Upper Ledge: Clawshot + City in the Sky Lobby Floor Near West Bridge: Nothing + +- Name: City in the Sky West Bridge Near Double Clawshot Maze Room + Region: City in the Sky + Exits: + City in the Sky Double Clawshot Maze Room Near West Bridge: Nothing + City in the Sky West Bridge Near Lobby: Double_Clawshots + +# CITY IN THE SKY DOUBLE CLAWSHOT MAZE ROOM + +- Name: City in the Sky Double Clawshot Maze Room Near West Bridge + Region: City in the Sky + Locations: + City in the Sky West Wing First Chest: Clawshot + Exits: + City in the Sky Double Clawshot Maze Room Near Baba Tower: Double_Clawshots + City in the Sky West Bridge Near Double Clawshot Maze Room: Nothing + +- Name: City in the Sky Double Clawshot Maze Room Near Baba Tower + Region: City in the Sky + Locations: + City in the Sky West Wing Baba Balcony Chest: Nothing + City in the Sky West Wing Narrow Ledge Chest: Nothing + City in the Sky West Wing Tile Worm Chest: Nothing + Exits: + City in the Sky Baba Tower Bottom: Nothing + +# CITY IN THE SKY BABA TOWER + +- Name: City in the Sky Baba Tower Bottom + Region: City in the Sky + Locations: + City in the Sky Baba Tower Top Small Chest: Can_Defeat_Baba_Serpent and Can_Defeat_Big_Baba and Double_Clawshots + City in the Sky Baba Tower Narrow Ledge Chest: Can_Defeat_Baba_Serpent and Can_Defeat_Big_Baba and Double_Clawshots + City in the Sky Baba Tower Alcove Chest: Can_Defeat_Baba_Serpent and Can_Defeat_Big_Baba and Double_Clawshots + Exits: + City in the Sky Baba Tower Top: Can_Defeat_Baba_Serpent and Can_Defeat_Big_Baba and Double_Clawshots + City in the Sky Double Clawshot Maze Room Near Baba Tower: Nothing + +- Name: City in the Sky Baba Tower Top + Region: City in the Sky + Exits: + City in the Sky West Garden Near Baba Tower: Nothing + City in the Sky Baba Tower Bottom: Double_Clawshots + +# CITY IN THE SKY WEST GARDEN + +- Name: City in the Sky West Garden Near Baba Tower + Region: City in the Sky + Exits: + City in the Sky West Garden Middle: Clawshot + City in the Sky Baba Tower Top: Nothing + +- Name: City in the Sky West Garden Middle + Region: City in the Sky + Locations: + City in the Sky West Garden Corner Chest: Nothing + City in the Sky West Garden Lone Island Chest: Double_Clawshots + City in the Sky Garden Island Poe: Double_Clawshots and Can_Defeat_Poe + Exits: + City in the Sky West Garden Near Peahat Train North: Double_Clawshots + +- Name: City in the Sky West Garden Near Peahat Train North + Region: City in the Sky + Locations: + City in the Sky West Garden Lower Chest: Nothing + Exits: + City in the Sky Peahat Train Room North Near West Garden: Nothing + City in the Sky West Garden Near Baba Tower: Clawshot + +- Name: City in the Sky West Garden Near Peahat Train South + Region: City in the Sky + Locations: + City in the Sky West Garden Ledge Chest: Nothing + Exits: + City in the Sky West Garden Middle: Nothing + City in the Sky Peahat Train Room South Near West Garden: Nothing + +# CITY IN THE SKY PEAHAT TRAIN ROOM + +- Name: City in the Sky Peahat Train Room North Near West Garden + Region: City in the Sky + Exits: + City in the Sky Peahat Train Room South Near West Garden: Double_Clawshots + City in the Sky Peahat Train Room Near Outside Central Tower: Double_Clawshots + City in the Sky West Garden Near Peahat Train North: Nothing + +- Name: City in the Sky Peahat Train Room South Near West Garden + Region: City in the Sky + Exits: + City in the Sky Peahat Train Room North Near West Garden: Double_Clawshots + City in the Sky Peahat Train Room Near Outside Central Tower: Double_Clawshots + City in the Sky West Garden Near Peahat Train South: Nothing + +- Name: City in the Sky Peahat Train Room Near Outside Central Tower + Region: City in the Sky + Exits: + City in the Sky Peahat Train Room North Near West Garden: Double_Clawshots + City in the Sky Peahat Train Room South Near West Garden: Double_Clawshots + City in the Sky Outside Central Tower Ground: Nothing + +# CITY IN THE SKY OUTSIDE CENTRAL TOWER + +- Name: City in the Sky Outside Central Tower Ground + Region: City in the Sky + Exits: + City in the Sky Outside Central Tower Ledge: Clawshot + City in the Sky Lobby Above Ceiling Fan: Nothing + City in the Sky Peahat Train Room Near Outside Central Tower: Nothing + +- Name: City in the Sky Outside Central Tower Ledge + Region: City in the Sky + Exits: + City in the Sky Outside Central Tower Ropes: Can_Use_Tightrope + +- Name: City in the Sky Outside Central Tower Ropes + Region: City in the Sky + Locations: + City in the Sky Central Outside Ledge Chest: Can_Use_Tightrope and Can_Climb_Vines + City in the Sky Central Outside Poe Island Chest: Can_Use_Tightrope and Can_Climb_Vines + City in the Sky Poe Above Central Fan: Can_Use_Tightrope and Can_Defeat_Poe + Exits: + City in the Sky Lobby Above Highest Grating: Nothing + City in the Sky Outside Central Tower Ground: Nothing + +# CITY IN THE SKY EAST BRIDGE + +- Name: City in the Sky East Bridge Near Lobby + Region: City in the Sky + Events: + Can Spin City in the Sky East Bridge: Spinner + Exits: + City in the Sky Lobby Floor: Nothing + City in the Sky East Bridge Near East Helmasaur Room: "'Can_Spin_City_in_the_Sky_East_Bridge'" + +- Name: City in the Sky East Bridge Near East Helmasaur Room + Region: City in the Sky + Exits: + City in the Sky East Helmasaur Room Top Near East Bridge: City_in_the_Sky_Small_Key or Small_Keys == Keysy + City in the Sky East Bridge Near Lobby: "'Can_Spin_City_in_the_Sky_East_Bridge'" + +- Name: City in the Sky Under East Bridge + Region: City in the Sky + Exits: + City in the Sky East Helmasaur Room Bottom Near East Bridge: Nothing + City in the Sky East Bridge Near Lobby: Double_Clawshots and 'Can_Spin_City_in_the_Sky_East_Bridge' + +# CITY IN THE SKY EAST HELMASAUR ROOM + +- Name: City in the Sky East Helmasaur Room Top Near East Bridge + Region: City in the Sky + Events: + Can Hit City in the Sky East Helmasaur Room Crystal Switch: Can_Hit_Crystal_Switch_at_Range + Exits: + City in the Sky East Helmasaur Room Top Near Tower Before Miniboss: "'Can_Hit_City_in_the_Sky_East_Helmasaur_Room_Crystal_Switch'" + City in the Sky East Bridge Near East Helmasaur Room: Nothing + +- Name: City in the Sky East Helmasaur Room Top Near Tower Before Miniboss + Region: City in the Sky + Exits: + City in the Sky Tower Before Miniboss Higher Caged Area: Nothing + City in the Sky East Helmasaur Room Top Near East Tileworm Room: "'Can_Hit_City_in_the_Sky_East_Helmasaur_Room_Crystal_Switch'" + City in the Sky East Helmasaur Room Top Near East Bridge: "'Can_Hit_City_in_the_Sky_East_Helmasaur_Room_Crystal_Switch'" + +- Name: City in the Sky East Helmasaur Room Top Near East Tileworm Room + Region: City in the Sky + Events: + Can Hit City in the Sky East Helmasaur Room Crystal Switch: Can_Hit_Crystal_Switch_at_Range + Exits: + City in the Sky East Tileworm Room Near East Helmasaur Room: Nothing + City in the Sky East Helmasaur Room Top Near Tower Before Miniboss: Nothing + +- Name: City in the Sky East Helmasaur Room Bottom Near Tower Before Miniboss + Region: City in the Sky + Exits: + City in the Sky East Helmasaur Room Bottom Near East Bridge: Double_Clawshots + City in the Sky Tower Before Miniboss Lower Caged Area: Nothing + +- Name: City in the Sky East Helmasaur Room Bottom Near East Bridge + Region: City in the Sky + Locations: + City in the Sky East Wing Lower Level Chest: Nothing + Exits: + City in the Sky Under East Bridge: Nothing + City in the Sky East Helmasaur Room Bottom Near Tower Before Miniboss: Double_Clawshots + +# CITY IN THE SKY EAST TILEWORM ROOM + +- Name: City in the Sky East Tileworm Room Near East Helmasaur Room + Region: City in the Sky + Locations: + City in the Sky East Tile Worm Small Chest: Nothing + Exits: + City in the Sky East Tileworm Room Near Double Dinalfos Room: Can_Launch_Tileworm + City in the Sky East Helmasaur Room Top Near East Tileworm Room: Nothing + +- Name: City in the Sky East Tileworm Room Near Double Dinalfos Room + Region: City in the Sky + Exits: + City in the Sky Double Dinalfos Room Bottom: Nothing + City in the Sky East Tileworm Room Near East Helmasaur Room: Clawshot or Can_Launch_Tileworm + +# CITY IN THE SKY DOUBLE DINALFOS ROOM + +- Name: City in the Sky Double Dinalfos Room Bottom + Region: City in the Sky + Exits: + City in the Sky Double Dinalfos Room Top: Can_Defeat_Dinalfos and Clawshot + City in the Sky East Tileworm Room Near Double Dinalfos Room: Can_Defeat_Dinalfos # Room locks when you go in + +- Name: City in the Sky Double Dinalfos Room Top + Region: City in the Sky + Exits: + City in the Sky Oocca Flight Room Near Double Dinalfos Room: Nothing + City in the Sky Double Dinalfos Room Bottom: Nothing + +# CITY IN THE SKY OOCCA FLIGHT ROOM + +- Name: City in the Sky Oocca Flight Room Near Double Dinalfos Room + Region: City in the Sky + Locations: + City in the Sky East Wing After Dinalfos Alcove Chest: Clawshot + City in the Sky East Wing After Dinalfos Ledge Chest: Nothing + Exits: + City in the Sky Oocca Flight Room Near Tower Before Miniboss: Clawshot + City in the Sky Double Dinalfos Room Top: Nothing + +- Name: City in the Sky Oocca Flight Room Near Tower Before Miniboss + Region: City in the Sky + Exits: + City in the Sky Tower Before Miniboss Top: Nothing + +# CITY IN THE SKY TOWER BEFORE MINIBOSS + +- Name: City in the Sky Tower Before Miniboss Top + Region: City in the Sky + Events: + Can Open City in the Sky Tower Before Miniboss Upper Gate: Clawshot + Exits: + City in the Sky Tower Before Miniboss Higher Caged Area: "'Can_Open_City_in_the_Sky_Tower_Before_Miniboss_Upper_Gate'" + City in the Sky Tower Before Miniboss Bottom: Nothing + +- Name: City in the Sky Tower Before Miniboss Bottom + Region: City in the Sky + Exits: + Aerolfos Miniboss Room: Nothing + City in the Sky Tower Before Miniboss Lower Caged Area: Double_Clawshots + +- Name: City in the Sky Tower Before Miniboss Higher Caged Area + Region: City in the Sky + Locations: + City in the Sky East First Wing Chest After Fans: Nothing + Exits: + City in the Sky East Helmasaur Room Top Near Tower Before Miniboss: Nothing + City in the Sky Tower Before Miniboss Bottom: "'Can_Open_City_in_the_Sky_Tower_Before_Miniboss_Upper_Gate'" + +- Name: City in the Sky Tower Before Miniboss Lower Caged Area + Region: City in the Sky + Exits: + City in the Sky East Helmasaur Room Bottom Near Tower Before Miniboss: Nothing + +# AEROLFOS MINIBOSS ROOM + +- Name: Aerolfos Miniboss Room + Locations: + City in the Sky Aeralfos Chest: Can_Defeat_Aerolfos + Exits: + City in the Sky Tower Before Miniboss Bottom: Can_Defeat_Aerolfos + +# CITY IN THE SKY NORTH FAN PASSAGEWAY + +- Name: City in the Sky North Fan Passageway Near Lobby + Region: City in the Sky + Exits: + City in the Sky North Fan Passageway Near North Tower: Double_Clawshots and 'Can_Turn_on_City_in_the_Sky_North_Fan' + City in the Sky Lobby Floor: Nothing + +- Name: City in the Sky North Fan Passageway Near North Tower + Region: City in the Sky + Locations: + City in the Sky Chest Behind North Fan: Clawshot + Exits: + City in the Sky North Tower Bottom: Nothing + City in the Sky North Fan Passageway Near Lobby: Double_Clawshots and 'Can_Turn_on_City_in_the_Sky_North_Fan' + +# CITY IN THE SKY NORTH TOWER + +- Name: City in the Sky North Tower Bottom + Region: City in the Sky + Exits: + City in the Sky North Tower Top: Can_Defeat_Aerolfos and Double_Clawshots + City in the Sky North Fan Passageway Near North Tower: Nothing + +- Name: City in the Sky North Tower Top + Exits: + City in the Sky Boss Room: City_in_the_Sky_Big_Key or Big_Keys == Keysy + City in the Sky North Tower Bottom: Nothing + +# CITY IN THE SKY BOSS ROOM + +- Name: City in the Sky Boss Room + Events: + Can Complete City in the Sky: Can_Defeat_Argorok + Locations: + City in the Sky Argorok Heart Container: Can_Defeat_Argorok + City in the Sky Dungeon Reward: Can_Defeat_Argorok + Exits: + City in the Sky Entrance: Can_Defeat_Argorok \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Forest Temple.yaml b/src/dusk/randomizer/data/world/dungeons/Forest Temple.yaml new file mode 100644 index 0000000000..fd51b0f64a --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Forest Temple.yaml @@ -0,0 +1,277 @@ + +# FOREST TEMPLE ENTRANCE ROOM + +- Name: Forest Temple Entrance + Region: Forest Temple + Dungeon Start Area: True + Events: + Can Free Monkey in Entrance Room: Can_Break_Monkey_Cage + Locations: + Forest Temple Entrance Vines Chest: Can_Defeat_Walltula or Clawshot + Exits: + Forest Temple Entrance Ledge Above Monkey Cage: Can_Defeat_Walltula and Can_Defeat_Bokoblin and Can_Break_Monkey_Cage and Can_Climb_Vines + North Faron Woods: Nothing + +- Name: Forest Temple Entrance Ledge Above Monkey Cage + Region: Forest Temple + Exits: + Forest Temple Lobby: Nothing + Forest Temple Entrance: Nothing + +# FOREST TEMPLE LOBBY + +- Name: Forest Temple Lobby + Region: Forest Temple + Locations: + Forest Temple Central Chest Behind Stairs: Gale_Boomerang # Incase the player blocks it by lighting the torches + Forest Temple Central Chest Hanging From Web: Can_Cut_Hanging_Web + Exits: + Forest Temple Lobby North Ledge: Can_Light_Torches + Forest Temple Lobby West Ledge: Clawshot or (Can_Swing_on_Monkeys and 'Can_Free_Monkey_on_Totem') + Forest Temple East Water Room Near Lobby: Can_Swing_on_Monkeys and 'Can_Free_Monkey_in_Entrance_Room' + Forest Temple Entrance Ledge Above Monkey Cage: Nothing + +- Name: Forest Temple Lobby North Ledge + Region: Forest Temple + Locations: + Forest Temple Central North Chest: Nothing + Exits: + Forest Temple Outside Center South Ledge: Nothing + Forest Temple Lobby: Nothing + +- Name: Forest Temple Lobby West Ledge + Region: Forest Temple + Locations: + Forest Temple Hint Sign: Nothing + Exits: + Forest Temple Lobby West Ledge Behind Web: Can_Break_Webs + Forest Temple Lobby: Nothing + +- Name: Forest Temple Lobby West Ledge Behind Web + Region: Forest Temple + Exits: + Forest Temple West Main Room: Nothing + Forest Temple Lobby West Ledge: Can_Break_Webs + +# FOREST TEMPLE WEST MAIN ROOM + +- Name: Forest Temple West Main Room + Region: Forest Temple + Locations: + Forest Temple West Deku Like Chest: Can_Defeat_Walltula + Exits: + Forest Temple West Main Room Ledge near Big Baba Room: Can_Defeat_Walltula + Forest Temple West Main Room Behind Boulder: Can_Pickup_Bomblings + Forest Temple West Main Room Ledge near Outside: Can_Climb_Vines + +- Name: Forest Temple West Main Room Ledge near Big Baba Room + Region: Forest Temple + Exits: + Forest Temple Big Baba Room: Nothing + Forest Temple West Main Room: Nothing + +- Name: Forest Temple West Main Room Behind Boulder + Region: Forest Temple + Exits: + Forest Temple West Tileworm Room: Nothing + Forest Temple West Main Room: Can_Smash + +- Name: Forest Temple West Main Room Ledge near Outside + Region: Forest Temple + Exits: + Forest Temple Outside West Ledge: Nothing + Forest Temple West Main Room: Nothing + +# FOREST TEMPLE BIG BABA ROOM + +- Name: Forest Temple Big Baba Room + Region: Forest Temple + Events: + Can Free Monkey in Big Baba Room: count(Forest_Temple_Small_Key, 4) or Small_Keys == Keysy + Locations: + Forest Temple Big Baba Key: Can_Defeat_Big_Baba + Exits: + Forest Temple West Main Room Ledge near Big Baba Room: Nothing + +# FOREST TEMPLE WEST TILEWORM ROOM +- Name: Forest Temple West Tileworm Room + Region: Forest Temple + Events: + Can Free Monkey in West Tileworm Room: Can_Light_Torches and (count(Forest_Temple_Small_Key, 4) or Small_Keys == Keysy) + Locations: + Forest Temple Totem Pole Chest: Can_Survive_One_Bonk + Forest Temple West Tile Worm Room Vines Chest: Nothing + Forest Temple West Tile Worm Chest Behind Stairs: Gale_Boomerang + Exits: + Forest Temple West Main Room Behind Boulder: Nothing + +# FOREST TEMPLE OUTSIDE WEST AND CENTER AREA + +- Name: Forest Temple Outside Center South Ledge + Region: Forest Temple + Exits: + Forest Temple Outside Center North Ledge: Can_Swing_on_Monkeys and 'Can_Free_Monkey_on_Totem' and + 'Can_Free_Monkey_in_Big_Baba_Room' and 'Can_Free_Monkey_in_West_Tileworm_Room' + Forest Temple Lobby North Ledge: Nothing + +- Name: Forest Temple Outside West Ledge + Region: Forest Temple + Exits: + Forest Temple Outside Center North Ledge: Gale_Boomerang + Forest Temple West Main Room Ledge near Outside: Nothing + +- Name: Forest Temple Outside Center North Ledge + Region: Forest Temple + Events: + Can Free Outside Monkey: Gale_Boomerang + Exits: + Ook Miniboss Room: Nothing + Forest Temple Outside West Ledge: Gale_Boomerang + +# OOK MINIBOSS ROOM + +- Name: Ook Miniboss Room + Locations: + Forest Temple Gale Boomerang: Can_Defeat_Ook + Exits: + Forest Temple Outside Center North Ledge: Gale_Boomerang + +# FOREST TEMPLE EAST WATER ROOM + +- Name: Forest Temple East Water Room Near Lobby + Region: Forest Temple + Exits: + Forest Temple East Water Room: Can_Pickup_Bomblings # Can burn the web with the bombling + Forest Temple Lobby: Nothing + +- Name: Forest Temple East Water Room + Region: Forest Temple + Locations: + Forest Temple Big Key Chest: Gale_Boomerang + Forest Temple East Water Cave Chest: Nothing + Exits: + Forest Temple Second Monkey Outside Room: count(Forest_Temple_Small_Key, 4) or Small_Keys == Keysy + Forest Temple Outside East: Nothing + Forest Temple East Water Room Near Lobby: Can_Break_Webs + +# FOREST TEMPLE SECOND MONKEY OUTSIDE ROOM + +- Name: Forest Temple Second Monkey Outside Room + Region: Forest Temple + Events: + Can Free Monkey on Totem: Can_Survive_Three_Bonks and Can_Defeat_Bokoblin + Locations: + Forest Temple Second Monkey Under Bridge Chest: Nothing + Exits: + Forest Temple East Water Room: count(Forest_Temple_Small_Key, 4) or Small_Keys == Keysy + +# FOREST TEMPLE OUTSIDE EAST + +- Name: Forest Temple Outside East + Region: Forest Temple + Exits: + Forest Temple North Cross Room South Side: Nothing + Forest Temple East Water Room: Nothing + +# FOREST TEMPLE NORTH CROSS ROOM + +- Name: Forest Temple North Cross Room South Side + Region: Forest Temple + Locations: + Forest Temple Windless Bridge Chest: Nothing + Exits: + Forest Temple North Cross Room East Side: Gale_Boomerang + Forest Temple North Cross Room West Side: Gale_Boomerang + Forest Temple North Cross Room North Side: Gale_Boomerang + Forest Temple Outside East: Nothing + +- Name: Forest Temple North Cross Room East Side + Region: Forest Temple + Exits: + Forest Temple East Tileworm Room: count(Forest_Temple_Small_Key, 4) or Small_Keys == Keysy + Forest Temple North Cross Room South Side: Gale_Boomerang + Forest Temple North Cross Room West Side: Nothing + Forest Temple North Cross Room North Side: Gale_Boomerang + +- Name: Forest Temple North Cross Room West Side + Region: Forest Temple + Exits: + Forest Temple Dark Spider Room: Nothing + Forest Temple North Cross Room South Side: Gale_Boomerang + Forest Temple North Cross Room East Side: Nothing + Forest Temple North Cross Room North Side: Gale_Boomerang + +- Name: Forest Temple North Cross Room North Side + Region: Forest Temple + Exits: + Forest Temple Boss Door Room South Side: Nothing + Forest Temple North Cross Room South Side: Gale_Boomerang + Forest Temple North Cross Room East Side: Gale_Boomerang + Forest Temple North Cross Room West Side: Gale_Boomerang + +# FOREST TEMPLE EAST TILEWORM ROOM + +- Name: Forest Temple East Tileworm Room + Region: Forest Temple + Events: + Can Free Monkey in East Tileworm Room: Can_Defeat_Tileworm and Can_Defeat_Skulltula and Can_Defeat_Walltula and Gale_Boomerang # or Tileworm Boost + Locations: + Forest Temple East Tile Worm Chest: Can_Defeat_Tileworm and Can_Defeat_Skulltula and Can_Defeat_Walltula and Gale_Boomerang # or Tileworm Boost + Exits: + Forest Temple North Cross Room East Side: count(Forest_Temple_Small_Key, 4) or Small_Keys == Keysy + +# FOREST TEMPLE DARK SPIDER ROOM + +- Name: Forest Temple Dark Spider Room + Region: Forest Temple + Events: + Can Free Monkey in Dark Spider Room: Can_Break_Webs and Can_Break_Monkey_Cage + Exits: + Forest Temple North Cross Room West Side: Nothing + +# FOREST TEMPLE BOSS DOOR ROOM + +- Name: Forest Temple Boss Door Room South Side + Region: Forest Temple + Exits: + Forest Temple Boss Door Room West Side: Gale_Boomerang or Clawshot + Forest Temple Boss Door Room North Side: Can_Swing_on_Monkeys and Can_Free_All_Monkeys_in_Forest_Temple + +- Name: Forest Temple Boss Door Room West Side + Region: Forest Temple + Exits: + Forest Temple Boss Door Room North Side: Clawshot + Forest Temple North Deku Like Room: Nothing + Forest Temple Boss Door Room South Side: Gale_Boomerang # Can do a jumpslash, but it's a bit precise + +- Name: Forest Temple Boss Door Room North Side + Region: Forest Temple + Events: + Fairy Access: Nothing + Exits: + Forest Temple Boss Room: Forest_Temple_Big_Key or Big_Keys == Keysy + Forest Temple Boss Door Room South Side: Can_Swing_on_Monkeys and Can_Free_All_Monkeys_in_Forest_Temple + Forest Temple Boss Door Room West Side: Clawshot + +# FOREST TEMPLE NORTH DEKU LIKE ROOM + +- Name: Forest Temple North Deku Like Room + Region: Forest Temple + Events: + Can Free Monkey in North Deku Like Room: Gale_Boomerang + Locations: + Forest Temple North Deku Like Chest: Can_Defeat_Deku_Like or Gale_Boomerang + Exits: + Forest Temple Boss Door Room West Side: Nothing + +# FOREST TEMPLE BOSS ROOM + +- Name: Forest Temple Boss Room + Events: + Can Complete Forest Temple: Can_Defeat_Diababa + Locations: + Forest Temple Diababa Heart Container: Can_Defeat_Diababa + Forest Temple Dungeon Reward: Can_Defeat_Diababa + Exits: + South Faron Woods: Can_Defeat_Diababa + \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Goron Mines.yaml b/src/dusk/randomizer/data/world/dungeons/Goron Mines.yaml new file mode 100644 index 0000000000..80b04aa222 --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Goron Mines.yaml @@ -0,0 +1,336 @@ + +# GORON MINES ENTRANCE ROOM + +- Name: Goron Mines Entrance + Region: Goron Mines + Dungeon Start Area: True + Exits: + Goron Mines Entrance Room Upper Platforms: Can_Break_Wooden_Barrier and Iron_Boots + Death Mountain Sumo Hall Goron Mines Tunnel: Nothing + +- Name: Goron Mines Entrance Room Upper Platforms + Region: Goron Mines + Events: + Can Open Goron Mines Entrance Room Iron Gate: Iron_Boots + Locations: + Goron Mines Entrance Chest: Nothing + Exits: + Goron Mines Entrance Room Near Central Magnet Room: "'Can_Open_Goron_Mines_Entrance_Room_Iron_Gate'" + Goron Mines Entrance: Nothing + +- Name: Goron Mines Entrance Room Near Central Magnet Room # Behind the gate that opens + Region: Goron Mines + Exits: + Goron Mines Central Magnet Room Near Entrance: Nothing + Goron Mines Entrance Room Upper Platforms: "'Can_Open_Goron_Mines_Entrance_Room_Iron_Gate'" + +# GORON MINES CENTRAL MAGNET ROOM + +- Name: Goron Mines Central Magnet Room Near Entrance + Region: Goron Mines + Locations: + Goron Mines Main Magnet Room Bottom Chest: Nothing + Exits: + Goron Mines Magnet Ceiling Room Lower: Goron_Mines_Small_Key or Small_Keys == Keysy + Goron Mines Central Magnet Room Center Tower: "'Can_Activate_Central_Tower_Magnet'" + Goron Mines Entrance Room Near Central Magnet Room: Nothing + +- Name: Goron Mines Central Magnet Room Center Tower + Region: Goron Mines + Events: + Can Activate Central Tower Magnet: Iron_Boots + Exits: + Goron Mines Central Magnet Room Near Crystal Switch Room: "'Can_Activate_Central_Tower_Magnet'" + Goron Mines Central Magnet Room Near Entrance: "'Can_Activate_Central_Tower_Magnet'" + Goron Mines Magnet Ceiling Room Near Central Magnet Room: Nothing + +- Name: Goron Mines Central Magnet Room Near Crystal Switch Room + Region: Goron Mines + Exits: + Goron Mines Crystal Switch Room Water Side: Nothing + Goron Mines Central Magnet Room Center Tower: "'Can_Activate_Central_Tower_Magnet'" + Goron Mines Central Magnet Room Near Ceiling Dodongo Room: "'Can_Activate_Highest_Central_Magnet'" + +- Name: Goron Mines Central Magnet Room Near Ceiling Dodongo Room + Region: Goron Mines + Events: + Can Activate Highest Central Magnet: Bow and Iron_Boots + Locations: + Goron Mines Main Magnet Room Top Chest: Nothing + Exits: + Goron Mines Central Magnet Room Near Crystal Switch Room: "'Can_Activate_Highest_Central_Magnet'" + Goron Mines Ceiling Dodongo Room Near Central Magnet Room: Nothing + +# GORON MINES MAGNET CEILING ROOM + +- Name: Goron Mines Magnet Ceiling Room Lower + Region: Goron Mines + Exits: + Goron Mines Magnet Ceiling Room Lower Past Stone Wall: Nothing + Goron Mines Central Magnet Room Near Entrance: Nothing + +- Name: Goron Mines Magnet Ceiling Room Lower Past Stone Wall + Region: Goron Mines + Exits: + Goron Mines First Magnet Floor Room Lower: Nothing + +- Name: Goron Mines Magnet Ceiling Room Upper Near First Magnet Floor Room + Region: Goron Mines + Exits: + Goron Mines Magnet Ceiling Room Ceiling: Iron_Boots + Goron Mines Magnet Ceiling Room Lower: Nothing + +- Name: Goron Mines Magnet Ceiling Room Ceiling + Region: Goron Mines + Locations: + Goron Mines Magnet Maze Chest: Nothing + Exits: + Goron Mines Magnet Ceiling Room Near Central Magnet Room: Nothing + Goron Mines Magnet Ceiling Room Upper Near First Magnet Floor Room: Nothing + Goron Mines Magnet Ceiling Room Lower: Nothing + +- Name: Goron Mines Magnet Ceiling Room Near Central Magnet Room + Region: Goron Mines + Exits: + Goron Mines Central Magnet Room Center Tower: Nothing + Goron Mines Magnet Ceiling Room Lower: Nothing + +# GORON MINES FIRST MAGNET FLOOR ROOM + +- Name: Goron Mines First Magnet Floor Room Lower + Region: Goron Mines + Exits: + Goron Mines First Magnet Floor Room Lower Near Gor Amatos Room: Iron_Boots + +- Name: Goron Mines First Magnet Floor Room Lower Near Gor Amatos Room + Region: Goron Mines + Exits: + Goron Mines Gor Amato Room Lower: Nothing + Goron Mines First Magnet Floor Room Lower: Nothing + +- Name: Goron Mines First Magnet Floor Room Upper Near Gor Amatos Room + Region: Goron Mines + Exits: + Goron Mines First Magnet Floor Room Upper Near Magnet Ceiling Room: Iron_Boots + Goron Mines First Magnet Floor Room Lower Near Gor Amatos Room: Nothing + Goron Mines Gor Amato Room Upper: Nothing + +- Name: Goron Mines First Magnet Floor Room Upper Near Magnet Ceiling Room + Region: Goron Mines + Exits: + Goron Mines Magnet Ceiling Room Upper Near First Magnet Floor Room: Nothing + Goron Mines First Magnet Floor Room Upper Near Gor Amatos Room: Iron_Boots + Goron Mines First Magnet Floor Room Lower: Nothing + +# GORON MINES GOR AMATO ROOM + +- Name: Goron Mines Gor Amato Room Lower + Region: Goron Mines + Locations: + Goron Mines Gor Amato Chest: Nothing + Goron Mines Gor Amato Small Chest: Nothing + Goron Mines Gor Amato Key Shard: Can_Talk_to_Humans + Exits: + Goron Mines Gor Amato Room Upper: Can_Climb_Ladders + Goron Mines First Magnet Floor Room Lower Near Gor Amatos Room: Nothing + +- Name: Goron Mines Gor Amato Room Upper + Region: Goron Mines + Exits: + Goron Mines First Magnet Floor Room Upper Near Gor Amatos Room: Nothing + Goron Mines Gor Amato Room Lower: Nothing + +# GORON MINES CRYSTAL SWITCH ROOM + +- Name: Goron Mines Crystal Switch Room Water Side + Region: Goron Mines + Locations: + Goron Mines Crystal Switch Room Underwater Chest: Iron_Boots + Goron Mines Crystal Switch Room Small Chest: Iron_Boots + Exits: + Goron Mines Crystal Switch Room Beamos Side: Can_Hit_Crystal_Switch_at_Range or (Can_Hit_Crystal_Switch and Iron_Boots) + Goron Mines Central Magnet Room Near Crystal Switch Room: Nothing + +- Name: Goron Mines Crystal Switch Room Beamos Side + Region: Goron Mines + Locations: + Goron Mines After Crystal Switch Room Magnet Wall Chest: Iron_Boots + Exits: + Goron Mines Crystal Switch Room Near Outside Room: Bow or (Sword and Iron_Boots) + Goron Mines Crystal Switch Room Water Side: Can_Hit_Crystal_Switch + +- Name: Goron Mines Crystal Switch Room Near Outside Room + Region: Goron Mines + Exits: + Goron Mines Outside Room: count(Goron_Mines_Small_Key, 2) or Small_Keys == Keysy + Goron Mines Crystal Switch Room Beamos Side: Nothing + +# GORON MINES OUTSIDE ROOM + +- Name: Goron Mines Outside Room + Region: Goron Mines + Locations: + Goron Mines Outside Beamos Chest: Nothing + Goron Mines Outside Underwater Chest: Iron_Boots and (Sword or Water_Bombs) # Can also just swim over the barrier + Goron Mines Outside Clawshot Chest: Clawshot and (Bow or Slingshot) + Exits: + Goron Mines Floor Turning Room Near Outside Room: count(Goron_Mines_Small_Key, 3) or Small_Keys == Keysy + Goron Mines Outside Room Near Boss Door Room: Clawshot or (Can_Defeat_Beamos and Iron_Boots and Bow) + +- Name: Goron Mines Outside Room Near Boss Door Room + Region: Goron Mines + Exits: + Goron Mines Boss Door Room Near Outside Room: Nothing + Goron Mines Outside Room: Nothing + +# GORON MINES FLOOR TURNING ROOM + +- Name: Goron Mines Floor Turning Room Near Outside Room + Region: Goron Mines + Exits: + Goron Mines Floor Turning Room Near Gor Ebizo Room Lower: Iron_Boots + Goron Mines Outside Room: Nothing + +- Name: Goron Mines Floor Turning Room Near Gor Ebizo Room Lower + Region: Goron Mines + Exits: + Goron Mines Gor Ebizo Room Lower: Nothing + Goron Mines Floor Turning Room Near Outside Room: Nothing + +- Name: Goron Mines Floor Turning Room Near Gor Ebizo Room Upper + Region: Goron Mines + Exits: + Goron Mines Floor Turning Room Near Miniboss Room: Iron_Boots + Goron Mines Floor Turning Room Near Gor Ebizo Room Lower: Nothing + Goron Mines Floor Turning Room Near Outside Room: Nothing + Goron Mines Gor Ebizo Room Upper: Nothing + +- Name: Goron Mines Floor Turning Room Near Miniboss Room + Region: Goron Mines + Locations: + Goron Mines Chest Before Dangoro: Nothing + Exits: + Dangoro Miniboss Room North Side: Nothing + Goron Mines Floor Turning Room Near Outside Room: Nothing + +# GORON MINES GOR EBIZO ROOM + +- Name: Goron Mines Gor Ebizo Room Lower + Region: Goron Mines + Locations: + Goron Mines Gor Ebizo Chest: Nothing + Goron Mines Gor Ebizo Key Shard: Can_Talk_to_Humans + Goron Mines Hint Sign: Nothing + Exits: + Goron Mines Gor Ebizo Room Upper: Can_Climb_Ladders + Goron Mines Floor Turning Room Near Gor Ebizo Room Lower: Nothing + +- Name: Goron Mines Gor Ebizo Room Upper + Region: Goron Mines + Exits: + Goron Mines Floor Turning Room Near Gor Ebizo Room Upper: Nothing + Goron Mines Gor Ebizo Room Lower: Nothing + +# DANGORO MINIBOSS ROOM + +- Name: Dangoro Miniboss Room North Side + Exits: + Dangoro Miniboss Room South Side: Can_Defeat_Dangoro + Goron Mines Floor Turning Room Near Miniboss Room: Nothing + +- Name: Dangoro Miniboss Room South Side + Exits: + Goron Mines Beamos Circle Room Near Miniboss Room: Nothing + Dangoro Miniboss Room North Side: Can_Defeat_Dangoro + +# GORON MINES BEAMOS CIRCLE ROOM + +- Name: Goron Mines Beamos Circle Room Near Miniboss Room + Region: Goron Mines + Events: + Can Cut Down Iron Platform in Beamos Circle Room: Bow + Locations: + Goron Mines Dangoro Chest: Nothing + Exits: + Goron Mines Beamos Circle Room Center: "'Can_Cut_Down_Iron_Platform_in_Beamos_Circle_Room'" + Dangoro Miniboss Room South Side: Nothing + +- Name: Goron Mines Beamos Circle Room Center + Region: Goron Mines + Events: + Can Kill Beamos in Beamos Circle Room: Can_Defeat_Beamos + Locations: + Goron Mines Beamos Room Chest: "'Can_Kill_Beamos_in_Beamos_Circle_Room'" + Exits: + Goron Mines Beamos Circle Room Near Gor Liggs Room: "'Can_Kill_Beamos_in_Beamos_Circle_Room'" + Goron Mines Beamos Circle Room Near Ceiling Dodongo Room: "'Can_Kill_Beamos_in_Beamos_Circle_Room'" + Goron Mines Beamos Circle Room Near Miniboss Room: "'Can_Cut_Down_Iron_Platform_in_Beamos_Circle_Room'" + +- Name: Goron Mines Beamos Circle Room Near Gor Liggs Room + Region: Goron Mines + Exits: + Goron Mines Gor Liggs Room: Nothing + Goron Mines Beamos Circle Room Center: "'Can_Kill_Beamos_in_Beamos_Circle_Room'" + +- Name: Goron Mines Beamos Circle Room Near Ceiling Dodongo Room + Region: Goron Mines + Exits: + Goron Mines Ceiling Dodongo Room Near Beamos Circle Room: Nothing + Goron Mines Beamos Circle Room Center: "'Can_Kill_Beamos_in_Beamos_Circle_Room'" + +# GORON MINES GOR LIGGS ROOM + +- Name: Goron Mines Gor Liggs Room + Region: Goron Mines + Locations: + Goron Mines Gor Liggs Chest: Nothing + Goron Mines Gor Liggs Key Shard: Can_Talk_to_Humans + Exits: + Goron Mines Beamos Circle Room Near Gor Liggs Room: Nothing + +# GORON MINES CEILING DODONGO ROOM + +- Name: Goron Mines Ceiling Dodongo Room Near Beamos Circle Room + Region: Goron Mines + Exits: + Goron Mines Ceiling Dodongo Room Near Central Magnet Room: Gale_Boomerang or Clawshot or Bow # to get rid of ceiling torch slugs + Goron Mines Beamos Circle Room Near Ceiling Dodongo Room: Nothing + +- Name: Goron Mines Ceiling Dodongo Room Near Ceiling Dodongo + Region: Goron Mines + Events: + Can Hit Ceiling Dodongo Switch: Iron_Boots and Bow + Exits: + Goron Mines Ceiling Dodongo Room Near Central Magnet Room: "'Can_Hit_Ceiling_Dodongo_Switch'" + Goron Mines Ceiling Dodongo Room Near Beamos Circle Room: Gale_Boomerang or Clawshot or Bow # to get rid of ceiling torch slugs + +- Name: Goron Mines Ceiling Dodongo Room Near Central Magnet Room + Region: Goron Mines + Exits: + Goron Mines Central Magnet Room Near Ceiling Dodongo Room: Nothing + Goron Mines Ceiling Dodongo Room Near Ceiling Dodongo: "'Can_Hit_Ceiling_Dodongo_Switch'" + +# GORON MINES BOSS DOOR ROOM + +- Name: Goron Mines Boss Door Room Near Outside Room + Region: Goron Mines + Exits: + Goron Mines Boss Door Room Near Boss Door: Bow + Goron Mines Outside Room Near Boss Door Room: Nothing + +- Name: Goron Mines Boss Door Room Near Boss Door + Region: Goron Mines + Exits: + Goron Mines Boss Room: count(Goron_Mines_Key_Shard, 3) or Big_Keys == Keysy + Goron Mines Boss Door Room Near Outside Room: Bow + +# GORON MINES BOSS ROOM + +- Name: Goron Mines Boss Room + Events: + Can Complete Goron Mines: Can_Defeat_Fyrus + Locations: + Goron Mines Fyrus Heart Container: Can_Defeat_Fyrus + Goron Mines Dungeon Reward: Can_Defeat_Fyrus + Exits: + Lower Kakariko Village: Can_Defeat_Fyrus \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml b/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml new file mode 100644 index 0000000000..df9936fdba --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml @@ -0,0 +1,214 @@ +# HYRULE CASTLE ENTRANCE + +- Name: Hyrule Castle Entrance + Region: Hyrule Castle + Dungeon Start Area: True + Locations: + Hyrule Castle Hint Sign: Nothing + Exits: + Hyrule Castle Entrance Near West Courtyard: Can_Defeat_Red_Bokoblin + Hyrule Castle Entrance Near East Courtyard: Can_Defeat_Red_Bokoblin + Hyrule Castle Main Hall Near Entrance: Can_Open_Doors and (Hyrule_Castle_Small_Key or Small_Keys == Keysy) + Castle Town North Inside Barrier: Nothing + +- Name: Hyrule Castle Entrance Near West Courtyard + Region: Hyrule Castle + Exits: + Hyrule Castle West Courtyard: Nothing + Hyrule Castle Entrance: Can_Defeat_Red_Bokoblin + +- Name: Hyrule Castle Entrance Near East Courtyard + Region: Hyrule Castle + Exits: + Hyrule Castle East Courtyard Near Entrance: Nothing + Hyrule Castle Entrance: Can_Defeat_Red_Bokoblin + +# HYRULE CASTLE WEST COURTYARD + +- Name: Hyrule Castle West Courtyard + Region: Hyrule Castle + Locations: + Hyrule Castle West Courtyard North Small Chest: Can_Defeat_Bokoblin + Hyrule Castle West Courtyard Central Small Chest: Can_Defeat_Bokoblin + Hyrule Castle King Bulblin Key: Can_Defeat_Bokoblin and Can_Defeat_King_Bulblin_Castle + +# HYRULE CASTLE EAST COURTYARD + +- Name: Hyrule Castle East Courtyard Near Entrance + Region: Hyrule Castle + Exits: + Hyrule Castle East Courtyard Near Graveyard: Human_Link # For riding the boar + Hyrule Castle Entrance Near East Courtyard: Nothing + +- Name: Hyrule Castle East Courtyard Near Graveyard + Region: Hyrule Castle + Locations: + Hyrule Castle East Wing Boomerang Puzzle Chest: Gale_Boomerang + Hyrule Castle East Wing Balcony Chest: Gale_Boomerang and Can_Climb_Ladders + Exits: + Hyrule Castle Graveyard: Can_Dig + Hyrule Castle East Courtyard Near Entrance: Gale_Boomerang and Can_Climb_Ladders + +# HYRULE CASTLE GRAVEYARD + +- Name: Hyrule Castle Graveyard + Region: Hyrule Castle + Events: + Can Refill Lantern Oil: Can_Smash + Locations: + Hyrule Castle Graveyard Grave Switch Room Right Chest: Can_Smash + Hyrule Castle Graveyard Grave Switch Room Front Left Chest: Can_Smash + Hyrule Castle Graveyard Grave Switch Room Back Left Chest: Can_Smash + Hyrule Castle Graveyard Owl Statue Chest: Can_Smash and Lantern and Restored_Dominion_Rod + +# HYRULE CASTLE MAIN HALL + +- Name: Hyrule Castle Main Hall Near Entrance + Region: Hyrule Castle + Exits: + Hyrule Castle Main Hall Bottom: Can_Defeat_Bokoblin and Can_Defeat_Lizalfos + Hyrule Castle Entrance: Nothing + +- Name: Hyrule Castle Main Hall Bottom + Region: Hyrule Castle + Locations: + Hyrule Castle Main Hall Northeast Chest: Clawshot and (Lantern or (Can_Defeat_Bokoblin and Can_Defeat_Lizalfos)) + Exits: + Hyrule Castle Main Hall Near Entrance: Can_Defeat_Bokoblin and Can_Defeat_Lizalfos + Hyrule Castle Main Hall Near First Darknut Room: Double_Clawshots + +- Name: Hyrule Castle Main Hall Near First Darknut Room + Region: Hyrule Castle + Exits: + Hyrule Castle First Darknut Room Near Main Hall: Can_Open_Doors + Hyrule Castle Main Hall Bottom: Nothing + +- Name: Hyrule Castle Main Hall Near Double Darknut Room + Region: Hyrule Castle + Locations: + Hyrule Castle Main Hall Southwest Chest: Nothing + Hyrule Castle Main Hall Northwest Chest: Double_Clawshots + Exits: + Hyrule Castle Main Hall Bottom: Nothing + Hyrule Castle Double Darknut Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Darknuts' + +- Name: Hyrule Castle Main Hall Near Double Dinalfos Room + Region: Hyrule Castle + Exits: + Hyrule Castle Double Dinalfos Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Dinalfos' + Hyrule Castle Main Hall Bottom: Nothing + + +# HYRULE CASTLE FIRST DARKNUT ROOM + +- Name: Hyrule Castle First Darknut Room Near Main Hall + Region: Hyrule Castle + Locations: + Hyrule Castle Lantern Staircase Chest: Can_Defeat_Darknut and Lantern and Gale_Boomerang + Exits: + Hyrule Castle First Darknut Room Past Lantern Staircase: Can_Defeat_Darknut and Lantern and Gale_Boomerang + Hyrule Castle Main Hall Near First Darknut Room: Can_Open_Doors + +- Name: Hyrule Castle First Darknut Room Past Lantern Staircase + Region: Hyrule Castle + Exits: + Hyrule Castle Torch Puzzle Room: Can_Open_Doors + Hyrule Castle Hanging Painting Room: Can_Open_Doors + Hyrule Castle First Darknut Room Near Main Hall: Can_Defeat_Darknut + +# HYRULE CASTLE HANGING PAINTING ROOM + +- Name: Hyrule Castle Hanging Painting Room + Region: Hyrule Castle + Exits: + Hyrule Castle Double Darknut Room: Can_Open_Doors and Can_Knock_Down_Hyrule_Castle_Painting + Hyrule Castle First Darknut Room Past Lantern Staircase: Can_Open_Doors + +# HYRULE CASTLE DOUBLE DARKNUT ROOM + +- Name: Hyrule Castle Double Darknut Room + Region: Hyrule Castle + Events: + Can Defeat Hyrule Castle Double Darknuts: Can_Defeat_Darknut + Exits: + Hyrule Castle Main Hall Near Double Darknut Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Darknuts' + Hyrule Castle Outside Balcony: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Darknuts' + Hyrule Castle Hanging Painting Room: Can_Open_Doors + +# HYRULE CASTLE TORCH PUZZLE ROOM + +- Name: Hyrule Castle Torch Puzzle Room + Region: Hyrule Castle + Exits: + Hyrule Castle Double Dinalfos Room: Can_Open_Doors and Lantern + Hyrule Castle First Darknut Room Past Lantern Staircase: Can_Open_Doors + +# HYRULE CASTLE DOUBLE DINALFOS ROOM + +- Name: Hyrule Castle Double Dinalfos Room + Region: Hyrule Castle + Events: + Can Defeat Hyrule Castle Double Dinalfos: Can_Defeat_Dinalfos + Exits: + Hyrule Castle Outside Balcony: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Dinalfos' + Hyrule Castle Main Hall Near Double Dinalfos Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Dinalfos' + Hyrule Castle Torch Puzzle Room: Can_Open_Doors + +# HYRULE CASTLE OUTSIDE BALCONY + +- Name: Hyrule Castle Outside Balcony + Region: Hyrule Castle + Locations: + Hyrule Castle Southeast Balcony Tower Chest: Can_Defeat_Aerolfos + Hyrule Castle Big Key Chest: Nothing + Exits: + Hyrule Castle Final Climb Near Outside Balcony: Can_Open_Doors and (count(Hyrule_Castle_Small_Key, 2) or Small_Keys == Keysy) + Hyrule Castle Double Darknut Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Darknuts' + Hyrule Castle Double Dinalfos Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Dinalfos' + +# HYRULE CASTLE FINAL CLIMB + +- Name: Hyrule Castle Final Climb Near Outside Balcony + Region: Hyrule Castle + Events: + Follow Ghost Soldiers: Can_Use_Senses + Exits: + Hyrule Castle Final Climb Top: "'Follow_Ghost_Soldiers' and Can_Defeat_Dinalfos and Can_Defeat_Darknut and Double_Clawshots and Spinner" + Hyrule Castle Outside Balcony: Can_Open_Doors + +- Name: Hyrule Castle Final Climb Top + Region: Hyrule Castle + Events: + Follow Ghost Soldiers: Can_Use_Senses + Exits: + Hyrule Castle Treasure Room: Can_Open_Doors and (count(Hyrule_Castle_Small_Key, 3) or Small_Keys == Keysy) + Hyrule Castle Throne Room: Hyrule_Castle_Big_Key or Big_Keys == Keysy + Hyrule Castle Final Climb Near Outside Balcony: "'Follow_Ghost_Soldiers' and Can_Defeat_Dinalfos and Can_Defeat_Darknut and Double_Clawshots and Spinner" + +# HYRULE CASTLE TREASURE ROOM + +- Name: Hyrule Castle Treasure Room + Region: Hyrule Castle + Locations: + Hyrule Castle Treasure Room First Small Chest: Nothing + Hyrule Castle Treasure Room Second Small Chest: Nothing + Hyrule Castle Treasure Room Third Small Chest: Nothing + Hyrule Castle Treasure Room Fourth Small Chest: Nothing + Hyrule Castle Treasure Room Fifth Small Chest: Nothing + Hyrule Castle Treasure Room Sixth Small Chest: Nothing + Hyrule Castle Treasure Room Seventh Small Chest: Nothing + Hyrule Castle Treasure Room Eighth Small Chest: Nothing + Hyrule Castle Treasure Room First Chest: Nothing + Hyrule Castle Treasure Room Second Chest: Nothing + Hyrule Castle Treasure Room Third Chest: Nothing + Hyrule Castle Treasure Room Fourth Chest: Nothing + Hyrule Castle Treasure Room Fifth Chest: Nothing + Exits: + Hyrule Castle Final Climb Top: Can_Open_Doors + +# HYRULE CASTLE THRONE ROOM + +- Name: Hyrule Castle Throne Room + Region: Hyrule Castle + Locations: + Defeat Ganondorf: Master_Sword and Wolf_Link and Ending_Blow \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Lakebed Temple.yaml b/src/dusk/randomizer/data/world/dungeons/Lakebed Temple.yaml new file mode 100644 index 0000000000..5c6b82ca71 --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Lakebed Temple.yaml @@ -0,0 +1,438 @@ + +# LAKEBED TEMPLE LOBBY + +- Name: Lakebed Temple Entrance + Region: Lakebed Temple + Dungeon Start Area: True + Exits: + Lakebed Temple Lobby: Zora_Armor + Lake Hylia Lakebed Temple Entrance: Nothing + +- Name: Lakebed Temple Lobby + Region: Lakebed Temple + Events: + Can Pull Lakebed Lobby Lever: Can_Pull_Lakebed_Levers + Locations: + Lakebed Temple Lobby Left Chest: Nothing + Lakebed Temple Lobby Rear Chest: Nothing + Exits: + Lakebed Temple Lobby Near Stalactite Room: "'Can_Pull_Lakebed_Lobby_Lever'" + Lakebed Temple Entrance: Zora_Armor + +- Name: Lakebed Temple Lobby Near Stalactite Room + Region: Lakebed Temple + Exits: + Lakebed Temple Stalactite Room Near Lobby: Nothing + Lakebed Temple Lobby: "'Can_Pull_Lakebed_Lobby_Lever'" + +# LAKEBED TEMPLE STALACTITE ROOM + +- Name: Lakebed Temple Stalactite Room Near Lobby + Region: Lakebed Temple + Exits: + Lakebed Temple Lobby Near Stalactite Room: Nothing + Lakebed Temple Stalactite Room Near Circling Current Room: Can_Launch_Bombs + +- Name: Lakebed Temple Stalactite Room Near Circling Current Room + Region: Lakebed Temple + Locations: + Lakebed Temple Stalactite Room Chest: Can_Launch_Bombs + Exits: + Lakebed Temple Circling Current Room South: Nothing + Lakebed Temple Stalactite Room Near Lobby: Nothing + +# LAKEBED TEMPLE CIRCLING CURRENT ROOM + +- Name: Lakebed Temple Circling Current Room South + Region: Lakebed Temple + Exits: + Lakebed Temple Central Room: Can_Open_Doors + Lakebed Temple Stalactite Room Near Circling Current Room: Nothing + +- Name: Lakebed Temple Circling Current Room East Lower + Region: Lakebed Temple + Exits: + Lakebed Temple East Waterwheel Room First Floor Near Circling Current Room: Nothing + Lakebed Temple Central Room: Nothing + +- Name: Lakebed Temple Circling Current Room East Upper + Region: Lakebed Temple + Exits: + Lakebed Temple Outside East Waterwheel Room Second Floor Near Circling Current Room: Nothing + Lakebed Temple Central Room: Nothing + +- Name: Lakebed Temple Circling Current Room West Lower Near Central Room + Region: Lakebed Temple + Locations: + Lakebed Temple Hint Sign: Nothing + Exits: + Lakebed Temple Circling Current Room West Lower Near West Waterwheel Room Lower: "'Can_Pull_East_Water_Supply_Lever' and 'Can_Turn_Lakebed_Staircase_with_Clawshot'" + Lakebed Temple Central Room: Nothing + +- Name: Lakebed Temple Circling Current Room West Lower Near West Waterwheel Room Lower + Region: Lakebed Temple + Exits: + Lakebed Temple Circling Current Room West Lower Near Central Room: "'Can_Pull_East_Water_Supply_Lever' and 'Can_Turn_Lakebed_Staircase_with_Clawshot'" + Lakebed Temple West Waterwheel Room Lower: Nothing + +- Name: Lakebed Temple Circling Current Room West Upper Near Central Room + Region: Lakebed Temple + Exits: + Lakebed Temple Circling Current Room West Upper Near West Waterwheel Room Upper: "'Can_Pull_West_Water_Supply_Lever'" + Lakebed Temple Central Room: Nothing + +- Name: Lakebed Temple Circling Current Room West Upper Near West Waterwheel Room Upper + Region: Lakebed Temple + Exits: + Lakebed Temple Circling Current Room West Upper Near Central Room: "'Can_Pull_West_Water_Supply_Lever'" + Lakebed Temple Outside West Waterwheel Room Upper Near Circling Current Room: Nothing + +# LAKEBED TEMPLE CENTRAL ROOM + +# We're just going to assume that you need to be human for everything in this room +# because I don't feel like splitting this room up into 5 different sections to account +# for two edge cases as wolf link that aren't going to be relevant any time soon +- Name: Lakebed Temple Central Room + Region: Lakebed Temple + Events: + Can Turn Lakebed Staircase: Human_Link + Can Turn Lakebed Staircase with Clawshot: Clawshot + Can Open Lakebed Temple Boss Door: (Lakebed_Temple_Big_Key or Big_Keys == Keysy) and 'Can_Pull_West_Water_Supply_Lever' and 'Can_Pull_East_Water_Supply_Lever' + Locations: + Lakebed Temple Central Room Small Chest: Human_Link + Lakebed Temple Central Room Chest: Human_Link + Lakebed Temple Chandelier Chest: Clawshot + Lakebed Temple Central Room Spire Chest: Iron_Boots and ('Can_Pull_West_Water_Supply_Lever' or 'Can_Pull_East_Water_Supply_Lever') + Exits: + Lakebed Temple Circling Current Room East Lower: "'Can_Turn_Lakebed_Staircase'" + Lakebed Temple Circling Current Room East Upper: "'Can_Turn_Lakebed_Staircase' and (Lakebed_Temple_Small_Key or Small_Keys == Keysy)" + Lakebed Temple Circling Current Room West Lower Near Central Room: "'Can_Turn_Lakebed_Staircase'" + Lakebed Temple Circling Current Room West Upper Near Central Room: "'Can_Turn_Lakebed_Staircase'" + Lakebed Temple Circling Current Room South: Can_Open_Doors and 'Can_Turn_Lakebed_Staircase' + Lakebed Temple Central Room Past Boss Door: "'Can_Open_Lakebed_Temple_Boss_Door'" + +- Name: Lakebed Temple Central Room Past Boss Door + Region: Lakebed Temple + Exits: + Lakebed Temple Boss Room: Nothing + Lakebed Temple Central Room: "'Can_Open_Lakebed_Temple_Boss_Door'" + +# LAKEBED TEMPLE EAST WATERWHEEL ROOM FIRST FLOOR + +- Name: Lakebed Temple East Waterwheel Room First Floor Near Circling Current Room + Region: Lakebed Temple + Exits: + Lakebed Temple East Waterwheel Room Waterwheel: "'Can_Pull_East_Water_Supply_Lever'" + Lakebed Temple East Waterwheel Room First Floor Lowest: Nothing + Lakebed Temple Circling Current Room East Lower: Nothing + +- Name: Lakebed Temple East Waterwheel Room Waterwheel # Being on the turning waterwheel + Region: Lakebed Temple + Exits: + Lakebed Temple East Waterwheel Room First Floor Near Circling Current Room: Nothing + Lakebed Temple East Waterwheel Room First Floor Near Water Jet Room Land Side: Nothing + Lakebed Temple East Waterwheel Room First Floor Near Water Jet Room Water Side: Nothing + +- Name: Lakebed Temple East Waterwheel Room First Floor Near Water Jet Room Land Side + Region: Lakebed Temple + Exits: + Lakebed Temple Water Jet Room Land Side: Nothing + Lakebed Temple East Waterwheel Room Waterwheel: "'Can_Pull_East_Water_Supply_Lever'" + Lakebed Temple East Waterwheel Room First Floor Lowest: Nothing + Lakebed Temple East Waterwheel Room First Floor Near Circling Current Room: Nothing # Can jump down to the right + +- Name: Lakebed Temple East Waterwheel Room First Floor Near Water Jet Room Water Side + Region: Lakebed Temple + Exits: + Lakebed Temple Water Jet Room Water Side Near East Waterwheel Room: Nothing + Lakebed Temple East Waterwheel Room Waterwheel: "'Can_Pull_East_Water_Supply_Lever'" + Lakebed Temple East Waterwheel Room First Floor Lowest: Nothing + +- Name: Lakebed Temple East Waterwheel Room First Floor Lowest + Region: Lakebed Temple + Locations: + Lakebed Temple East Lower Waterwheel Stalactite Chest: Can_Launch_Bombs and Can_Climb_Vines + Lakebed Temple East Lower Waterwheel Bridge Chest: Clawshot and 'Can_Pull_West_Water_Supply_Lever' and 'Can_Turn_Lakebed_Staircase' + Exits: + Lakebed Temple East Waterwheel Room First Floor Near Circling Current Room: Can_Climb_Vines or ('Can_Pull_West_Water_Supply_Lever' and 'Can_Turn_Lakebed_Staircase') + +# LAKEBED TEMPLE OUTSIDE EAST WATERWHEEL ROOM SECOND FLOOR + +- Name: Lakebed Temple Outside East Waterwheel Room Second Floor Near Circling Current Room + Region: Lakebed Temple + Events: + Can Pull Outside East Waterwheel Room Second Floor Lever: Clawshot or (Can_Launch_Bombs and Can_Climb_Vines) + Locations: + Lakebed Temple East Second Floor Southwest Chest: Nothing + Exits: + Lakebed Temple Outside East Waterwheel Room Second Floor North: Clawshot or (Can_Launch_Bombs and Can_Climb_Vines) + Lakebed Temple Circling Current Room East Upper: Nothing + +- Name: Lakebed Temple Outside East Waterwheel Room Second Floor North + Region: Lakebed Temple + Exits: + Lakebed Temple Outside East Waterwheel Room Second Floor North Near East Water Supply Room: Can_Smash + Lakebed Temple East Waterwheel Room Second Floor: Nothing + +- Name: Lakebed Temple Outside East Waterwheel Room Second Floor North Near East Water Supply Room + Region: Lakebed Temple + Exits: + Lakebed Temple East Water Supply Room: Nothing + Lakebed Temple Outside East Waterwheel Room Second Floor North: Can_Smash + +- Name: Lakebed Temple Outside East Waterwheel Room Second Floor South Near East Water Supply Room + Region: Lakebed Temple + Exits: + Lakebed Temple Outside East Waterwheel Room Second Floor South: Nothing + Lakebed Temple East Water Supply Room: Nothing + +- Name: Lakebed Temple Outside East Waterwheel Room Second Floor South + Region: Lakebed Temple + Locations: + Lakebed Temple East Second Floor Southeast Chest: Nothing + Exits: + Lakebed Temple East Waterwheel Room Second Floor: Nothing + +# LAKEBED TEMPLE EAST WATERWHEEL ROOM SECOND FLOOR + +- Name: Lakebed Temple East Waterwheel Room Second Floor + Region: Lakebed Temple + Exits: + Lakebed Temple Outside East Waterwheel Room Second Floor North: Nothing + Lakebed Temple Outside East Waterwheel Room Second Floor South: Nothing + Lakebed Temple East Waterwheel Room First Floor Lowest: Nothing + +# LAKEBED TEMPLE EAST WATER SUPPLY ROOM + +- Name: Lakebed Temple East Water Supply Room + Region: Lakebed Temple + Exits: + Lakebed Temple East Water Supply Room Past Locked Door: count(Lakebed_Temple_Small_Key, 3) or Small_Keys == Keysy or (Small_Keys == Vanilla and count(Lakebed_Temple_Small_Key, 2)) + Lakebed Temple Outside East Waterwheel Room Second Floor North Near East Water Supply Room: Nothing + Lakebed Temple Outside East Waterwheel Room Second Floor South Near East Water Supply Room: Nothing + +- Name: Lakebed Temple East Water Supply Room Past Locked Door + Region: Lakebed Temple + Events: + Can Pull East Water Supply Lever: Can_Climb_Vines and Can_Climb_Ladders and Can_Pull_Lakebed_Levers + Locations: + Lakebed Temple East Water Supply Small Chest: Can_Climb_Vines and Iron_Boots # Boots required incase someone activates the lever and falls down + Lakebed Temple East Water Supply Clawshot Chest: Can_Climb_Vines and Clawshot and Iron_Boots + +# LAKEBED TEMPLE WATER JET ROOM (the room before the miniboss) + +- Name: Lakebed Temple Water Jet Room Water Side Near East Waterwheel Room + Region: Lakebed Temple + Exits: + Lakebed Temple Water Jet Room Water Side Underwater: count(Lakebed_Temple_Small_Key, 3) or Small_Keys == Keysy + Lakebed Temple East Waterwheel Room First Floor Near Water Jet Room Water Side: Nothing + +- Name: Lakebed Temple Water Jet Room Water Side Underwater + Region: Lakebed Temple + Locations: + Lakebed Temple Before Deku Toad Underwater Left Chest: Iron_Boots + Lakebed Temple Before Deku Toad Underwater Right Chest: Iron_Boots + Exits: + Lakebed Temple Water Jet Room Water Side Near East Waterwheel Room: count(Lakebed_Temple_Small_Key, 3) or Small_Keys == Keysy + Lakebed Temple Water Jet Room Water Side Near MiniBoss Room: Iron_Boots and Water_Bombs + +- Name: Lakebed Temple Water Jet Room Water Side Near MiniBoss Room + Region: Lakebed Temple + Exits: + Deku Toad Miniboss Room Water Tunnel: Zora_Armor + Lakebed Temple Water Jet Room Water Side Underwater: Zora_Armor and Iron_Boots and Water_Bombs + +- Name: Lakebed Temple Water Jet Room Land Side + Region: Lakebed Temple + Locations: + Lakebed Temple Before Deku Toad Alcove Chest: Nothing + Exits: + Deku Toad Miniboss Room Near Water Jet Room Land Side: Nothing + Lakebed Temple East Waterwheel Room First Floor Near Water Jet Room Land Side: Nothing + +# DEKU TOAD MINIBOSS ROOM + +- Name: Deku Toad Miniboss Room Water Tunnel + Exits: + Deku Toad Miniboss Room Battle Arena: Zora_Armor + Lakebed Temple Water Jet Room Water Side Near MiniBoss Room: Zora_Armor + +- Name: Deku Toad Miniboss Room Battle Arena + Events: + Can Pull Deku Toad Miniboss Room Lever: Clawshot + Locations: + Lakebed Temple Deku Toad Chest: Can_Defeat_Deku_Toad + Exits: + Deku Toad Miniboss Room Near Water Jet Room Land Side: Clawshot + +- Name: Deku Toad Miniboss Room Near Water Jet Room Land Side + Exits: + Deku Toad Miniboss Room Battle Arena: "'Can_Pull_Deku_Toad_Miniboss_Room_Lever'" + Lakebed Temple Water Jet Room Land Side: Nothing + +# LAKEBED TEMPLE WEST WATERWHEEL ROOM + +- Name: Lakebed Temple West Waterwheel Room Lower + Region: Lakebed Temple + Locations: + Lakebed Temple West Lower Small Chest: Clawshot + Exits: + Lakebed Temple West Waterwheel Room Upper North: Clawshot + Lakebed Temple Circling Current Room West Lower Near West Waterwheel Room Lower: Nothing + Lakebed Temple West Waterwheel Room Lower Near Underwater Maze Room: Clawshot and 'Can_Pull_West_Water_Supply_Lever' + +- Name: Lakebed Temple West Waterwheel Room Upper North + Region: Lakebed Temple + Locations: + Lakebed Temple West Second Floor Central Small Chest: Clawshot + Exits: + Lakebed Temple West Waterwheel Room Upper Near North Door: Clawshot + Lakebed Temple West Waterwheel Room Lower: Clawshot + +- Name: Lakebed Temple West Waterwheel Room Upper Near North Door + Region: Lakebed Temple + Exits: + Lakebed Temple Outside West Waterwheel Room North: Nothing + Lakebed Temple West Waterwheel Room Upper North: Clawshot + +- Name: Lakebed Temple West Waterwheel Room On Waterwheels + Region: Lakebed Temple + Exits: + Lakebed Temple Outside West Waterwheel Room Southeast: Nothing + Lakebed Temple Outside West Waterwheel Room Southwest: Nothing + Lakebed Temple West Waterwheel Room Upper Near North Door: Clawshot + Lakebed Temple West Waterwheel Room Upper North: Clawshot + Lakebed Temple West Waterwheel Room Lower: Nothing + +- Name: Lakebed Temple West Waterwheel Room Lower Near Underwater Maze Room + Region: Lakebed Temple + Exits: + Lakebed Temple Underwater Maze Room Near Waterwheel Room: Nothing + Lakebed Temple West Waterwheel Room Lower: Clawshot and 'Can_Pull_West_Water_Supply_Lever' + +# LAKEBED TEMPLE OUTSIDE WEST WATERWHEEL ROOM + +- Name: Lakebed Temple Outside West Waterwheel Room Upper Near Circling Current Room + Region: Lakebed Temple + Exits: + Lakebed Temple Outside West Waterwheel Room Southeast: "'Can_Pull_Outside_West_Waterwheel_Room_Southeast_Lever'" + Lakebed Temple Circling Current Room West Upper Near West Waterwheel Room Upper: Nothing + +- Name: Lakebed Temple Outside West Waterwheel Room Southeast + Region: Lakebed Temple + Events: + Can Pull Outside West Waterwheel Room Southeast Lever: Clawshot + Locations: + Lakebed Temple West Second Floor Southeast Chest: Nothing + Exits: + Lakebed Temple West Waterwheel Room On Waterwheels: Nothing + Lakebed Temple Outside West Waterwheel Room Upper Near Circling Current Room: "'Can_Pull_Outside_West_Waterwheel_Room_Southeast_Lever'" + +- Name: Lakebed Temple Outside West Waterwheel Room Southwest + Region: Lakebed Temple + Exits: + Lakebed Temple West Waterwheel Room On Waterwheels: Nothing + Lakebed Temple Outside West Waterwheel Room Southwest Pond: "'Can_Pull_West_Water_Supply_Lever'" + +- Name: Lakebed Temple Outside West Waterwheel Room Southwest Pond + Region: Lakebed Temple + Locations: + Lakebed Temple West Second Floor Southwest Underwater Chest: Iron_Boots + Exits: + Lakebed Temple Outside West Waterwheel Room Southwest Near West Water Supply Room: Clawshot or 'Can_Pull_West_Water_Supply_Lever' + Lakebed Temple Outside West Waterwheel Room Southwest: "'Can_Pull_West_Water_Supply_Lever'" + +- Name: Lakebed Temple Outside West Waterwheel Room Southwest Near West Water Supply Room + Region: Lakebed Temple + Exits: + Lakebed Temple West Water Supply Room: Nothing + Lakebed Temple Outside West Waterwheel Room Southwest Pond: Nothing + +- Name: Lakebed Temple Outside West Waterwheel Room Northwest Near West Water Supply Room + Region: Lakebed Temple + Exits: + Lakebed Temple Outside West Waterwheel Room Tektite Area: Nothing + Lakebed Temple West Water Supply Room: Nothing + +- Name: Lakebed Temple Outside West Waterwheel Room Tektite Area + Region: Lakebed Temple + Events: + Can Pull Outside West Waterwheel Room Northeast Lever: Clawshot + Exits: + Lakebed Temple Outside West Waterwheel Room North: "'Can_Pull_Outside_West_Waterwheel_Room_Northeast_Lever'" + Lakebed Temple Outside West Waterwheel Room Northwest Near West Water Supply Room: Clawshot + +- Name: Lakebed Temple Outside West Waterwheel Room North + Region: Lakebed Temple + Locations: + Lakebed Temple West Second Floor Northeast Chest: "'Can_Pull_West_Water_Supply_Lever'" + Exits: + Lakebed Temple Outside West Waterwheel Room Tektite Area: Can_Launch_Bombs + Lakebed Temple West Waterwheel Room Upper Near North Door: Nothing + +# LAKEBED TEMPLE WEST WATER SUPPLY ROOM + +- Name: Lakebed Temple West Water Supply Room + Region: Lakebed Temple + Events: + Can Pull West Water Supply Lever: Clawshot and Can_Climb_Ladders and Iron_Boots + Locations: + Lakebed Temple West Water Supply Small Chest: Clawshot and Iron_Boots + Lakebed Temple West Water Supply Chest: Clawshot and Iron_Boots + Exits: + Lakebed Temple Outside West Waterwheel Room Southwest Near West Water Supply Room: Nothing + Lakebed Temple Outside West Waterwheel Room Northwest Near West Water Supply Room: Nothing + +# LAKEBED TEMPLE UNDERWATER MAZE ROOM + +- Name: Lakebed Temple Underwater Maze Room Near Waterwheel Room + Region: Lakebed Temple + Exits: + Lakebed Temple Underwater Maze Room Near Big Key Room Lower: Zora_Armor and Iron_Boots and Water_Bombs + Lakebed Temple West Waterwheel Room Lower Near Underwater Maze Room: Nothing + +- Name: Lakebed Temple Underwater Maze Room Near Big Key Room Lower + Region: Lakebed Temple + Locations: + Lakebed Temple Underwater Maze Small Chest: Zora_Armor + Exits: + Lakebed Temple Underwater Maze Room Near Waterwheel Room: Zora_Armor and Iron_Boots and Water_Bombs + Lakebed Temple Underwater Maze Room Near Big Key Room Upper: Zora_Armor and Iron_Boots and Water_Bombs + Lakebed Temple Big Key Room Lower: Zora_Armor and Iron_Boots + +- Name: Lakebed Temple Underwater Maze Room Near Big Key Room Upper + Region: Lakebed Temple + Exits: + Lakebed Temple Big Key Room Upper: Nothing + Lakebed Temple Underwater Maze Room Near Big Key Room Lower: Zora_Armor and Iron_Boots and Water_Bombs + +# LAKEBED TEMPLE BIG KEY ROOM + +- Name: Lakebed Temple Big Key Room Upper + Region: Lakebed Temple + Exits: + Lakebed Temple Big Key Room Middle: Clawshot + Lakebed Temple Underwater Maze Room Near Big Key Room Upper: Nothing + +- Name: Lakebed Temple Big Key Room Middle + Region: Lakebed Temple + Locations: + Lakebed Temple Big Key Chest: Nothing + Exits: + Lakebed Temple Big Key Room Lower: Nothing + +- Name: Lakebed Temple Big Key Room Lower + Region: Lakebed Temple + Exits: + Lakebed Temple Underwater Maze Room Near Big Key Room Lower: Zora_Armor and Iron_Boots + +# LAKEBED TEMPLE BOSS ROOM + +- Name: Lakebed Temple Boss Room + Events: + Can Complete Lakebed Temple: Can_Defeat_Morpheel + Locations: + Lakebed Temple Morpheel Heart Container: Can_Defeat_Morpheel + Lakebed Temple Dungeon Reward: Can_Defeat_Morpheel + Exits: + Lake Hylia Lanayru Spring: Can_Defeat_Morpheel diff --git a/src/dusk/randomizer/data/world/dungeons/Palace of Twilight.yaml b/src/dusk/randomizer/data/world/dungeons/Palace of Twilight.yaml new file mode 100644 index 0000000000..8891f962cf --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Palace of Twilight.yaml @@ -0,0 +1,194 @@ +# PALACE OF TWILIGHT ENTRANCE + +- Name: Palace of Twilight Entrance + Region: Palace of Twilight + Dungeon Start Area: True + Events: + Can Place East Sol at Palace of Twilight Entrance: "'Can_Bring_East_Sol_to_Palace_of_Twilight_Entrance'" + Can Place West Sol at Palace of Twilight Entrance: "'Can_Bring_West_Sol_to_Palace_of_Twilight_Entrance'" + Locations: + Palace of Twilight Collect Both Sols: "'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance' and 'Can_Place_West_Sol_at_Palace_of_Twilight_Entrance'" + Palace of Twilight Hint Sign: Nothing + Exits: + Palace of Twilight West Wing First Room Near Entrance: Nothing + Palace of Twilight Entrance Near East Wing First Room: Nothing + Palace of Twilight Entrance Near North Wing: Light_Sword + Mirror Chamber Portal: Nothing + +- Name: Palace of Twilight Entrance Near East Wing First Room + Region: Palace of Twilight + Exits: + Palace of Twilight East Wing First Room Near Entrance: Nothing + Palace of Twilight Entrance: "'Can_Place_West_Sol_at_Palace_of_Twilight_Entrance'" + +- Name: Palace of Twilight Entrance Near North Wing + Region: Palace of Twilight + Exits: + Palace of Twilight North Wing First Room Bottom: Nothing + Palace of Twilight Entrance: Light_Sword + +# PALACE OF TWILIGHT WEST WING FIRST ROOM + +- Name: Palace of Twilight West Wing First Room Near Entrance + Region: Palace of Twilight + Locations: + Palace of Twilight West Wing First Room Central Chest: Can_Defeat_Zant_Head + Palace of Twilight West Wing Chest Behind Wall of Darkness: Light_Sword and Clawshot + Exits: + Palace of Twilight West Wing First Room Near Second Room: Clawshot + Palace of Twilight Entrance: Nothing + +- Name: Palace of Twilight West Wing First Room Near Second Room + Region: Palace of Twilight + Exits: + Palace of Twilight West Wing Second Room Near First Room: count(Palace_of_Twilight_Small_Key, 6) or (Small_Keys == Vanilla and count(Palace_of_Twilight_Small_Key, 3)) or Small_Keys == Keysy + Palace of Twilight West Wing First Room Near Entrance: Nothing + +# PALACE OF TWILIGHT WEST WING SECOND ROOM + +- Name: Palace of Twilight West Wing Second Room Near First Room + Region: Palace of Twilight + Exits: + Palace of Twilight West Wing Second Room Middle: Nothing + Palace of Twilight West Wing First Room Near Second Room: Nothing + +- Name: Palace of Twilight West Wing Second Room Middle + Region: Palace of Twilight + Locations: + Palace of Twilight West Wing Second Room Central Chest: Can_Defeat_Zant_Head + Palace of Twilight West Wing Second Room Lower South Chest: Can_Defeat_Zant_Head + Palace of Twilight West Wing Second Room Southeast Chest: Double_Clawshots + Exits: + Palace of Twilight West Wing Second Room Near Phantom Zant Room: Clawshot + Palace of Twilight West Wing Second Room Near First Room: "'Can_Bring_West_Sol_to_Palace_of_Twilight_Entrance'" + +- Name: Palace of Twilight West Wing Second Room Near Phantom Zant Room + Region: Palace of Twilight + Exits: + Palace of Twilight West Wing Phantom Zant Room: count(Palace_of_Twilight_Small_Key, 7) or Small_Keys == Keysy + Palace of Twilight West Wing Second Room Middle: Nothing + +# PALACE OF TWILIGHT WEST WING PHANTOM ZANT ROOM + +- Name: Palace of Twilight West Wing Phantom Zant Room + Region: Palace of Twilight + Events: + Can Bring West Sol to Palace of Twilight Entrance: Can_Defeat_Phantom_Zant and Human_Link # Only human can carry the sol + Exits: + Palace of Twilight West Wing Second Room Near Phantom Zant Room: "'Can_Bring_West_Sol_to_Palace_of_Twilight_Entrance'" + +# PALACE OF TWILIGHT EAST WING FIRST ROOM + +- Name: Palace of Twilight East Wing First Room Near Entrance + Region: Palace of Twilight + Exits: + Palace of Twilight East Wing First Room Bottom: Nothing + Palace of Twilight East Wing First Room Near Second Room: Clawshot + +- Name: Palace of Twilight East Wing First Room Bottom + Region: Palace of Twilight + Locations: + Palace of Twilight East Wing First Room East Alcove: Light_Sword or 'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance' + Palace of Twilight East Wing First Room West Alcove: Light_Sword or 'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance' + Exits: + Palace of Twilight East Wing First Room Near Second Room: Double_Clawshots or Light_Sword or 'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance' + +- Name: Palace of Twilight East Wing First Room Near Second Room + Region: Palace of Twilight + Locations: + Palace of Twilight East Wing First Room North Small Chest: Nothing + Palace of Twilight East Wing First Room Zant Head Chest: Can_Defeat_Zant_Head + Exits: + Palace of Twilight East Wing Second Room Near First Room: count(Palace_of_Twilight_Small_Key, 6) or (Small_Keys == Vanilla and count(Palace_of_Twilight_Small_Key, 3)) or Small_Keys == Keysy + Palace of Twilight East Wing First Room Bottom: Nothing + Palace of Twilight East Wing First Room Near Entrance: Light_Sword or 'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance' + +# PALACE OF TWILIGHT EAST WING SECOND ROOM + +- Name: Palace of Twilight East Wing Second Room Near First Room + Region: Palace of Twilight + Exits: + Palace of Twilight East Wing Second Room Near Phantom Zant Room: Clawshot and Can_Defeat_Zant_Head and Can_Defeat_Shadow_Beast + Palace of Twilight East Wing First Room Near Second Room: Nothing + +- Name: Palace of Twilight East Wing Second Room Near Phantom Zant Room + Region: Palace of Twilight + Locations: + Palace of Twilight East Wing Second Room Northeast Chest: Double_Clawshots + Palace of Twilight East Wing Second Room Northwest Chest: Clawshot + Palace of Twilight East Wing Second Room Southwest Chest: Double_Clawshots + Palace of Twilight East Wing Second Room Southeast Chest: Double_Clawshots and Can_Defeat_Zant_Head and Can_Defeat_Shadow_Beast + Exits: + Palace of Twilight East Wing Phantom Zant Room: count(Palace_of_Twilight_Small_Key, 7) or Small_Keys == Keysy + Palace of Twilight East Wing Second Room Near First Room: Double_Clawshots and 'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance' + +# PALACE OF TWILIGHT EAST PHANTOM ZANT ROOM + +- Name: Palace of Twilight East Wing Phantom Zant Room + Region: Palace of Twilight + Events: + Can Bring East Sol to Palace of Twilight Entrance: Can_Defeat_Phantom_Zant and Human_Link + Exits: + Palace of Twilight East Wing Second Room Near Phantom Zant Room: "'Can_Place_East_Sol_at_Palace_of_Twilight_Entrance'" + +# PALACE OF TWILIGHT NORTH WING FIRST ROOM + +- Name: Palace of Twilight North Wing First Room Bottom + Region: Palace of Twilight + Locations: + Palace of Twilight Central First Room Chest: Light_Sword and Can_Defeat_Zant_Head + Exits: + Palace of Twilight Entrance Near North Wing: Nothing + Palace of Twilight North Wing First Room Top: Light_Sword + +- Name: Palace of Twilight North Wing First Room Top + Region: Palace of Twilight + Exits: + Palace of Twilight North Wing Outside Room: count(Palace_of_Twilight_Small_Key, 5) or Small_Keys == Keysy + Palace of Twilight North Wing First Room Bottom: Nothing + +# PALACE OF TWILIGHT NORTH WING OUTSIDE ROOM + +- Name: Palace of Twilight North Wing Outside Room + Region: Palace of Twilight + Locations: + Palace of Twilight Big Key Chest: Light_Sword and Double_Clawshots + Palace of Twilight Central Outdoor Chest: Light_Sword and Can_Defeat_Zant_Head + Exits: + Palace of Twilight North Wing Tower Bottom: count(Palace_of_Twilight_Small_Key, 6) or Small_Keys == Keysy + Palace of Twilight North Wing First Room Top: Nothing + +# PALACE OF TWILIGHT NORTH WING TOWER + +- Name: Palace of Twilight North Wing Tower Bottom + Region: Palace of Twilight + Locations: + Palace of Twilight Central Tower Chest: Clawshot and Light_Sword and Can_Defeat_Zant_Head + Exits: + Palace of Twilight North Wing Tower Top: Clawshot and Light_Sword + Palace of Twilight North Wing Outside Room: Nothing + +- Name: Palace of Twilight North Wing Tower Top + Region: Palace of Twilight + Exits: + Palace of Twilight Boss Door Room: count(Palace_of_Twilight_Small_Key, 7) or Small_Keys == Keysy + Palace of Twilight North Wing Tower Bottom: Nothing + +# PALACE OF TWILIGHT BOSS DOOR ROOM + +- Name: Palace of Twilight Boss Door Room + Region: Palace of Twilight + Exits: + Palace of Twilight Boss Room: Can_Defeat_Shadow_Beast and (Palace_of_Twilight_Big_Key or Big_Keys == Keysy) + Palace of Twilight North Wing Tower Top: Nothing + +# PALACE OF TWILIGHT BOSS ROOM + +- Name: Palace of Twilight Boss Room + Events: + Can Complete Palace of Twilight: Can_Defeat_Zant + Locations: + Palace of Twilight Zant Heart Container: Can_Defeat_Zant + Exits: + Palace of Twilight Entrance: Can_Defeat_Zant + \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Snowpeak Ruins.yaml b/src/dusk/randomizer/data/world/dungeons/Snowpeak Ruins.yaml new file mode 100644 index 0000000000..b5657258c8 --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Snowpeak Ruins.yaml @@ -0,0 +1,346 @@ +# SNOWPEAK RUINS ENTRANCE + +- Name: Snowpeak Ruins Entrance + Region: Snowpeak Ruins + Dungeon Start Area: True + Locations: + Snowpeak Ruins Lobby West Armor Chest: Can_Break_Armor + Snowpeak Ruins Lobby East Armor Chest: Can_Break_Armor + Snowpeak Ruins Lobby Armor Poe: Can_Break_Armor and Can_Use_Senses + Snowpeak Ruins Lobby Poe: Can_Use_Senses + Exits: + Snowpeak Ruins Entrance Near Caged Freezard Room: Clawshot and 'Can_Break_Snowpeak_Entrance_Ice_Wall_Shortcut' + Snowpeak Ruins Yetas Room: Can_Open_Doors + Snowpeak Ruins Room Below Broken Floor Near Entrance: Can_Open_Doors + Snowpeak Ruins East Door Interior: Can_Open_Doors + Snowpeak Ruins West Door Interior: Can_Open_Doors + +- Name: Snowpeak Ruins Entrance Near Caged Freezard Room + Region: Snowpeak Ruins + Events: + Can Break Snowpeak Entrance Ice Wall Shortcut: Can_Break_Ice + Locations: + Snowpeak Ruins Lobby Chandelier Chest: Ball_and_Chain + Exits: + Snowpeak Ruins Entrance Near Second Floor Mini Freezard Room: Ball_and_Chain + Snowpeak Ruins Caged Freezard Room Second Floor: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 3) or Small_Keys == Keysy) + Snowpeak Ruins Entrance: Nothing + +- Name: Snowpeak Ruins Entrance Near Second Floor Mini Freezard Room + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Second Floor Mini Freezard Room: Nothing + Snowpeak Ruins Entrance Near Caged Freezard Room: Ball_and_Chain + +# SNOWPEAK RUINS YETAS ROOM + +- Name: Snowpeak Ruins Yetas Room + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Mansion Map: Can_Talk_to_Humans + Snowpeak Ruins Hint Sign: Nothing + Exits: + Snowpeak Ruins Kitchen: Can_Open_Doors + Snowpeak Ruins West Courtyard: Ordon_Pumpkin or Small_Keys == Keysy + Snowpeak Ruins Caged Freezard Room First Floor: Ordon_Cheese or Small_Keys == Keysy + Snowpeak Ruins Entrance: Can_Open_Doors + +# SNOWPEAK RUINS KITCHEN + +- Name: Snowpeak Ruins Kitchen + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Block Puzzle Room Near Kitchen: Can_Open_Doors + Snowpeak Ruins Yetas Room: Can_Open_Doors + +# SNOWPEAK RUINS BLOCK PUZZLE ROOM + +- Name: Snowpeak Ruins Block Puzzle Room Near Kitchen + Region: Snowpeak Ruins + Events: + Can Press Snowpeak Block Puzzle Ice Switch: Can_Break_Ice + Exits: + Snowpeak Ruins East Courtyard Near Block Puzzle Room First Floor: Can_Open_Doors + Snowpeak Ruins Block Puzzle Room Near Northeast Chilfos Room First Floor: "'Can_Push_Snowpeak_Block_Puzzle_Highest_Block'" + Snowpeak Ruins Kitchen: Can_Open_Doors + +- Name: Snowpeak Ruins Block Puzzle Room Near Northeast Chilfos Room First Floor + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Northeast Chilfos Room Near Block Puzzle Room First Floor: Can_Open_Doors + Snowpeak Ruins Block Puzzle Room Near Kitchen: Nothing + Snowpeak Ruins Block Puzzle Room Second Floor: "'Can_Push_Snowpeak_Block_Puzzle_Highest_Block'" + +- Name: Snowpeak Ruins Block Puzzle Room Second Floor + Region: Snowpeak Ruins + Events: + Can Push Snowpeak Block Puzzle Highest Block: Nothing + Exits: + Snowpeak Ruins Second Floor Mini Freezard Room: Can_Open_Doors + Snowpeak Ruins East Courtyard Balcony Near Block Puzzle Room: Can_Open_Doors and 'Can_Press_Snowpeak_Block_Puzzle_Ice_Switch' + Snowpeak Ruins Block Puzzle Room Near Kitchen: "'Can_Push_Snowpeak_Block_Puzzle_Highest_Block'" + +- Name: Snowpeak Ruins Block Puzzle Room Near Northeast Chilfos Room Second Floor + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Northeast Chilfos Room Near Block Puzzle Room Second Floor: Nothing + Snowpeak Ruins Block Puzzle Room Near Northeast Chilfos Room First Floor: Nothing + +# SNOWPEAK RUINS EAST COURTYARD + +- Name: Snowpeak Ruins East Courtyard Near Block Puzzle Room First Floor + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins East Courtyard: Can_Dig + Snowpeak Ruins East Courtyard Hallway: Can_Break_Ice + Snowpeak Ruins Block Puzzle Room Near Kitchen: Can_Open_Doors + +- Name: Snowpeak Ruins East Courtyard + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins East Courtyard Buried Chest: Can_Dig + Snowpeak Ruins East Courtyard Chest: Nothing + Exits: + Snowpeak Ruins East Courtyard Hallway: Can_Open_Doors + Snowpeak Ruins West Courtyard: Can_Break_Ice + +- Name: Snowpeak Ruins East Courtyard Hallway + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Triple Mini Freezard Room: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 4) or Small_Keys == Keysy) + Snowpeak Ruins East Courtyard: Can_Open_Doors + Snowpeak Ruins East Courtyard Near Block Puzzle Room First Floor: Can_Break_Ice + +- Name: Snowpeak Ruins East Courtyard Balcony Near Block Puzzle Room + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins East Courtyard Balcony Near Northeast Chilfos Room: Can_Defeat_Chilfos and Clawshot + Snowpeak Ruins East Courtyard Near Block Puzzle Room First Floor: Nothing + Snowpeak Ruins East Courtyard: Nothing + Snowpeak Ruins East Courtyard Hallway: Nothing + Snowpeak Ruins Block Puzzle Room Second Floor: Can_Open_Doors + +- Name: Snowpeak Ruins East Courtyard Balcony Near Northeast Chilfos Room + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Northeast Chilfos Room Near East Courtyard Balcony: Can_Open_Doors + +# SNOWPEAK RUINS TRIPLE MINI FREEZARD ROOM + +- Name: Snowpeak Ruins Triple Mini Freezard Room + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Northeast Chilfos Room First Floor: Can_Defeat_Mini_Freezard + Snowpeak Ruins East Courtyard Hallway: Can_Defeat_Mini_Freezard and (count(Snowpeak_Ruins_Small_Key, 4) or Small_Keys == Keysy) + +# SNOWPEAK RUINS NORTHEAST CHILLFOS ROOM + +- Name: Snowpeak Ruins Northeast Chilfos Room First Floor + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Northeast Chilfos Room Near Block Puzzle Room First Floor: Can_Defeat_Chilfos and Can_Open_Doors + Snowpeak Ruins Northeast Chilfos Room Near East Courtyard Balcony: Clawshot and 'Can_Break_Snowpeak_Northeast_Chilfos_Room_Ice_Wall_Shortcut' + Snowpeak Ruins Triple Mini Freezard Room: Can_Defeat_Chilfos and Can_Open_Doors + +- Name: Snowpeak Ruins Northeast Chilfos Room Near Block Puzzle Room First Floor + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Ordon Pumpkin Chest: Nothing + Exits: + Snowpeak Ruins Block Puzzle Room Near Northeast Chilfos Room First Floor: Can_Open_Doors + Snowpeak Ruins Northeast Chilfos Room First Floor: Can_Open_Doors + +- Name: Snowpeak Ruins Northeast Chilfos Room Near East Courtyard Balcony + Region: Snowpeak Ruins + Events: + Can Break Snowpeak Northeast Chilfos Room Ice Wall Shortcut: Can_Break_Ice + Exits: + Snowpeak Ruins Northeast Chilfos Room Near Block Puzzle Room Second Floor: Ball_and_Chain + Snowpeak Ruins Northeast Chilfos Room First Floor: Nothing + +- Name: Snowpeak Ruins Northeast Chilfos Room Near Block Puzzle Room Second Floor + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Northeast Chandelier Chest: Nothing + Exits: + Snowpeak Ruins Block Puzzle Room Near Northeast Chilfos Room Second Floor: Can_Open_Doors + Snowpeak Ruins Northeast Chilfos Room Near East Courtyard Balcony: Ball_and_Chain + Snowpeak Ruins Northeast Chilfos Room First Floor: Nothing + +# SNOWPEAK RUINS WEST COURTYARD + +- Name: Snowpeak Ruins West Courtyard + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins West Courtyard Buried Chest: Can_Dig + Snowpeak Ruins Courtyard Central Chest: Can_Break_Ice + Exits: + Snowpeak Ruins West Canon Room Near Courtyard: Can_Open_Doors + Snowpeak Ruins East Courtyard: Can_Break_Ice + Snowpeak Ruins West Courtyard Hallway Near Ladder: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 4) or Small_Keys == Keysy) + Darkhammer Miniboss Room: Can_Open_Doors and (Ball_and_Chain or (Can_Launch_Canonball and 'Can_Load_Snowpeak_West_Courtyard_Hallway_Canonball_Holder')) + +- Name: Snowpeak Ruins West Courtyard Hallway Near Ladder + Region: Snowpeak Ruins + Events: + Can Load Snowpeak West Courtyard Hallway Canonball Holder: Human_Link + Exits: + Snowpeak Ruins West Courtyard North Balcony: Can_Climb_Ladders and 'Can_Kill_Snowpeak_Courtyard_Balcony_Freezard' + Snowpeak Ruins West Hallway Near Caged Freezard Room: "'Can_Push_Snowpeak_West_Courtyard_Hallway_Block'" + Snowpeak Ruins West Courtyard: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 4) or Small_Keys == Keysy) + +- Name: Snowpeak Ruins West Hallway Near Caged Freezard Room + Region: Snowpeak Ruins + Events: + Can Load Snowpeak Hallway to Caged Freezard Canonball Holder: Human_Link + Can Push Snowpeak West Courtyard Hallway Block: Nothing + Exits: + Snowpeak Ruins Caged Freezard Room First Floor: Can_Open_Doors + Snowpeak Ruins West Courtyard Hallway Near Ladder: "'Can_Push_Snowpeak_West_Courtyard_Hallway_Block'" + +- Name: Snowpeak Ruins West Courtyard South Balcony + Region: Snowpeak Ruins + Events: + Can Break Snowpeak Balcony Ice Wall Shortcut: Can_Break_Ice + Can Kill Snowpeak Courtyard Balcony Freezard: Can_Launch_Canonball and 'Can_Load_Snowpeak_Double_Freezard_Room_Canonball_Holder' + Exits: + Snowpeak Ruins Double Freezard Room Behind Freezard: Can_Open_Doors + Snowpeak Ruins West Courtyard: Nothing + Snowpeak Ruins West Courtyard Hallway Near Ladder: Nothing + Snowpeak Ruins East Courtyard: Nothing + +- Name: Snowpeak Ruins West Courtyard North Balcony + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Chapel: Can_Open_Doors + Snowpeak Ruins Boss Room: Snowpeak_Ruins_Bedroom_Key or Big_Keys == Keysy + +# SNOWPEAK RUINS WEST CANON ROOM + +- Name: Snowpeak Ruins West Canon Room Near Courtyard + Region: Snowpeak Ruins + Events: + Can Launch West Canon Room Canonballs: Can_Launch_Canonball + Locations: + Snowpeak Ruins West Cannon Room Central Chest: Can_Break_Ice + Snowpeak Ruins West Cannon Room Corner Chest: Can_Break_Ice or 'Can_Launch_West_Canon_Room_Canonballs' + Exits: + Snowpeak Ruins West Canon Room Near Wooden Beam Room: Can_Break_Ice or 'Can_Launch_West_Canon_Room_Canonballs' + Snowpeak Ruins West Courtyard: Can_Open_Doors + +- Name: Snowpeak Ruins West Canon Room Near Wooden Beam Room + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Wooden Beam Room First Floor: Can_Open_Doors + Snowpeak Ruins West Canon Room Near Courtyard: Can_Break_Ice or 'Can_Launch_West_Canon_Room_Canonballs' + +# SNOWPEAK RUINS WOODEN BEAM ROOM + +- Name: Snowpeak Ruins Wooden Beam Room First Floor + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Wooden Beam Central Chest: Can_Defeat_Ice_Keese + Snowpeak Ruins Wooden Beam Northwest Chest: Can_Defeat_Ice_Keese + Exits: + Snowpeak Ruins West Canon Room Near Wooden Beam Room: Can_Open_Doors + +- Name: Snowpeak Ruins Wooden Beam Room Second Floor + Region: Snowpeak Ruins + Events: + Can Break Snowpeak Wooden Beam Room Ice Shortcut: Can_Break_Ice + Locations: + Snowpeak Ruins Wooden Beam Chandelier Chest: Ball_and_Chain + Exits: + Snowpeak Ruins Wooden Beam Room First Floor: Nothing + Snowpeak Ruins Caged Freezard Room Second Floor: Can_Open_Doors + +# DARKHAMMER MINIBOSS ROOM + +- Name: Darkhammer Miniboss Room + Locations: + Snowpeak Ruins Ball and Chain: Can_Defeat_Darkhammer + Snowpeak Ruins Chest After Darkhammer: Can_Break_Ice and Can_Defeat_Darkhammer + Exits: + Snowpeak Ruins West Courtyard: Can_Open_Doors + +# SNOWPEAK RUINS CAGED FREEZARD ROOM + +- Name: Snowpeak Ruins Caged Freezard Room First Floor + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Caged Freezard Room Second Floor: Can_Break_Ice + Snowpeak Ruins West Hallway Near Caged Freezard Room: Can_Open_Doors + Snowpeak Ruins Yetas Room: Can_Open_Doors + +- Name: Snowpeak Ruins Caged Freezard Room Second Floor + Region: Snowpeak Ruins + Events: + Can Launch Snowpeak Caged Freezard Room Canonballs: Can_Launch_Canonball and Ball_and_Chain and 'Can_Load_Snowpeak_Hallway_to_Caged_Freezard_Canonball_Holder' + Exits: + Snowpeak Ruins Wooden Beam Room Second Floor: Can_Open_Doors + Snowpeak Ruins Room Below Broken Floor: Can_Smash + Snowpeak Ruins Entrance Near Caged Freezard Room: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 3) or Small_Keys == Keysy) + Snowpeak Ruins Double Freezard Room: "'Can_Push_Snowpeak_Double_Freezard_Room_Block'" + +# SNOWPEAK RUINS ROOM BELOW BROKEN FLOOR + +- Name: Snowpeak Ruins Room Below Broken Floor Near Entrance + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins Entrance: Can_Open_Doors + +- Name: Snowpeak Ruins Room Below Broken Floor + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Broken Floor Chest: Nothing + Exits: + Snowpeak Ruins Caged Freezard Room Second Floor: Clawshot # There's no collision from the bottom + +# SNOWPEAK RUINS SECOND FLOOR MINI FREEZARD ROOM + +- Name: Snowpeak Ruins Second Floor Mini Freezard Room + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Ice Room Poe: Can_Break_Ice and Can_Use_Senses + Exits: + Snowpeak Ruins Block Puzzle Room Second Floor: Can_Open_Doors + Snowpeak Ruins Double Freezard Room: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 4) or Small_Keys == Keysy) + Snowpeak Ruins Entrance Near Second Floor Mini Freezard Room: Can_Open_Doors + +# SNOWPEAK RUINS DOUBLE FREEZARD ROOM + +- Name: Snowpeak Ruins Double Freezard Room + Region: Snowpeak Ruins + Events: + Can Push Snowpeak Double Freezard Room Block: Can_Defeat_Freezard + Can Load Snowpeak Double Freezard Room Canonball Holder: Can_Defeat_Freezard and 'Can_Launch_Snowpeak_Caged_Freezard_Room_Canonballs' and 'Can_Push_Snowpeak_Double_Freezard_Room_Block' + Exits: + Snowpeak Ruins Double Freezard Room Behind Freezard: Can_Defeat_Freezard + Snowpeak Ruins Caged Freezard Room Second Floor: "'Can_Push_Snowpeak_Double_Freezard_Room_Block'" + Snowpeak Ruins Second Floor Mini Freezard Room: Can_Open_Doors and (count(Snowpeak_Ruins_Small_Key, 4) or Small_Keys == Keysy) + +- Name: Snowpeak Ruins Double Freezard Room Behind Freezard + Region: Snowpeak Ruins + Exits: + Snowpeak Ruins West Courtyard North Balcony: Can_Open_Doors + Snowpeak Ruins Double Freezard Room: Can_Defeat_Freezard + +# SNOWPEAK RUINS CHAPEL + +- Name: Snowpeak Ruins Chapel + Region: Snowpeak Ruins + Locations: + Snowpeak Ruins Chapel Chest: Can_Defeat_Chilfos and Can_Open_Doors + Exits: + Snowpeak Ruins West Courtyard North Balcony: Can_Open_Doors + +# SNOWPEAK RUINS BOSS ROOM + +- Name: Snowpeak Ruins Boss Room + Events: + Can Complete Snowpeak Ruins: Can_Defeat_Blizzeta + Locations: + Snowpeak Ruins Blizzeta Heart Container: Can_Defeat_Blizzeta + Snowpeak Ruins Dungeon Reward: Can_Defeat_Blizzeta + Exits: + Snowpeak Summit Lower: Can_Defeat_Blizzeta \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Temple of Time.yaml b/src/dusk/randomizer/data/world/dungeons/Temple of Time.yaml new file mode 100644 index 0000000000..84d6d01dd0 --- /dev/null +++ b/src/dusk/randomizer/data/world/dungeons/Temple of Time.yaml @@ -0,0 +1,205 @@ +# Bringing down the giant statue is handled via a chain of events for each +# individual traversal between teleporters. + +# TEMPLE OF TIME ENTRANCE + +- Name: Temple of Time Entrance + Region: Temple of Time + Dungeon Start Area: True + Events: + Can Refill Lantern Oil: Nothing + Can Open Door of Time: Dominion_Rod and 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Entrance' + Locations: + Temple of Time Lobby Lantern Chest: Lantern + Temple of Time First Hint Sign: Nothing + Exits: + Temple of Time Yellow Gates Corridor Near Entrance: Temple_of_Time_Small_Key or Small_Keys == Keysy + Temple of Time Entrance Near Crumbling Corridor: Open_Door_of_Time == On or 'Can_Open_Door_of_Time' + Sacred Grove Past Behind Window: Nothing + +- Name: Temple of Time Entrance Near Crumbling Corridor + Region: Temple of Time + Exits: + Temple of Time Crumbling Corridor Near Entrance: Nothing + Temple of Time Entrance: "'Can_Open_Door_of_Time'" + +# TEMPLE OF TIME YELLOW GATES CORRIDOR + +- Name: Temple of Time Yellow Gates Corridor Near Entrance + Region: Temple of Time + Locations: + Temple of Time First Staircase Gohma Gate Chest: Nothing + Exits: + Temple of Time Yellow Gates Corridor Near Central Mechnical Platform Room: Clawshot or Gale_Boomerang or Bow or Ball_and_Chain + Temple of Time Entrance: Nothing + +- Name: Temple of Time Yellow Gates Corridor Near Central Mechnical Platform Room + Region: Temple of Time + Events: + Can Teleport Giant Statue to Temple of Time Entrance: Dominion_Rod and 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Yellow_Gates_Corridor' + Locations: + Temple of Time First Staircase Window Chest: Nothing + Temple of Time First Staircase Armos Chest: Can_Defeat_Armos + Exits: + Temple of Time Central Mechanical Platform Room Bottom: Human_Link # To pick up statues + Temple of Time Yellow Gates Corridor Near Entrance: Clawshot or 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Entrance' + +# TEMPLE OF TIME CENTRAL MECHANICAL PLATFORM ROOM + +- Name: Temple of Time Central Mechanical Platform Room Bottom + Region: Temple of Time + Locations: + Temple of Time Poe Behind Gate: Dominion_Rod and Can_Use_Senses + Exits: + Temple of Time Central Mechanical Platform Room Top: Spinner + Temple of Time Yellow Gates Corridor Near Central Mechnical Platform Room: Nothing + +- Name: Temple of Time Central Mechanical Platform Room Top + Region: Temple of Time + Events: + Can Teleport Giant Statue to Temple of Time Yellow Gates Corridor: Dominion_Rod and 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Central_Mechanical_Platform' + Exits: + Temple of Time Central Mechanical Platform Room Near Armos Antechamber: Human_Link # To pick up statues + Temple of Time Moving Walls Corridor Near Central Platform Room: count(Temple_of_Time_Small_Key, 2) or Small_Keys == Keysy + Temple of Time Central Mechanical Platform Room Bottom: Nothing + +- Name: Temple of Time Central Mechanical Platform Room Near Armos Antechamber + Region: Temple of Time + Exits: + Temple of Time Armos Antechamber: Nothing + Temple of Time Central Mechanical Platform Room Top: Nothing + +# TEMPLE OF TIME ARMOS ANTECHAMBER + +- Name: Temple of Time Armos Antechamber + Region: Temple of Time + Locations: + Temple of Time Armos Antechamber East Chest: Can_Defeat_Armos + Temple of Time Armos Antechamber North Chest: Nothing + Temple of Time Armos Antechamber Statue Chest: Dominion_Rod + Exits: + Temple of Time Central Mechanical Platform Room Near Armos Antechamber: Nothing + +# TEMPLE OF TIME MOVING WALLS CORRIDOR + +- Name: Temple of Time Moving Walls Corridor Near Central Platform Room + Region: Temple of Time + Exits: + Temple of Time Moving Walls Corridor Middle: Bow or Clawshot + Temple of Time Central Mechanical Platform Room Top: Nothing + +- Name: Temple of Time Moving Walls Corridor Middle # This area's logic assumes you already have Bow or Clawshot to get here + Region: Temple of Time + Events: + Can Teleport Giant Statue to Temple of Time Central Mechanical Platform: Dominion_Rod and 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Moving_Walls_Corridor' + Locations: + Temple of Time Moving Wall Beamos Room Chest: Nothing + Temple of Time Moving Wall Dinalfos Room Chest: Dominion_Rod + Temple of Time Second Hint Sign: Nothing + Exits: + Temple of Time Moving Walls Corridor Near Scales Room: Bow or Clawshot + Temple of Time Moving Walls Corridor Near Central Platform Room: Bow or Clawshot + +- Name: Temple of Time Moving Walls Corridor Near Scales Room + Region: Temple of Time + Exits: + Temple of Time Scale Room Bottom: Nothing + Temple of Time Moving Walls Corridor Middle: Bow or Clawshot + +# TEMPLE OF TIME SCALES ROOM + +- Name: Temple of Time Scale Room Bottom + Region: Temple of Time + Locations: + Temple of Time Scales Gohma Chest: Can_Defeat_Young_Gohma and Can_Defeat_Baby_Gohma + Exits: + Temple of Time Scales Room Top: Clawshot and Spinner + Temple of Time Scales Room Near Spike Trap Corridor: Human_Link # Need to throw a statue + +- Name: Temple of Time Scales Room Top + Region: Temple of Time + Locations: + Temple of Time Scales Upper Chest: Nothing + Temple of Time Poe Above Scales: Can_Use_Senses + Exits: + Temple of Time Floor Switch Puzzle Room: Nothing + Temple of Time Scales Room Near Spike Trap Corridor: Nothing + Temple of Time Scale Room Bottom: Nothing + +- Name: Temple of Time Scales Room Near Spike Trap Corridor + Region: Temple of Time + Events: + Can Teleport Giant Statue to Temple of Time Moving Walls Corridor: Dominion_Rod and 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Scales_Room' + Exits: + Temple of Time Spike Trap Corridor Near Scales Room: Nothing + Temple of Time Scale Room Bottom: Nothing + +# TEMPLE OF TIME FLOOR SWITCH PUZZLE ROOM + +- Name: Temple of Time Floor Switch Puzzle Room + Region: Temple of Time + Locations: + Temple of Time Big Key Chest: Can_Defeat_Helmasaur and Clawshot + Temple of Time Floor Switch Puzzle Room Upper Chest: Clawshot + Exits: + Temple of Time Scales Room Top: Nothing + +# TEMPLE OF TIME SPIKE TRAP CORRIDOR + +- Name: Temple of Time Spike Trap Corridor Near Scales Room + Region: Temple of Time + Locations: + Temple of Time Gilloutine Chest: Nothing + Exits: + Temple of Time Spike Trap Corridor Baby Gohma Area: Human_Link # to pickup pot/statue + Temple of Time Scales Room Near Spike Trap Corridor: Nothing + +- Name: Temple of Time Spike Trap Corridor Baby Gohma Area + Region: Temple of Time + Locations: + Temple of Time Chest Before Darknut: Can_Defeat_Armos and Can_Defeat_Baby_Gohma and Can_Defeat_Young_Gohma + Exits: + Temple of Time Spike Trap Corridor Near Miniboss Room: Can_Defeat_Armos + Temple of Time Spike Trap Corridor Near Scales Room: "'Can_Teleport_Giant_Statue_to_Temple_of_Time_Scales_Room'" + +- Name: Temple of Time Spike Trap Corridor Near Miniboss Room + Region: Temple of Time + Events: + Can Teleport Giant Statue to Temple of Time Scales Room: Dominion_Rod and 'Can_Teleport_Giant_Statue_to_Temple_of_Time_Spike_Trap_Corridor' + Exits: + Darknut Miniboss Room: count(Temple_of_Time_Small_Key, 3) or Small_Keys == Keysy + Temple of Time Spike Trap Corridor Baby Gohma Area: "'Can_Teleport_Giant_Statue_to_Temple_of_Time_Scales_Room'" + +# DARKNUT MINIBOSS ROOM + +- Name: Darknut Miniboss Room + Events: + Can Teleport Giant Statue to Temple of Time Spike Trap Corridor: Dominion_Rod and Open_Door_of_Time == Off + Locations: + Temple of Time Darknut Chest: Can_Defeat_Darknut + Exits: + Temple of Time Spike Trap Corridor Near Miniboss Room: "'Can_Teleport_Giant_Statue_to_Temple_of_Time_Spike_Trap_Corridor'" + +# TEMPLE OF TIME CRUMBLING CORRIDOR + +- Name: Temple of Time Crumbling Corridor Near Entrance + Region: Temple of Time + Exits: + Temple of Time Crumbling Corridor Near Boss Door: Dominion_Rod + Temple of Time Entrance Near Crumbling Corridor: Nothing + +- Name: Temple of Time Crumbling Corridor Near Boss Door + Region: Temple of Time + Exits: + Temple of Time Boss Room: Temple_of_Time_Big_Key or Big_Keys == Keysy + +# TEMPLE OF TIME BOSS ROOM + +- Name: Temple of Time Boss Room + Events: + Can Complete Temple of Time: Can_Defeat_Armogohma + Locations: + Temple of Time Armogohma Heart Container: Can_Defeat_Armogohma + Temple of Time Dungeon Reward: Can_Defeat_Armogohma + Exits: + Sacred Grove Past Behind Window: Can_Defeat_Armogohma \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/overworld/Eldin Province.yaml b/src/dusk/randomizer/data/world/overworld/Eldin Province.yaml new file mode 100644 index 0000000000..2a12ad48c4 --- /dev/null +++ b/src/dusk/randomizer/data/world/overworld/Eldin Province.yaml @@ -0,0 +1,594 @@ + +# KAKARIKO GORGE + +- Name: Kakariko Gorge + Map Sector: Eldin Province + Region: Kakariko Gorge + Twilight: Eldin + Can Warp: True + Locations: + Kakariko Gorge Owl Statue Chest: Restored_Dominion_Rod + Kakariko Gorge Double Clawshot Chest: Double_Clawshots + Kakariko Gorge Spire Heart Piece: Clawshot or Gale_Boomerang + Kakariko Gorge Owl Statue Sky Character: Restored_Dominion_Rod + Kakariko Gorge Male Pill Bug: Nothing + Kakariko Gorge Female Pill Bug: Nothing + Kakariko Gorge Poe: Can_Use_Senses and Can_Complete_MDH and Can_Complete_All_Twilight and Night + Kakariko Gorge Hint Sign: Nothing + Exits: + Kakariko Gorge Cave Entrance: Can_Smash + Kakariko Gorge Keese Grotto: Can_Dig + Kakariko Gorge Behind Gate: Nothing + Eldin Field: Can_Smash + Faron Field: Nothing + +- Name: Kakariko Gorge Cave Entrance + Map Sector: Eldin Province + Region: Kakariko Gorge + Twilight: Eldin + Can Warp: True + Exits: + Kakariko Gorge: Can_Smash + Eldin Lantern Cave: Nothing + +- Name: Eldin Lantern Cave + Locations: + Eldin Lantern Cave First Chest: Can_Break_Webs + Eldin Lantern Cave Lantern Chest: Lantern + Eldin Lantern Cave Second Chest: Can_Break_Webs + Eldin Lantern Cave Poe: Can_Break_Webs and Can_Use_Senses + Exits: + Kakariko Gorge Cave Entrance: Nothing + +- Name: Kakariko Gorge Keese Grotto + Exits: + Kakariko Gorge: Nothing + +- Name: Kakariko Gorge Behind Gate + Region: Kakariko Gorge + Twilight: Eldin + Exits: + Lower Kakariko Village: Nothing + Kakariko Gorge: Wolf_Link or Gate_Keys or Small_Keys == Keysy or Twilight + +# KAKARIKO VILLAGE & GRAVEYARD + +- Name: Lower Kakariko Village + Map Sector: Eldin Province + Region: Kakariko Village + Twilight: Eldin + Can Warp: True + Events: + Can Start Springwater Rush: Can_Talk_to_Springwater_Goron + Locations: + Eldin Spring Underwater Chest: Can_Smash + Kakariko Village Bomb Rock Spire Heart Piece: Bombs and Gale_Boomerang + Kakariko Village Hint Sign: Nothing + Exits: + Renados Sanctuary Front West Door Exterior: Nothing + Renados Sanctuary Front East Door Exterior: Nothing + Renados Sanctuary Back West Door Exterior: Nothing + Renados Sanctuary Back East Door Exterior: Nothing + Kakariko Renados Sanctuary: Twilight + Kakariko Graveyard: Nothing + Kakariko Malo Mart: Twilight or (Can_Open_Doors and Day) + Elde Inn North Door Exterior: Nothing + Elde Inn South Door Exterior: Nothing + Kakariko Elde Inn: Twilight + Kakariko Bug House Door: Nothing + Kakariko Bug House Ceiling Hole: Nothing + Kakariko Barnes Bomb Shop Lower: Twilight or (Can_Open_Doors and Day) + Upper Kakariko Village: Can_Smash or ('Can_Complete_Goron_Mines' and Day) + Death Mountain Near Kakariko: Nothing + Kakariko Village Behind Gate: Not_Twilight + Kakariko Gorge Behind Gate: Nothing + +- Name: Renados Sanctuary Front East Door Exterior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Front East Door Interior: Can_Open_Doors + Lower Kakariko Village: Nothing + +- Name: Renados Sanctuary Front West Door Exterior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Front West Door Interior: Can_Open_Doors + Lower Kakariko Village: Nothing + +- Name: Renados Sanctuary Back East Door Exterior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Back East Door Interior: Can_Open_Doors + Lower Kakariko Village: Nothing + +- Name: Renados Sanctuary Back West Door Exterior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Back West Door Interior: Can_Open_Doors + Lower Kakariko Village: Nothing + +- Name: Renados Sanctuary Front East Door Interior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Front East Door Exterior: Can_Open_Doors + Kakariko Renados Sanctuary: Nothing + +- Name: Renados Sanctuary Front West Door Interior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Front West Door Exterior: Can_Open_Doors + Kakariko Renados Sanctuary: Nothing + +- Name: Renados Sanctuary Back East Door Interior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Back East Door Exterior: Can_Open_Doors + Kakariko Renados Sanctuary: Nothing + +- Name: Renados Sanctuary Back West Door Interior + Twilight: Eldin + Can Transform: Never + Exits: + Renados Sanctuary Back West Door Exterior: Can_Open_Doors + Kakariko Renados Sanctuary: Nothing + +- Name: Kakariko Renados Sanctuary + Twilight: Eldin + Can Transform: If Transform Anywhere + Events: + Can Show Ilia Wooden Statue: Wooden_Statue + Can Show Ilia Ilias Charm: Ilias_Charm + Locations: + Renados Letter: "'Can_Complete_Temple_of_Time'" + Ilia Memory Reward: "'Can_Show_Ilia_Ilias_Charm'" + Exits: + Kakariko Renados Sanctuary Basement: Nothing + Renados Sanctuary Front East Door Interior: Nothing + Renados Sanctuary Front West Door Interior: Nothing + Renados Sanctuary Back East Door Interior: Nothing + Renados Sanctuary Back West Door Interior: Nothing + +- Name: Kakariko Renados Sanctuary Basement + Twilight: Eldin + Locations: + Sanctuary Basement Twilit Insect 1: Can_Defeat_Eldin_Twilit_Insect + Sanctuary Basement Twilit Insect 2: Can_Defeat_Eldin_Twilit_Insect + Sanctuary Basement Twilit Insect 3: Can_Defeat_Eldin_Twilit_Insect + Exits: + Kakariko Renados Sanctuary: Human_Link + +- Name: Kakariko Graveyard + Map Sector: Eldin Province + Region: Kakariko Graveyard + Twilight: Eldin + Can Warp: True + Events: + Can Follow Rutella: Gate_Keys or Small_Keys == Keysy + Locations: + Kakariko Graveyard Lantern Chest: Lantern + Kakariko Graveyard Male Ant: Nothing + Kakariko Graveyard Grave Poe: Can_Use_Senses and Night + Kakariko Graveyard Open Poe: Can_Use_Senses and Night + Kakariko Graveyard Golden Wolf: "'Howl_at_Snowpeak_Mountain_Howling_Stone'" + Exits: + Kakariko Graveyard Pond: "'Can_Follow_Rutella'" + Lower Kakariko Village: Nothing + +- Name: Kakariko Graveyard Pond + Map Sector: Eldin Province + Region: Kakariko Graveyard + Twilight: Eldin + Can Warp: True + Locations: + Rutelas Blessing: "'Can_Follow_Rutella'" + Gift From Ralis: Asheis_Sketch and 'Can_Follow_Rutella' + Kakariko Graveyard Hint Sign: Nothing + Exits: + Lake Hylia: Water_Bombs and (Iron_Boots or Zora_Armor) + Kakariko Graveyard: "'Can_Follow_Rutella'" + +- Name: Kakariko Malo Mart + Twilight: Eldin + Can Transform: If Transform Anywhere + Events: + Can Buy Wooden Shield: "'Can_Farm_Lots_of_Rupees'" + Can Fund Malo Mart: "'Can_Farm_Lots_of_Rupees'" + Locations: + Kakariko Village Malo Mart Hylian Shield: "'Can_Farm_Lots_of_Rupees'" + Kakariko Village Malo Mart Hawkeye: "'Can_Farm_Lots_of_Rupees'" + Kakariko Village Malo Mart Red Potion: "'Can_Farm_Lots_of_Rupees'" + Kakariko Village Malo Mart Wooden Shield: "'Can_Farm_Lots_of_Rupees'" + Exits: + Lower Kakariko Village: Nothing + +- Name: Elde Inn North Door Exterior + Twilight: Eldin + Can Transform: Never + Exits: + Elde Inn North Door Interior: Can_Open_Doors and Day + Lower Kakariko Village: Nothing + +- Name: Elde Inn South Door Exterior + Twilight: Eldin + Can Transform: Never + Exits: + Elde Inn South Door Interior: Can_Open_Doors and Day + Lower Kakariko Village: Nothing + +- Name: Elde Inn North Door Interior + Twilight: Eldin + Can Transform: Never + Exits: + Elde Inn North Door Exterior: Can_Open_Doors and Day + Kakariko Elde Inn: Nothing + +- Name: Elde Inn South Door Interior + Twilight: Eldin + Can Transform: Never + Exits: + Elde Inn South Door Exterior: Can_Open_Doors and Day + Kakariko Elde Inn: Nothing + +- Name: Kakariko Elde Inn + Twilight: Eldin + Locations: + Kakariko Inn Chest: Nothing + Kakariko Inn Twilit Insect: Can_Defeat_Eldin_Twilit_Insect + Exits: + Elde Inn North Door Interior: Nothing + Elde Inn South Door Interior: Nothing + +- Name: Kakariko Bug House Door + Twilight: Eldin + Can Transform: Never + Exits: + Kakariko Bug House: Can_Open_Doors + Lower Kakariko Village: Nothing + +- Name: Kakariko Bug House Ceiling Hole + Twilight: Eldin + Can Transform: Never + Exits: + Kakariko Bug House: Nothing + Lower Kakariko Village: Nothing + +- Name: Kakariko Bug House + Twilight: Eldin + Locations: + Kakariko Village Female Ant: Nothing + Kakariko Bug House Twilit Insect: Can_Defeat_Eldin_Twilit_Insect + Exits: + Kakariko Bug House Door: Can_Open_Doors + Kakariko Bug House Ceiling Hole: Can_Midna_Jump or Twilight + +- Name: Kakariko Barnes Bomb Shop Lower + Twilight: Eldin + Events: + Can Refill Regular Bombs: "'Can_Farm_Lots_of_Rupees'" + Can Refill Water Bombs: "'Can_Farm_Lots_of_Rupees'" + Locations: + Barnes Bomb Bag: "'Can_Farm_Lots_of_Rupees'" + Exits: + Kakariko Barnes Bomb Shop Upper: Nothing + Lower Kakariko Village: Can_Open_Doors + +- Name: Kakariko Barnes Bomb Shop Upper + Twilight: Eldin + Locations: + Barnes Bomb Shop Twilit Insect: Can_Defeat_Eldin_Twilit_Insect and Can_Survive_One_Bonk + Exits: + Kakariko Barnes Bomb Shop Lower: Nothing + Upper Kakariko Village: Nothing + +- Name: Upper Kakariko Village + Map Sector: Eldin Province + Region: Kakariko Village + Twilight: Eldin + Can Warp: True + Locations: + Kakariko Village Bomb Shop Poe: Can_Use_Senses and Night + Kakariko Village Watchtower Poe: Can_Use_Senses and Night + Kakariko Watchtower Alcove Chest: Can_Smash + Kakariko Destroyed Building Twilit Insect 1: Can_Defeat_Eldin_Twilit_Insect + Kakariko Destroyed Building Twilit Insect 2: Can_Defeat_Eldin_Twilit_Insect + Kakariko Destroyed Building Twilit Insect 3: Can_Defeat_Eldin_Twilit_Insect + Exits: + Kakariko Watchtower Lower Door: Nothing + Kakariko Watchtower Dig Spot: Nothing + Kakariko Top of Watchtower: Day and 'Can_Complete_Goron_Mines' + Kakariko Barnes Bomb Shop Upper: Nothing + Lower Kakariko Village: Nothing + +- Name: Kakariko Watchtower Lower Door + Twilight: Eldin + Can Transform: Never + Exits: + Kakariko Watchtower Lower Interior: Can_Open_Doors + Upper Kakariko Village: Nothing + +- Name: Kakariko Watchtower Dig Spot + Twilight: Eldin + Can Transform: Never + Exits: + Kakariko Watchtower Lower Interior: Can_Dig or Twilight + Upper Kakariko Village: Nothing + +- Name: Kakariko Watchtower Lower Interior + Twilight: Eldin + Exits: + Kakariko Watchtower Upper Interior: Can_Climb_Ladders + Kakariko Watchtower Lower Door: Can_Open_Doors + Kakariko Watchtower Dig Spot: Can_Dig or Twilight + +- Name: Kakariko Watchtower Upper Interior + Twilight: Eldin + Locations: + Kakariko Watchtower Chest: Nothing + Exits: + Kakariko Watchtower Lower Interior: Nothing + Kakariko Top of Watchtower: Can_Open_Doors + +- Name: Kakariko Top of Watchtower + Map Sector: Eldin Province + Region: Kakariko Village + Twilight: Eldin + Can Warp: True + Locations: + Talo Sharpshooting: Day and Can_Climb_Ladders and Bow and 'Can_Complete_Goron_Mines' + Exits: + Kakariko Watchtower Upper Interior: Can_Open_Doors + +- Name: Kakariko Village Behind Gate + Map Sector: Eldin Province + Region: Kakariko Village + Twilight: Eldin + Can Warp: True + Exits: + Eldin Field: Nothing + Lower Kakariko Village: Gate_Keys or Small_Keys == Keysy + +# DEATH MOUNTAIN + +- Name: Death Mountain Near Kakariko + Map Sector: Eldin Province + Region: Death Mountain + Twilight: Eldin + Can Warp: True + Exits: + Death Mountain Trail: Iron_Boots or 'Can_Complete_Goron_Mines' or Twilight + Lower Kakariko Village: Nothing + +- Name: Death Mountain Trail + Map Sector: Eldin Province + Region: Death Mountain + Twilight: Eldin + Can Warp: True + Events: + Howl at Death Mountain Howling Stone: Can_Howl + Locations: + Death Mountain Trail Twilit Insect Near Howling Stone: Can_Defeat_Eldin_Twilit_Insect + Death Mountain Alcove Chest: Clawshot or 'Can_Complete_Goron_Mines' + Death Mountain Trail Poe: Can_Use_Senses and 'Can_Complete_Goron_Mines' + Exits: + Death Mountain Volcano: Nothing + Death Mountain Near Kakariko: Nothing + +- Name: Death Mountain Volcano + Map Sector: Eldin Province + Region: Death Mountain + Twilight: Eldin + Can Warp: True + Locations: + Death Mountain Trail Twilit Insect on Wall: Can_Defeat_Eldin_Twilit_Insect + Death Mountain Trail Twilit Insect in Hot Spring: Can_Defeat_Eldin_Twilit_Insect + Exits: + Death Mountain Lower Elevator: Goron_Mines_Entrance == Open or 'Can_Access_Death_Mountain_Lower_Elevator' + Death Mountain Outside Sumo Hall: Iron_Boots and (Can_Defeat_Goron or 'Can_Complete_Goron_Mines') + Death Mountain Trail: Nothing + +- Name: Death Mountain Lower Elevator + Map Sector: Eldin Province + Region: Death Mountain + Twilight: Eldin + Can Warp: True + Exits: + Death Mountain Sumo Hall Elevator: Iron_Boots + Death Mountain Volcano: Nothing + +- Name: Death Mountain Outside Sumo Hall + Map Sector: Eldin Province + Region: Death Mountain + Twilight: Eldin + Can Warp: True + Exits: + Death Mountain Sumo Hall: Nothing + Death Mountain Volcano: Nothing + +- Name: Death Mountain Sumo Hall Elevator + Map Sector: Eldin Province + Region: Death Mountain + Twilight: Eldin + Can Warp: True + Exits: + Death Mountain Sumo Hall: Goron_Mines_Entrance != Closed or 'Can_Wrestle_Goron_Elder' + Death Mountain Lower Elevator: Iron_Boots + +- Name: Death Mountain Sumo Hall + Can Transform: If Transform Anywhere + Events: + Can Wrestle Goron Elder: Iron_Boots + Exits: + Death Mountain Sumo Hall Goron Mines Tunnel: Goron_Mines_Entrance != Closed or 'Can_Wrestle_Goron_Elder' + Death Mountain Sumo Hall Elevator: Goron_Mines_Entrance != Closed or 'Can_Wrestle_Goron_Elder' + Death Mountain Outside Sumo Hall: Nothing + +- Name: Death Mountain Sumo Hall Goron Mines Tunnel + Exits: + Goron Mines Entrance: Nothing + Death Mountain Sumo Hall: Goron_Mines_Entrance != Closed or 'Can_Wrestle_Goron_Elder' + +# ELDIN FIELD + +- Name: Eldin Field + Map Sector: Eldin Province + Region: Eldin Field + Twilight: Eldin + Can Warp: True + Events: + Can Finish Goron Springwater Rush: "'Can_Start_Springwater_Rush' and 'Can_Fund_Malo_Mart'" + Locations: + Eldin Field Bomb Rock Chest: Can_Smash + Bridge of Eldin Owl Statue Chest: Restored_Dominion_Rod + Goron Springwater Rush: "'Can_Finish_Goron_Springwater_Rush'" + Eldin Field Male Grasshopper: Nothing + Eldin Field Female Grasshopper: Nothing + Bridge of Eldin Male Phasmid: Clawshot or Gale_Boomerang + Eldin Field Hint Sign: Nothing + Exits: + # Only allow logical access to the other side if we've already been there + Eldin Field Near Castle Town: "'Can_Access_Eldin_Field_Near_Castle_Town'" + Eldin Field Bomskit Grotto: Can_Dig + Eldin Field Water Bomb Fish Grotto: Can_Dig + Eldin Field North of Bridge: Nothing + Kakariko Gorge: Can_Smash + Kakariko Village Behind Gate: Nothing + +- Name: Eldin Field Near Castle Town + Map Sector: Eldin Province + Region: Eldin Field + Twilight: Eldin + Can Warp: True + Exits: + Outside Castle Town East: Nothing + Eldin Field: "'Can_Fund_Malo_Mart'" + +- Name: Eldin Field Bomskit Grotto + Locations: + Eldin Field Bomskit Grotto Left Chest: Nothing + Eldin Field Bomskit Grotto Lantern Chest: Lantern + Exits: + Eldin Field: Nothing + +- Name: Eldin Field Water Bomb Fish Grotto + Events: + Can Refill Water Bombs: Fishing_Rod + Locations: + Eldin Field Water Bomb Fish Grotto Chest: Nothing + Exits: + Eldin Field: Nothing + +- Name: Eldin Field North of Bridge + Map Sector: Eldin Province + Region: North Eldin + Twilight: Eldin + Can Warp: True + Locations: + Bridge of Eldin Owl Statue Sky Character: Restored_Dominion_Rod + Bridge of Eldin Female Phasmid: Clawshot or Gale_Boomerang + Exits: + Eldin Field Lava Cave Upper Ledge: Clawshot + North Eldin Field: Can_Smash + Eldin Field: Nothing + +- Name: Eldin Field Lava Cave Upper Ledge + Map Sector: Eldin Province + Region: North Eldin + Twilight: Eldin + Can Warp: True + Exits: + Eldin Field Lava Cave Upper: Nothing + Eldin Field North of Bridge: Nothing + +- Name: Eldin Field Lava Cave Upper + Locations: + Eldin Stockcave Upper Chest: Iron_Boots + Exits: + Eldin Field Lava Cave Lower: Iron_Boots + Eldin Field Lava Cave Upper Ledge: Nothing + +- Name: Eldin Field Lava Cave Lower + Locations: + Eldin Stockcave Lantern Chest: Lantern + Eldin Stockcave Lowest Chest: Nothing + Exits: + Eldin Field Lava Cave Lower Ledge: Nothing + +- Name: Eldin Field Lava Cave Lower Ledge + Map Sector: Eldin Province + Region: North Eldin + Twilight: Eldin + Can Warp: True + Exits: + Eldin Field North of Bridge: Clawshot + Eldin Field Lava Cave Lower: Nothing + +- Name: North Eldin Field + Map Sector: Eldin Province + Region: North Eldin + Twilight: Eldin + Can Warp: True + Locations: + North Eldin Field Hint Sign: Nothing + Exits: + Eldin Field Grotto Platform: Spinner + Eldin Field Outside Hidden Village: "'Can_Show_Ilia_Wooden_Statue'" + Lanayru Field: Nothing + Eldin Field North of Bridge: Nothing + +- Name: Eldin Field Grotto Platform + Map Sector: Eldin Province + Region: North Eldin + Twilight: Eldin + Can Warp: True + Exits: + Eldin Field Stalfos Grotto: Can_Dig + North Eldin Field: Spinner # TODO: Check Savewarp + +- Name: Eldin Field Stalfos Grotto + Locations: + Eldin Field Stalfos Grotto Right Small Chest: Nothing + Eldin Field Stalfos Grotto Left Small Chest: Nothing + Eldin Field Stalfos Grotto Stalfos Chest: Can_Defeat_Stalfos + Exits: + Eldin Field Grotto Platform: Nothing + +- Name: Eldin Field Outside Hidden Village + Map Sector: Eldin Province + Region: North Eldin + Twilight: Eldin + Can Warp: True + Exits: + Hidden Village: Nothing + North Eldin Field: "'Can_Show_Ilia_Wooden_Statue'" + +# HIDDEN VILLAGE + +- Name: Hidden Village + Map Sector: Eldin Province + Region: Hidden Village + Can Warp: True + Can Transform: If Transform Anywhere + Events: + Howl at Hidden Village Howling Stone: Can_Howl + Locations: + Cats Hide and Seek Minigame: Can_Talk_to_Animals and Bow and Clawshot and 'Can_Show_Ilia_Ilias_Charm' + Ilia Charm: Bow + Hidden Village Poe: Night and Can_Talk_to_Animals and Bow and Clawshot and 'Can_Show_Ilia_Ilias_Charm' + Hidden Village Hint Sign: Nothing + Exits: + Hidden Village Impaz House: Bow and Dominion_Rod and Can_Open_Doors + Eldin Field Outside Hidden Village: Nothing + +- Name: Hidden Village Impaz House + Can Transform: If Transform Anywhere + Locations: + Skybook From Impaz: Bow and Dominion_Rod and 'Can_Access_Hidden_Village' + Exits: + Hidden Village: Nothing \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/overworld/Faron Province.yaml b/src/dusk/randomizer/data/world/overworld/Faron Province.yaml new file mode 100644 index 0000000000..5234d97ac7 --- /dev/null +++ b/src/dusk/randomizer/data/world/overworld/Faron Province.yaml @@ -0,0 +1,385 @@ + +# SOUTH FARON WOODS + +- Name: South Faron Woods + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Events: + Can Refill Lantern Oil: "'Can_Farm_Rupees'" + Locations: + Coro Bottle: Can_Complete_Prologue + South Faron Woods Twilit Insect in Tunnel 1: Can_Defeat_Faron_Twilit_Insect + South Faron Woods Twilit Insect in Tunnel 2: Can_Defeat_Faron_Twilit_Insect + South Faron Woods Hint Sign: Nothing + Exits: + South Faron Woods Coros Ledge: Can_Midna_Jump or Twilight + Faron Woods Coros House Lower: Can_Open_Doors + South Faron Woods Behind Gate: Nothing # Coro Key is Vanilla if forest is closed + South Faron Woods Owl Statue Area: Can_Smash + Faron Field: Can_Clear_Forest + Ordon Bridge: Nothing + +- Name: South Faron Woods Coros Ledge + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Exits: + South Faron Woods: Nothing + Faron Woods Coros House Upper: Nothing + +- Name: Faron Woods Coros House Upper + Twilight: Faron + Exits: + Faron Woods Coros House Lower: Nothing + South Faron Woods Coros Ledge: Nothing + +- Name: Faron Woods Coros House Lower + Twilight: Faron + Locations: + Faron Woods Coros House Twilit Insect 1: Can_Defeat_Faron_Twilit_Insect + Faron Woods Coros House Twilit Insect 2: Can_Defeat_Faron_Twilit_Insect + Exits: + Faron Woods Coros House Upper: Wolf_Link # Only wolf link can climb the ledge + South Faron Woods: Can_Open_Doors + +- Name: South Faron Woods Owl Statue Area + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Events: + Can Move Faron Woods Owl Statue: Can_Clear_Forest and Restored_Dominion_Rod + Locations: + Faron Woods Owl Statue Sky Character: Can_Clear_Forest and Restored_Dominion_Rod + Exits: + South Faron Woods Above Owl Statue: Can_Midna_Jump and 'Can_Move_Faron_Woods_Owl_Statue' + South Faron Woods: Can_Smash + +- Name: South Faron Woods Above Owl Statue + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Exits: + Mist Area Near Owl Statue Chest: Nothing + South Faron Woods Owl Statue Area: Nothing + +- Name: South Faron Woods Behind Gate + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + South Faron Woods Twilit Insect Behind Gate: Can_Defeat_Faron_Twilit_Insect + Exits: + Faron Woods Cave South: Nothing + South Faron Woods: Can_Dig or Can_Clear_Forest or 'Can_Access_South_Faron_Woods' + +# FARON WOODS CAVE + +- Name: Faron Woods Cave South + Twilight: Faron + Exits: + Faron Woods Cave: Nothing + South Faron Woods Behind Gate: Nothing + +- Name: Faron Woods Cave + Twilight: Faron + Locations: + South Faron Cave Chest: Nothing + Exits: + Faron Woods Cave North: Nothing + Faron Woods Cave South: Nothing + +- Name: Faron Woods Cave North + Twilight: Faron + Exits: + Mist Area Near Faron Woods Cave: Nothing + Faron Woods Cave: Nothing + +# MIST AREA + +- Name: Mist Area Near Faron Woods Cave + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + Faron Mist Twilit Insect on Wall: Can_Defeat_Faron_Twilit_Insect + Exits: + Mist Area Inside Mist: Lantern + Mist Area Under Owl Statue Chest: Can_Midna_Jump or Twilight + Faron Woods Cave North: Nothing + +- Name: Mist Area Inside Mist + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + Faron Mist Stump Chest: Lantern and Can_Complete_Prologue + Faron Mist North Chest: Lantern and Can_Complete_Prologue + Faron Mist South Chest: Lantern and Can_Complete_Prologue + Exits: + Mist Area Near Faron Woods Cave: Lantern + Mist Area Under Owl Statue Chest: Lantern + Mist Area Outside Faron Mist Cave: Lantern + Mist Area Near North Faron Woods: Lantern + +- Name: Mist Area Under Owl Statue Chest + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Exits: + Mist Area Inside Mist: Lantern + Mist Area Center Stump: Can_Midna_Jump or Twilight + +- Name: Mist Area Near Owl Statue Chest + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + Faron Woods Owl Statue Chest: Nothing + Exits: + Mist Area Under Owl Statue Chest: Nothing + South Faron Woods Above Owl Statue: Nothing + +- Name: Mist Area Center Stump + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + Faron Mist Poe: Can_Use_Senses and Can_Complete_Prologue + Faron Mist Twilit Insect on Center Stump 1: Can_Defeat_Faron_Twilit_Insect + Faron Mist Twilit Insect on Center Stump 2: Can_Defeat_Faron_Twilit_Insect + Exits: + Mist Area Inside Mist: Lantern + Mist Area Near North Faron Woods: Can_Midna_Jump or Twilight + +- Name: Mist Area Outside Faron Mist Cave + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Exits: + Mist Area Faron Mist Cave: Nothing + Mist Area Inside Mist: Lantern + +- Name: Mist Area Near North Faron Woods + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + Faron Mist Burrowing Twilit Insect 1: Can_Defeat_Faron_Twilit_Insect + Faron Mist Burrowing Twilit Insect 2: Can_Defeat_Faron_Twilit_Insect + Exits: + North Faron Woods: North_Faron_Woods_Gate_Key or Skip_Prologue == On + Mist Area Near Faron Woods Cave: Can_Midna_Jump or Twilight + Mist Area Inside Mist: Lantern + +- Name: Mist Area Faron Mist Cave + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Locations: + Faron Mist Cave Open Chest: Nothing + Faron Mist Cave Lantern Chest: Lantern + Exits: + Mist Area Outside Faron Mist Cave: Nothing + +# NORTH FARON WOODS + +- Name: North Faron Woods + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can_Warp: True + Can Change Time: True + Events: + Can Refill Lantern Oil: "'Can_Farm_Rupees'" + Can Refill Slingshot Seeds: Can_Defeat_Deku_Baba + Locations: + North Faron Woods Deku Baba Chest: Nothing + Faron Woods Golden Wolf: Nothing + North Faron Woods Twilit Insect 1: Can_Defeat_Faron_Twilit_Insect + North Faron Woods Twilit Insect 2: Can_Defeat_Faron_Twilit_Insect + Exits: + North Faron Lost Woods Ledge: Can_Midna_Jump + Forest Temple Entrance: Nothing + Mist Area Near North Faron Woods: Nothing + +- Name: North Faron Lost Woods Ledge + Map Sector: Faron Province + Region: Faron Woods + Twilight: Faron + Can Warp: True + Can Change Time: True + Events: + Howl at North Faron Woods Howling Stone: Can_Howl + Exits: + Lost Woods: Nothing + # In North Faron Woods, when you are near the Lost Woods entrance, + # you can save-warp to the main part of North Faron Woods. + North Faron Woods: Nothing + + +# SACRED GROVE + +- Name: Lost Woods + Map Sector: Faron Province + Region: Sacred Grove + Can Warp: True + Can Change Time: True + Events: + Can Refill Arrows: Nothing + Lost Woods Skull Kid: Can_Defeat_Skull_Kid + Locations: + Lost Woods Lantern Chest: Lantern + Lost Woods Waterfall Poe: Can_Use_Senses and Night + Exits: + Lost Woods Lower Battle Arena: Sacred_Grove_Does_Not_Require_Skull_Kid == On or ('Lost_Woods_Skull_Kid' and Wolf_Link) + Lost Woods Upper Battle Arena: Sacred_Grove_Does_Not_Require_Skull_Kid == On or ('Lost_Woods_Skull_Kid' and Wolf_Link) + North Faron Lost Woods Ledge: Nothing + +- Name: Lost Woods Lower Battle Arena + Map Sector: Faron Province + Region: Sacred Grove + Can Warp: True + Can Change Time: True + Events: + Can Smash Lost Woods Rock: Can_Smash + Locations: + Sacred Grove Spinner Chest: Spinner + Lost Woods Boulder Poe: Can_Use_Senses and (Can_Defeat_Skull_Kid or Sacred_Grove_Does_Not_Require_Skull_Kid == On) + Exits: + # Human Link can't dig into the grotto, so we need to hide away blowing up the rock behind an event. + # By requiring only wolf in the direct logic statement, this tells the search algorithm that only wolf is + # allowed to go through this exit. + Lost Woods Baba Serpent Grotto: Can_Dig and 'Can_Smash_Lost_Woods_Rock' + Sacred Grove Lower: Can_Defeat_Skull_Kid or Sacred_Grove_Does_Not_Require_Skull_Kid == On + +- Name: Lost Woods Upper Battle Arena + Map Sector: Faron Province + Region: Sacred Grove + Can Warp: True + Can Change Time: True + Exits: + Sacred Grove Before Block: Can_Defeat_Skull_Kid or Sacred_Grove_Does_Not_Require_Skull_Kid == On + +- Name: Lost Woods Baba Serpent Grotto + Locations: + Sacred Grove Baba Serpent Grotto Chest: Can_Defeat_Baba_Serpent and Can_Knock_Down_Hanging_Baba + Exits: + Lost Woods Lower Battle Arena: Nothing + +- Name: Sacred Grove Before Block + Map Sector: Faron Province + Region: Sacred Grove + Can Warp: True + Exits: + Sacred Grove Upper: Nothing + Lost Woods Upper Battle Arena: Nothing + +- Name: Sacred Grove Upper + Map Sector: Faron Province + Region: Sacred Grove + Can Warp: True + Locations: + Sacred Grove Hint Sign: Nothing + Exits: + Sacred Grove Lower: Nothing + Sacred Grove Past: Has_Sword_For_Temple_of_Time and 'Can_Access_Sacred_Grove_Lower' and Can_Defeat_Shadow_Beast + +- Name: Sacred Grove Lower + Map Sector: Faron Province + Region: Sacred Grove + Can Warp: True + Locations: + Sacred Grove Male Snail: Clawshot or Gale_Boomerang + Sacred Grove Master Sword Poe: Can_Use_Senses and Night + Sacred Grove Pedestal Master Sword: Nothing + Sacred Grove Pedestal Shadow Crystal: Nothing + Exits: + Lost Woods Lower Battle Arena: Nothing + Sacred Grove Upper: "'Can_Access_Sacred_Grove_Before_Block'" + +- Name: Sacred Grove Past + Locations: + Sacred Grove Past Owl Statue Chest: Dominion_Rod + Sacred Grove Female Snail: Clawshot or Gale_Boomerang + Sacred Grove Temple of Time Owl Statue Poe: Dominion_Rod and Can_Use_Senses + Exits: + Sacred Grove Past Behind Window: Has_Sword_For_Temple_of_Time + Sacred Grove Upper: Nothing + +- Name: Sacred Grove Past Behind Window + Exits: + Sacred Grove Past: Nothing + Temple of Time Entrance: Nothing + +# FARON FIELD + +- Name: Faron Field + Map Sector: Faron Province + Region: Faron Field + Can_Warp: True + Events: + Can Refill Arrows: Nothing + Locations: + Faron Field Bridge Chest: Clawshot + Faron Field Tree Heart Piece: Clawshot or Gale_Boomerang # or Ball and Chain if trick + Faron Field Male Beetle: Nothing + Faron Field Female Beetle: Clawshot or Gale_Boomerang # or Ball and Chain if trick + Faron Field Poe: Can_Use_Senses and Night + Faron Field Hint Sign: Nothing + Exits: + Faron Field Behind Boulder: Can_Use_Hot_Spring_Water and 'Can_Access_Outside_Castle_Town_South' + Kakariko Gorge: Nothing + Lake Hylia Bridge: Gate_Keys or Small_Keys == Keysy + Faron Field Corner Grotto: Can_Dig + Faron Field Fishing Grotto: Can_Dig + South Faron Woods: Nothing + +- Name: Faron Field Behind Boulder + Map Sector: Faron Province + Region: Faron Field + Can_Warp: True + Exits: + # If you enter Outside Castle Town from here while the boulder is still there, + # you get stuck and are forced to save-warp or portal-warp. + Outside Castle Town South Inside Boulder: Nothing + Faron Field: Can_Use_Hot_Spring_Water and 'Can_Access_Outside_Castle_Town_South' + +- Name: Faron Field Corner Grotto + Locations: + Faron Field Corner Grotto Right Chest: Nothing + Faron Field Corner Grotto Left Chest: Nothing + Faron Field Corner Grotto Rear Chest: Nothing + # Faron Field Corner Grotto Main Chest: Nothing # HD Only + Exits: + Faron Field: Nothing + +- Name: Faron Field Fishing Grotto + Exits: + Faron Field: Nothing diff --git a/src/dusk/randomizer/data/world/overworld/Gerudo Desert.yaml b/src/dusk/randomizer/data/world/overworld/Gerudo Desert.yaml new file mode 100644 index 0000000000..5939a65d23 --- /dev/null +++ b/src/dusk/randomizer/data/world/overworld/Gerudo Desert.yaml @@ -0,0 +1,188 @@ + +# GERUDO DESERT + +- Name: Gerudo Desert + Map Sector: Desert Province + Region: South Gerudo Desert + Can Warp: True + Locations: + Gerudo Desert Peahat Ledge Chest: Clawshot + Gerudo Desert East Canyon Chest: Nothing + Gerudo Desert Lone Small Chest: Nothing + Gerudo Desert West Canyon Chest: Clawshot + Gerudo Desert South Chest Behind Wooden Gates: Can_Defeat_Bulblin + Gerudo Desert Owl Statue Chest: Restored_Dominion_Rod + Gerudo Desert Owl Statue Sky Character: Restored_Dominion_Rod + Gerudo Desert Male Dayfly: Nothing + Gerudo Desert Female Dayfly: Nothing + Gerudo Desert East Poe: Can_Use_Senses and Night + Gerudo Desert Hint Sign: Nothing + Exits: + Gerudo Desert Skulltula Grotto: Can_Dig + Gerudo Desert Cave of Ordeals Plateau: Clawshot and Can_Defeat_Shadow_Beast + Gerudo Desert Basin: Nothing + Lake Hylia: Nothing # Modified rando savewarp + +- Name: Gerudo Desert Skulltula Grotto + Locations: + Gerudo Desert Skulltula Grotto Chest: Can_Defeat_Skulltula + Exits: + Gerudo Desert: Nothing + +- Name: Gerudo Desert Cave of Ordeals Plateau + Map Sector: Desert Province + Region: South Gerudo Desert + Can Warp: True + Locations: + Gerudo Desert Poe Above Cave of Ordeals: Can_Use_Senses and Night + Exits: + Cave of Ordeals: Nothing + Gerudo Desert: Nothing + +- Name: Gerudo Desert Basin + Map Sector: Desert Province + Region: North Gerudo Desert + Can Warp: True + Locations: + Gerudo Desert Northeast Chest Behind Gates: Can_Defeat_Bulblin and Can_Ride_Boars + Gerudo Desert Campfire North Chest: Nothing + Gerudo Desert Campfire East Chest: Can_Defeat_Bulblin and Can_Ride_Boars + Gerudo Desert Campfire West Chest: Can_Defeat_Bulblin and Can_Ride_Boars + Gerudo Desert Northwest Chest Behind Gates: Can_Defeat_Bulblin and Can_Ride_Boars + Exits: + Gerudo Desert North East Ledge: Clawshot + Gerudo Desert Chu Grotto: Can_Dig + Gerudo Desert Outside Bulblin Camp: Can_Defeat_Bulblin and Can_Ride_Boars + Gerudo Desert: Can_Defeat_Bulblin and Can_Ride_Boars + +- Name: Gerudo Desert North East Ledge + Map Sector: Desert Province + Region: North Gerudo Desert + Can Warp: True + Locations: + Gerudo Desert North Peahat Poe: Night and Can_Defeat_Poe + Exits: + Gerudo Desert Rock Grotto: Can_Dig + Gerudo Desert Basin: Nothing + +- Name: Gerudo Desert Rock Grotto + Locations: + Gerudo Desert Rock Grotto Lantern Chest: Can_Light_Torches + Gerudo Desert Rock Grotto First Poe: Can_Defeat_Poe + Gerudo Desert Rock Grotto Second Poe: Can_Defeat_Poe + Exits: + Gerudo Desert North East Ledge: Nothing + +- Name: Gerudo Desert Chu Grotto + Exits: + Gerudo Desert Basin: Nothing + +- Name: Gerudo Desert Outside Bulblin Camp + Map Sector: Desert Province + Region: North Gerudo Desert + Can Warp: True + Locations: + Gerudo Desert North Small Chest Before Bulblin Camp: Nothing + Outside Bulblin Camp Poe: Night and Can_Defeat_Poe + Gerudo Desert Golden Wolf: "'Howl_at_Lake_Hylia_Howling_Stone'" + Exits: + Bulblin Camp: Nothing + Gerudo Desert Basin: Can_Defeat_Bulblin and 'Can_Access_Gerudo_Desert_Basin' + +# BULBLIN CAMP + +- Name: Bulblin Camp + Map Sector: Desert Province + Region: Bulblin Camp + Can Warp: True + Locations: + Bulblin Camp First Chest Under Tower At Entrance: Nothing + Bulblin Camp Small Chest in Back of Camp: Nothing + Bulblin Camp Roasted Boar: Has_Damaging_Item + Bulblin Camp Poe: Night and Can_Defeat_Poe and (Gerudo_Desert_Bulblin_Camp_Key or Small_Keys == Keysy or Arbiters_Does_Not_Require_Bulblin_Camp == On) + Bulblin Guard Key: Can_Defeat_Bulblin + Bulblin Camp Hint Sign: Nothing + Exits: + Outside Arbiters Grounds: Arbiters_Does_Not_Require_Bulblin_Camp == On or (Can_Defeat_King_Bulblin_Desert and (Gerudo_Desert_Bulblin_Camp_Key or Small_Keys == Keysy)) + Gerudo Desert Outside Bulblin Camp: Nothing + +- Name: Outside Arbiters Grounds + Map Sector: Desert Province + Region: Bulblin Camp + Can Warp: True + Locations: + Outside Arbiters Grounds Lantern Chest: Can_Light_Torches + Outside Arbiters Grounds Poe: Night and Can_Defeat_Poe + Exits: + Arbiters Grounds Entrance: Nothing + Bulblin Camp: Nothing + +# MIRROR CHAMBER + +- Name: Mirror Chamber Lower + Map Sector: Desert Province + Region: None + Can Warp: True + Exits: + Mirror Chamber Upper: Nothing + Arbiters Grounds Boss Room: Nothing + +- Name: Mirror Chamber Upper + Map Sector: Desert Province + Region: Mirror Chamber + Can Warp: True + Exits: + Mirror Chamber Portal: Can_Defeat_Shadow_Beast and + (Palace_of_Twilight_Requirements == Open or + (Palace_of_Twilight_Requirements == Fused_Shadows and count(Progressive_Fused_Shadow, 3)) or + (Palace_of_Twilight_Requirements == Mirror_Shards and count(Progressive_Mirror_Shard, 4)) or + (Palace_of_Twilight_Requirements == Vanilla and 'Can_Complete_City_in_the_Sky')) + Mirror Chamber Lower: Can_Defeat_Shadow_Beast + +- Name: Mirror Chamber Portal + Exits: + Palace of Twilight Entrance: Nothing + Mirror Chamber Upper: Can_Defeat_Shadow_Beast + +# CAVE OF ORDEALS + +- Name: Cave of Ordeals + Events: + Can Beat 10 CoO Floors: Can_Defeat_Bokoblin and Can_Defeat_Keese and Can_Defeat_Rat and Can_Defeat_Baba_Serpent and + Can_Defeat_Skulltula and Can_Defeat_Bulblin and Can_Defeat_Torch_Slug and Can_Defeat_Fire_Keese and + Can_Defeat_Dodongo and Can_Defeat_Tektite and Can_Defeat_Lizalfos + Can Beat 20 CoO Floors: Spinner and 'Can_Beat_10_CoO_Floors' and Can_Defeat_Helmasaur and Can_Defeat_Rat and + Can_Defeat_Chu and Can_Defeat_Chu_Worm and Can_Defeat_Bubble and Can_Defeat_Bulblin and + Can_Defeat_Keese and Can_Defeat_Rat and Can_Defeat_Stalhound and Can_Defeat_Poe and Can_Defeat_Leever + Can Beat 30 CoO Floors: Ball_and_Chain and 'Can_Beat_20_CoO_Floors' and Can_Defeat_Bokoblin and Can_Defeat_Ice_Keese and + Can_Defeat_Keese and Can_Defeat_Rat and Can_Defeat_Ghoul_Rat and Can_Defeat_Stalchild and + Can_Defeat_Redead_Knight and Can_Defeat_Bulblin and Can_Defeat_Stalfos and Can_Defeat_Skulltula and + Can_Defeat_Bubble and Can_Defeat_Lizalfos and Can_Defeat_Fire_Bubble + Can Beat 40 CoO Floors: Restored_Dominion_Rod and 'Can_Beat_30_CoO_Floors' and Can_Defeat_Beamos and Can_Defeat_Keese and + Can_Defeat_Torch_Slug and Can_Defeat_Fire_Keese and Can_Defeat_Dodongo and Can_Defeat_Fire_Bubble and + Can_Defeat_Redead_Knight and Can_Defeat_Poe and Can_Defeat_Ghoul_Rat and Can_Defeat_Chu and + Can_Defeat_Ice_Keese and Can_Defeat_Freezard and Can_Defeat_Chilfos and Can_Defeat_Ice_Bubble and + Can_Defeat_Leever and Can_Defeat_Darknut + Can Beat 50 CoO Floors: Double_Clawshots and 'Can_Beat_40_CoO_Floors' and Can_Defeat_Armos and Can_Defeat_Bokoblin and + Can_Defeat_Baba_Serpent and Can_Defeat_Lizalfos and Can_Defeat_Bulblin and Can_Defeat_Dinalfos and + Can_Defeat_Poe and Can_Defeat_Redead_Knight and Can_Defeat_Chu and Can_Defeat_Freezard and + Can_Defeat_Chilfos and Can_Defeat_Ghoul_Rat and Can_Defeat_Rat and Can_Defeat_Stalchild and + Can_Defeat_Aerolfos and Can_Defeat_Darknut + Locations: + # Chest are HD Only + # Cave of Ordeals Floor 10 Chest: "'Can_Beat_10_CoO_Floors'" + # Cave of Ordeals Floor 20 Chest: "'Can_Beat_20_CoO_Floors'" + # Cave of Ordeals Floor 30 Chest: "'Can_Beat_30_CoO_Floors'" + # Cave of Ordeals Floor 40 Chest: "'Can_Beat_40_CoO_Floors'" + # Cave of Ordeals Floor 50 Chest: "'Can_Beat_50_CoO_Floors'" + Cave of Ordeals Great Fairy Reward: "'Can_Beat_50_CoO_Floors'" + Cave of Ordeals Floor 17 Poe: Spinner and 'Can_Beat_10_CoO_Floors' and Can_Defeat_Helmasaur and Can_Defeat_Rat and Can_Defeat_Chu and + Can_Defeat_Chu_Worm and Can_Defeat_Bubble and Can_Defeat_Bulblin and Can_Defeat_Keese and Can_Defeat_Poe + Cave of Ordeals Floor 33 Poe: Restored_Dominion_Rod and 'Can_Beat_30_CoO_Floors' and Can_Defeat_Beamos and Can_Defeat_Keese and + Can_Defeat_Torch_Slug and Can_Defeat_Fire_Keese and Can_Defeat_Dodongo and Can_Defeat_Fire_Bubble and + Can_Defeat_Redead_Knight and Can_Defeat_Poe + Cave of Ordeals Floor 44 Poe: Double_Clawshots and 'Can_Beat_40_CoO_Floors' and Can_Defeat_Armos and Can_Defeat_Bokoblin and + Can_Defeat_Baba_Serpent and Can_Defeat_Lizalfos and Can_Defeat_Bulblin and Can_Defeat_Dinalfos and + Can_Defeat_Poe + Exits: + Gerudo Desert Cave of Ordeals Plateau: Clawshot diff --git a/src/dusk/randomizer/data/world/overworld/Lanayru Province.yaml b/src/dusk/randomizer/data/world/overworld/Lanayru Province.yaml new file mode 100644 index 0000000000..68117421d9 --- /dev/null +++ b/src/dusk/randomizer/data/world/overworld/Lanayru Province.yaml @@ -0,0 +1,728 @@ + +# LANAYRU FIELD + +- Name: Lanayru Field + Map Sector: Lanayru Province + Region: Lanayru Field + Twilight: Lanayru + Can Warp: True + Locations: + Lanayru Field Behind Gate Underwater Chest: Iron_Boots + Lanayru Field Male Stag Beetle: Clawshot or Gale_Boomerang + Lanayru Field Female Stag Beetle: Clawshot or Gale_Boomerang + Lanayru Field Bridge Poe: Night and Can_Use_Senses and Can_Complete_MDH and Can_Complete_All_Twilight + Lanayru Field Hint Sign: Nothing + Exits: + Lanayru Field Near Zoras Domain: Can_Smash + Lanayru Field Cave Entrance: Can_Smash + Lanayru Field Chu Grotto: Can_Dig + Lanayru Field Skulltula Grotto: Can_Dig + Lanayru Field Poe Grotto: Can_Dig + Hyrule Field Near Spinner Rails: Can_Smash + Outside Castle Town West: Nothing + North Eldin Field: Nothing + Upper Zoras River: Impossible # To satisfy entrance rando + +- Name: Lanayru Field Near Zoras Domain + Map Sector: Lanayru Province + Region: Lanayru Field + Twilight: Lanayru + Can Warp: True + Exits: + Zoras Domain West Ledge: Nothing + Lanayru Field: Can_Smash + +- Name: Lanayru Field Cave Entrance + Map Sector: Lanayru Province + Region: Lanayru Field + Twilight: Lanayru + Can Warp: True + Exits: + Lanayru Ice Puzzle Cave: Nothing + Lanayru Field: Can_Smash + +- Name: Lanayru Ice Puzzle Cave + Locations: + Lanayru Ice Block Puzzle Cave Chest: Ball_and_Chain + Exits: + Lanayru Field Cave Entrance: Nothing + +- Name: Lanayru Field Chu Grotto + # Locations: + # Lanayru Field Chu Grotto Chest: Nothing # HD Only + Exits: + Lanayru Field: Nothing + +- Name: Lanayru Field Skulltula Grotto + Locations: + Lanayru Field Skulltula Grotto Chest: Lantern + Exits: + Lanayru Field: Nothing + +- Name: Lanayru Field Poe Grotto + Locations: + Lanayru Field Poe Grotto Left Poe: Can_Use_Senses + Lanayru Field Poe Grotto Right Poe: Can_Use_Senses + Exits: + Lanayru Field: Nothing + +- Name: Hyrule Field Near Spinner Rails + Map Sector: Lanayru Province + Region: Lanayru Field + Twilight: Lanayru + Can Warp: True + Locations: + Lanayru Field Spinner Track Chest: Spinner + Exits: + Lake Hylia Bridge: Can_Smash + Lanayru Field: Can_Smash + +# OUTSIDE CASTLE TOWN WEST + +- Name: Outside Castle Town West + Map Sector: Lanayru Province + Region: Beside Castle Town + Twilight: Lanayru + Can Warp: True + Locations: + Hyrule Field Amphitheater Owl Statue Chest: Restored_Dominion_Rod + Hyrule Field Amphitheater Owl Statue Sky Character: Restored_Dominion_Rod + West Hyrule Field Male Butterfly: Nothing + West Hyrule Field Female Butterfly: Gale_Boomerang + Hyrule Field Amphitheater Poe: Can_Use_Senses and Night + West Hyrule Field Golden Wolf: Can_Climb_Vines and 'Howl_at_Upper_Zoras_River_Howling_Stone' + Beside Castle Town Hint Sign: Can_Climb_Vines + Exits: + Outside Castle Town West Grotto Ledge: Clawshot + Castle Town West: Nothing + Lake Hylia Bridge: Nothing + Lanayru Field: Nothing + +- Name: Outside Castle Town West Grotto Ledge + Map Sector: Lanayru Province + Region: Beside Castle Town + Twilight: Lanayru + Can Warp: True + Locations: + West Hyrule Field Female Butterfly: Nothing + Exits: + Outside Castle Town West Helmasaur Grotto: Can_Dig + Outside Castle Town West: Nothing + +- Name: Outside Castle Town West Helmasaur Grotto + Locations: + West Hyrule Field Helmasaur Grotto Chest: Can_Defeat_Helmasaur + Exits: + Outside Castle Town West Grotto Exit: Nothing + +# If you exit the grotto as wolf, you jump over the ledge and fall +# down to Outside Castle Town West +- Name: Outside Castle Town West Grotto Exit + Can Transform: Never + Exits: + Outside Castle Town West Grotto Ledge: Human_Link + Outside Castle Town West: Wolf_Link + +# CASTLE TOWN + +- Name: Castle Town West + Map Sector: Lanayru Province + Region: Castle Town + Twilight: Lanayru + Can Warp: True + Can Transform: If Transform Anywhere + Locations: + Charlo Donation Blessing: "'Can_Farm_Lots_of_Rupees'" + Exits: + Castle Town STAR Game: Nothing + Castle Town Center: Nothing + Castle Town South: Nothing + Outside Castle Town West: Nothing + +- Name: Castle Town STAR Game + Twilight: Lanayru + Can Transform: If Transform Anywhere + Locations: + STAR Prize 1: Clawshot + STAR Prize 2: Double_Clawshots + Exits: + Castle Town West: Nothing + +- Name: Castle Town Center + Map Sector: Lanayru Province + Region: Castle Town + Twilight: Lanayru + Can Warp: True + Can Transform: If Transform Anywhere + Locations: + Castle Town Center Hint Sign: Nothing + Exits: + Castle Town Goron House West Door Exterior: Nothing + Castle Town Goron House East Door Exterior: Nothing + Castle Town Malo Mart: Can_Open_Doors and 'Can_Farm_Rupees' + Castle Town North: Nothing + Castle Town East: Nothing + Castle Town South: Nothing + Castle Town West: Nothing + +- Name: Castle Town Goron House West Door Exterior + Can Transform: Never + Exits: + Castle Town Goron House West Door Interior: Can_Open_Doors + Castle Town Center: Nothing + +- Name: Castle Town Goron House East Door Exterior + Can Transform: Never + Exits: + Castle Town Goron House East Door Interior: Can_Open_Doors + Castle Town Center: Nothing + +- Name: Castle Town Goron House West Door Interior + Can Transform: Never + Exits: + Castle Town Goron House West Door Exterior: Can_Open_Doors + Castle Town Goron House: Nothing + +- Name: Castle Town Goron House East Door Interior + Can Transform: Never + Exits: + Castle Town Goron House East Door Exterior: Can_Open_Doors + Castle Town Goron House: Nothing + +- Name: Castle Town Goron House + Can Transform: If Transform Anywhere + Exits: + Castle Town Goron House West Door Interior: Nothing + Castle Town Goron House East Door Interior: Nothing + Castle Town Goron House Ledge: Nothing + +- Name: Castle Town Goron House Ledge + Twilight: Lanayru + Can Transform: If Transform Anywhere + Exits: + Castle Town Goron House: Nothing + +- Name: Castle Town Malo Mart + Can Transform: If Transform Anywhere + Locations: + # Castle Town Malo Mart Stamp: Can_Talk_to_Humans and 'Can_Farm_Lots_of_Rupees' and 'Can_Fund_Malo_Mart' # HD only + Castle Town Malo Mart Magic Armor: Can_Talk_to_Humans and 'Can_Farm_Lots_of_Rupees' and 'Can_Fund_Malo_Mart' and + (Increase_Wallet_Capacity == On or Big_Wallet) + Exits: + Castle Town Center: Can_Open_Doors + +- Name: Castle Town North + Map Sector: Lanayru Province + Region: Castle Town + Twilight: Lanayru + Can Warp: True + Exits: + Castle Town North Behind First Door: Can_Complete_MDH + Castle Town Center: Nothing + +- Name: Castle Town North Behind First Door + Twilight: Lanayru + Locations: + North Castle Town Golden Wolf: "'Howl_at_Hidden_Village_Howling_Stone'" + Exits: + Castle Town North Inside Barrier: Can_Break_Hyrule_Castle_Barrier + Castle Town North: Can_Complete_MDH + +- Name: Castle Town North Inside Barrier + Twilight: Lanayru + Exits: + Hyrule Castle Entrance: Nothing + Castle Town North Behind First Door: Can_Complete_MDH + +- Name: Castle Town East + Map Sector: Lanayru Province + Region: Castle Town + Twilight: Lanayru + Can Warp: True + Exits: + Castle Town Doctors Office West Door Exterior: Nothing + Castle Town Doctors Office East Door Exterior: Nothing + Outside Castle Town East: Nothing + Castle Town South: Nothing + Castle Town Center: Nothing + +- Name: Castle Town Doctors Office West Door Exterior + Can Transform: Never + Exits: + Castle Town Doctors Office West Door Interior: Can_Open_Doors + Castle Town East: Nothing + +- Name: Castle Town Doctors Office East Door Exterior + Can Transform: Never + Exits: + Castle Town Doctors Office East Door Interior: Can_Open_Doors + Castle Town East: Nothing + +- Name: Castle Town Doctors Office West Door Interior + Can Transform: Never + Exits: + Castle Town Doctors Office West Door Exterior: Can_Open_Doors + Castle Town Doctors Office Entrance: Nothing + +- Name: Castle Town Doctors Office East Door Interior + Can Transform: Never + Exits: + Castle Town Doctors Office East Door Exterior: Can_Open_Doors + Castle Town Doctors Office Entrance: Nothing + +- Name: Castle Town Doctors Office Entrance + Can Transform: If Transform Anywhere + Exits: + Castle Town Doctors Office Lower: Invoice + Castle Town Doctors Office West Door Interior: Nothing + Castle Town Doctors Office East Door Interior: Nothing + +- Name: Castle Town Doctors Office Lower + Events: + Medicine Scent: Can_Sniff + Exits: + Castle Town Doctors Office Upper: Wolf_Link + Castle Town Doctors Office Entrance: Invoice + +- Name: Castle Town Doctors Office Upper + Exits: + Castle Town Doctors Office Lower: Nothing + Castle Town Doctors Office Balcony: Nothing + +- Name: Castle Town Doctors Office Balcony + Twilight: Lanayru + Can Transform: If Transform Anywhere + Locations: + Doctors Office Balcony Chest: Nothing + Exits: + Castle Town East: Nothing + Castle Town Doctors Office Upper: Nothing + +- Name: Outside Castle Town East + Can Transform: If Transform Anywhere + Map Sector: Lanayru Province + Region: Castle Town + Can Warp: True + Locations: + East Castle Town Bridge Poe: Night and Can_Use_Senses + Exits: + Eldin Field Near Castle Town: Nothing + Castle Town East: Nothing + +- Name: Castle Town South + Map Sector: Lanayru Province + Region: Castle Town + Twilight: Lanayru + Can Warp: True + Events: + Can Buy Hot Spring Water: "'Can_Finish_Goron_Springwater_Rush'" + Can Learn About Wooden Statue: "'Medicine_Scent'" + Locations: + Castle Town Twilit Insect: Can_Defeat_Lanayru_Twilit_Insect + Exits: + Castle Town Agithas House: Can_Open_Doors + Castle Town Seer House: Can_Open_Doors + Castle Town Jovanis House: Can_Dig + Castle Town Telmas Bar: Can_Open_Doors + Outside Castle Town South: Nothing + Castle Town West: Nothing + Castle Town East: Nothing + Castle Town Center: Nothing + +- Name: Castle Town Agithas House + Can Transform: If Transform Anywhere + Locations: + Agitha Female Ant Reward: Female_Ant and Can_Talk_to_Humans + Agitha Female Beetle Reward: Female_Beetle and Can_Talk_to_Humans + Agitha Female Butterfly Reward: Female_Butterfly and Can_Talk_to_Humans + Agitha Female Dayfly Reward: Female_Dayfly and Can_Talk_to_Humans + Agitha Female Dragonfly Reward: Female_Dragonfly and Can_Talk_to_Humans + Agitha Female Grasshopper Reward: Female_Grasshopper and Can_Talk_to_Humans + Agitha Female Ladybug Reward: Female_Ladybug and Can_Talk_to_Humans + Agitha Female Mantis Reward: Female_Mantis and Can_Talk_to_Humans + Agitha Female Phasmid Reward: Female_Phasmid and Can_Talk_to_Humans + Agitha Female Pill Bug Reward: Female_Pill_Bug and Can_Talk_to_Humans + Agitha Female Snail Reward: Female_Snail and Can_Talk_to_Humans + Agitha Female Stag Beetle Reward: Female_Stag_Beetle and Can_Talk_to_Humans + Agitha Male Ant Reward: Male_Ant and Can_Talk_to_Humans + Agitha Male Beetle Reward: Male_Beetle and Can_Talk_to_Humans + Agitha Male Butterfly Reward: Male_Butterfly and Can_Talk_to_Humans + Agitha Male Dayfly Reward: Male_Dayfly and Can_Talk_to_Humans + Agitha Male Dragonfly Reward: Male_Dragonfly and Can_Talk_to_Humans + Agitha Male Grasshopper Reward: Male_Grasshopper and Can_Talk_to_Humans + Agitha Male Ladybug Reward: Male_Ladybug and Can_Talk_to_Humans + Agitha Male Mantis Reward: Male_Mantis and Can_Talk_to_Humans + Agitha Male Phasmid Reward: Male_Phasmid and Can_Talk_to_Humans + Agitha Male Pill Bug Reward: Male_Pill_Bug and Can_Talk_to_Humans + Agitha Male Snail Reward: Male_Snail and Can_Talk_to_Humans + Agitha Male Stag Beetle Reward: Male_Stag_Beetle and Can_Talk_to_Humans + # Agitha 12 Golden Bugs Reward: golden_bugs(12) and Can_Talk_to_Humans # HD Only + Exits: + Castle Town South: Can_Open_Doors + +- Name: Castle Town Seer House + Can Transform: If Transform Anywhere + Exits: + Castle Town South: Can_Open_Doors + +- Name: Castle Town Jovanis House + Twilight: Lanayru + Can Transform: If Transform Anywhere + Events: + Can Farm Lots of Rupees: Can_Talk_to_Animals and 'Can_Talk_to_Jovani_in_Telmas_Bar' + Freed Jovani: count(Poe_Soul, 60) + Locations: + Jovani House Poe: Can_Use_Senses + Jovani 20 Poe Soul Reward: count(Poe_Soul, 20) + Jovani 60 Poe Soul Reward: "'Freed_Jovani'" + # Gengle 60 Poe Soul Reward: Can_Talk_to_Animals and 'Can_Talk_to_Jovani_in_Telmas_Bar' # HD Only + Exits: + Castle Town South: Nothing + +- Name: Castle Town Telmas Bar + Can Transform: If Transform Anywhere + Events: + Can Talk to Jovani in Telmas Bar: Can_Talk_to_Humans and 'Freed_Jovani' + Locations: + Telma Invoice: Renados_Letter + Exits: + Castle Town South: Can_Open_Doors + +# OUTSIDE CASTLE TOWN SOUTH + +- Name: Outside Castle Town South + Map Sector: Lanayru Province + Region: Outside Castle Town + Twilight: Lanayru + Can Warp: True + Locations: + Outside South Castle Town Tightrope Chest: Clawshot and Can_Use_Tightrope + Outside South Castle Town Fountain Chest: Spinner and Clawshot + Outside South Castle Town Double Clawshot Chasm Chest: Double_Clawshots + Outside South Castle Town Male Ladybug: Nothing + Outside South Castle Town Female Ladybug: Nothing + Outside South Castle Town Poe: Night and Can_Use_Senses + Outside South Castle Town Golden Wolf: "'Howl_at_North_Faron_Woods_Howling_Stone'" + Wooden Statue: "'Can_Learn_About_Wooden_Statue'" + Outside South Castle Town Hint Sign: Nothing + Exits: + Outside Castle Town South Tektite Grotto Platform: Can_Climb_Vines + Faron Field Behind Boulder: Can_Use_Hot_Spring_Water + Lake Hylia: Nothing + Castle Town South: Nothing + +- Name: Outside Castle Town South Tektite Grotto Platform + Map Sector: Lanayru Province + Region: Outside Castle Town + Twilight: Lanayru + Can Warp: True + Exits: + Outside Castle Town South Tektite Grotto: Can_Dig + Outside Castle Town South: Nothing + +- Name: Outside Castle Town South Tektite Grotto + Locations: + Outside South Castle Town Tektite Grotto Chest: Can_Defeat_Tektite + Exits: + Outside Castle Town South Tektite Grotto Platform: Nothing + +# If you enter Outside Castle Town South from there while the boulder is still there, +# you get stuck and are forced to save-warp or portal-warp +- Name: Outside Castle Town South Inside Boulder + Map Sector: Lanayru Province + Region: Outside Castle Town + Twilight: Lanayru + Can Warp: True + Exits: + Outside Castle Town South: Can_Use_Hot_Spring_Water and 'Can_Access_Outside_Castle_Town_South' + +# LAKE HYLIA BRIDGE + +- Name: Lake Hylia Bridge + Map Sector: Lanayru Province + Region: Lake Hylia Bridge + Twilight: Lanayru + Can Warp: True + Locations: + Lake Hylia Bridge Vines Chest: Clawshot + Lake Hylia Bridge Owl Statue Chest: Clawshot and Restored_Dominion_Rod + Lake Hylia Bridge Owl Statue Sky Character: Clawshot and Restored_Dominion_Rod + Lake Hylia Bridge Male Mantis: Clawshot or Gale_Boomerang + Lake Hylia Bridge Female Mantis: Clawshot or Gale_Boomerang + Lake Hylia Bridge Hint Sign: Clawshot + Exits: + Lake Hylia Bridge Grotto Ledge: Can_Launch_Bombs and Clawshot + Hyrule Field Near Spinner Rails: Can_Smash + Flight by Fowl: Can_Open_Doors + Faron Field: Gate_Keys or Small_Keys == Keysy + Outside Castle Town West: Nothing + Lake Hylia: Twilight + +- Name: Lake Hylia Bridge Grotto Ledge + Map Sector: Lanayru Province + Region: Lake Hylia Bridge + Twilight: Lanayru + Can Warp: True + Locations: + Lake Hylia Bridge Cliff Chest: Nothing + Lake Hylia Bridge Cliff Poe: Can_Use_Senses and Can_Complete_MDH and Can_Complete_All_Twilight + Exits: + Lake Hylia Bridge Bubble Grotto: Can_Dig + Lake Hylia Bridge: Nothing + +- Name: Lake Hylia Bridge Bubble Grotto + Locations: + Lake Hylia Bridge Bubble Grotto Chest: Can_Defeat_Bubble and Can_Defeat_Fire_Bubble and Can_Defeat_Ice_Bubble + Exits: + Lake Hylia Bridge Grotto Ledge: Nothing + +# LAKE HYLIA + +- Name: Lake Hylia + Map Sector: Lanayru Province + Region: Lake Hylia + Twilight: Lanayru + Can Warp: True + Events: + Can Farm Lots of Rupees: Nothing + Locations: + Lake Hylia Twilit Insect Between Bridges: Can_Defeat_Lanayru_Twilit_Insect + Lake Hylia Burrowing Twilit Insect: Can_Defeat_Lanayru_Twilit_Insect + Lake Hylia Twilit Insect on Docks: Can_Defeat_Lanayru_Twilit_Insect + Lake Hylia Twilit Bloat: count(Lanayru_Twilight_Tear, 11) and Can_Defeat_Lanayru_Twilit_Insect + Zora's River Twilit Insect 1: Can_Defeat_Lanayru_Twilit_Insect + Zora's River Twilit Insect 2: Can_Defeat_Lanayru_Twilit_Insect + Zora's River Twilit Insect 3: Can_Defeat_Lanayru_Twilit_Insect + Lake Hylia Underwater Chest: Iron_Boots + Lake Hylia Alcove Poe: Night and Can_Use_Senses + Lake Hylia Dock Poe: Night and Can_Use_Senses + Plumm Fruit Balloon Minigame: Can_Howl + Exits: + Lake Hylia Upper Area: Can_Climb_Ladders + Flight by Fowl: Can_Talk_to_Humans + Lake Hylia Lakebed Temple Entrance: Zora_Armor and (Lakebed_Does_Not_Require_Water_Bombs == On or (Iron_Boots and Water_Bombs)) + Lake Hylia Lanayru Spring: Nothing + City in the Sky Entrance: Clawshot and (City_Does_Not_Require_Filled_Skybook == On or count(Progressive_Sky_Book, 7)) + Gerudo Desert: Aurus_Memo + Upper Zoras River: Can_Howl or Twilight + Kakariko Graveyard Pond: Impossible # To satisfy entrance rando + Outside Castle Town South: Impossible # To saitisfy entrance rando + +# Area after the ladder +- Name: Lake Hylia Upper Area + Map Sector: Lanayru Province + Region: Lake Hylia + Twilight: Lanayru + Can Warp: True + Events: + Howl at Lake Hylia Howling Stone: Can_Howl + Locations: + Auru Gift To Fyer: Can_Climb_Ladders + Lake Hylia Tower Poe: Night and Can_Use_Senses + Exits: + Lake Hylia Cave Entrance: Can_Smash + Lake Hylia Water Toadpoli Grotto: Can_Dig + Lake Hylia: Nothing + +- Name: Lake Hylia Cave Entrance + Map Sector: Lanayru Province + Region: Lake Hylia + Can Warp: True + Exits: + Lake Hylia Long Cave: Nothing + Lake Hylia: Can_Smash + +- Name: Lake Hylia Long Cave + Locations: + Lake Lantern Cave First Chest: Can_Smash and Lantern + Lake Lantern Cave Second Chest: Can_Smash and Lantern + Lake Lantern Cave Third Chest: Can_Smash and Lantern + Lake Lantern Cave Fourth Chest: Can_Smash and Lantern + Lake Lantern Cave Fifth Chest: Can_Smash and Lantern + Lake Lantern Cave Sixth Chest: Can_Smash and Lantern + Lake Lantern Cave Seventh Chest: Can_Smash and Lantern + Lake Lantern Cave Eighth Chest: Can_Smash and Lantern + Lake Lantern Cave Ninth Chest: Can_Smash and Lantern + Lake Lantern Cave Tenth Chest: Can_Smash and Lantern + Lake Lantern Cave Eleventh Chest: Can_Smash and Lantern + Lake Lantern Cave Twelfth Chest: Can_Smash and Lantern + Lake Lantern Cave Thirteenth Chest: Can_Smash and Lantern + Lake Lantern Cave Fourteenth Chest: Can_Smash and Lantern + Lake Lantern Cave End Lantern Chest: Can_Smash and Lantern + Lake Lantern Cave First Poe: Can_Smash and Lantern and Can_Use_Senses + Lake Lantern Cave Second Poe: Can_Smash and Lantern and Can_Use_Senses + Lake Lantern Cave Final Poe: Can_Smash and Lantern and Can_Use_Senses + Lake Lantern Cave Hint Sign: Can_Smash and Lantern + Exits: + Lake Hylia Cave Entrance: Nothing + +- Name: Lake Hylia Water Toadpoli Grotto + Locations: + Lake Hylia Water Toadpoli Grotto Chest: Can_Defeat_Water_Toadpoli + Exits: + Lake Hylia Upper Area: Nothing + +- Name: Flight by Fowl + Map Sector: Lanayru Province + Region: Lake Hylia + Twilight: Lanayru + Can Warp: True + Locations: + Outside Lanayru Spring Left Statue Chest: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Outside Lanayru Spring Right Statue Chest: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Flight By Fowl Top Platform Reward: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Flight By Fowl Second Platform Chest: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Flight By Fowl Third Platform Chest: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Flight By Fowl Fourth Platform Chest: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Flight By Fowl Fifth Platform Chest: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Flight By Fowl Ledge Poe: Night and Can_Use_Senses and Can_Talk_to_Humans and 'Can_Farm_Rupees' + Isle of Riches Poe: Night and Can_Use_Senses and Can_Talk_to_Humans and 'Can_Farm_Rupees' + Lake Hylia Hint Sign: Nothing + Exits: + Lake Hylia Shell Blade Grotto Ledge: Can_Talk_to_Humans + Lake Hylia Bridge: Can_Open_Doors + Lake Hylia: Can_Talk_to_Humans + +- Name: Lake Hylia Shell Blade Grotto Ledge + Map Sector: Lanayru Province + Region: Lake Hylia + Twilight: Lanayru + Can Warp: True + Exits: + Lake Hylia Shell Blade Grotto: Can_Dig + Lake Hylia: Nothing + +- Name: Lake Hylia Shell Blade Grotto + Locations: + Lake Hylia Shell Blade Grotto Chest: Can_Defeat_Shell_Blade + Exits: + Lake Hylia Shell Blade Grotto Ledge: Nothing + +- Name: Lake Hylia Lanayru Spring + Map Sector: Lanayru Province + Region: Lanayru Spring + Twilight: Lanayru + Can Warp: True + Locations: + Lanayru Spring Underwater Left Chest: Iron_Boots or (Can_Do_Niche_Stuff and Magic_Armor) + Lanayru Spring Underwater Right Chest: Iron_Boots or (Can_Do_Niche_Stuff and Magic_Armor) + Lanayru Spring Back Room Left Chest: Clawshot + Lanayru Spring Back Room Right Chest: Clawshot + Lanayru Spring Back Room Lantern Chest: Clawshot and Lantern + Lanayru Spring East Double Clawshot Chest: Double_Clawshots + Lanayru Spring West Double Clawshot Chest: Double_Clawshots + Lanayru Spring Hint Sign: Iron_Boots or (Can_Do_Niche_Stuff and Magic_Armor) + Exits: + Lake Hylia: Nothing + +- Name: Lake Hylia Lakebed Temple Entrance + Map Sector: Lanayru Province + Region: Lake Hylia + Twilight: Lanayru + Exits: + Lakebed Temple Entrance: Nothing + Lake Hylia: Zora_Armor and ((Lakebed_Does_Not_Require_Water_Bombs == On) or (Iron_Boots and Water_Bombs)) + + +# UPPER ZORAS RIVER + +- Name: Upper Zoras River + Map Sector: Lanayru Province + Region: Upper Zoras River + Twilight: Lanayru + Can Warp: True + Events: + Howl at Upper Zoras River Howling Stone: Can_Howl + Locations: + Upper Zoras River Twilit Insect: Can_Defeat_Lanayru_Twilit_Insect + Upper Zoras River Female Dragonfly: Nothing + Upper Zoras River Poe: Can_Use_Senses + Exits: + Upper Zoras River Izas House: Can_Open_Doors and Upper_Zoras_River_Warp_Portal + Fishing Hole: Can_Open_Doors + Zoras Domain: Nothing + Lanayru Field: Nothing + +- Name: Upper Zoras River Izas House + Can Warp: True + Locations: + Iza Helping Hand: Bow and Upper_Zoras_River_Warp_Portal + Iza Raging Rapids Minigame: Bow and Upper_Zoras_River_Warp_Portal + Exits: + Upper Zoras River: Can_Open_Doors + +- Name: Fishing Hole + Map Sector: Lanayru Province + Region: Upper Zoras River + Can Warp: True + Locations: + Fishing Hole Heart Piece: Clawshot + Fishing Hole Bottle: Fishing_Rod + Fishing Hole Hint Sign: Nothing + Exits: + Fishing Hole House: Can_Open_Doors + Upper Zoras River: Can_Open_Doors + +- Name: Fishing Hole House + Locations: + Fishing Hole Heart Piece: "'Can_Farm_Rupees'" + Exits: + Fishing Hole: Can_Open_Doors + +# ZORAS DOMAIN + +- Name: Zoras Domain + Map Sector: Lanayru Province + Region: Zoras Domain + Twilight: Lanayru + Can Warp: True + Events: + Reekfish Scent: Coral_Earring + Locations: + Zoras Domain Twilit Insect near Lilypads: Can_Defeat_Lanayru_Twilit_Insect + Zoras Domain Burrowing Twilit Insect: Can_Defeat_Lanayru_Twilit_Insect + Zoras Domain Chest By Mother and Child Isles: Nothing + Zoras Domain Chest Behind Waterfall: Can_Midna_Jump + Zoras Domain Male Dragonfly: Nothing + Zoras Domain Mother and Child Isle Poe: Can_Use_Senses + Zoras Domain Waterfall Poe: Can_Midna_Jump and Can_Use_Senses + Exits: + Zoras Domain West Ledge: Clawshot or Can_Midna_Jump or Twilight + Zoras Domain Top of Waterfall: Clawshot or Can_Midna_Jump or Twilight + Snowpeak Climb Lower: Not_Twilight + Upper Zoras River: Nothing + +- Name: Zoras Domain West Ledge + Map Sector: Lanayru Province + Region: Zoras Domain + Twilight: Lanayru + Can Warp: True + Locations: + Zoras Domain Hint Sign: Nothing + Exits: + Zoras Domain Top of Waterfall: Can_Smash + Zoras Domain: Nothing + Lanayru Field Near Zoras Domain: Nothing + +- Name: Zoras Domain Top of Waterfall + Map Sector: Lanayru Province + Region: Zoras Domain + Twilight: Lanayru + Can Warp: True + Exits: + Zoras Throne Room: Nothing + Zoras Domain West Ledge: Can_Smash + Zoras Domain: Nothing + +- Name: Zoras Throne Room + Map Sector: Lanayru Province + Region: Zoras Domain + Twilight: Lanayru + Can Warp: True + Locations: + Zoras Domain Throne Room Twilit Insect: Can_Defeat_Lanayru_Twilit_Insect + Zoras Domain Light All Torches Chest: Can_Light_Torches and Iron_Boots + Zoras Domain Extinguish All Torches Chest: Can_Extinguish_Torches and Iron_Boots + Zoras Domain Underwater Goron: Water_Bombs and Iron_Boots and Zora_Armor + Exits: + Zoras Domain Top of Waterfall: Nothing diff --git a/src/dusk/randomizer/data/world/overworld/Ordona Province.yaml b/src/dusk/randomizer/data/world/overworld/Ordona Province.yaml new file mode 100644 index 0000000000..1c2527545d --- /dev/null +++ b/src/dusk/randomizer/data/world/overworld/Ordona Province.yaml @@ -0,0 +1,165 @@ + +# OUTSIDE LINK'S HOUSE + +- Name: Outside Links House + Map Sector: Ordona Province + Region: Ordon + Can Warp: True + Can Change Time: True + Locations: + Ordon Hint Sign: Nothing + Exits: + Ordon Village: Nothing + Ordon Spring: Nothing + Ordon Links House: Can_Climb_Ladders and Can_Open_Doors + +- Name: Ordon Links House + Exits: + Outside Links House: Can_Open_Doors + Locations: + Wooden Sword Chest: Nothing + Links Basement Chest: Lantern + +# ORDON SPRING + +- Name: Ordon Spring + Map Sector: Ordona Province + Region: Ordon + Can Warp: True + Can Change Time: True + Events: + Ordon Spring Warp Portal: Nothing + Locations: + Ordon Spring Golden Wolf: "'Howl_at_Death_Mountain_Howling_Stone'" + Exits: + Outside Links House: Nothing + Ordon Bridge: Skip_Prologue == On or ('Can_Access_Outside_Links_House' and Sword and Slingshot) + +- Name: Ordon Bridge + Map Sector: Ordona Province + Region: Ordon + Can Warp: True + Locations: + # We can't assume repeatable non-twilight access to any of faron woods until after the prologue and twilight sections + # are finished. We put this location here when the prologue is on because the north faron woods gate key gets placed + # here as a vanilla item when the prologue is on. This allows the search algorithm to find the key for logical progression + # without needing to assume repeatable access to Faron Woods. Entrance randomizer doesn't need to be taken into account + # because entrance randomizer with the prologue on is not allowed. + Faron Mist Cave Open Chest: Skip_Prologue == Off + Exits: + South Faron Woods: Can_Complete_Prologue + Ordon Spring: Skip_Prologue == On or ('Can_Access_Outside_Links_House' and Sword and Slingshot) + +# ORDON VILLAGE + +- Name: Ordon Village + Map Selector: Ordona Province + Region: Ordon + Can Warp: True + Can Change Time: True + Events: + Can Farm Rupees: Nothing # Can break pumpkins + Can Refill Slingshot Seeds: Nothing # Can break pumpkins + Fish for Ordon Cat: Day and Fishing_Rod + Locations: + Uli Cradle Delivery: Day and Human_Link + Exits: + Ordon Seras Shop: Day and Can_Open_Doors + Ordon Sword House: Day and Can_Open_Doors + Ordon Shield House: Day and Can_Open_Doors + Ordon Shield House Upper Ledge: Impossible # To satisfy Entrance Rando + Ordon Fados House: Day and Can_Open_Doors + Ordon Bos House Left Door Exterior: Nothing + Ordon Bos House Right Door Exterior: Nothing + Ordon Ranch Village Pathway: Nothing + Outside Links House: Nothing + +- Name: Ordon Seras Shop + Can Transform: If Transform Anywhere + Events: + Can Refill Lantern Oil: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Locations: + Ordon Cat Rescue: "'Fish_for_Ordon_Cat'" + Sera Shop Slingshot: Can_Talk_to_Humans and 'Can_Farm_Rupees' + Exits: + Ordon Village: Nothing + +- Name: Ordon Sword House + Locations: + Ordon Sword: Can_Complete_Prologue or Faron_Twilight_Cleared == On + Exits: + Ordon Village: Can_Open_Doors + +- Name: Ordon Shield House + Exits: + Ordon Shield House Upper Ledge: Wolf_Link + Ordon Village: Can_Open_Doors + +- Name: Ordon Shield House Upper Ledge + Locations: + Ordon Shield: (Can_Complete_Prologue or Faron_Twilight_Cleared == On) and Can_Survive_Two_Bonks + Exits: + Ordon Village: Wolf_Link + +- Name: Ordon Fados House + Exits: + Ordon Village: Can_Open_Doors + +- Name: Ordon Bos House Left Door Exterior + Can Transform: Never + Exits: + Ordon Bos House Left Door Interior: Can_Open_Doors + Ordon Village: Nothing + +- Name: Ordon Bos House Right Door Exterior + Can Transform: Never + Exits: + Ordon Bos House Right Door Interior: Can_Open_Doors + Ordon Village: Nothing + +- Name: Ordon Bos House Left Door Interior + Can Transform: Never + Exits: + Ordon Bos House Left Door Exterior: Can_Open_Doors + Ordon Bos House: Nothing + +- Name: Ordon Bos House Right Door Interior + Can Transform: Never + Exits: + Ordon Bos House Right Door Exterior: Can_Open_Doors + Ordon Bos House: Nothing + +- Name: Ordon Bos House + Can Transform: Never + Locations: + Wrestling With Bo: Human_Link + Exits: + Ordon Bos House Right Door Interior: Nothing + Ordon Bos House Right Door Interior: Nothing + +# ORDON RANCH + +- Name: Ordon Ranch Village Pathway + Map Sector: Ordona Province + Region: Ordon + Can Warp: True + Can Change Time: True + Exits: + Ordon Village: Nothing + Ordon Ranch: Day + +- Name: Ordon Ranch + Map Sector: Ordona Province + Region: Ordon + Can Warp: True + Locations: + Herding Goats Reward: Can_Complete_Prologue and Day + Exits: + Ordon Ranch Grotto: Wolf_Link + Ordon Ranch Village Pathway: Day + +- Name: Ordon Ranch Grotto + Locations: + Ordon Ranch Grotto Lantern Chest: Can_Light_Torches + Exits: + Ordon Ranch: Nothing diff --git a/src/dusk/randomizer/data/world/overworld/Snowpeak Province.yaml b/src/dusk/randomizer/data/world/overworld/Snowpeak Province.yaml new file mode 100644 index 0000000000..ae5edeb475 --- /dev/null +++ b/src/dusk/randomizer/data/world/overworld/Snowpeak Province.yaml @@ -0,0 +1,90 @@ +- Name: Snowpeak Climb Lower + Map Sector: Snowpeak Province + Region: Snowpeak Mountain + Can Warp: True + Locations: + Ashei Sketch: Can_Talk_to_Humans + Snowpeak Hint Sign: Nothing + Exits: + Snowpeak Climb Upper: Snowpeak_Does_Not_Require_Reekfish_Scent == On or 'Reekfish_Scent' + Zoras Domain: Nothing + +- Name: Snowpeak Climb Upper + Map Sector: Snowpeak Province + Region: Snowpeak Mountain + Can Warp: True + Events: + Howl at Snowpeak Mountain Howling Stone: Can_Howl + Locations: + Snowpeak Above Freezard Grotto Poe: Can_Use_Senses + Snowpeak Blizzard Poe: Can_Use_Senses + Snowpeak Poe Among Trees: Can_Use_Senses and Night + Exits: + Snowpeak Ice Keese Grotto: Can_Dig + Snowpeak Freezard Grotto: Can_Dig + Snowpeak Summit Cave: Can_Dig + Snowpeak Climb Lower: Nothing + +- Name: Snowpeak Ice Keese Grotto + Exits: + Snowpeak Climb Upper: Nothing + +- Name: Snowpeak Freezard Grotto + Locations: + Snowpeak Freezard Grotto Chest: Can_Defeat_Freezard + Exits: + Snowpeak Climb Upper: Nothing + +- Name: Snowpeak Summit Cave + Map Sector: Snowpeak Province + Region: Snowpeak Mountain + Can Warp: True + Locations: + Snowpeak Cave Ice Lantern Chest: Can_Light_Torches and Ball_and_Chain + Snowpeak Cave Ice Poe: Ball_and_Chain and Can_Use_Senses + Exits: + Snowpeak Summit Upper: Can_Climb_Vines + Snowpeak Climb Upper: Can_Dig + +- Name: Snowpeak Summit Upper + Map Sector: Snowpeak Province + Region: Snowpeak Mountain + Can Warp: True + Locations: + Snowboard Racing Prize: Can_Defeat_Shadow_Beast and 'Can_Complete_Snowpeak_Ruins' + Exits: + Snowpeak Summit Lower: Can_Defeat_Shadow_Beast and Can_Survive_One_Bonk + +- Name: Snowpeak Summit Lower + Map Sector: Snowpeak Province + Region: Snowpeak Mountain + Can Warp: True + Locations: + Snowpeak Icy Summit Poe: Can_Use_Senses + Exits: + Snowpeak Ruins East Door Exterior: Nothing + Snowpeak Ruins West Door Exterior: Nothing + +- Name: Snowpeak Ruins East Door Exterior + Can Transform: Never + Exits: + Snowpeak Ruins East Door Interior: Can_Open_Doors + Snowpeak Summit Lower: Nothing + +- Name: Snowpeak Ruins West Door Exterior + Can Transform: Never + Exits: + Snowpeak Ruins West Door Interior: Can_Open_Doors + Snowpeak Summit Lower: Nothing + +- Name: Snowpeak Ruins East Door Interior + Can Transform: Never + Exits: + Snowpeak Ruins East Door Exterior: Can_Open_Doors + Snowpeak Ruins Entrance: Nothing + +- Name: Snowpeak Ruins West Door Interior + Can Transform: Never + Exits: + Snowpeak Ruins West Door Exterior: Can_Open_Doors + Snowpeak Ruins Entrance: Nothing \ No newline at end of file diff --git a/src/dusk/randomizer/logic/area.cpp b/src/dusk/randomizer/logic/area.cpp new file mode 100644 index 0000000000..c6d627069c --- /dev/null +++ b/src/dusk/randomizer/logic/area.cpp @@ -0,0 +1,273 @@ +#include "area.hpp" + +#include "search.hpp" +#include "world.hpp" + +#include +#include +#include + +namespace tphdr::logic::area +{ + + int LocationAccess::_idCounter = 0; + int Area::_idCounter = 0; + + LocationAccess::LocationAccess(tphdr::logic::location::Location* loc, + const tphdr::logic::requirement::Requirement& req, + Area* area): + _loc(loc), _req(std::move(req)), _area(area) + { + this->_id = this->_idCounter++; + } + + tphdr::logic::location::Location* LocationAccess::GetLocation() const + { + return this->_loc; + } + const tphdr::logic::requirement::Requirement& LocationAccess::GetRequirement() + { + return this->_req; + } + Area* LocationAccess::GetArea() const + { + return this->_area; + } + int LocationAccess::GetID() const + { + return this->_id; + } + + EventAccess::EventAccess(const tphdr::logic::requirement::Requirement& req, Area* area, const int& eventIndex): + _req(std::move(req)), _area(area), _eventIndex(eventIndex) + { + } + + const tphdr::logic::requirement::Requirement& EventAccess::GetRequirement() + { + return this->_req; + } + Area* EventAccess::GetArea() const + { + return this->_area; + } + int EventAccess::GetEventIndex() const + { + return this->_eventIndex; + } + + std::string EventAccess::GetName() const + { + return this->_area->GetWorld()->GetEventName(this->_eventIndex); + } + + Area::Area(const std::string& name, tphdr::logic::world::World* world): _name(name), _world(world) + { + this->_id = this->_idCounter++; + } + + std::string Area::GetName() const + { + return this->_name; + } + void Area::SetHardAssignedRegion(const std::string& _hardAssignedRegion) + { + this->_hardAssignedRegion = _hardAssignedRegion; + } + std::string Area::GetHardAssignRegion() const + { + return this->_hardAssignedRegion; + } + void Area::SetEvents(std::list>& events) + { + this->_events = std::move(events); + } + + std::list Area::GetEvents() const + { + std::list events; + for (const auto& event : this->_events) + { + events.emplace_back(event.get()); + } + return events; + } + + void Area::SetLocations(std::list>& locations) + { + this->_locations = std::move(locations); + } + + std::list Area::GetLocations() const + { + std::list locations; + for (const auto& loc : this->_locations) + { + locations.emplace_back(loc.get()); + } + return locations; + } + + void Area::SetExits(std::list>& exits) + { + this->_exits = std::move(exits); + } + + std::list Area::GetExits() const + { + std::list exits; + for (const auto& exit : this->_exits) + { + exits.emplace_back(exit.get()); + } + return exits; + } + + void Area::AddExit(std::unique_ptr& exit) + { + this->_exits.push_back(std::move(exit)); + } + + void Area::RemoveExit(tphdr::logic::entrance::Entrance* exit) + { + auto removed = std::remove_if(this->_exits.begin(), this->_exits.end(), [&](const auto& e) { return e.get() == exit; }); + this->_exits.erase(removed, this->_exits.end()); + } + + void Area::AddEntrance(tphdr::logic::entrance::Entrance* entrance) + { + this->_entrances.emplace_back(entrance); + } + + void Area::RemoveEntrance(tphdr::logic::entrance::Entrance* entrance) + { + auto removed = std::remove(this->_entrances.begin(), this->_entrances.end(), entrance); + this->_entrances.erase(removed, this->_entrances.end()); + } + + std::list Area::GetEntrances() const + { + return this->_entrances; + } + tphdr::logic::world::World* Area::GetWorld() const + { + return this->_world; + } + void Area::SetCanChangeTime(const bool& canChangeTime) + { + this->_canChangeTime = canChangeTime; + } + bool Area::CanChangeTime() const + { + return this->_canChangeTime; + } + void Area::SetCanTransform(const bool& canTransform) + { + this->_canTransform = canTransform; + } + bool Area::CanTransform() const + { + return this->_canTransform; + } + void Area::AddHintRegion(const std::string& region) + { + this->_hintRegions.emplace(region); + } + std::set Area::GetHintRegions() + { + return this->_hintRegions; + } + void Area::SetTwilightCompletedMacroIndex(const int& macroIndex) + { + this->_twilightCompletedMacroIndex = macroIndex; + } + int Area::GetTwilightCompletedMacroIndex() const + { + return this->_twilightCompletedMacroIndex; + } + + bool Area::TwilightCleared(tphdr::logic::search::Search* search) const + { + return this->_twilightCompletedMacroIndex == -1 || tphdr::logic::requirement::EvaluateRequirementAtFormTime( + this->GetWorld()->GetMacro(this->_twilightCompletedMacroIndex), + search, + tphdr::logic::requirement::FormTime::ALL, + this->GetWorld()); + } + + void Area::AssignHintRegionsAndDungeonLocations() + { + std::set hintRegions = {}; + std::unordered_set alreadyChecked = {}; + std::list areaQueue = {this}; + + while (!areaQueue.empty()) + { + auto area = areaQueue.back(); + areaQueue.pop_back(); + alreadyChecked.insert(area); + + // If this area has a hard assigned region, then we won't assign it any other regions + auto hardAssignedRegion = area->GetHardAssignRegion(); + if (hardAssignedRegion != "") + { + // If the region is None, then don't assign it. None is meant to be a blocker that prevents other regions + // from assigning themselves through this area + if (hardAssignedRegion != "None") + { + hintRegions.insert(hardAssignedRegion); + } + continue; + } + + // If this area isn't assigned any hint regions, add its entrancs' parent areas to the queue as long as they + // haven't been checked yet + for (const auto& entrance : area->GetEntrances()) + { + if (!alreadyChecked.contains(entrance->GetParentArea())) + { + areaQueue.push_back(entrance->GetParentArea()); + } + } + } + + // When determining which regions to assign the area to, overworld regions will take complete priority over dungeon + // regions. Dungeon regions should only be assigned if dungeons are the only regions listed. So if we have any overworld + // hint regions, filter out the dungeon ones. + const auto& dungeons = this->GetWorld()->GetDungeonTable(); + std::set dungeonRegions = {}; + std::copy_if(hintRegions.begin(), + hintRegions.end(), + std::inserter(dungeonRegions, dungeonRegions.begin()), + [&](const auto& hintRegion) { return dungeons.contains(hintRegion); }); + // If we have less dungeons than total hint regions, we have at least one overworld hint region + // So erase all the dungeons in that case. + if (dungeonRegions.size() < hintRegions.size()) + { + for (const auto& dungeon : dungeonRegions) + { + hintRegions.erase(dungeon); + } + } + + // Assign the found hint regions to the area + for (const auto& region : hintRegions) + { + this->AddHintRegion(region); + LOG_TO_DEBUG("Assigned \"" + region + "\" as hint region to \"" + this->GetName() + "\""); + + // Also assign any loactions in this area to the dungeon if there are any dungeon regions + if (dungeons.contains(region)) + { + auto locAccs = this->GetLocations(); + auto dungeon = this->GetWorld()->GetDungeon(region); + for (const auto& locAcc : locAccs) + { + auto location = locAcc->GetLocation(); + dungeon->AddLocation(location); + } + } + } + } + +} // namespace tphdr::logic::area diff --git a/src/dusk/randomizer/logic/area.hpp b/src/dusk/randomizer/logic/area.hpp new file mode 100644 index 0000000000..d1a5934a32 --- /dev/null +++ b/src/dusk/randomizer/logic/area.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include "entrance.hpp" +#include "requirement.hpp" + +#include +#include + +// Forward Declarations +namespace tphdr::logic::location +{ + class Location; +} + +namespace tphdr::logic::search +{ + class Search; +} + +namespace tphdr::logic::world +{ + class World; +} + +namespace tphdr::logic::area +{ + class Area; + class LocationAccess + { + public: + LocationAccess(tphdr::logic::location::Location* loc, const tphdr::logic::requirement::Requirement& req, Area* area); + + tphdr::logic::location::Location* GetLocation() const; + const tphdr::logic::requirement::Requirement& GetRequirement(); + Area* GetArea() const; + int GetID() const; + + private: + static int _idCounter; + + int _id = -1; + tphdr::logic::location::Location* _loc = nullptr; + tphdr::logic::requirement::Requirement _req; + Area* _area = nullptr; + }; + + class EventAccess + { + public: + EventAccess(const tphdr::logic::requirement::Requirement& req, Area* area, const int& eventIndex); + + const tphdr::logic::requirement::Requirement& GetRequirement(); + Area* GetArea() const; + int GetEventIndex() const; + std::string GetName() const; + + private: + tphdr::logic::requirement::Requirement _req; + Area* _area = nullptr; + int _eventIndex = -1; + }; + + class Area + { + public: + Area(const std::string& name, tphdr::logic::world::World* world); + + std::string GetName() const; + void SetHardAssignedRegion(const std::string& _hardAssignedRegion); + std::string GetHardAssignRegion() const; + void SetEvents(std::list>& events); + std::list GetEvents() const; + void SetLocations(std::list>& locations); + std::list GetLocations() const; + void SetExits(std::list>& exits); + std::list GetExits() const; + void AddExit(std::unique_ptr& exit); + void RemoveExit(tphdr::logic::entrance::Entrance* exit); + void AddEntrance(tphdr::logic::entrance::Entrance* entrance); + void RemoveEntrance(tphdr::logic::entrance::Entrance* entrance); + std::list GetEntrances() const; + tphdr::logic::world::World* GetWorld() const; + void SetCanChangeTime(const bool& canChangeTime); + bool CanChangeTime() const; + void SetCanTransform(const bool& canTransform); + bool CanTransform() const; + void AddHintRegion(const std::string& region); + std::set GetHintRegions(); + void SetTwilightCompletedMacroIndex(const int& macroIndex); + int GetTwilightCompletedMacroIndex() const; + bool TwilightCleared(tphdr::logic::search::Search* search) const; + + /** + * @brief Assigns this area's hint regions(s) as well as assigns any locations within the area to a dungeon if the + * area's hint region is a dungeon + */ + void AssignHintRegionsAndDungeonLocations(); + + private: + static int _idCounter; + + int _id = -1; + std::string _name = ""; + std::string _hardAssignedRegion = ""; + std::set _hintRegions = {}; + std::list> _events = {}; + std::list> _locations = {}; + std::list> _exits = {}; + std::list _entrances = {}; + tphdr::logic::world::World* _world; + bool _canChangeTime = false; + bool _canTransform = false; + int _twilightCompletedMacroIndex = -1; + }; + +} // namespace tphdr::logic::area diff --git a/src/dusk/randomizer/logic/dungeon.cpp b/src/dusk/randomizer/logic/dungeon.cpp new file mode 100644 index 0000000000..86965a36bd --- /dev/null +++ b/src/dusk/randomizer/logic/dungeon.cpp @@ -0,0 +1,127 @@ +#include "dungeon.hpp" + +#include "area.hpp" +#include "entrance.hpp" +#include "item.hpp" +#include "world.hpp" + +#include "../utility/container.hpp" +#include "../utility/log.hpp" + +namespace tphdr::logic::dungeon +{ + Dungeon::Dungeon(const std::string& name, tphdr::logic::world::World* world): _name(name), _world(world) {} + + std::string Dungeon::GetName() const + { + return this->_name; + } + + void Dungeon::SetSmallKey(tphdr::logic::item::Item* item) + { + this->_smallKey = item; + LOG_TO_DEBUG("Set \"" + item->GetName() + "\" as small key for dungeon " + this->_name); + } + + tphdr::logic::item::Item* Dungeon::GetSmallKey() const + { + return this->_smallKey; + } + + void Dungeon::SetBigKey(tphdr::logic::item::Item* item) + { + this->_bigKey = item; + LOG_TO_DEBUG("Set \"" + item->GetName() + "\" as big key for dungeon " + this->_name); + } + + tphdr::logic::item::Item* Dungeon::GetBigKey() const + { + return this->_bigKey; + } + + void Dungeon::SetCompass(tphdr::logic::item::Item* item) + { + this->_compass = item; + LOG_TO_DEBUG("Set \"" + item->GetName() + "\" as compass for dungeon " + this->_name); + } + + tphdr::logic::item::Item* Dungeon::GetCompass() const + { + return this->_compass; + } + + void Dungeon::SetDungeonMap(tphdr::logic::item::Item* item) + { + this->_dungeonMap = item; + LOG_TO_DEBUG("Set \"" + item->GetName() + "\" as dungeon map for dungeon " + this->_name); + } + + tphdr::logic::item::Item* Dungeon::GetDungeonMap() const + { + return this->_dungeonMap; + } + + void Dungeon::SetStartingArea(tphdr::logic::area::Area* startingArea) + { + this->_startingArea = startingArea; + LOG_TO_DEBUG("Set \"" + startingArea->GetName() + "\" as starting area for dungeon " + this->_name) + } + + tphdr::logic::area::Area* Dungeon::GetStartingAreas() + { + return this->_startingArea; + } + + void Dungeon::AddStartingEntrance(tphdr::logic::entrance::Entrance* startingEntrance) + { + this->_startingEntrances.insert(startingEntrance); + LOG_TO_DEBUG("Added \"" + startingEntrance->GetOriginalName() + "\" as starting entrance for dungeon " + this->_name) + } + + std::unordered_set Dungeon::GetStartingEntrances() const + { + return this->_startingEntrances; + }; + + void Dungeon::AddLocation(tphdr::logic::location::Location* location) + { + if (!tphdr::utility::container::ElementInContainer(this->_locations, location)) + { + this->_locations.push_back(location); + LOG_TO_DEBUG(location->GetName() + " has been assigned to dungeon " + this->_name); + } + } + + tphdr::logic::location::LocationPool Dungeon::GetLocations() + { + return this->_locations; + } + + void Dungeon::SetGoalLocation(tphdr::logic::location::Location* goalLocation) + { + this->_goalLocation = goalLocation; + LOG_TO_DEBUG(goalLocation->GetName() + " has been assigned as goal location to dungeon " + this->_name); + } + + tphdr::logic::location::Location* Dungeon::GetGoalLocation() + { + return this->_goalLocation; + } + + void Dungeon::SetRequired(const bool& required) + { + this->_required = required; + LOG_TO_DEBUG(this->_name + " has been set as required."); + } + + bool Dungeon::IsRequired() const + { + return this->_required; + } + + bool Dungeon::ShouldBeBarren() const + { + return !this->_required && this->_world->Setting("Unrequired Dungeons Are Barren") == "On"; + } + +} // namespace tphdr::logic::dungeon diff --git a/src/dusk/randomizer/logic/dungeon.hpp b/src/dusk/randomizer/logic/dungeon.hpp new file mode 100644 index 0000000000..08839c51a4 --- /dev/null +++ b/src/dusk/randomizer/logic/dungeon.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "location.hpp" + +#include + +// Forward declarations +namespace tphdr::logic::item +{ + class Item; +} + +namespace tphdr::logic::area +{ + class Area; +} + +namespace tphdr::logic::entrance +{ + class Entrance; +} +namespace tphdr::logic::world +{ + class World; +} + +namespace tphdr::logic::dungeon +{ + /** + * @brief Holds dungeon specific data + */ + class Dungeon + { + public: + Dungeon(const std::string& name, tphdr::logic::world::World* world); + + std::string GetName() const; + void SetSmallKey(tphdr::logic::item::Item* item); + tphdr::logic::item::Item* GetSmallKey() const; + void SetBigKey(tphdr::logic::item::Item* item); + tphdr::logic::item::Item* GetBigKey() const; + void SetCompass(tphdr::logic::item::Item* item); + tphdr::logic::item::Item* GetCompass() const; + void SetDungeonMap(tphdr::logic::item::Item* item); + tphdr::logic::item::Item* GetDungeonMap() const; + void SetStartingArea(tphdr::logic::area::Area* startingArea); + tphdr::logic::area::Area* GetStartingAreas(); + void AddStartingEntrance(tphdr::logic::entrance::Entrance* startingEntrance); + std::unordered_set GetStartingEntrances() const; + void AddLocation(tphdr::logic::location::Location* location); + tphdr::logic::location::LocationPool GetLocations(); + void SetGoalLocation(tphdr::logic::location::Location* goalLocation); + tphdr::logic::location::Location* GetGoalLocation(); + void SetRequired(const bool& required); + bool IsRequired() const; + + /** + * @brief Returns whether or not the dungeon should be barren given the current settings and placement of dungeon + * rewards and/or plandomized items + */ + bool ShouldBeBarren() const; + + private: + std::string _name = ""; + tphdr::logic::world::World* _world; + tphdr::logic::item::Item* _smallKey; + tphdr::logic::item::Item* _bigKey; + tphdr::logic::item::Item* _compass; + tphdr::logic::item::Item* _dungeonMap; + tphdr::logic::area::Area* _startingArea; + std::unordered_set _startingEntrances; + tphdr::logic::location::Location* _goalLocation; + tphdr::logic::location::LocationPool _locations = {}; + bool _required = false; + }; +} // namespace tphdr::logic::dungeon diff --git a/src/dusk/randomizer/logic/entrance.cpp b/src/dusk/randomizer/logic/entrance.cpp new file mode 100644 index 0000000000..5500642443 --- /dev/null +++ b/src/dusk/randomizer/logic/entrance.cpp @@ -0,0 +1,337 @@ +#include "entrance.hpp" + +#include "area.hpp" +#include "world.hpp" +#include "../utility/log.hpp" +#include "../utility/string.hpp" + +namespace tphdr::logic::entrance +{ + std::unordered_set NON_ASSUMED_TYPES = {Type::SPAWN, Type::WARP_PORTAL}; + + Type TypeFromStr(const std::string& str) + { + std::unordered_map types = {{"Spawn", Type::SPAWN}, + {"Warp Portal", Type::WARP_PORTAL}, + {"Dungeon", Type::DUNGEON}, + {"Boss", Type::BOSS}, + {"Grotto", Type::GROTTO}, + {"Mixed Pool 1", Type::MIXED_POOL_1}, + {"Mixed Pool 2", Type::MIXED_POOL_2}, + {"Mixed Pool 3", Type::MIXED_POOL_3}, + {"Mixed Pool 4", Type::MIXED_POOL_4}, + {"Mixed Pool 5", Type::MIXED_POOL_5}, + {"Cave", Type::CAVE}, + {"Interior", Type::INTERIOR}, + {"Overworld", Type::OVERWORLD}}; + + if (!types.contains(str)) + { + return Type::INVALID; + } + + return types.at(str); + } + + std::string TypeToStr(const Type& type) + { + std::unordered_map types = {{Type::SPAWN, "Spawn"}, + {Type::WARP_PORTAL, "Warp Portal"}, + {Type::DUNGEON, "Dungeon"}, + {Type::DUNGEON_REVERSE, "Dungeon Reverse"}, + {Type::BOSS, "Boss"}, + {Type::BOSS_REVERSE, "Boss Reverse"}, + {Type::GROTTO, "Grotto"}, + {Type::GROTTO_REVERSE, "Grotto Reverse"}, + {Type::MIXED_POOL_1, "Mixed Pool 1"}, + {Type::MIXED_POOL_2, "Mixed Pool 2"}, + {Type::MIXED_POOL_3, "Mixed Pool 3"}, + {Type::MIXED_POOL_4, "Mixed Pool 4"}, + {Type::MIXED_POOL_5, "Mixed Pool 5"}, + {Type::CAVE, "Cave"}, + {Type::CAVE_REVERSE, "Cave Reverse"}, + {Type::INTERIOR, "Interior"}, + {Type::INTERIOR_REVERSE, "Interior Reverse"}, + {Type::OVERWORLD, "Overworld"}}; + + if (!types.contains(type)) + { + return "INVALID"; + } + + return types.at(type); + } + + Type TypeToReverse(const Type& type) + { + std::unordered_map reverse = {{Type::DUNGEON, Type::DUNGEON_REVERSE}, + {Type::DUNGEON_REVERSE, Type::DUNGEON}, + {Type::BOSS, Type::BOSS_REVERSE}, + {Type::BOSS_REVERSE, Type::BOSS}, + {Type::GROTTO, Type::GROTTO_REVERSE}, + {Type::GROTTO_REVERSE, Type::GROTTO}, + {Type::CAVE, Type::CAVE_REVERSE}, + {Type::CAVE_REVERSE, Type::CAVE}, + {Type::INTERIOR, Type::INTERIOR_REVERSE}, + {Type::INTERIOR_REVERSE, Type::INTERIOR}, + // Yes, this is intentional for the overworld type + {Type::OVERWORLD, Type::OVERWORLD}}; + + if (!reverse.contains(type)) + { + return Type::INVALID; + } + + return reverse.at(type); + } + + Entrance::Entrance(tphdr::logic::area::Area* parentArea, + tphdr::logic::area::Area* connectedArea, + const tphdr::logic::requirement::Requirement& req, + tphdr::logic::world::World* world): + _parentArea(parentArea), + _connectedArea(connectedArea), + _originalConnectedArea(connectedArea), + _req(std::move(req)), + _world(world) + { + this->_originalName = this->GetCurrentName(); + this->_computedRequirement._type = tphdr::logic::requirement::Type::IMPOSSIBLE; + } + + void Entrance::SetID(const int& id) + { + this->_id = id; + } + + int Entrance::GetID() const + { + return this->_id; + } + + std::string Entrance::GetCurrentName() const + { + std::string parentName = this->_parentArea ? this->_parentArea->GetName() : "None"; + std::string connectedName = this->_connectedArea ? this->_connectedArea->GetName() : "None"; + return parentName + " -> " + connectedName; + } + + std::string Entrance::GetOriginalName() const + { + return this->_originalName; + } + + void Entrance::GeneralizeOriginalName() + { + tphdr::utility::str::Erase(this->_originalName, " North", " South", " East", " West", " Right", " Left"); + } + + tphdr::logic::area::Area* Entrance::GetParentArea() const + { + return this->_parentArea; + } + + tphdr::logic::area::Area* Entrance::GetConnectedArea() const + { + return this->_connectedArea; + } + + tphdr::logic::area::Area* Entrance::GetOriginalConnectedArea() const + { + return this->_originalConnectedArea; + } + + void Entrance::SetType(const Type& type) + { + this->_type = type; + if (this->_originalType == Type::INVALID) + { + this->_originalType = type; + } + } + + Type Entrance::GetType() const + { + return this->_type; + } + + Type Entrance::GetOriginalType() const + { + return this->_originalType; + } + + void Entrance::SetRequirement(const tphdr::logic::requirement::Requirement& req) + { + this->_req = req; + } + + const tphdr::logic::requirement::Requirement& Entrance::GetRequirement() + { + return this->_req; + } + + void Entrance::SetComputedRequirement(const tphdr::logic::requirement::Requirement& computedRequirement) + { + this->_computedRequirement = computedRequirement; + } + + tphdr::logic::requirement::Requirement Entrance::GetComputedRequirement() + { + return this->_computedRequirement; + } + + tphdr::logic::world::World* Entrance::GetWorld() const + { + return this->_world; + } + + bool Entrance::CanStartAt() const + { + return this->_canStartAt; + } + + void Entrance::SetShuffled(const bool& shuffled) + { + this->_shuffled = shuffled; + } + + bool Entrance::IsShuffled() const + { + return this->_shuffled; + } + + void Entrance::SetDecoupled(const bool& decoupled) + { + this->_decoupled = decoupled; + } + + bool Entrance::IsDecoupled() const + { + return this->_decoupled; + } + + void Entrance::SetDisbled(const bool& disabled) + { + this->_disabled = disabled; + LOG_TO_DEBUG(this->GetOriginalName() + " disabled status set to " + (disabled ? "True" : "False")); + } + + bool Entrance::IsDisabled() const + { + return this->_disabled; + } + + void Entrance::SetPrimary(const bool& primary) + { + this->_primary = primary; + LOG_TO_DEBUG(this->GetOriginalName() + " primary status set to " + (primary ? "True" : "False")); + } + + bool Entrance::IsPrimary() const + { + return this->_primary; + } + + void Entrance::SetTarget(const bool& target) + { + this->_target = target; + } + + bool Entrance::IsTarget() const + { + return this->_target; + } + + void Entrance::SetReplaces(Entrance* replaces) + { + this->_replaces = replaces; + } + + Entrance* Entrance::GetReplaces() const + { + return this->_replaces; + } + + void Entrance::SetReverse(Entrance* reverse) + { + this->_reverse = reverse; + } + + Entrance* Entrance::GetReverse() const + { + return this->_reverse; + } + + Entrance* Entrance::GetAssumed() const + { + return this->_assumed; + } + + void Entrance::Connect(tphdr::logic::area::Area* newConnectedArea) + { + this->_connectedArea = newConnectedArea; + newConnectedArea->AddEntrance(this); + } + + tphdr::logic::area::Area* Entrance::Disconnect() + { + this->_connectedArea->RemoveEntrance(this); + auto previouslyConnected = this->_connectedArea; + this->_connectedArea = nullptr; + return previouslyConnected; + } + + void Entrance::BindTwoWay(Entrance* returnEntrance) + { + this->SetReverse(returnEntrance); + returnEntrance->SetReverse(this); + } + + Entrance* Entrance::GetNewTarget() + { + auto root = this->_world->GetRootArea(); + auto targetEntrance = + std::make_unique(root, nullptr, tphdr::logic::requirement::NO_REQUIREMENT, this->_world); + auto target = targetEntrance.get(); + root->AddExit(targetEntrance); // This moves the variable, so we have to use the pointer for the rest of the function + target->Connect(this->_connectedArea); + target->SetReplaces(this); + target->SetTarget(true); + return target; + } + + Entrance* Entrance::AssumeReachable() + { + if (this->_assumed == nullptr) + { + this->_assumed = this->GetNewTarget(); + this->Disconnect(); + } + return this->_assumed; + } + + std::tuple GetParentAndConnectedAreaNames(const std::string& originalName) + { + std::string parentAreaName; + std::string connectedAreaName; + if (tphdr::utility::str::Contains(originalName, " -> ")) + { + auto separatorIndex = originalName.find(" -> "); + parentAreaName = originalName.substr(0, separatorIndex); + connectedAreaName = originalName.substr(separatorIndex + 4); + } + else if (tphdr::utility::str::Contains(originalName, " from ")) + { + auto separatorIndex = originalName.find(" from "); + connectedAreaName = originalName.substr(0, separatorIndex); + parentAreaName = originalName.substr(separatorIndex + 6); + } + else + { + throw std::runtime_error("Could not parse area names from entrance string \"" + originalName + + "\". Please make sure your syntax is correct"); + } + + return {parentAreaName, connectedAreaName}; + } +} // namespace tphdr::logic::entrance diff --git a/src/dusk/randomizer/logic/entrance.hpp b/src/dusk/randomizer/logic/entrance.hpp new file mode 100644 index 0000000000..1c3f8d9042 --- /dev/null +++ b/src/dusk/randomizer/logic/entrance.hpp @@ -0,0 +1,204 @@ +#pragma once + +#include "requirement.hpp" + +#include +#include +#include + +// Forward Declarations +namespace tphdr::logic::area +{ + class Area; +} + +namespace tphdr::logic::world +{ + class World; +} + +namespace tphdr::logic::entrance +{ + enum Type + { + INVALID = 0, + // The order of this enum is also the order in which the different types of entrances + // will be shuffled. So this ordering is important. Generally we want to shuffle entrances + // near the "outside" of the world graph first (dungeon entrances/grottos) and then follow that + // up with entrance types that are closer to the "inside" of the world graph which have more + // consequences to being shuffled. The mixed pools are thrown in the middle since this is the most stable + // place for them to be to not interfere with the ordering too much. + SPAWN, + WARP_PORTAL, + GROTTO, + GROTTO_REVERSE, + BOSS, + BOSS_REVERSE, + DUNGEON, + DUNGEON_REVERSE, + MIXED_POOL_1, + MIXED_POOL_2, + MIXED_POOL_3, + MIXED_POOL_4, + MIXED_POOL_5, + CAVE, + CAVE_REVERSE, + INTERIOR, + INTERIOR_REVERSE, + OVERWORLD, + ALL, + }; + + extern std::unordered_set NON_ASSUMED_TYPES; + + /** + * @brief Takes a string representation of a Type and returns the + * associated enum value. + * + * @param str The string representation of a Type. + * @return The associated enum value for the passed in type. + */ + Type TypeFromStr(const std::string& str); + + std::string TypeToStr(const Type& type); + + Type TypeToReverse(const Type& type); + + class Entrance + { + public: + Entrance(tphdr::logic::area::Area* parentArea, + tphdr::logic::area::Area* connectedArea, + const tphdr::logic::requirement::Requirement& req, + tphdr::logic::world::World* world); + + void SetID(const int& id); + int GetID() const; + std::string GetCurrentName() const; + std::string GetOriginalName() const; + /** + * @brief Removes cardinal/direction specifiers from the entrance's name (North, South, East, West, Left, Right) + */ + void GeneralizeOriginalName(); + tphdr::logic::area::Area* GetParentArea() const; + tphdr::logic::area::Area* GetConnectedArea() const; + tphdr::logic::area::Area* GetOriginalConnectedArea() const; + void SetType(const Type& type); + Type GetType() const; + Type GetOriginalType() const; + void SetRequirement(const tphdr::logic::requirement::Requirement& req); + const tphdr::logic::requirement::Requirement& GetRequirement(); + void SetComputedRequirement(const tphdr::logic::requirement::Requirement& computedRequirement); + tphdr::logic::requirement::Requirement GetComputedRequirement(); + tphdr::logic::world::World* GetWorld() const; + bool CanStartAt() const; + void SetShuffled(const bool& shuffled); + bool IsShuffled() const; + void SetDecoupled(const bool& decoupled); + bool IsDecoupled() const; + void SetDisbled(const bool& disabled); + bool IsDisabled() const; + void SetPrimary(const bool& primary); + bool IsPrimary() const; + void SetTarget(const bool& target); + bool IsTarget() const; + + void SetReplaces(Entrance* replaces); + Entrance* GetReplaces() const; + void SetReverse(Entrance* reverse); + Entrance* GetReverse() const; + Entrance* GetAssumed() const; + + /** + * @brief Connect this entrance to the passed in area, and add this entrance to the list of entrances for the passed in + * area + * + * @param newConnectedArea The area to connect this entrance to + */ + void Connect(tphdr::logic::area::Area* newConnectedArea); + + /** + * @brief Disconnect this entrance from the area it leads to. Will also remove this entrance from it's connected area's + * entrances. + * + * @return The area this entrance was previously connected to + */ + tphdr::logic::area::Area* Disconnect(); + + /** + * @brief Links two entrances by setting them as each others' reverse entrance + */ + void BindTwoWay(Entrance* returnEntrance); + + /** + * @brief Creates a new target entrance that corresponds to where this one leads, and + * attaches it to the root of the world graph. + */ + Entrance* GetNewTarget(); + + /** + * @brief Create this entrance's target and disconnect it from the original entrance. + * This assumes reachable access to the entrance for the entrance shuffling algorithm + */ + Entrance* AssumeReachable(); + + private: + int _id = -1; + tphdr::logic::area::Area* _parentArea = nullptr; + tphdr::logic::area::Area* _connectedArea = nullptr; + tphdr::logic::area::Area* _originalConnectedArea = nullptr; + Type _type = Type::INVALID; + Type _originalType = Type::INVALID; + std::string _originalName = ""; + tphdr::logic::world::World* _world = nullptr; + + /** + * @brief The local requirement for this entrance assuming we have access to its parent area. + */ + tphdr::logic::requirement::Requirement _req; + + /** + * @brief The flattened requirement which includes everything necessary to reach this entrance from the root of the + * world graph. + */ + tphdr::logic::requirement::Requirement _computedRequirement; + + // Variables used for entrance shuffling + bool _canStartAt = false; + bool _shuffled = false; + bool _decoupled = false; + bool _disabled = false; + + // A target entrance is one created to mimic the effect of going + // through a specific real entrance. The target is attatched to + // the root of the world graph and is connected to it's correpsonding + // entrance's connected area. + bool _target = false; + + // Primary entrances are those that we think of as + // "going into" areas. Entering dungeons, entering grottos, + // and entering doors are all primary entrances. The opposite + // idea, "leaving" areas, are not primary entrances. + bool _primary = false; + + // The reverse is the entrance that sends the player in the + // natural opposite direction of this entrance. The reverse entrance + // of entering a grotto would be the entrance that leaves the grotto + Entrance* _reverse = nullptr; + + // If the entrance is shuffled, _replaces is the target entrance that replaces + // this one. If this *is* a target entrance, then _replaces holds the + // entrance that this target *corresponds* to. + Entrance* _replaces = nullptr; + + // If the entrance is shuffled, _assumed is the target entrance that *corresponds* + // to this one. So if the entrance is North Faron Woods -> Forest Temple Entrance, + // then _assumed is the target entrance Root -> Forest Temple Entrance + Entrance* _assumed = nullptr; + }; + + using EntrancePool = std::vector; + using EntrancePools = std::map; + + std::tuple GetParentAndConnectedAreaNames(const std::string& originalName); +} // namespace tphdr::logic::entrance diff --git a/src/dusk/randomizer/logic/entrance_shuffle.cpp b/src/dusk/randomizer/logic/entrance_shuffle.cpp new file mode 100644 index 0000000000..0e8f8ce940 --- /dev/null +++ b/src/dusk/randomizer/logic/entrance_shuffle.cpp @@ -0,0 +1,780 @@ +#include "entrance_shuffle.hpp" + +#include "item_pool.hpp" +#include "search.hpp" + +#include "../utility/file.hpp" +#include "../utility/random.hpp" +#include "../utility/yaml.hpp" + +using namespace tphdr::logic::entrance; + +namespace tphdr::logic::entrance_shuffle +{ + void ShuffleWorldEntrances(tphdr::logic::world::World* world, tphdr::logic::world::WorldPool& worlds) + { + SetAllEntrancesData(world); + + auto entrancePools = CreateEntrancePools(world); + auto targetEntrancePools = CreateTargetPools(entrancePools); + + // Set plando entrances first + SetPlandomizedEntrances(world, worlds, entrancePools, targetEntrancePools); + + // Then shuffle non-assumed types (currently this is just spawn) + ShuffleNonAssumedEntrancesPools(world, worlds, entrancePools, targetEntrancePools); + + // Shuffle the rest of the entrance pools + for (auto& [entranceType, entrancePool] : entrancePools) + { + ShuffleEntrancePool(world, worlds, entrancePool, targetEntrancePools[entranceType]); + } + + // Validate the world one last time to ensure everything worked + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + ValidateWorld(world, worlds, nullptr, completeItemPool); + } + + void SetAllEntrancesData(tphdr::logic::world::World* world) + { + auto filepath = DATA_PATH "entrance_shuffle_data.yaml"; + tphdr::utility::file::Verify(filepath); + + // Keep track of which double door entrances are together + std::unordered_map> coupledDoors = {}; + + auto entranceDataTree = LoadYAML(filepath); + for (const auto& entranceDataNode : entranceDataTree) + { + // Check to make sure all required fields are present + YAMLVerifyFields(entranceDataNode, "Type", "Forward"); + + auto typeStr = entranceDataNode["Type"].as(); + auto type = tphdr::logic::entrance::TypeFromStr(typeStr); + if (type == tphdr::logic::entrance::Type::INVALID) + { + throw std::runtime_error("Unknown entrance type \"" + typeStr + "\" in entrance shuffle node:\n" + + YAML::Dump(entranceDataNode)); + } + + auto& forwardEntry = entranceDataNode["Forward"]; + // Check to make sure all required fields are present for the forward entry + YAMLVerifyFields(forwardEntry, "Connection" /*, "Info" */); + + auto forwardEntrance = world->GetEntrance(forwardEntry["Connection"].as()); + forwardEntrance->SetType(type); + // TODO: Set actual entrance data + forwardEntrance->SetID(world->GetNewEntranceID()); + forwardEntrance->SetPrimary(true); + + if (entranceDataNode["Return"]) + { + auto& returnEntry = entranceDataNode["Return"]; + YAMLVerifyFields(returnEntry, "Connection" /*, "Info" */); + + auto returnEntrance = world->GetEntrance(returnEntry["Connection"].as()); + returnEntrance->SetType(type); + // TODO: Set actual entrance data + returnEntrance->SetID(world->GetNewEntranceID()); + forwardEntrance->BindTwoWay(returnEntrance); + + // Add double door entrances to their respective tag group + if (entranceDataNode["Door Couple Tag"]) + { + auto tag = entranceDataNode["Door Couple Tag"].as(); + if (!coupledDoors.contains(tag)) + { + coupledDoors[tag] = {}; + } + coupledDoors.at(tag).push_back(forwardEntrance); + coupledDoors.at(tag).push_back(returnEntrance); + } + } + } + + // If double doors are coupled, add the coupled door's info to the main door, remove the coupled door entrance, and + // rename the main door to be more general + if (world->Setting("Decouple Double Door Entrances") == "Off") + { + for (auto& [tag, doors] : coupledDoors) + { + while (!doors.empty()) + { + auto mainDoor = doors.back(); + doors.pop_back(); + + auto coupledDoorItr = + std::find_if(doors.begin(), + doors.end(), + [&](const auto& door) { return door->IsPrimary() == mainDoor->IsPrimary(); }); + auto coupledDoor = *coupledDoorItr; + + // TODO: Add the coupled door's info to the main door + + // Completely remove the coupled door from the world graph + doors.erase(coupledDoorItr); + coupledDoor->GetConnectedArea()->RemoveEntrance(coupledDoor); + coupledDoor->GetParentArea()->RemoveExit(coupledDoor); + + // Change the main door's name to be more general + mainDoor->GeneralizeOriginalName(); + } + } + } + } + + EntrancePools CreateEntrancePools(tphdr::logic::world::World* world) + { + EntrancePools entrancePools = {}; + + // Spawn + if (world->Setting("Randomize Starting Spawn") == "On") + { + entrancePools[Type::SPAWN] = world->GetShuffleableEntrances(Type::SPAWN); + } + + // Dungeon Entrances + if (world->Setting("Randomize Dungeon Entrances") >= "On") + { + entrancePools[Type::DUNGEON] = world->GetShuffleableEntrances(Type::DUNGEON, /*onlyPrimary = */ true); + + // Remove Hyrule Castle if it's not being shuffled + if (world->Setting("Randomize Dungeon Entrances") != "On + Hyrule Castle") + { + auto& dungeonPool = entrancePools[Type::DUNGEON]; + auto removed = std::remove_if( + dungeonPool.begin(), + dungeonPool.end(), + [](const auto& entrance) + { return entrance->GetOriginalName() == "Castle Town North Inside Barrier -> Hyrule Castle Entrance"; }); + dungeonPool.erase(removed, dungeonPool.end()); + } + + if (world->Setting("Decouple Entrances") == "On") + { + entrancePools[Type::DUNGEON_REVERSE] = GetReverseEntrances(entrancePools[Type::DUNGEON]); + } + } + + // Boss Entrances + if (world->Setting("Randomize Boss Entrances") == "On") + { + entrancePools[Type::BOSS] = world->GetShuffleableEntrances(Type::BOSS, /*onlyPrimary = */ true); + + if (world->Setting("Decouple Entrances") == "On") + { + entrancePools[Type::BOSS_REVERSE] = GetReverseEntrances(entrancePools[Type::BOSS]); + } + } + + // Grotto Entrances + if (world->Setting("Randomize Grotto Entrances") == "On") + { + entrancePools[Type::GROTTO] = world->GetShuffleableEntrances(Type::GROTTO, /*onlyPrimary = */ true); + + if (world->Setting("Decouple Entrances") == "On") + { + entrancePools[Type::GROTTO_REVERSE] = GetReverseEntrances(entrancePools[Type::GROTTO]); + } + } + + // Cave Entrances + if (world->Setting("Randomize Cave Entrances") == "On") + { + entrancePools[Type::CAVE] = world->GetShuffleableEntrances(Type::CAVE, /*onlyPrimary = */ true); + + if (world->Setting("Decouple Entrances") == "On") + { + entrancePools[Type::CAVE_REVERSE] = GetReverseEntrances(entrancePools[Type::CAVE]); + } + } + + // Interior Entrances + if (world->Setting("Randomize Interior Entrances") == "On") + { + entrancePools[Type::INTERIOR] = world->GetShuffleableEntrances(Type::INTERIOR, /*onlyPrimary = */ true); + + if (world->Setting("Decouple Entrances") == "On") + { + entrancePools[Type::INTERIOR_REVERSE] = GetReverseEntrances(entrancePools[Type::INTERIOR]); + } + } + + // Overworld Entrances + if (world->Setting("Randomize Overworld Entrances") == "On") + { + // Normally we allow any overworld entrances to link together. + // However, if overworld entrances are mixed with other entrance types + // that expect to only match with exclusively primary or non-primary + // entrances, we have to separate overworld entrances by their primary/ + // non-primary distinction to fit with the other entrances + const auto& mixedPools = world->GetSettings().GetMixedEntrancePools(); + bool excludeOverworldReverse = + world->Setting("Decouple Entrances") == "Off" && + std::any_of( + mixedPools.begin(), + mixedPools.end(), + [](const auto& pool) + { return tphdr::utility::container::ElementInContainer(pool, "Overworld"); }); /*Overworld in a mixed pool*/ + entrancePools[Type::OVERWORLD] = + world->GetShuffleableEntrances(Type::OVERWORLD, /*onlyPrimary = */ excludeOverworldReverse); + } + + // Match pool types + for (auto& [entranceType, entrancePool] : entrancePools) + { + for (auto& entrance : entrancePool) + { + entrance->SetType(entranceType); + } + } + + SetShuffledEntrances(entrancePools); + + // Set appropriate types as decoupled + auto potentiallyDecoupledTypes = { + Type::DUNGEON, + Type::DUNGEON_REVERSE, + Type::BOSS, + Type::BOSS_REVERSE, + Type::GROTTO, + Type::GROTTO_REVERSE, + Type::CAVE, + Type::CAVE_REVERSE, + Type::INTERIOR, + Type::INTERIOR_REVERSE, + Type::OVERWORLD, + }; + if (world->Setting("Decouple Entrances") == "On") + { + for (const auto& type : potentiallyDecoupledTypes) + { + if (entrancePools.contains(type)) + { + for (auto& entrance : entrancePools.at(type)) + { + entrance->SetDecoupled(true); + } + } + } + } + + // Combine the Mixed pools into their respective pools + const auto& mixedPoolList = world->GetSettings().GetMixedEntrancePools(); + int counter = 1; + for (const auto& mixedPool : mixedPoolList) + { + auto mixedType = TypeFromStr("Mixed Pool " + std::to_string(counter)); + for (const auto& typeStr : mixedPool) + { + auto type = TypeFromStr(typeStr); + if (type == Type::INVALID) + { + throw std::runtime_error("Unknown entrance type \"" + typeStr + "\" in mixed pools"); + } + // Only bother with entrance types that are being shuffled + for (const auto& entranceType : {type, TypeToReverse(type)}) + { + if (entrancePools.contains(entranceType)) + { + // Create the Mixed Pool entry if it doesn't exist + if (!entrancePools.contains(mixedType)) + { + entrancePools[mixedType] = {}; + } + for (const auto& entrance : entrancePools.at(entranceType)) + { + entrancePools.at(mixedType).push_back(entrance); + } + // Delete the original pool once it's been added + entrancePools.erase(entranceType); + } + } + } + counter += 1; + } + + return entrancePools; + } + + EntrancePools CreateTargetPools(EntrancePools& entrancePools) + { + EntrancePools targetEntrancePools = {}; + for (auto& [type, entrancePool] : entrancePools) + { + if (type == Type::SPAWN) + { + EntrancePool spawnPool = {}; + auto world = entrancePool[0]->GetWorld(); + // Get all the entrances of these types to use as spawn targets + for (const auto& type : {Type::SPAWN, Type::INTERIOR, Type::CAVE, Type::OVERWORLD, Type::GROTTO}) + { + auto entrances = world->GetShuffleableEntrances(type); + for (const auto& entrance : entrances) + { + auto newTarget = entrance->GetNewTarget(); + spawnPool.push_back(newTarget); + + // Don't assume we have access to random spawn targets. We're only connecting to one of them + // so assuming we have access to all of them would be erroneous. + newTarget->SetRequirement(tphdr::logic::requirement::IMPOSSIBLE_REQUIREMENT); + } + } + targetEntrancePools[type] = spawnPool; + for (auto& entrance : entrancePool) + { + entrance->Disconnect(); + } + } + else + { + targetEntrancePools[type] = AssumeEntrancePool(entrancePool); + } + } + return targetEntrancePools; + } + + EntrancePool AssumeEntrancePool(EntrancePool& entrancePool) + { + EntrancePool assumedPool = {}; + for (auto& entrance : entrancePool) + { + auto assumedForward = entrance->AssumeReachable(); + if (entrance->GetReverse() && !entrance->IsDecoupled()) + { + auto assumedReturn = entrance->GetReverse()->AssumeReachable(); + assumedForward->BindTwoWay(assumedReturn); + } + assumedPool.push_back(assumedForward); + } + return assumedPool; + } + + void SetPlandomizedEntrances(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + EntrancePools& entrancePools, + EntrancePools& targetEntrancePools) + { + LOG_TO_DEBUG("Now placing plandomizer entrances"); + auto itemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + + for (auto& [plandoEntrance, plandoTarget] : world->GetPlandomizerEntrances()) + { + auto entranceToConnect = plandoEntrance; + auto targetToConnect = plandoTarget; + auto entranceType = plandoEntrance->GetType(); + + // Throw error if entrance/target types are not shuffleable + if (entranceType == Type::INVALID) + { + throw std::runtime_error("Plandomizer Error: " + entranceToConnect->GetOriginalName() + + " is not an entrance that can be shuffled"); + } + if (plandoTarget->GetType() == Type::INVALID) + { + throw std::runtime_error("Plandomizer Error: " + plandoTarget->GetOriginalName() + + " is not an entrance that can be shuffled"); + } + + // Throw error if entrance type is shuffleable, but the type itself is not randomized currently + if (!entrancePools.contains(entranceType)) + { + throw std::runtime_error("Plandomizer Error: " + entranceToConnect->GetOriginalName() + "'s type " + + TypeToStr(entranceType) + " is not being shuffled and thus can't be plandomized."); + } + + // Get the appropriate pools + auto& entrancePool = entrancePools.at(entranceType); + auto& targetPool = targetEntrancePools.at(entranceType); + + // If entrances are coupled, but the user tries to plandomize a non-primary connection, get the primary connection + // instead + if (world->Setting("Decouple Entrances") == "Off" && + tphdr::utility::container::ElementInContainer(entrancePool, entranceToConnect->GetReverse())) + { + entranceToConnect = entranceToConnect->GetReverse(); + targetToConnect = targetToConnect->GetReverse(); + } + + if (tphdr::utility::container::ElementInContainer(entrancePool, entranceToConnect)) + { + bool validTargetFound = false; + for (auto& target : targetPool) + { + // If we've found the proper target + if (targetToConnect == target->GetReplaces()) + { + try + { + CheckEntrancesCompatibility(entranceToConnect, target); + ChangeConnections(entranceToConnect, target); + // If the spawn entrance isn't placed, then we can't validate the world + if (world->GetEntrance("Links Spawn -> Outside Links House")->GetConnectedArea() != nullptr) + { + ValidateWorld(world, worlds, entranceToConnect, itemPool); + } + validTargetFound = true; + ConfirmReplacement(entranceToConnect, target); + } + catch(const EntranceShuffleError& e) + { + throw std::runtime_error("Could not connect plandomized entrance " + + entranceToConnect->GetOriginalName() + " to " + target->GetOriginalName() + + " Reason:\n" + e.what()); + } + if (validTargetFound) + { + break; + } + } + } + + // If we found our target, delete the entrance and it's now connected target from their respective pools + if (validTargetFound) + { + tphdr::utility::container::Erase(entrancePool, entranceToConnect); + tphdr::utility::container::Erase(targetPool, targetToConnect->GetAssumed()); + } + // Otherwise, the target is invalid + else + { + throw std::runtime_error("Entrance " + targetToConnect->GetOriginalName() + " is not a valid target for " + + entranceToConnect->GetOriginalName()); + } + } + else + { + throw std::runtime_error("Plandomizer Error: " + entranceToConnect->GetOriginalName() + + " for some reason could not be found."); + } + } + + LOG_TO_DEBUG("All plandomized entrances have been placed."); + } + + void ShuffleNonAssumedEntrancesPools(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + EntrancePools& entrancePools, + EntrancePools& targetEntrancePools) + { + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + + // The idea here is we want to try shuffling all the non-assumed entrances + // at the same time since we can't validate the world after each one individually + // (That would require assuming access to entrances which we can't guarantee access to) + // Realistically, this should never take more than 1 or 2 tries unless there's some wacky + // plandomizer stuff going on. Currently the only non-assumed entrance we're shuffling is the randomized spawn + // but if we ever shuffle warp portals, they'll go here too. + + int retries = 20; + while (retries > 0) + { + std::unordered_map rollbacks = {}; + // Connect each non-assumed entrance to a random target in its pool + for (auto& [entranceType, entrancePool] : entrancePools) + { + if (NON_ASSUMED_TYPES.contains(entranceType)) + { + auto& targetEntrancePool = targetEntrancePools.at(entranceType); + for (auto& entrance : entrancePool) + { + tphdr::utility::random::ShufflePool(targetEntrancePool); + + // Loop through and find a valid target entrance to connect to + for (auto& target : targetEntrancePool) + { + // If this target has already been used, skip over it + if (target->GetConnectedArea() == nullptr) + { + continue; + } + + LOG_TO_DEBUG("Attempting to connect " + entrance->GetOriginalName() + " to " + + target->GetConnectedArea()->GetName() + " [W" + + std::to_string(entrance->GetWorld()->GetID()) + "]"); + ChangeConnections(entrance, target); + rollbacks[entrance] = target; + break; + } + } + } + } + + // After each entrance is connected, then try to validate the world + bool successfulConnection = false; + try + { + ValidateWorld(world, worlds, nullptr, completeItemPool); + for (auto& [entrance, target] : rollbacks) + { + ConfirmReplacement(entrance, target); + tphdr::utility::container::Erase(targetEntrancePools[entrance->GetType()], target); + } + // Once we've made a valid world, delete all other targets that didn't get used + for (auto& [entranceType, targetPool] : targetEntrancePools) + { + if (NON_ASSUMED_TYPES.contains(entranceType)) + { + for (auto& target : targetPool) + { + DeleteTargetEntrance(target); + } + // Also delete the non-assumed entrance type from the pool + entrancePools.erase(entranceType); + } + } + successfulConnection = true; + } + catch(const EntranceShuffleError& e) + { + // If we're unsuccessful, revert all connections and try again + LOG_TO_DEBUG(std::string("Failed to connect non-assumed entrances. Reason: ") + e.what()); + retries -= 1; + for (auto& [entrance, target] : rollbacks) + { + RestoreConnections(entrance, target); + } + } + if(successfulConnection) + { + break; + } + } + + if (retries <= 0) + { + throw std::runtime_error("Ran out of retries when attempting to place non-assumed entrances"); + } + } + + void ShuffleEntrancePool(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + EntrancePool& entrancePool, + EntrancePool& targetEntrancePool, + int retries /* = 20*/) + { + while (retries > 0) + { + retries -= 1; + std::unordered_map rollbacks = {}; + try + { + ShuffleEntrances(worlds, entrancePool, targetEntrancePool, rollbacks); + for (auto& [entrance, target] : rollbacks) + { + ConfirmReplacement(entrance, target); + } + return; + } + catch(const EntranceShuffleError& e) + { + for (auto& [entrance, target] : rollbacks) + { + RestoreConnections(entrance, target); + } + LOG_TO_DEBUG("Failed to place all entrances in a pool for World " + std::to_string(world->GetID()) + + ". Will retry " + std::to_string(retries) + " more times"); + LOG_TO_DEBUG(e.what()); + } + } + + throw std::runtime_error( + "Ran out of retries when shuffling entrances. If you see this error, try using a few different seeds to see if any " + "generate successfully."); + } + + void ShuffleEntrances(tphdr::logic::world::WorldPool& worlds, + EntrancePool& entrancePool, + EntrancePool& targetEntrancePool, + std::unordered_map& rollbacks) + { + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + tphdr::utility::random::ShufflePool(entrancePool); + + for (auto& entrance : entrancePool) + { + // If this entrance is already connected, don't connect it to another area + if (entrance->GetConnectedArea() != nullptr) + { + continue; + } + tphdr::utility::random::ShufflePool(targetEntrancePool); + + // Loop through and find a valid target entrance to connect to + for (auto& target : targetEntrancePool) + { + // If this target has already been used, skip over it + if (target->GetConnectedArea() == nullptr) + { + continue; + } + + LOG_TO_DEBUG("Attempting to connect " + entrance->GetOriginalName() + " to " + + target->GetConnectedArea()->GetName() + " from " + + target->GetReplaces()->GetParentArea()->GetName() + " [W" + + std::to_string(entrance->GetWorld()->GetID()) + + "]"); + if (ReplaceEntrance(worlds, entrance, target, rollbacks, completeItemPool)) + { + break; + } + } + + // If this entrance was unable to connect to target, throw an error + if (entrance->GetConnectedArea() == nullptr) + { + throw EntranceShuffleError("No more valid entrances to replace " + entrance->GetOriginalName() + " in world " + + std::to_string(entrance->GetWorld()->GetID())); + } + } + + // Check to make sure there are no dangling targets. If there are, something is very wrong + for (auto& target : targetEntrancePool) + { + if (target->GetConnectedArea() != nullptr) + { + throw std::runtime_error("Dangling Target Entrance " + target->GetReplaces()->GetOriginalName()); + } + } + } + + bool ReplaceEntrance(tphdr::logic::world::WorldPool& worlds, + Entrance* entrance, + Entrance* target, + std::unordered_map& rollbacks, + const tphdr::logic::item_pool::ItemPool& completeItemPool) + { + try + { + CheckEntrancesCompatibility(entrance, target); + ChangeConnections(entrance, target); + ValidateWorld(entrance->GetWorld(), worlds, entrance, completeItemPool); + rollbacks[entrance] = target; + return true; + } + catch(const EntranceShuffleError& e) + { + LOG_TO_DEBUG("Failed to connect " + entrance->GetOriginalName() + " to " + + target->GetReplaces()->GetOriginalName() + " (Reason: " + e.what() + ") World " + + std::to_string(entrance->GetWorld()->GetID())); + if (entrance->GetConnectedArea() != nullptr) + { + RestoreConnections(entrance, target); + } + } + return false; + } + + void CheckEntrancesCompatibility(Entrance* entrance, Entrance* target) + { + if (entrance->GetReverse() && entrance->GetReverse() == target->GetReplaces()) + { + throw EntranceShuffleError("Attempted self-connection"); + } + } + + void ChangeConnections(Entrance* entrance, Entrance* target) + { + entrance->Connect(target->Disconnect()); + entrance->SetReplaces(target->GetReplaces()); + // If entrances are coupled, set the opposite connection as well + if (entrance->GetReverse() != nullptr && !entrance->IsDecoupled()) + { + target->GetReplaces()->GetReverse()->Connect(entrance->GetReverse()->GetAssumed()->Disconnect()); + target->GetReplaces()->GetReverse()->SetReplaces(entrance->GetReverse()); + } + } + + void RestoreConnections(Entrance* entrance, Entrance* target) + { + target->Connect(entrance->Disconnect()); + entrance->SetReplaces(nullptr); + if (entrance->GetReverse() && !entrance->IsDecoupled()) + { + entrance->GetReverse()->GetAssumed()->Connect(target->GetReplaces()->GetReverse()->Disconnect()); + target->GetReplaces()->GetReverse()->SetReplaces(nullptr); + } + } + + void ConfirmReplacement(Entrance* entrance, Entrance* target) + { + DeleteTargetEntrance(target); + LOG_TO_DEBUG("Finalized Connection " + entrance->GetOriginalName() + " to " + entrance->GetConnectedArea()->GetName() + + " [W" + std::to_string(entrance->GetWorld()->GetID()) + "]"); + if (entrance->GetReverse() != nullptr && !entrance->IsDecoupled()) + { + auto replacedReverse = entrance->GetReplaces()->GetReverse(); + LOG_TO_DEBUG("Finalized Connection " + replacedReverse->GetOriginalName() + " to " + + replacedReverse->GetConnectedArea()->GetName() + " [W" + + std::to_string(entrance->GetWorld()->GetID()) + "]"); + DeleteTargetEntrance(entrance->GetReverse()->GetAssumed()); + } + } + + void DeleteTargetEntrance(Entrance* target) + { + if (target->GetConnectedArea() != nullptr) + { + target->Disconnect(); + } + if (target->GetParentArea() != nullptr) + { + target->GetParentArea()->RemoveExit(target); + } + } + + void ValidateWorld(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + Entrance* entrance, + const tphdr::logic::item_pool::ItemPool& completeItemPool) + { + // Validate that all logic is still satisfied + auto verifyLogicError = tphdr::logic::search::VerifyLogic(&worlds, completeItemPool); + if (verifyLogicError.has_value()) + { + throw EntranceShuffleError("Not all logic is satisfied! Reason:\n" + verifyLogicError.value()); + } + + // Check to make sure there's at least 1 sphere zero location available + auto sphereZeroSearch = tphdr::logic::search::Search::SphereZero(&worlds); + sphereZeroSearch.SearchWorlds(); + const auto& foundLocations = sphereZeroSearch._visitedLocations; + auto numSphereZeroLocations = std::count_if(foundLocations.begin(), + foundLocations.end(), + [](const auto& location) { return location->IsProgression(); }); + + // If there are no sphere zero locations available and we didn't find a disconnected exit, then this world will not + // be valid. Often times when many entrances are randomized we won't find any locations, but will find disconnected + // exits that haven't been shuffled yet. In this case we can usually wait until these exits are connected and more often + // than not this will lead us to sphere zero locations. + if (numSphereZeroLocations == 0 && !sphereZeroSearch._foundDisconnectedExit) + { + throw EntranceShuffleError("No sphere 0 locations reachable at the start!"); + } + } + + void SetShuffledEntrances(EntrancePools& entrancePools) + { + for (auto& [entranceType, entrancePool] : entrancePools) + { + for (auto& entrance : entrancePool) + { + entrance->SetShuffled(true); + if (entrance->GetReverse() != nullptr) + { + entrance->GetReverse()->SetShuffled(true); + } + } + } + } + + EntrancePool GetReverseEntrances(const EntrancePool& entrances) + { + EntrancePool reverseEntrances = {}; + for (const auto& entrance : entrances) + { + reverseEntrances.push_back(entrance->GetReverse()); + } + return reverseEntrances; + } +} // namespace tphdr::logic::entrance_shuffle diff --git a/src/dusk/randomizer/logic/entrance_shuffle.hpp b/src/dusk/randomizer/logic/entrance_shuffle.hpp new file mode 100644 index 0000000000..f4ff2adf24 --- /dev/null +++ b/src/dusk/randomizer/logic/entrance_shuffle.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "entrance.hpp" +#include "world.hpp" + +namespace tphdr::logic::entrance_shuffle +{ + void ShuffleWorldEntrances(tphdr::logic::world::World* world, tphdr::logic::world::WorldPool& worlds); + void SetAllEntrancesData(tphdr::logic::world::World* world); + tphdr::logic::entrance::EntrancePools CreateEntrancePools(tphdr::logic::world::World* world); + tphdr::logic::entrance::EntrancePools CreateTargetPools(tphdr::logic::entrance::EntrancePools& entrancePools); + tphdr::logic::entrance::EntrancePool AssumeEntrancePool(tphdr::logic::entrance::EntrancePool& entrancePool); + void SetPlandomizedEntrances(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + tphdr::logic::entrance::EntrancePools& entrancePools, + tphdr::logic::entrance::EntrancePools& targetEntrancePools); + void ShuffleNonAssumedEntrancesPools(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + tphdr::logic::entrance::EntrancePools& entrancePools, + tphdr::logic::entrance::EntrancePools& targetEntrancePools); + void ShuffleEntrancePool(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + tphdr::logic::entrance::EntrancePool& entrancePool, + tphdr::logic::entrance::EntrancePool& targetEntrancePool, + int retries = 20); + void ShuffleEntrances(tphdr::logic::world::WorldPool& worlds, + tphdr::logic::entrance::EntrancePool& entrancePool, + tphdr::logic::entrance::EntrancePool& targetEntrancePool, + std::unordered_map& rollbacks); + bool ReplaceEntrance(tphdr::logic::world::WorldPool& worlds, + tphdr::logic::entrance::Entrance* entrance, + tphdr::logic::entrance::Entrance* target, + std::unordered_map& rollbacks, + const tphdr::logic::item_pool::ItemPool& completeItemPool); + + void CheckEntrancesCompatibility(tphdr::logic::entrance::Entrance* entrance, tphdr::logic::entrance::Entrance* target); + void ChangeConnections(tphdr::logic::entrance::Entrance* entrance, tphdr::logic::entrance::Entrance* target); + void RestoreConnections(tphdr::logic::entrance::Entrance* entrance, tphdr::logic::entrance::Entrance* target); + void ConfirmReplacement(tphdr::logic::entrance::Entrance* entrance, tphdr::logic::entrance::Entrance* target); + void DeleteTargetEntrance(tphdr::logic::entrance::Entrance* target); + void ValidateWorld(tphdr::logic::world::World* world, + tphdr::logic::world::WorldPool& worlds, + tphdr::logic::entrance::Entrance* entrance, + const tphdr::logic::item_pool::ItemPool& completeItemPool); + + void SetShuffledEntrances(tphdr::logic::entrance::EntrancePools& entrancePools); + tphdr::logic::entrance::EntrancePool GetReverseEntrances(const tphdr::logic::entrance::EntrancePool& entrances); + + class EntranceShuffleError: public std::runtime_error + { + public: + explicit EntranceShuffleError(const std::string& message): std::runtime_error(message) {} + }; +} // namespace tphdr::logic::entrance_shuffle diff --git a/src/dusk/randomizer/logic/fill.cpp b/src/dusk/randomizer/logic/fill.cpp new file mode 100644 index 0000000000..47d1e48960 --- /dev/null +++ b/src/dusk/randomizer/logic/fill.cpp @@ -0,0 +1,529 @@ +#include "fill.hpp" + +#include "item_pool.hpp" +#include "search.hpp" +#include "../utility/random.hpp" +#include "../utility/string.hpp" +#include "../utility/time.hpp" + +#include +#include + +namespace tphdr::logic::fill +{ + void FillWorlds(tphdr::logic::world::WorldPool& worlds) + { + // Place each world's restricted items first + for (auto& world : worlds) + { + PlaceRestrictedItems(world, worlds); + } + + tphdr::logic::item_pool::ItemPool itemPool = {}; + tphdr::logic::location::LocationPool locationPool = {}; + + // Combine all worlds' item pools and location pools + for (const auto& world : worlds) + { + for (const auto& item : world->GetItemPool()) + { + itemPool.emplace_back(item); + } + for (const auto& location : world->GetAllLocations()) + { + locationPool.emplace_back(location); + } + } + + // Place remaining major items in progress locations + auto majorItems = + tphdr::utility::container::FilterAndEraseFromVector(itemPool, [](const auto& item) { return item->IsMajor(); }); + auto progressLocations = + tphdr::utility::container::FilterFromVector(locationPool, + [](const auto& location) { return location->IsProgression(); }); + AssumedFill(worlds, majorItems, itemPool, progressLocations); + + // Place Minor items in progression locations if possible + auto minorItems = + tphdr::utility::container::FilterAndEraseFromVector(itemPool, [](const auto& item) { return item->IsMinor(); }); + FastFill(minorItems, progressLocations); + + // If there are still minor items left, add them back to the main item pool + for (const auto& minorItem : minorItems) + { + itemPool.push_back(minorItem); + } + + // Then place everything else anywhere + FastFill(itemPool, locationPool); + + // Verify that all logic is satisfied + auto verifyLogicError = tphdr::logic::search::VerifyLogic(&worlds); + if (verifyLogicError.has_value()) + { + throw std::runtime_error("Not all logic satisfied! Reason:\n" + verifyLogicError.value()); + } + } + + void AssumedFill(tphdr::logic::world::WorldPool& worlds, + tphdr::logic::item_pool::ItemPool& itemsToPlacePool, + const tphdr::logic::item_pool::ItemPool& itemsNotYetPlaced, + tphdr::logic::location::LocationPool allowedLocations, + const int& worldToFill /* = -1 */) + { + // Assumed Fill may sometimes place items in such a way that accidentally locks out being able to place specific items + // later on. Allow the algorithm to retry a reasonable amount of times before returning an error. + int retries = 10; + bool unsuccessfulPlacement = true; + while (unsuccessfulPlacement) + { + if (retries <= 0) + { + std::string errorMsg = "Ran out of retries while attempting to place the following items:\n"; + int count = itemsToPlacePool.size() > 5 ? 5 : itemsToPlacePool.size(); + + for (int i = 0; i < count; i++) + { + auto& item = itemsToPlacePool[i]; + errorMsg += "- " + item->GetName() + "\n"; + } + + if (count < itemsToPlacePool.size()) + { + errorMsg += "- (" + std::to_string(itemsToPlacePool.size() - count) + " more)"; + } + + throw std::runtime_error(errorMsg); + } + + retries -= 1; + unsuccessfulPlacement = false; + + tphdr::utility::random::ShufflePool(itemsToPlacePool); + auto itemsToPlace = itemsToPlacePool; + tphdr::logic::location::LocationPool rollbacks = {}; + + while (!itemsToPlace.empty()) + { + // Get a random item to place + auto itemToPlace = itemsToPlace.back(); + itemsToPlace.pop_back(); + + tphdr::utility::random::ShufflePool(allowedLocations); + tphdr::logic::location::Location* spotToFill = nullptr; + + // Assume we have all the items which haven't been played yet, except the one we're about to place + auto assumedItems = itemsNotYetPlaced; + assumedItems.insert(assumedItems.end(), itemsToPlace.begin(), itemsToPlace.end()); + auto search = tphdr::logic::search::Search::Accessible(&worlds, assumedItems, worldToFill); + search.SearchWorlds(); + // search.DumpWorldGraph(); + // return 1; + + // Loop through the shuffled locations until we find a valid one. + // If a world is only checking for beatable logic, then we can ignore + // any access checks and just choose a random location if the world is already beatable + auto beatableOnlyLogic = itemToPlace->GetWorld()->Setting("Logic Rules") == "Beatable Only"; + bool canChooseAnyLocation = + search._ownedItems.contains(itemToPlace->GetWorld()->GetGameWinningItem()) && beatableOnlyLogic; + + for (const auto& location : allowedLocations) + { + // Get all reachable LocationAccess spots for this location + std::list locAccList; + for (const auto& locAcc : location->GetAccessList()) + { + if (canChooseAnyLocation || search._visitedAreas.contains(locAcc->GetArea())) + { + locAccList.push_back(locAcc); + } + } + + // If this location is not empty, or has no potentially reachable LocationAccess spot, or is forbidden from + // having this item, then we can't place the item here + if (!location->IsEmpty() || locAccList.empty() || location->GetForbiddenItems().contains(itemToPlace)) + { + continue; + } + + // If any of the LocationAccess spots evaluate to complete, then we can place an item here + if (std::any_of(locAccList.begin(), + locAccList.end(), + [&](const auto& la) + { + return canChooseAnyLocation || + tphdr::logic::requirement::EvaluateLocationRequirement(&search, la) == + tphdr::logic::requirement::EvalSuccess::COMPLETE; + })) + { + spotToFill = location; + break; + } + } + + // If we couldn't find a spot to place this item, undo all item placements within this fill attempt and try + // again from the top. + if (spotToFill == nullptr) + { + LOG_TO_DEBUG("No accessible locations to place " + itemToPlace->GetName() + ". Retrying " + + std::to_string(retries) + " more times."); + for (auto& location : rollbacks) + { + itemsToPlace.push_back(location->GetCurrentItem()); + location->RemoveCurrentItem(); + } + + // Also add back the randomly selected item + itemsToPlace.push_back(itemToPlace); + rollbacks.clear(); + // Break out of the item placement loop and flag an unsuccessful placement attempt to try again + unsuccessfulPlacement = true; + break; + } + + // Place the item at the location + spotToFill->SetCurrentItem(itemToPlace); + rollbacks.push_back(spotToFill); + } + } + } + + void FastFill(tphdr::logic::item_pool::ItemPool& itemsToPlace, tphdr::logic::location::LocationPool allowedLocations) + { + auto emptyLocations = + tphdr::utility::container::FilterFromVector(allowedLocations, + [](const auto& location) { return location->IsEmpty(); }); + + if (itemsToPlace.size() > emptyLocations.size()) + { + std::cout << "WARNING: More items than locations when placing items with fast fill. Items: " << itemsToPlace.size() + << " Locations: " << emptyLocations.size() << std::endl; + } + + tphdr::utility::random::ShufflePool(emptyLocations); + for (auto& location : emptyLocations) + { + if (itemsToPlace.empty()) + { + break; + } + location->SetCurrentItem(tphdr::utility::random::PopRandomElement(itemsToPlace)); + } + } + + void PlaceRestrictedItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + PlacePrologueItems(world, worlds); + PlaceGoalLocationItems(world, worlds); + PlaceOwnDungeonItems(world, worlds); + PlaceAnywhereDungeonRewards(world, worlds); + + // Determine required dungeons now so that we can place "any dungeon" items appropriately + world->DetermineRequiredDungeons(); + + PlaceAnyDungeonItems(world, worlds); + PlaceOverworldItems(world, worlds); + } + + void PlacePrologueItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + if (world->Setting("Skip Prologue") == "Off") + { + // Filter out the slingshot and progressive swords to place first. The first slingshot and sword have a very limited + // pool of locations and have to be found in the intro. We also include the lantern, shadow crystal, and progressive + // fishing rod because those items can lock prologue locations also. + auto& itemPool = world->GetItemPool(); + auto prologueItems = tphdr::utility::container::FilterAndEraseFromVector( + itemPool, + [](const auto& item) + { + return item->GetName() == "Slingshot" || item->GetName() == "Progressive Sword" || + item->GetName() == "Lantern" || item->GetName() == "Progressive Fishing Rod" || + item->IsShadowCrystal(); + }); + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, prologueItems, completeItemPool, world->GetAllLocations()); + } + } + + void PlaceGoalLocationItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + // If dungeon rewards can be anywhere, then return early and place them later + if (world->Setting("Dungeon Rewards Can Be Anywhere") == "On") + { + return; + } + + auto allLocations = world->GetAllLocations(); + tphdr::logic::location::LocationPool goalLocations = {}; + + // Filter out goal locations + goalLocations = tphdr::utility::container::FilterFromVector( + allLocations, + [](const auto& location) { return location->IsGoalLocation() && location->IsEmpty(); }); + + // Filter out goal items + std::set goalItemNames = {"Progressive Mirror Shard", "Progressive Fused Shadow"}; + + auto goalItems = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) { return goalItemNames.contains(item->GetName()); }); + + // Return an error if there aren't enough goal locations + if (goalItems.size() > goalLocations.size()) + { + throw std::runtime_error("Not enough goal locations to place dungeon rewards on goal locations."); + } + + // Place goal items at goal locations + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, goalItems, completeItemPool, goalLocations); + } + + void PlaceOwnDungeonItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) + { + // Filter hint signs out of dungeon locations + auto dungeonLocations = dungeon->GetLocations(); + tphdr::utility::container::FilterAndEraseFromVector(dungeonLocations, + [](const auto& location) + { return location->HasCategories("Hint Sign"); }); + + // Clang doesn't like passing structured binding variables to lambda functions via reference, so we create these + // temporary variables to serve the purpose + auto& dungeon_ = dungeon; + auto& dungeonName_ = dungeonName; + + // Small Keys + if (world->Setting("Small Keys") == "Own Dungeon") + { + auto smallKeys = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) + { + return item == dungeon_->GetSmallKey() || + (dungeonName_ == "Snowpeak Ruins" && + (item->GetName() == "Ordon Pumpkin" || item->GetName() == "Ordon Cheese")); + }); + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, smallKeys, completeItemPool, dungeonLocations); + } + + // Big Keys + if (world->Setting("Big Keys") == "Own Dungeon") + { + auto bigKeys = tphdr::utility::container::FilterAndEraseFromVector(world->GetItemPool(), + [&](const auto& item) + { return item == dungeon_->GetBigKey(); }); + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, bigKeys, completeItemPool, dungeonLocations); + } + + // Place maps and compasses last with fast fill since they're junk items + if (world->Setting("Maps and Compasses") == "Own Dungeon") + { + auto mapsCompasses = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) { return item == dungeon_->GetCompass() || item == dungeon_->GetDungeonMap(); }); + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + FastFill(mapsCompasses, dungeonLocations); + } + } + } + + void PlaceAnywhereDungeonRewards(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + // If dungeon rewards can't be anywhere, then return early as we placed them earlier + if (world->Setting("Dungeon Rewards Can Be Anywhere") == "Off") + { + return; + } + + auto allLocations = world->GetAllLocations(); + + // Filter out goal items + std::set goalItemNames = {"Progressive Mirror Shard", "Progressive Fused Shadow"}; + + auto goalItems = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) { return goalItemNames.contains(item->GetName()); }); + + // Place the items + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, goalItems, completeItemPool, allLocations); + } + + void PlaceAnyDungeonItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + tphdr::logic::item_pool::ItemPool anyDungeonItems = {}; + tphdr::logic::location::LocationPool anyDungeonLocations = {}; + + // Split the placement of any dungeon items into two pools. Dungeon items from dungeons which should be barren + // will only be distributed among barren dungeons, where as items from nonbarren dungeons will be distributed + // among nonbarren dungeons + std::list nonBarrenDungeons = {}; + std::list barrenDungeons = {}; + for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) + { + if (dungeon->ShouldBeBarren()) + { + barrenDungeons.push_back(dungeon.get()); + } + else + { + nonBarrenDungeons.push_back(dungeon.get()); + } + } + + // Loop through each pool separately + for (const auto& dungeons : {nonBarrenDungeons, barrenDungeons}) + { + anyDungeonItems.clear(); + anyDungeonLocations.clear(); + // Gather all the appropriate items and locations for the dungeon in this pool + for (const auto& dungeon : dungeons) + { + // Clang doesn't like passing structured binding variables to lambda functions via reference, so we create these + // temporary variables to serve the purpose + auto& dungeon_ = dungeon; + // Add small keys to the pool if small keys are any dungeon + if (world->Setting("Small Keys") == "Any Dungeon") + { + auto smallKeys = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) + { + return item == dungeon_->GetSmallKey() || + (dungeon_->GetName() == "Snowpeak Ruins" && + (item->GetName() == "Ordon Pumpkin" || item->GetName() == "Ordon Cheese")); + }); + std::copy(smallKeys.begin(), smallKeys.end(), std::back_inserter(anyDungeonItems)); + } + + // Add big keys to the pool if big keys are any dungeon + if (world->Setting("Big Keys") == "Any Dungeon") + { + auto bigKeys = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) { return item == dungeon_->GetBigKey(); }); + std::copy(bigKeys.begin(), bigKeys.end(), std::back_inserter(anyDungeonItems)); + } + + // Add maps and compasses to the pool if maps and compasses are any dungeon + if (world->Setting("Maps and Compasses") == "Any Dungeon") + { + auto mapsCompasses = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) { return item == dungeon_->GetCompass() || item == dungeon_->GetDungeonMap(); }); + std::copy(mapsCompasses.begin(), mapsCompasses.end(), std::back_inserter(anyDungeonItems)); + } + + // Add this dungeon's locations to the anyDungeonLocations pool. If this is a nonbarren dungeon, only include + // locations which are still progression. If it's a barren dungeon, include all the locations + auto dungeonLocations = dungeon->GetLocations(); + std::copy_if(dungeonLocations.begin(), + dungeonLocations.end(), + std::back_inserter(anyDungeonLocations), + [&](const auto& location) { return dungeon->ShouldBeBarren() || location->IsProgression(); }); + } + + // Place the dungeon items in the appropriate dungeon locations + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, anyDungeonItems, completeItemPool, anyDungeonLocations); + } + } + + void PlaceOverworldItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds) + { + tphdr::logic::item_pool::ItemPool overworldItems = {}; + tphdr::logic::location::LocationPool overworldLocations = world->GetAllLocations(); + // Filter out any nonprogress locations + tphdr::utility::container::FilterAndEraseFromVector(overworldLocations, + [](const auto& location) { return !location->IsProgression(); }); + + for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) + { + // Clang doesn't like passing structured binding variables to lambda functions via reference, so we create these + // temporary variables to serve the purpose + auto& dungeon_ = dungeon; + auto& dungeonName_ = dungeonName; + + // Add small keys to the pool if small keys are overworld + if (world->Setting("Small Keys") == "Overworld") + { + auto smallKeys = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) + { + return item == dungeon_->GetSmallKey() || + (dungeonName_ == "Snowpeak Ruins" && + (item->GetName() == "Ordon Pumpkin" || item->GetName() == "Ordon Cheese")); + }); + std::copy(smallKeys.begin(), smallKeys.end(), std::back_inserter(overworldItems)); + } + + // Add big keys to the pool if big keys are overworld + if (world->Setting("Big Keys") == "Overworld") + { + auto bigKeys = tphdr::utility::container::FilterAndEraseFromVector(world->GetItemPool(), + [&](const auto& item) + { return item == dungeon_->GetBigKey(); }); + std::copy(bigKeys.begin(), bigKeys.end(), std::back_inserter(overworldItems)); + } + + // Add maps and compasses to the pool if maps and compasses are overworld + if (world->Setting("Maps and Compasses") == "Overworld") + { + auto mapsCompasses = tphdr::utility::container::FilterAndEraseFromVector( + world->GetItemPool(), + [&](const auto& item) { return item == dungeon_->GetCompass() || item == dungeon_->GetDungeonMap(); }); + std::copy(mapsCompasses.begin(), mapsCompasses.end(), std::back_inserter(overworldItems)); + } + + // Remove this dungeon's locations from the overworldLocations pool + overworldLocations = tphdr::utility::container::FilterFromVector( + overworldLocations, + [&](const auto& location) + { return !tphdr::utility::container::ElementInContainer(dungeon_->GetLocations(), location); }); + } + + // Place the dungeon items in the overworld locations + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + AssumedFill(worlds, overworldItems, completeItemPool, overworldLocations); + } + + void CacheExitTimeForms(tphdr::logic::world::WorldPool& worlds) + { + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(worlds); + auto searchWithItems = tphdr::logic::search::Search::AllLocationsReachable(&worlds, completeItemPool); + searchWithItems.SearchWorlds(); + + for (auto& world : worlds) + { + LOG_TO_DEBUG("Caching timeforms for world " + std::to_string(world->GetID())); + auto& exitTimeFormCache = world->GetExitTimeFormCache(); + exitTimeFormCache.clear(); + for (const auto& [areaName, area] : world->GetAreaTable()) + { + const auto& areaFormTimes = searchWithItems._areaFormTime[area.get()]; + for (const auto& exit : area->GetExits()) + { + auto req = exit->GetRequirement(); + exitTimeFormCache[exit] = tphdr::logic::requirement::FormTime::NONE; + for (const auto& formTime : tphdr::logic::requirement::FormTime::ALL_FORM_TIMES) + { + if (formTime & areaFormTimes && + tphdr::logic::requirement::EvaluateRequirementAtFormTime(req, + &searchWithItems, + formTime, + world.get())) + { + exitTimeFormCache[exit] |= formTime; + } + } + } + } + } + } +} // namespace tphdr::logic::fill diff --git a/src/dusk/randomizer/logic/fill.hpp b/src/dusk/randomizer/logic/fill.hpp new file mode 100644 index 0000000000..340fc3ebe8 --- /dev/null +++ b/src/dusk/randomizer/logic/fill.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "item.hpp" +#include "world.hpp" + +namespace tphdr::logic::fill +{ + + void FillWorlds(tphdr::logic::world::WorldPool& worlds); + + /** + * @brief Assumed fill is an algorithm which statistically places items more + * evenly across the world compared to forward fill. The idea is that + * we first start with all the items, take an item out, search for + * available locations (picking up any placed items along the way), + * and choose a random location of the available ones to place the item. + * Repeat for all items in the itemsToPlacePool. + * + * @param worlds The worlds to fill with items + * @param itemsToPlacePool The pool of items which we want to place + * @param itemsNotYetPlaced The pool of items which aren't placed yet, but will be later. This is important for the assumed + * fill algorithm since we need to assume we have these items. + * @param allowedLocations Locations where items in itemsToPlacePool are allowed to be filled. + */ + void AssumedFill(tphdr::logic::world::WorldPool& worlds, + tphdr::logic::item_pool::ItemPool& itemsToPlacePool, + const tphdr::logic::item_pool::ItemPool& itemsNotYetPlaced, + tphdr::logic::location::LocationPool allowedLocations, + const int& worldToFill = -1); + + /** + * @brief Places items in locations completely randomly without any logic checks. + * + * @param itemsToPlace The pool of items to place + * @param allowedLocations The locations where the items can be placed + */ + void FastFill(tphdr::logic::item_pool::ItemPool& itemsToPlace, tphdr::logic::location::LocationPool allowedLocations); + + void PlaceRestrictedItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds); + + /** + * @brief If the prologue is not being skipped, place the sword and slingshot early on to prevent possible placement + * failures later. + * + * @param world The world to place the prologue items in + * @param worlds All the worlds being generated + */ + void PlacePrologueItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds); + + void PlaceGoalLocationItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds); + + void PlaceOwnDungeonItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds); + + void PlaceAnywhereDungeonRewards(std::unique_ptr& world, + tphdr::logic::world::WorldPool& worlds); + + void PlaceAnyDungeonItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds); + + void PlaceOverworldItems(std::unique_ptr& world, tphdr::logic::world::WorldPool& worlds); + + /** + * @brief Cache all the possible timeforms for each exit. This way, the search algorithm doesn't end up testing for + * timeforms that we know ahead of time wouldn't be possible anyway + * @param worlds The worlds to calculate and cache the possible timeforms for + */ + void CacheExitTimeForms(tphdr::logic::world::WorldPool& worlds); +} // namespace tphdr::logic::fill diff --git a/src/dusk/randomizer/logic/flatten/bits.cpp b/src/dusk/randomizer/logic/flatten/bits.cpp new file mode 100644 index 0000000000..77318119dc --- /dev/null +++ b/src/dusk/randomizer/logic/flatten/bits.cpp @@ -0,0 +1,275 @@ +#include + +#include "bits.hpp" +#include "../item.hpp" + +#include + +BitVector::BitVector(std::list bits) +{ + for (auto& i : bits) + { + this->set(i); + } +} + +bool BitVector::isEmpty() const +{ + return bitset.none(); +} + +std::set BitVector::ints() const +{ + return intset; +} + +void BitVector::set(const int& i) +{ + bitset.set(i, true); + intset.insert(i); +} + +void BitVector::clear(const int& i) +{ + if (intset.contains(i)) + { + intset.erase(i); + bitset.set(i, false); + } +} + +bool BitVector::test(const int& i) const +{ + return intset.contains(i); +} + +int BitVector::size() const +{ + return intset.size(); +} + +void BitVector::and_(const BitVector& other) +{ + std::set intersection = {}; + std::set_intersection(intset.begin(), + intset.end(), + other.intset.begin(), + other.intset.end(), + std::inserter(intersection, intersection.begin())); + intset = intersection; + bitset &= other.bitset; +} + +void BitVector::or_(const BitVector& other) +{ + intset.insert(other.intset.begin(), other.intset.end()); + bitset |= other.bitset; +} + +bool BitVector::isSubsetOf(const BitVector& other) const +{ + return (bitset | other.bitset) == other.bitset; +} + +bool BitVector::equals(const BitVector& other) const +{ + return bitset == other.bitset; +} + +bool includedIn(const std::bitset<512>& a, const std::bitset<512>& b) +{ + return (a | b) == b; +} + +DNF::DNF(std::vector> terms_): terms(terms_) {} + +bool DNF::isTriviallyFalse() const +{ + return terms.size() == 0; +} + +bool DNF::isTriviallyTrue() const +{ + return std::any_of(terms.begin(), terms.end(), [](const auto& i) { return i == 0; }); +} + +DNF DNF::or_(const DNF& other) +{ + auto new_terms = terms; + new_terms.insert(new_terms.end(), other.terms.begin(), other.terms.end()); + return DNF(new_terms); +} + +// Removes all redundent terms +DNF DNF::dedup() +{ + std::vector> filtered = {}; + for (const auto& candidate : terms) + { + std::vector toPop = {}; + bool nextTerm = false; + for (int existing_idx = 0; existing_idx < filtered.size(); existing_idx++) + { + const auto& existing = filtered[existing_idx]; + if (includedIn(existing, candidate)) + { + // Existing requires fewer or equal things than candidate + nextTerm = true; + break; + } + else if (includedIn(candidate, existing)) + { + // Candidate requires strictly fewer things than existing + toPop.push_back(existing_idx); + } + } + + if (!nextTerm) + { + // Did not break to next term + for (auto c_iter = toPop.rbegin(); c_iter != toPop.rend(); c_iter++) + { + const auto& c = *c_iter; + if (c == filtered.size() - 1) + { + filtered.pop_back(); + } + else + { + // Remove c without shifting elements by replacing + // it with the last element + filtered[c] = filtered.back(); + filtered.pop_back(); + } + } + filtered.push_back(candidate); + } + } + + return DNF(filtered); +} + +// Returns useful, self.or_(other) +// useful is True if other contained at least one term that +// was not redundant. +std::pair DNF::or_useful(const DNF& other) +{ + auto filtered_this = terms; + std::vector> filtered_other = {}; + bool useful = false; + + for (const auto& candidate : other.terms) + { + bool nextTerm = false; + for (const auto& existing : filtered_this) + { + if (includedIn(existing, candidate)) + { + nextTerm = true; + break; + } + } + + if (!nextTerm) + { + filtered_other.push_back(candidate); + useful = true; + } + } + + filtered_this.insert(filtered_this.end(), filtered_other.begin(), filtered_other.end()); + return {useful, DNF(filtered_this)}; +} + +DNF DNF::and_(const DNF& other) +{ + std::vector> d = {}; + for (const auto& t1 : terms) + { + for (const auto& t2 : other.terms) + { + d.push_back(t1 | t2); + } + } + + // Dedup incase things are getting too big + DNF dnf = DNF(d); + if (d.size() > 500) + { + dnf = dnf.dedup(); + } + return dnf; +} + +int BitIndex::bump() +{ + auto c = counter; + counter++; + return c; +} + +int BitIndex::reqBit(const tphdr::logic::requirement::Requirement& req) +{ + uint32_t expectedCount; + tphdr::logic::item::Item* item; + std::string key; + + switch (req._type) + { + case tphdr::logic::requirement::Type::ITEM: + item = std::get(req._args[0]); + key = item->GetName() + "::1"; + if (itemBits.contains(key)) + { + return itemBits[key]; + } + else + { + itemBits[key] = counter; + reverseIndex.push_back(req); + return bump(); + } + case tphdr::logic::requirement::Type::COUNT: + expectedCount = std::get(req._args[0]); + item = std::get(req._args[1]); + key = item->GetName() + "::" + std::to_string(expectedCount); + if (itemBits.contains(key)) + { + return itemBits[key]; + } + else + { + itemBits[key] = counter; + reverseIndex.push_back(req); + return bump(); + } + // case tphdr::logic::requirement::Type::HEALTH: + // key = std::to_string(std::get(req._args[0])); + // if (heartCount.contains(key)) + // { + // return heartCount[key]; + // } + // else + // { + // heartCount[key] = counter; + // reverseIndex.push_back(req); + // return bump(); + // } + case tphdr::logic::requirement::Type::GOLDEN_BUGS: + key = std::to_string(std::get(req._args[0])); + if (goldenBugCount.contains(key)) + { + return goldenBugCount[key]; + } + else + { + goldenBugCount[key] = counter; + reverseIndex.push_back(req); + return bump(); + } + default: + // Not a flattening requirement + return -1; + } + return -1; +} diff --git a/src/dusk/randomizer/logic/flatten/bits.hpp b/src/dusk/randomizer/logic/flatten/bits.hpp new file mode 100644 index 0000000000..fee276564d --- /dev/null +++ b/src/dusk/randomizer/logic/flatten/bits.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "../requirement.hpp" + +#include +#include +#include +#include +#include + +class BitVector +{ + public: + BitVector() = default; + BitVector(std::list); + + bool isEmpty() const; + std::set ints() const; + void set(const int& i); + void clear(const int& i); + bool test(const int& i) const; + int size() const; + void and_(const BitVector& other); + void or_(const BitVector& other); + bool isSubsetOf(const BitVector& other) const; + bool equals(const BitVector& other) const; + + std::bitset<512> bitset; + std::set intset; +}; + +bool includedIn(const std::bitset<512>& a, const std::bitset<512>& b); + +// A logical expression in disjunctive normal form. +// Disjuncts are bit-vectors, but we don't use the BitVector class here +// because it doesn't seem necessary since our bitvectors are small +// and we only need bit set access after the propagation code is done +class DNF +{ + public: + DNF() = default; + DNF(std::vector> terms); + + static DNF True() { return DNF({0}); } + + static DNF False() { return DNF(std::vector> {}); } + + bool isTriviallyFalse() const; + bool isTriviallyTrue() const; + DNF or_(const DNF& other); + DNF dedup(); + std::pair or_useful(const DNF& other); + DNF and_(const DNF& other); + + std::vector> terms = {}; +}; + +class BitIndex +{ + public: + BitIndex() = default; + + int bump(); + int reqBit(const tphdr::logic::requirement::Requirement& req); + + std::unordered_map itemBits = {}; + // std::unordered_map heartCount = {}; + std::unordered_map goldenBugCount = {}; + std::vector reverseIndex = {}; + int counter = 0; +}; diff --git a/src/dusk/randomizer/logic/flatten/flatten.cpp b/src/dusk/randomizer/logic/flatten/flatten.cpp new file mode 100644 index 0000000000..bc69248130 --- /dev/null +++ b/src/dusk/randomizer/logic/flatten/flatten.cpp @@ -0,0 +1,505 @@ +#include "flatten.hpp" +#include "../world.hpp" + +FlattenSearch::FlattenSearch(tphdr::logic::world::World* world_) +{ + world = world_; + + for (auto& [name, area] : world->GetAreaTable()) + { + for (auto& exit : area->GetExits()) + { + auto visit = visitor(exit, this); + visitReq(exit->GetRequirement(), visit, world); + } + + for (auto& event : area->GetEvents()) + { + auto visit = visitor(event, this); + visitReq(event->GetRequirement(), visit, world); + } + } + + auto root = world->GetRootArea(); + // Start with all formtimes at the root, false for everything else + auto formTimes = tphdr::logic::requirement::FormTime::ALL_FORM_AND_DAY_TIMES; + formTimes.push_back(tphdr::logic::requirement::FormTime::TWILIGHT); + for (const auto& [areaName, area] : world->GetAreaTable()) + { + for (const auto& formTime : formTimes) + { + if (area.get() == root) + { + areaExprs[formTime][area.get()] = DNF::True(); + } + else + { + areaExprs[formTime][area.get()] = DNF::False(); + } + } + } + newlyUpdatedAreas.insert(root); + newThingsFound = true; + + for (auto& exit : root->GetExits()) + { + if (exit->GetConnectedArea() != nullptr) + { + exitsToTry.insert(exit); + } + } +} + +void FlattenSearch::doSearch() +{ + // This algorithm works in three stages: + // 1. Compute area and event requirements -> DNFs + // 2. Compute location requirement -> DNF + // 3. Simplify location requirement -> Requirement + + // This is step 1. This computes everything that requirements + // can depend on in a fixpoint algorithm - namely, area access and events. + newThingsFound = true; + while (newThingsFound) + { + recentlyUpdatedAreas = newlyUpdatedAreas; + recentlyUpdatedEvents = newlyUpdatedEvents; + newlyUpdatedAreas = {}; + newlyUpdatedEvents = {}; + newThingsFound = false; + tryExits(); + tryEvents(); + tryTimeFormExpansion(); + } + + std::unordered_map> itemLocations = {}; + for (auto& [name, area] : world->GetAreaTable()) + { + for (auto& locAccess : area->GetLocations()) + { + auto locationName = locAccess->GetLocation()->GetName(); + if (!itemLocations.contains(locationName)) + { + itemLocations[locationName] = {}; + } + itemLocations[locationName].push_back(locAccess); + } + } + // TODO this immediately combines the "local" requirements with the implicit + // area requirement. It has been hypothesized that converting them + // separately may produce better tooltips, but at that point you need the + // TWWR-Tracker boolean-expression multi-level simplification code + + // Step 2: for every location, OR all the ways to access it + + auto formTimes = tphdr::logic::requirement::FormTime::ALL_FORM_AND_DAY_TIMES; + formTimes.push_back(tphdr::logic::requirement::FormTime::TWILIGHT); + for (auto& [locName, accessList] : itemLocations) + { + auto expr = DNF::False(); + for (auto& locAcc : accessList) + { + for (const auto& formTime : formTimes) + { + expr = expr.or_(tryLocationAtFormTime(locAcc, formTime)); + } + } + + // Step 3: simplify + auto location = world->GetLocation(locName); + location->SetComputedRequirement(DNFToExpr(bitIndex, expr.dedup())); + // world->locationTable[locName]->computedRequirement.simplifyParenthesis(); + // world->locationTable[locName]->computedRequirement.sortArgs(); + } + + // Do the same for any shuffled entrances so that we can give them tooltips in the tracker + for (auto& [name, area] : world->GetAreaTable()) + { + for (auto& exit : area->GetExits()) + { + if (exit->IsShuffled()) + { + auto expr = DNF::False(); + auto& validFormTimes = exit->GetWorld()->GetExitTimeFormCache()[exit]; + for (const auto& formTime : tphdr::logic::requirement::FormTime::ALL_FORM_TIMES) + { + if (formTime & validFormTimes) + { + expr = expr.or_(tryExitAtFormTime(exit, formTime)); + } + } + exit->SetComputedRequirement(DNFToExpr(bitIndex, expr.dedup())); + } + } + } +} + +// Check for a thing in area whether its logical dependencies +// have recently been updated. +bool FlattenSearch::wasUpdated(tphdr::logic::area::Area* area, void* thing) +{ + if (recentlyUpdatedAreas.contains(area)) + { + return true; + } + + auto& remoteEventReqs = remoteEventRequirements[thing]; + for (auto& event : remoteEventReqs) + { + if (recentlyUpdatedEvents.contains(event)) + { + return true; + } + } + // auto& remoteAreaReqs = remoteAreaRequirements[thing]; + // for (auto& areaStr : remoteAreaReqs) + // { + // tphdr::logic::area::Area* area2; + // world->GetArea(areaStr, area2); + // if (recentlyUpdatedAreas.contains(area2)) + // { + // return true; + // } + // } + + return false; +} + +void FlattenSearch::tryExits() +{ + using namespace tphdr::logic::requirement; + auto exits = exitsToTry; + for (auto& exit : exits) + { + if (!wasUpdated(exit->GetParentArea(), (void*)exit)) + { + continue; + } + auto& validFormTimes = exit->GetWorld()->GetExitTimeFormCache()[exit]; + auto connectedTwilight = exit->GetConnectedArea()->GetTwilightCompletedMacroIndex() != -1; + if (connectedTwilight) + { + validFormTimes |= FormTime::TWILIGHT; + } + for (const auto& formTime : FormTime::ALL_FORM_TIMES_AND_TWILIGHT) + { + if (formTime & validFormTimes) + { + auto connectedArea = exit->GetConnectedArea(); + auto& oldExpr = areaExprs[formTime][connectedArea]; + auto newPartial = tryExitAtFormTime(exit, formTime); + + // Add the twilight completed macro for access to this area if it's part of a twilight + if (connectedTwilight && formTime != FormTime::TWILIGHT) + { + auto& oldExprTwilight = areaExprs[FormTime::TWILIGHT][connectedArea]; + auto [useful, newExpr] = oldExprTwilight.or_useful(newPartial); + if (useful) + { + newlyUpdatedAreas.insert(connectedArea); + newThingsFound = true; + areaExprs[FormTime::TWILIGHT][connectedArea] = newExpr.dedup(); + for (auto& event : connectedArea->GetEvents()) + { + eventsToTry.insert(event); + } + for (auto& areaExit : connectedArea->GetExits()) + { + if (areaExit->GetConnectedArea() != nullptr) + { + exitsToTry.insert(areaExit); + } + } + areasToTry.insert(connectedArea); + } + + newPartial = newPartial.and_( + evaluatePartialRequirement(bitIndex, + exit->GetWorld()->GetMacro(connectedArea->GetTwilightCompletedMacroIndex()), + this, + 0)); + } + + auto [useful, newExpr] = oldExpr.or_useful(newPartial); + if (useful) + { + newlyUpdatedAreas.insert(connectedArea); + newThingsFound = true; + areaExprs[formTime][connectedArea] = newExpr.dedup(); + for (auto& event : connectedArea->GetEvents()) + { + eventsToTry.insert(event); + } + for (auto& areaExit : connectedArea->GetExits()) + { + if (areaExit->GetConnectedArea() != nullptr) + { + exitsToTry.insert(areaExit); + } + } + areasToTry.insert(connectedArea); + } + } + } + } +} + +void FlattenSearch::tryEvents() +{ + for (auto& event : eventsToTry) + { + if (!wasUpdated(event->GetArea(), (void*)event)) + { + continue; + } + + auto& oldExpr = eventExprs[event->GetEventIndex()]; + auto newPartial = DNF::False(); + for (const auto& formTime : tphdr::logic::requirement::FormTime::ALL_FORM_AND_DAY_TIMES) + { + newPartial = newPartial.or_(tryEventAtFormTime(event, formTime)); + } + auto [useful, newExpr] = oldExpr.or_useful(newPartial); + if (useful) + { + newlyUpdatedEvents.insert(event->GetEventIndex()); + newThingsFound = true; + eventExprs[event->GetEventIndex()] = newExpr.dedup(); + } + } +} + +void FlattenSearch::tryTimeFormExpansion() +{ + using namespace tphdr::logic::requirement; + for (auto& area : areasToTry) + { + if (!recentlyUpdatedAreas.contains(area)) + { + continue; + } + if (area->CanTransform()) + { + auto shadowCrystal = area->GetWorld()->GetShadowCrystal(); + auto shadowCrystalDNF = evaluatePartialRequirement(bitIndex, Requirement {Type::ITEM, {shadowCrystal}}, this, 0); + for (const auto& formTime : FormTime::ALL_FORM_TIMES) + { + auto& oldExpr = areaExprs[formTime][area]; + int oppositeFormTime = FormTime::NONE; + switch (formTime) + { + case FormTime::HUMAN_DAY: + oppositeFormTime = FormTime::WOLF_DAY; + break; + case FormTime::HUMAN_NIGHT: + oppositeFormTime = FormTime::WOLF_NIGHT; + break; + case FormTime::WOLF_DAY: + oppositeFormTime = FormTime::HUMAN_DAY; + break; + case FormTime::WOLF_NIGHT: + oppositeFormTime = FormTime::HUMAN_NIGHT; + } + auto newPartial = areaExprs[oppositeFormTime][area]; + if (!newPartial.isTriviallyFalse()) + { + // Transforming requires shadow crystal + newPartial = newPartial.and_(shadowCrystalDNF); + auto [useful, newExpr] = oldExpr.or_useful(newPartial); + if (useful) + { + newlyUpdatedAreas.insert(area); + newThingsFound = true; + areaExprs[formTime][area] = newExpr.dedup(); + } + } + } + } + if (area->CanChangeTime()) + { + for (const auto& formTime : FormTime::ALL_FORM_TIMES) + { + auto& oldExpr = areaExprs[formTime][area]; + int oppositeFormTime = FormTime::NONE; + switch (formTime) + { + case FormTime::HUMAN_DAY: + oppositeFormTime = FormTime::HUMAN_NIGHT; + break; + case FormTime::HUMAN_NIGHT: + oppositeFormTime = FormTime::HUMAN_DAY; + break; + case FormTime::WOLF_DAY: + oppositeFormTime = FormTime::WOLF_NIGHT; + break; + case FormTime::WOLF_NIGHT: + oppositeFormTime = FormTime::WOLF_DAY; + } + auto newPartial = areaExprs[oppositeFormTime][area]; + if (!newPartial.isTriviallyFalse()) + { + auto [useful, newExpr] = oldExpr.or_useful(newPartial); + if (useful) + { + newlyUpdatedAreas.insert(area); + newThingsFound = true; + areaExprs[formTime][area] = newExpr.dedup(); + } + } + } + } + this->andAreaFormTimes(area); + } +} + +void FlattenSearch::andAreaFormTimes(tphdr::logic::area::Area* area) +{ + using namespace tphdr::logic::requirement; + + auto& areaHumanDay = this->areaExprs[FormTime::HUMAN_DAY][area]; + auto& areaWolfDay = this->areaExprs[FormTime::WOLF_DAY][area]; + auto& areaHumanNight = this->areaExprs[FormTime::HUMAN_NIGHT][area]; + auto& areaWolfNight = this->areaExprs[FormTime::WOLF_NIGHT][area]; + + this->areaExprs[FormTime::DAY][area] = areaHumanDay.and_(areaWolfDay); + this->areaExprs[FormTime::NIGHT][area] = areaHumanNight.and_(areaWolfNight); +} + +DNF FlattenSearch::tryEventAtFormTime(tphdr::logic::area::EventAccess* event, const int& formTime) +{ + return areaExprs[formTime][event->GetArea()].and_( + evaluatePartialRequirement(bitIndex, event->GetRequirement(), this, formTime)); +} + +DNF FlattenSearch::tryLocationAtFormTime(tphdr::logic::area::LocationAccess* location, const int& formTime) +{ + return areaExprs[formTime][location->GetArea()].and_( + evaluatePartialRequirement(bitIndex, location->GetRequirement(), this, formTime)); +} + +DNF FlattenSearch::tryExitAtFormTime(tphdr::logic::entrance::Entrance* exit, const int& formTime) +{ + return areaExprs[formTime][exit->GetParentArea()].and_( + evaluatePartialRequirement(bitIndex, exit->GetRequirement(), this, formTime)); +} + +DNF evaluatePartialRequirement(BitIndex& bitIndex, + const tphdr::logic::requirement::Requirement& req, + FlattenSearch* search, + const int& formTime) +{ + uint32_t expectedCount = 0; + uint32_t expectedHearts = 0; + uint32_t totalHearts = 0; + std::bitset<512> bits = 0; + tphdr::logic::item::Item* item; + int event; + DNF d = DNF(); + tphdr::logic::area::Area* area; + + switch (req._type) + { + case tphdr::logic::requirement::Type::NOTHING: + return DNF::True(); + + case tphdr::logic::requirement::Type::IMPOSSIBLE: + return DNF::False(); + + case tphdr::logic::requirement::Type::OR: + d = DNF::False(); + for (auto& arg : req._args) + { + d = d.or_(evaluatePartialRequirement(bitIndex, + std::get(arg), + search, + formTime)); + } + return d; + + case tphdr::logic::requirement::Type::AND: + d = DNF::True(); + for (auto& arg : req._args) + { + d = d.and_(evaluatePartialRequirement(bitIndex, + std::get(arg), + search, + formTime)); + } + return d; + + case tphdr::logic::requirement::Type::GOLDEN_BUGS: + [[fallthrough]]; + case tphdr::logic::requirement::Type::ITEM: + // [[fallthrough]]; + // case tphdr::logic::requirement::Type::HEALTH: + bits[bitIndex.reqBit(req)] = 1; + return DNF({bits}); + + case tphdr::logic::requirement::Type::EVENT: + event = std::get(req._args[0]); + return search->eventExprs[event]; + + case tphdr::logic::requirement::Type::MACRO: + return evaluatePartialRequirement(bitIndex, search->world->GetMacro(std::get(req._args[0])), search, formTime); + + // count requirements frequently have to unify with weaker terms, + // so a count requirement always requires all lesser item counts too. + // this ensures redundant terms can be eliminated + case tphdr::logic::requirement::Type::COUNT: + expectedCount = std::get(req._args[0]); + item = std::get(req._args[1]); + for (auto i = 1; i <= expectedCount; i++) + { + tphdr::logic::requirement::Requirement newReq; + if (i == 1) + { + newReq = tphdr::logic::requirement::Requirement {tphdr::logic::requirement::Type::ITEM, {item}}; + } + else + { + newReq = tphdr::logic::requirement::Requirement {tphdr::logic::requirement::Type::COUNT, {i, item}}; + } + bits[bitIndex.reqBit(newReq)] = 1; + } + return DNF({bits}); + + case tphdr::logic::requirement::Type::DAY: + return (formTime & tphdr::logic::requirement::FormTime::DAY) ? DNF::True() : DNF::False(); + + case tphdr::logic::requirement::Type::NIGHT: + return (formTime & tphdr::logic::requirement::FormTime::NIGHT) ? DNF::True() : DNF::False(); + + case tphdr::logic::requirement::Type::HUMAN_LINK: + return (formTime & tphdr::logic::requirement::FormTime::HUMAN) ? DNF::True() : DNF::False(); + + case tphdr::logic::requirement::Type::WOLF_LINK: + return (formTime & tphdr::logic::requirement::FormTime::WOLF) ? DNF::True() : DNF::False(); + + case tphdr::logic::requirement::Type::TWILIGHT: + return (formTime & tphdr::logic::requirement::FormTime::TWILIGHT) ? DNF::True() : DNF::False(); + + case tphdr::logic::requirement::Type::INVALID: + default: + // actually needs to be some error state? + return DNF::False(); + } + return DNF::False(); +} + +void visitReq(const tphdr::logic::requirement::Requirement& req, + std::function f, + tphdr::logic::world::World* world) +{ + f(req); + if (req._type == tphdr::logic::requirement::Type::AND || req._type == tphdr::logic::requirement::Type::OR) + { + for (auto& arg : req._args) + { + visitReq(std::get(arg), f, world); + } + } + else if (req._type == tphdr::logic::requirement::Type::MACRO) + { + visitReq(world->GetMacro(std::get(req._args[0])), f, world); + } +} diff --git a/src/dusk/randomizer/logic/flatten/flatten.hpp b/src/dusk/randomizer/logic/flatten/flatten.hpp new file mode 100644 index 0000000000..e78de822ee --- /dev/null +++ b/src/dusk/randomizer/logic/flatten/flatten.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "../entrance.hpp" +#include "simplify_algebraic.hpp" +#include "../../utility/log.hpp" + +#include +#include +#include + +namespace tphdr::logic::area +{ + class EventAccess; + class Area; +} // namespace tphdr::logic::area + +namespace tphdr::logic::world +{ + class World; +} + +class FlattenSearch +{ + public: + FlattenSearch() = default; + FlattenSearch(tphdr::logic::world::World* world_); + + tphdr::logic::world::World* world = nullptr; + BitIndex bitIndex = BitIndex(); + + // partially computed requirements for areas at a + // given timeform and for events + std::unordered_map eventExprs = {}; + std::unordered_map> areaExprs = {}; + + // nodes we haven't looked at we don't even need to bother with + std::set exitsToTry = {}; + std::set eventsToTry = {}; + std::set areasToTry = {}; + + // we only re-check an exit or an event if its dependencies changed. + // dependencies can be the implicit parent area (for events and exits), + // formtime expansion in the area, and "remote" requirements arising + // from the expression itself mentioning an event or an area via can_access + std::set recentlyUpdatedAreas = {}; + std::set recentlyUpdatedEvents = {}; + + std::set newlyUpdatedAreas = {}; + std::set newlyUpdatedEvents = {}; + + std::unordered_map> remoteEventRequirements = {}; + std::unordered_map> remoteAreaRequirements = {}; + bool newThingsFound = false; + + void doSearch(); + bool wasUpdated(tphdr::logic::area::Area* area, void* thing); + void tryExits(); + void tryEvents(); + void tryTimeFormExpansion(); + void andAreaFormTimes(tphdr::logic::area::Area* area); + + DNF tryEventAtFormTime(tphdr::logic::area::EventAccess* event, const int& formTime); + DNF tryLocationAtFormTime(tphdr::logic::area::LocationAccess* location, const int& formTime); + DNF tryExitAtFormTime(tphdr::logic::entrance::Entrance* exit, const int& formTime); +}; + +template +std::function visitor(T* thing, FlattenSearch* search) +{ + auto thingPtr = (void*)thing; + std::function handler = + [=](const tphdr::logic::requirement::Requirement& req) + { + if (req._type == tphdr::logic::requirement::Type::EVENT) + { + if (!search->remoteEventRequirements.contains(thingPtr)) + { + search->remoteEventRequirements[thingPtr] = {}; + } + search->remoteEventRequirements[thingPtr].insert(std::get(req._args[0])); + } + }; + + return handler; +} + +void visitReq(const tphdr::logic::requirement::Requirement& req, + std::function f, + tphdr::logic::world::World* world); + +DNF evaluatePartialRequirement(BitIndex& bitIndex, + const tphdr::logic::requirement::Requirement& req, + FlattenSearch* search, + const int& formTime); diff --git a/src/dusk/randomizer/logic/flatten/simplify_algebraic.cpp b/src/dusk/randomizer/logic/flatten/simplify_algebraic.cpp new file mode 100644 index 0000000000..20967002ec --- /dev/null +++ b/src/dusk/randomizer/logic/flatten/simplify_algebraic.cpp @@ -0,0 +1,424 @@ +#include "simplify_algebraic.hpp" +#include "../../utility/container.hpp" + +// Turns a bit-based DNF (a two-level sum-of-products) back into +// a readable multi-level requirement. +tphdr::logic::requirement::Requirement DNFToExpr(BitIndex& bitIndex, DNF dnf) +{ + if (dnf.isTriviallyFalse()) + { + return tphdr::logic::requirement::Requirement {tphdr::logic::requirement::Type::IMPOSSIBLE, {}}; + } + + if (dnf.isTriviallyTrue()) + { + return tphdr::logic::requirement::Requirement {tphdr::logic::requirement::Type::NOTHING, {}}; + } + + // really make sure no dupes exist, not sure if needed + dnf = dnf.dedup(); + + // Map to BitVectors. Since DNFs don't offer bit-level access, + // we have to manually go through every bit to build BitVectors. + // This is definitely not cheap but it probably saves more time + // than keeping the intsets around during search + std::vector expr = {}; + for (const auto& t : dnf.terms) + { + std::list bits = {}; + for (int bit = 0; bit < bitIndex.counter; bit++) + { + if (t.test(bit)) + { + bits.push_back(bit); + } + } + expr.emplace_back(bits); + } + + // at this point we must remove weaker requirements. E.g. + // imagine Beedle existed in this rando and an item required + // (Wallet x1 and Wallet x2) or (Wallet x1 and ExtraWallet x1 and ExtraWallet x2) + // then this code would pull out Wallet x1 first, resulting in + // Wallet x1 and (Wallet x2 or ExtraWallet x1 and ExtraWallet x2) which is not + // reasonable at all and at that point not even the TWWR-Tracker simplifications can save us + for (auto& term : expr) + { + for (const auto& bit : term.ints()) + { + auto& req = bitIndex.reverseIndex[bit]; + if (req._type == tphdr::logic::requirement::Type::COUNT) + { + auto count = std::get(req._args[0]); + auto item = std::get(req._args[1]); + for (int i = 1; i < count; i++) + { + auto lesserBit = bitIndex.reqBit( + tphdr::logic::requirement::Requirement {tphdr::logic::requirement::Type::COUNT, {i, item}}); + term.clear(lesserBit); + } + } + } + } + + auto commonFactors = expr[0].ints(); + for (const auto& term : expr) + { + std::set intersection = {}; + std::set_intersection(commonFactors.begin(), + commonFactors.end(), + term.intset.begin(), + term.intset.end(), + std::inserter(intersection, intersection.begin())); + commonFactors = intersection; + } + + // build a list of variables that appear in our expression, + // excluding common factors. + std::set varSet = {}; + for (auto& term : expr) + { + for (const auto& c : commonFactors) + { + term.clear(c); + } + for (const auto& b : term.ints()) + { + varSet.insert(b); + } + } + + std::vector variables = std::vector(varSet.begin(), varSet.end()); + + if (variables.empty()) + { + return createAnd(lookupRequirements(bitIndex, commonFactors)); + } + + std::vector seen = {}; + auto kernels = findKernels(expr, variables, BitVector(), seen); + kernels = tphdr::utility::container::FilterFromVector(kernels, [](const auto& k) { return !k.coKernel.isEmpty(); }); + + // columns are unique cubes in all kernels + std::vector columns = {}; + for (const auto& kernel : kernels) + { + for (const auto& kCube : kernel.kernel) + { + if (std::none_of(columns.begin(), columns.end(), [&](const auto& c) { return kCube.equals(c); })) + { + columns.push_back(kCube); + } + } + } + + // rows are unique co-kernels + auto& rows = kernels; + if (!rows.empty() && !columns.empty()) + { + std::vector> matrix = {}; + for (const auto& row : rows) + { + matrix.emplace_back(columns.size(), 0); + } + // create a matrix that is 1 where column cubes appear in row kernels. + // since kernels are the result of a single division (by the co-kernel), + // this essentially creates ones where division by another cube would be possible + for (int col = 0; col < columns.size(); col++) + { + auto& kCube = columns[col]; + for (int row = 0; row < rows.size(); row++) + { + auto& coKernel = rows[row]; + if (std::any_of(coKernel.kernel.begin(), coKernel.kernel.end(), [&](const auto& k) { return kCube.equals(k); })) + { + matrix[row][col] = 1; + } + } + } + + // Find the best rectangle. This optimizes for #literals saved + // in the resulting expression, which is a good heuristic for + // minimizing the length of the expression. + auto rowWeight = [&](const int& row) { return rows[row].coKernel.size() + 1; }; + auto colWeight = [&](const int& col) { return columns[col].size(); }; + + auto value = [&](const int& col, const int& row) + { + auto cpy = rows[row].coKernel; + cpy.or_(columns[col]); + return cpy.size(); + }; + + auto literalsSaved = [&](const std::tuple, std::vector>& rect) + { + auto [rectRows, rectCols] = rect; + int weight = 0; + for (const auto& row : rectRows) + { + for (const auto& col : rectCols) + { + if (matrix[row][col]) + { + weight += value(col, row); + } + } + } + + for (const auto& row : rectRows) + { + weight -= rowWeight(row); + } + for (const auto& col : rectCols) + { + weight -= colWeight(col); + } + + return weight; + }; + + std::vector, std::vector>> allRects = {}; + std::vector rows_; + std::vector cols_; + for (int i = 0; i < rows.size(); i++) + { + rows_.push_back(i); + } + for (int i = 0; i < columns.size(); i++) + { + cols_.push_back(i); + } + genRectangles(rows_, + cols_, + matrix, + [&](const std::vector& rows__, const std::vector& cols__) + { allRects.push_back({rows__, cols__}); }); + + if (!allRects.empty()) + { + auto& [bestRows, bestCols] = *std::max_element(allRects.begin(), + allRects.end(), + [&](const auto& rect1, const auto& rect2) + { return literalsSaved(rect1) < literalsSaved(rect2); }); + + // divisor is created by OR-ing column cubes + std::vector divisor = {}; + for (const auto& c : bestCols) + { + divisor.push_back(columns[c]); + } + auto [quot, remainder] = algebraicDivision(expr, divisor); + + // and re-assemble a Requirement that sort of looks like + // common_factors * (quotient * divisor + remainder) + auto product = tphdr::logic::requirement::Requirement(); + product._type = tphdr::logic::requirement::Type::AND; + std::vector> quotBits; + std::vector> divisorBits; + for (const auto& c : quot) + { + quotBits.push_back(c.bitset); + } + for (const auto& c : divisor) + { + divisorBits.push_back(c.bitset); + } + product._args.push_back(DNFToExpr(bitIndex, DNF(quotBits))); + product._args.push_back(DNFToExpr(bitIndex, DNF(divisorBits))); + + auto sum = tphdr::logic::requirement::Requirement(); + if (!remainder.empty()) + { + std::vector> remainderBits; + for (const auto& c : remainder) + { + remainderBits.push_back(c.bitset); + } + sum._type = tphdr::logic::requirement::Type::OR; + sum._args.push_back(product); + sum._args.push_back(DNFToExpr(bitIndex, DNF(remainderBits))); + } + else + { + sum = product; + } + auto terms = lookupRequirements(bitIndex, commonFactors); + terms.push_back(sum); + return createAnd(terms); + } + } + + // here we didn't do our complicated rectangle extraction, so just extract + // the common factors + auto terms = tphdr::logic::requirement::Requirement(); + terms._type = tphdr::logic::requirement::Type::OR; + for (const auto& c : expr) + { + terms._args.push_back(createAnd(lookupRequirements(bitIndex, c.ints()))); + } + + // common_factor1 AND common_factor2 AND ... AND (terms without common factors ORed) + auto finalTerms = lookupRequirements(bitIndex, commonFactors); + finalTerms.push_back(terms); + return createAnd(finalTerms); +} + +tphdr::logic::requirement::Requirement createAnd(std::vector terms) +{ + if (terms.size() > 1) + { + tphdr::logic::requirement::Requirement req; + req._type = tphdr::logic::requirement::Type::AND; + for (auto& term : terms) + { + req._args.push_back(term); + } + return req; + } + return terms[0]; +} + +// Recursively computes kernels and co-kernels of the expression `cubes`. +// A co-kernel is a cube (product term) such that for `expr / co-kernel = kernel`, +// `kernel` contains at least two terms but there's no factor to factor out. + +// This effectively tries every combination of variables in this expression +// as a co-kernel. seenCoKernels is a bit of book-keeping to not create +// duplicate kernels, and min_idx ensures we don't try e.g. ab and ba separately +std::vector findKernels(const std::vector& cubes, + const std::vector& variables, + const BitVector& coKernelPath, + std::vector& seenCoKernels, + int minIdx /* = 0 */) +{ + std::vector kernels = {}; + for (int idx = 0; idx < variables.size(); idx++) + { + auto& bit = variables[idx]; + // we won't find any useful kernels by trying these *again* + if (idx < minIdx) + { + continue; + } + + std::vector s = {}; + for (auto& c : cubes) + { + if (c.test(bit)) + { + s.push_back(c); + } + } + + if (s.size() >= 2) + { + auto co = s[0]; + for (const auto& c : s) + { + co.and_(c); + } + auto subPath = coKernelPath; + subPath.or_(co); + auto [quot, remainder] = algebraicDivision(cubes, {co}); + auto subKernels = findKernels(quot, variables, subPath, seenCoKernels, idx + 1); + + for (const auto& sub : subKernels) + { + if (std::none_of(seenCoKernels.begin(), + seenCoKernels.end(), + [=](const auto& seenCo) { return seenCo.equals(sub.coKernel); })) + { + seenCoKernels.push_back(sub.coKernel); + kernels.push_back(sub); + } + } + } + } + + // cube-free expr is always its own kernel, with trivial co-kernel 1 + if (std::none_of(seenCoKernels.begin(), + seenCoKernels.end(), + [=](const auto& seenCo) { return seenCo.equals(coKernelPath); })) + { + kernels.push_back(FoundKernel {cubes, coKernelPath}); + } + + return kernels; +} + +// Computes the algebraic division of expr / divisor, returning +// the quotient and the remainder. These satisfy the formula + +// expr = quotient * divisor + remainder +std::pair, std::vector> algebraicDivision(const std::vector& expr, + const std::vector& divisor) +{ + std::vector quot = {}; + // for every "cube"/product term in our divisor... + for (const auto& divCube : divisor) + { + // get a list of all cubes that this can be divided by + std::vector c = {}; + std::copy_if(expr.begin(), expr.end(), std::back_inserter(c), [=](const auto& e) { return divCube.isSubsetOf(e); }); + + if (c.empty()) + { + // division not possible, remainder is the entire expression + return {{}, expr}; + } + + // "cross out" the bits of this divisor cube + for (auto& ci : c) + { + for (const auto& bit : divCube.ints()) + { + ci.clear(bit); + } + } + + // compute the intersection of the divided expr with the divided expr in other cubes + if (quot.empty()) + { + quot = c; + } + else + { + // this is literally set intersection, NOT an OR or an AND + std::vector newQuot = {}; + std::copy_if(quot.begin(), + quot.end(), + std::back_inserter(newQuot), + [=](const auto& qc) + { return std::any_of(c.begin(), c.end(), [=](const auto& cc) { return cc.equals(qc); }); }); + quot = newQuot; + } + } + + // finally, compute the remainder essentially by computing + // remainder = expr - quotient * divisor + // * is AND + std::vector> quotBits = {}; + for (auto& i : quot) + { + quotBits.push_back(i.bitset); + } + std::vector> divisorBits = {}; + for (auto& i : divisor) + { + divisorBits.push_back(i.bitset); + } + DNF product = DNF(quotBits).and_(DNF(divisorBits)).dedup(); + + std::vector remainder = {}; + std::copy_if(expr.begin(), + expr.end(), + std::back_inserter(remainder), + [=](const auto& e) + { + return std::none_of(product.terms.begin(), + product.terms.end(), + [=](const auto& productTerm) { return includedIn(productTerm, e.bitset); }); + }); + + return {quot, remainder}; +} diff --git a/src/dusk/randomizer/logic/flatten/simplify_algebraic.hpp b/src/dusk/randomizer/logic/flatten/simplify_algebraic.hpp new file mode 100644 index 0000000000..5cfe941c73 --- /dev/null +++ b/src/dusk/randomizer/logic/flatten/simplify_algebraic.hpp @@ -0,0 +1,186 @@ +// Algebraic simplification techniques treat all requirements as unrelated variables +// and allow us to turn our two-level DNF/sum-of-products form into a simpler +// multi-level expression. + +// The approach taken here is mostly used in hardware logic synthesis, and described in: + +// * https://faculty.sist.shanghaitech.edu.cn/faculty/zhoupq/Teaching/Spr16/07-Multi-Level-Logic-Synthesis.pdf +// * Some lecture slides about the topic. These make the concepts of kernels, +// algebraic division, and rectangles very accessible, but e.g. how to actually find rectangles is left open. +// * Rudell 1989, Logic Synthesis for VLSI Design +// https://www2.eecs.berkeley.edu/Pubs/TechRpts/1989/ERL-89-49.pdf (pp. 41-70) +// * Rudell's PhD thesis is where this stuff was originally researched. +// The pseudocode for generating prime rectangles is found there and quite useful. + +// This approach does not exploit boolean properties like x & !x = false or x | !x = true +// but logic doesn't need this since we only have positive terms. The other thing +// these techniques don't handle are "implies" relations like Beetle x 2 => Beetle x 1, +// so we may use different techniques for those. + +#pragma once + +#include "bits.hpp" +#include +#include +#include + +struct FoundKernel +{ + std::vector kernel; + BitVector coKernel; +}; + +tphdr::logic::requirement::Requirement DNFToExpr(BitIndex& bitIndex, DNF dnf); + +tphdr::logic::requirement::Requirement createAnd(std::vector terms); + +// Generates all prime rectangles in this matrix. A rectangle is a set of columns and rows +// such that for every row and column, matrix[row][colum] is not zero. A prime rectangle +// is a rectangle that is not included in any other rectangle. +template +void genRectangles(std::vector& rows, std::vector& cols, std::vector>& matrix, Func callback) +{ + // generate trivial prime rectangles first + // trivial rectangles are rectangles with only + // one row or one column + for (const auto& row : rows) + { + // Find the ones in this row + std::vector ones = {}; + std::copy_if(cols.begin(), cols.end(), std::back_inserter(ones), [=](const int& c) { return matrix[row][c]; }); + // if this row has ones and there's no other row that + // has ones in the same positions, this row is part of + // a trivial row prime rectangle + if (!ones.empty() and + std::none_of( + rows.begin(), + rows.end(), + [=](const int& r) + { return r != row && std::all_of(ones.begin(), ones.end(), [=](const int& c) { return matrix[r][c]; }); })) + { + callback({row}, ones); + } + } + + for (const auto& col : cols) + { + // Same as above + std::vector ones = {}; + std::copy_if(rows.begin(), rows.end(), std::back_inserter(ones), [=](const int& r) { return matrix[r][col]; }); + + if (!ones.empty() and + std::none_of( + rows.begin(), + rows.end(), + [=](const int& c) + { return c != col && std::all_of(ones.begin(), ones.end(), [=](const int& r) { return matrix[r][c]; }); })) + { + callback(ones, {col}); + } + } + + genRectanglesRecursive(rows, cols, matrix, 0, {}, {}, callback); +} + +// Recursively generates non-trivial prime rectangles based on the +// existing prime rectangle given by matrix and rect_cols. Rectangles generated +// by this function will have fewer rows but more columns than the passed rectangle. + +// Args: +// all_rows: A list of all row indices, for convenience. +// all_cols: A list of all column indices, for convenience. +// matrix: The matrix being searched for rectangles. +// index: Grow the rectangle starting from this column +// rect_rows: Rows of the prime rectangle. +// rect_cols: Columns of the prime rectangle. +// callback: Called for every prime rectangle. +template +void genRectanglesRecursive(std::vector& allRows, + std::vector& allCols, + std::vector>& matrix, + const int& index, + std::vector rectRows, + std::vector rectCols, + Func callback) +{ + // do not consider columns before the starting index, and require + // this column to have two or more ones (otherwise we'd generate a trivial rectangle) + for (const auto& c : allCols) + { + if (c >= index && std::count_if(allRows.begin(), allRows.end(), [=](const int& row) { return matrix[row][c]; }) >= 2) + { + // create submatrix, only keeping rows where the column has a one + // all other rows are zeroed + std::vector> m1 = {}; + for (int rowIdx = 0; rowIdx < matrix.size(); rowIdx++) + { + auto& row = matrix[rowIdx]; + m1.push_back(matrix[rowIdx][c] ? row : std::vector(row.size(), 0)); + } + + // create new rect rows based on this column. If we had an existing + // rectangle in the recursive case, this shrinks the rectangle, otherwise + // it creates the first rectangle + std::vector rect1Rows; + std::copy_if(allRows.begin(), + allRows.end(), + std::back_inserter(rect1Rows), + [=](const int& row) { return matrix[row][c]; }); + std::vector rect1Cols = rectCols; + + bool prune = false; + // add column c and all columns with EXACTLY the same number of ones + for (const auto& c1 : allCols) + { + if (std::count_if(allRows.begin(), allRows.end(), [=](const int& row) { return m1[row][c1]; }) == + std::count_if(allRows.begin(), allRows.end(), [=](const int& row) { return matrix[row][c]; })) + { + if (c1 < c) + { + // "if a column of 1's occurs for a column index less than + // the starting index, then all rectangles in the current + // submatrix have already been examined when that column + // was processed" (Rudell) + prune = true; + break; + } + else + { + // add the column to our rectangle + rect1Cols.push_back(c1); + for (const auto& row : allRows) + { + m1[row][c1] = 0; + } + } + } + } + + if (!prune) + { + callback(rect1Rows, rect1Cols); + genRectanglesRecursive(allRows, allCols, m1, c, rect1Rows, rect1Cols, callback); + } + } + } +} + +std::vector findKernels(const std::vector& cubes, + const std::vector& variables, + const BitVector& coKernelPath, + std::vector& seenCoKernels, + int minIdx = 0); + +std::pair, std::vector> algebraicDivision(const std::vector& expr, + const std::vector& divisor); + +template +std::vector lookupRequirements(BitIndex& bitIndex, Container r) +{ + std::vector reqs; + for (auto& bit : r) + { + reqs.push_back(bitIndex.reverseIndex[bit]); + } + return reqs; +} diff --git a/src/dusk/randomizer/logic/generate.cpp b/src/dusk/randomizer/logic/generate.cpp new file mode 100644 index 0000000000..7724862033 --- /dev/null +++ b/src/dusk/randomizer/logic/generate.cpp @@ -0,0 +1,114 @@ +#include "generate.hpp" + +#include "entrance_shuffle.hpp" +#include "fill.hpp" +#include "flatten/flatten.hpp" +#include "plandomizer.hpp" +#include "search.hpp" +#include "spoiler_log.hpp" +#include "../seedgen/config.hpp" +#include "../seedgen/settings.hpp" +#include "../utility/log.hpp" +#include "../utility/time.hpp" + +#include + +namespace tphdr::logic::generate +{ + tphdr::logic::world::WorldPool GenerateWorlds() + { +#ifdef ENABLE_TIMING + tphdr::utility::time::ScopedTimer<"Seed generation took ", std::chrono::milliseconds> timer; +#endif + tphdr::seedgen::config::Config config; + config.LoadFromFile(SETTINGS_PATH, PREFERENCES_PATH); + + tphdr::utility::platform::Log(std::string("Seed: ") + config.GetSeed()); + + tphdr::logic::world::WorldPool worlds = {}; + GenerateRandomizer(config, worlds); + + return std::move(worlds); + } + + void GenerateRandomizer(tphdr::seedgen::config::Config& config, tphdr::logic::world::WorldPool& worlds) + { + tphdr::seedgen::config::SeedRNG(config, true, false); + // Set the hash now before anything else random is decided. This allows us to show the hash for a seed + // before generating it later + auto hash = config.GetHash(); + tphdr::utility::platform::Log(std::string("Hash: ") + hash); + + // Build all worlds + int worldId = 1; + for (const auto& settings : config.GetSettingsList()) + { + std::unique_ptr world = std::make_unique(worldId++); + world->SetSettings(settings); + world->ResolveRandomSettings(); + world->ResolveConflictingSettings(); + world->Build(); + worlds.emplace_back(std::move(world)); + } + + // Give each world a pointer to the world pool + for (auto& world : worlds) + { + world->SetWorlds(&worlds); + } + + // Process Plando Data for all worlds + if (config.IsUsingPlandomizer()) + { + tphdr::logic::plandomizer::LoadPlandomizerData(worlds, config.GetPlandomizerPath()); + } + + // Pre Entrance Shuffle Tasks + for (auto& world : worlds) + { + world->PerformPreEntranceShuffleTasks(); + } + + tphdr::utility::platform::Log("Shuffling Entrances..."); + for (auto& world : worlds) + { + tphdr::logic::entrance_shuffle::ShuffleWorldEntrances(world.get(), worlds); + } + + // Post Entrance Shuffle Tasks + for (auto& world : worlds) + { + world->PerformPostEntranceShuffleTasks(); + } + tphdr::logic::fill::CacheExitTimeForms(worlds); + + // Flattening isn't used for anything yet, but flattens down the requirements for + // each location and entrance into a single statement. This will be useful for hints and could potentially + // be used to speed up the fill algorithm (but the fill algorithm is already pretty fast, so we'd only gain maybe like + // 0.2 seconds back or something) + tphdr::utility::platform::Log("Flattening..."); + FlattenSearch search = FlattenSearch(worlds.at(0).get()); + search.doSearch(); + + tphdr::utility::platform::Log("Filling Worlds..."); + tphdr::logic::fill::FillWorlds(worlds); + + // Post Fill Tasks + for (auto& world : worlds) + { + world->PerformPostFillTasks(); + } + + // Generate Playthrough + tphdr::logic::search::GeneratePlaythrough(&worlds); + + // TODO: Generate Hints + + // Write Logs + if (config.IsGeneratingSpoilerLog()) + { + tphdr::logic::spoiler_log::GenerateSpoilerLog(worlds, config); + } + tphdr::logic::spoiler_log::GenerateAntiSpoilerLog(worlds, config); + } +} // namespace tphdr::logic::generate diff --git a/src/dusk/randomizer/logic/generate.hpp b/src/dusk/randomizer/logic/generate.hpp new file mode 100644 index 0000000000..336229ee44 --- /dev/null +++ b/src/dusk/randomizer/logic/generate.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "world.hpp" +#include "../seedgen/config.hpp" + +namespace tphdr::logic::generate +{ + /** + * @brief Generates a complete randomizer seed + * + * @param worlds The list of worlds for the generated randomizer seed + * @return the worldpool which was generated + */ + tphdr::logic::world::WorldPool GenerateWorlds(); + + /** + * @brief Generates a complete randomizer seed with the provided config + * + * @param config The configuration to use for this seed + * @param worlds The list of worlds for the generated randomizer seed + * @return 0 if no errors. 1 if there were errors + */ + void GenerateRandomizer(tphdr::seedgen::config::Config& config, tphdr::logic::world::WorldPool& worlds); +} // namespace tphdr::logic::generate diff --git a/src/dusk/randomizer/logic/item.cpp b/src/dusk/randomizer/logic/item.cpp new file mode 100644 index 0000000000..9198118c3e --- /dev/null +++ b/src/dusk/randomizer/logic/item.cpp @@ -0,0 +1,157 @@ +#include "item.hpp" + +#include "world.hpp" + +namespace tphdr::logic::item +{ + + Importance ImportanceFromStr(const std::string& str) + { + std::unordered_map importances = {{"Major", Importance::MAJOR}, + {"Minor", Importance::MINOR}, + {"Junk", Importance::JUNK}}; + + if (!importances.contains(str)) + { + return Importance::INVALID; + } + + return importances.at(str); + } + + Item::Item(const int& id, + const std::string& name, + tphdr::logic::world::World* world, + const Importance& importance, + const bool& gameWinningItem, + const bool& dungeonSmallKey, + const bool& bigKey, + const bool& compass, + const bool& dungeonMap): + _id(id), + _name(name), + _world(world), + _importance(importance), + _gameWinningItem(gameWinningItem), + _dungeonSmallKey(dungeonSmallKey), + _bigKey(bigKey), + _compass(compass), + _dungeonMap(dungeonMap) + { + if (name.starts_with("Male") || name.starts_with("Female")) + { + this->_goldenBug = true; + } + else if (name == "Shadow Crystal") + { + this->_shadowCrystal = true; + } + else if (name.starts_with("Bottle") || name == "Empty Bottle") + { + this->_bottle = true; + } + else if (name.starts_with("Stamp")) + { + this->_stamp = true; + } + } + + int Item::GetID() const + { + return this->_id; + } + + std::string Item::GetName() const + { + return this->_name; + } + + tphdr::logic::world::World* Item::GetWorld() const + { + return this->_world; + } + + Importance Item::GetImportance() const + { + return this->_importance; + } + + bool Item::IsMajor() const + { + return this->_importance == Importance::MAJOR; + } + + bool Item::IsMinor() const + { + return this->_importance == Importance::MINOR; + } + + bool Item::isJunk() const + { + return this->_importance == Importance::JUNK; + } + + bool Item::IsGameWinningItem() const + { + return this->_gameWinningItem; + } + + std::list Item::GetChainLocations() const + { + return this->_chainLocations; + } + + bool Item::IsDungeonSmallKey() const + { + return this->_dungeonSmallKey; + } + + bool Item::IsBigKey() const + { + return this->_bigKey; + } + + bool Item::IsDungeonMap() const + { + return this->_dungeonMap; + } + + bool Item::IsCompass() const + { + return this->_compass; + } + + bool Item::IsGoldenBug() const + { + return this->_goldenBug; + } + + bool Item::IsShadowCrystal() const + { + return this->_shadowCrystal; + } + + bool Item::IsBottle() const + { + return this->_bottle; + } + + bool Item::IsStamp() const + { + return this->_stamp; + } + + bool Item::operator==(const Item& rhs) const + { + return this->_id == rhs._id && this->_world->GetID() == rhs._world->GetID(); + } + + bool Item::operator<(const Item& rhs) const + { + return (this->_world->GetID() == rhs._world->GetID()) ? this->_id < rhs._id + : this->_world->GetID() < rhs._world->GetID(); + } + + std::unique_ptr Nothing = + std::make_unique(-1, "Nothing", nullptr, Importance::JUNK, false, false, false, false, false); +} // namespace tphdr::logic::item diff --git a/src/dusk/randomizer/logic/item.hpp b/src/dusk/randomizer/logic/item.hpp new file mode 100644 index 0000000000..63585c49a2 --- /dev/null +++ b/src/dusk/randomizer/logic/item.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +namespace tphdr::logic::world +{ + class World; +}; +class Location; +namespace tphdr::logic::item +{ + enum Importance + { + INVALID, + MAJOR, + MINOR, + JUNK, + }; + + Importance ImportanceFromStr(const std::string& str); + + class Item + { + public: + Item() = default; + Item(const int& id, + const std::string& name, + tphdr::logic::world::World* world, + const Importance& importance, + const bool& gameWinningItem, + const bool& dungeonSmallKey, + const bool& bigKey, + const bool& compass, + const bool& dungeonMap); + + int GetID() const; + std::string GetName() const; + tphdr::logic::world::World* GetWorld() const; + Importance GetImportance() const; + bool IsMajor() const; + bool IsMinor() const; + bool isJunk() const; + bool IsGameWinningItem() const; + std::list GetChainLocations() const; + bool IsDungeonSmallKey() const; + bool IsBigKey() const; + bool IsDungeonMap() const; + bool IsCompass() const; + bool IsGoldenBug() const; + bool IsShadowCrystal() const; + bool IsBottle() const; + bool IsStamp() const; + + bool operator==(const Item& rhs) const; + bool operator<(const Item& rhs) const; + + private: + int _id = -1; + std::string _name; + tphdr::logic::world::World* _world = nullptr; + Importance _importance = INVALID; + bool _gameWinningItem = false; + std::list _chainLocations; + + bool _dungeonSmallKey = false; + bool _bigKey = false; + bool _dungeonMap = false; + bool _compass = false; + bool _goldenBug = false; + bool _bottle = false; + bool _shadowCrystal = false; + bool _stamp = false; + }; + + extern std::unique_ptr Nothing; +} // namespace tphdr::logic::item \ No newline at end of file diff --git a/src/dusk/randomizer/logic/item_pool.cpp b/src/dusk/randomizer/logic/item_pool.cpp new file mode 100644 index 0000000000..6d15e29b53 --- /dev/null +++ b/src/dusk/randomizer/logic/item_pool.cpp @@ -0,0 +1,348 @@ +#include "item_pool.hpp" + +#include "world.hpp" + +#include + +namespace tphdr::logic::item_pool +{ + + std::map minimalItemPool = { + {"Shadow Crystal", 1}, + {"Slingshot", 1}, + {"Lantern", 1}, + {"Gale Boomerang", 1}, + {"Iron Boots", 1}, + {"Bomb Bag", 1}, + {"Spinner", 1}, + {"Ball and Chain", 1}, + + {"Progressive Fishing Rod", 2}, + {"Progressive Sword", 4}, + {"Progressive Bow", 1}, + {"Progressive Clawshot", 2}, + {"Progressive Dominion Rod", 2}, + {"Progressive Wallet", 2}, + {"Progressive Sky Book", 7}, + + {"Aurus Memo", 1}, + {"Asheis Sketch", 1}, + {"Renados Letter", 1}, + {"Zora Armor", 1}, + {"Hylian Shield", 1}, + {"Ordon Shield", 1}, + {"Empty Bottle", 4}, + {"Progressive Hidden Skill", 1}, + {"Poe Soul", 60}, + + {"Progressive Fused Shadow", 3}, + {"Progressive Mirror Shard", 4}, + + // Golden Bugs + {"Male Ant", 1}, + {"Female Ant", 1}, + {"Male Beetle", 1}, + {"Female Beetle", 1}, + {"Male Pill Bug", 1}, + {"Female Pill Bug", 1}, + {"Male Phasmid", 1}, + {"Female Phasmid", 1}, + {"Male Grasshopper", 1}, + {"Female Grasshopper", 1}, + {"Male Stag Beetle", 1}, + {"Female Stag Beetle", 1}, + {"Male Butterfly", 1}, + {"Female Butterfly", 1}, + {"Male Ladybug", 1}, + {"Female Ladybug", 1}, + {"Male Mantis", 1}, + {"Female Mantis", 1}, + {"Male Dragonfly", 1}, + {"Female Dragonfly", 1}, + {"Male Dayfly", 1}, + {"Female Dayfly", 1}, + {"Male Snail", 1}, + {"Female Snail", 1}, + + // Keys + {"Gate Keys", 1}, + {"Gerudo Desert Bulblin Camp Key", 1}, + {"North Faron Woods Gate Key", 1}, + {"Forest Temple Small Key", 4}, + {"Goron Mines Small Key", 3}, + {"Lakebed Temple Small Key", 3}, + {"Arbiters Grounds Small Key", 5}, + {"Snowpeak Ruins Small Key", 4}, + {"Ordon Pumpkin", 1}, + {"Ordon Cheese", 1}, + {"Temple of Time Small Key", 3}, + {"City in the Sky Small Key", 1}, + {"Palace of Twilight Small Key", 7}, + {"Hyrule Castle Small Key", 3}, + + // Big Keys + {"Forest Temple Big Key", 1}, + {"Goron Mines Key Shard", 3}, + {"Lakebed Temple Big Key", 1}, + {"Arbiters Grounds Big Key", 1}, + {"Snowpeak Ruins Bedroom Key", 1}, + {"Temple of Time Big Key", 1}, + {"City in the Sky Big Key", 1}, + {"Palace of Twilight Big Key", 1}, + {"Hyrule Castle Big Key", 1}, + + // Maps and Compasses + {"Forest Temple Compass", 1}, + {"Goron Mines Compass", 1}, + {"Lakebed Temple Compass", 1}, + {"Arbiters Grounds Compass", 1}, + {"Snowpeak Ruins Compass", 1}, + {"Temple of Time Compass", 1}, + {"City in the Sky Compass", 1}, + {"Palace of Twilight Compass", 1}, + {"Hyrule Castle Compass", 1}, + {"Forest Temple Dungeon Map", 1}, + {"Goron Mines Dungeon Map", 1}, + {"Lakebed Temple Dungeon Map", 1}, + {"Arbiters Grounds Dungeon Map", 1}, + {"Snowpeak Ruins Dungeon Map", 1}, + {"Temple of Time Dungeon Map", 1}, + {"City in the Sky Dungeon Map", 1}, + {"Palace of Twilight Dungeon Map", 1}, + {"Hyrule Castle Dungeon Map", 1}, + + // Junk we should always have + {"Purple Rupee Links House", 1}, + {"Green Rupee", 2}, + {"Orange Rupee", 50}, + {"Silver Rupee", 2}, + }; + + // This is intended to be added on top of the minimal item pool + std::map standardItemPool = { + {"Bomb Bag", 2}, + {"Progressive Bow", 2}, + {"Progressive Wallet", 1}, + {"Magic Armor", 1}, + {"Hawkeye", 1}, + {"Giant Bomb Bag", 1}, + {"Horse Call", 1}, + // {"Bottle with Half Milk", 1}, // Special bottles replace Empty Bottles after the fill algorithm is done + // {"Bottle with Lantern Oil", 1}, + // {"Bottle with Great Fairies Tears", 1}, + {"Progressive Hidden Skill", 6}, + + {"Heart Container", 8}, + {"Piece of Heart", 45}, + }; + + // This is intended to be added on top of the minimal and standard pools + std::map plentifulItemPool = { + {"Shadow Crystal", 1}, + {"Slingshot", 1}, + {"Lantern", 1}, + {"Gale Boomerang", 1}, + {"Iron Boots", 1}, + {"Bomb Bag", 1}, + {"Spinner", 1}, + {"Ball and Chain", 1}, + + {"Progressive Fishing Rod", 1}, + {"Progressive Sword", 4}, + {"Progressive Bow", 1}, + {"Progressive Clawshot", 1}, + {"Progressive Dominion Rod", 1}, + {"Progressive Wallet", 1}, + {"Progressive Sky Book", 1}, + + {"Aurus Memo", 1}, + {"Asheis Sketch", 1}, + // {"Renados Letter", 1}, Vanilla until flag issues are figured out + {"Zora Armor", 1}, + {"Magic Armor", 1}, + {"Hylian Shield", 1}, + {"Empty Bottle", 1}, + {"Progressive Hidden Skill", 1}, + + // Keys + {"Gate Keys", 1}, + {"Forest Temple Small Key", 1}, + {"Goron Mines Small Key", 1}, + {"Lakebed Temple Small Key", 1}, + {"Arbiters Grounds Small Key", 1}, + {"Snowpeak Ruins Small Key", 1}, + {"Ordon Pumpkin", 1}, + {"Ordon Cheese", 1}, + {"Temple of Time Small Key", 1}, + {"City in the Sky Small Key", 1}, + {"Palace of Twilight Small Key", 1}, + {"Hyrule Castle Small Key", 1}, + + // Big Keys + {"Forest Temple Big Key", 1}, + {"Goron Mines Key Shard", 1}, + {"Lakebed Temple Big Key", 1}, + {"Arbiters Grounds Big Key", 1}, + {"Snowpeak Ruins Bedroom Key", 1}, + {"Temple of Time Big Key", 1}, + {"City in the Sky Big Key", 1}, + {"Palace of Twilight Big Key", 1}, + {"Hyrule Castle Big Key", 1}, + }; + + std::map initialJunkPool = { + {"Bombs 5", 8}, + {"Bombs 10", 2}, + {"Bombs 20", 1}, + {"Bombs 30", 1}, + {"Arrows 10", 13}, + {"Arrows 20", 6}, + {"Arrows 30", 2}, + {"Seeds 50", 2}, + {"Water Bombs 5", 3}, + {"Water Bombs 10", 5}, + {"Water Bombs 15", 3}, + {"Bomblings 5", 2}, + {"Bomblings 10", 2}, + {"Blue Rupee", 1}, + {"Yellow Rupee", 6}, + {"Red Rupee", 6}, + {"Purple Rupee", 12}, + }; + + void GenerateItemPool(tphdr::logic::world::World* world) + { + auto itemPool = minimalItemPool; + + // Minimal item pool things + if (world->Setting("Item Scarcity") == "Minimal") + { + // If glitched logic, include magic armor + } + + // Add the vanilla item pool if necessary + if (world->Setting("Item Scarcity").IsAnyOf("Vanilla", "Plentiful")) + { + for (const auto& [itemName, count] : standardItemPool) + { + itemPool[itemName] += count; + } + } + + // Add the plentiful item pool if necessary + if (world->Setting("Item Scarcity") == "Plentiful") + { + for (const auto& [itemName, count] : plentifulItemPool) + { + itemPool[itemName] += count; + } + } + + // Remove the North Faron Woods Gate Key if we're skipping prologue + if (world->Setting("Skip Prologue") == "On") + { + itemPool.at("North Faron Woods Gate Key") = 0; + } + + // Remove the bulblin camp key if we're skipping bulblin camp + if (world->Setting("Arbiters Does Not Require Bulblin Camp") == "On") + { + itemPool.at("Gerudo Desert Bulblin Camp Key") = 0; + } + + // Remove sky book characters if we're starting with the sky canon open + if (world->Setting("City Does Not Require Filled Skybook") == "On") + { + itemPool.at("Progressive Sky Book") = 0; + } + + // Remove Small Keys if we're playing without them + if (world->Setting("Small Keys") == "Keysy") + { + std::list smallKeys = { + {"Gate Keys"}, + {"Forest Temple Small Key"}, + {"Goron Mines Small Key"}, + {"Lakebed Temple Small Key"}, + {"Arbiters Grounds Small Key"}, + {"Snowpeak Ruins Small Key"}, + {"Ordon Pumpkin"}, + {"Ordon Cheese"}, + {"Temple of Time Small Key"}, + {"City in the Sky Small Key"}, + {"Palace of Twilight Small Key"}, + {"Hyrule Castle Small Key"}, + }; + for (const auto& key : smallKeys) + { + itemPool.at(key) = 0; + } + } + + // Remove Big Keys if we're playing without them + if (world->Setting("Big Keys") == "Keysy") + { + std::list bigKeys = { + {"Forest Temple Big Key"}, + {"Goron Mines Key Shard"}, + {"Lakebed Temple Big Key"}, + {"Arbiters Grounds Big Key"}, + {"Snowpeak Ruins Bedroom Key"}, + {"Temple of Time Big Key"}, + {"City in the Sky Big Key"}, + {"Palace of Twilight Big Key"}, + {"Hyrule Castle Big Key"}, + }; + for (const auto& key : bigKeys) + { + itemPool.at(key) = 0; + } + } + + // Add items to the world's _itemPool + auto& worldItemPool = world->GetItemPool(); + for (const auto& [itemName, count] : itemPool) + { + auto item = world->GetItem(itemName); + for (auto i = 0; i < count; i++) + { + worldItemPool.push_back(item); + } + } + } + + void GenerateStartingItemPool(tphdr::logic::world::World* world) + { + const auto& startingItems = world->GetSettings().GetStartingInventory(); + auto& startingItemPool = world->GetStartingItemPool(); + auto& itemPool = world->GetItemPool(); + + // Add each item to the world's _startingItemPool and erase it from the regular _itemPool + for (const auto& [itemName, count] : startingItems) + { + auto item = world->GetItem(itemName); + for (auto i = 0; i < count; i++) + { + startingItemPool.push_back(item); + } + tphdr::utility::container::Erase(itemPool, item, count); + } + } + + std::map GetInitialJunkPool() + { + return initialJunkPool; + } + + ItemPool GetCompleteItemPool(tphdr::logic::world::WorldPool& worlds) + { + ItemPool completeItemPool = {}; + for (const auto& world : worlds) + { + auto& worldItemPool = world->GetItemPool(); + std::copy(worldItemPool.begin(), worldItemPool.end(), std::back_inserter(completeItemPool)); + } + + return completeItemPool; + } +} // namespace tphdr::logic::item_pool diff --git a/src/dusk/randomizer/logic/item_pool.hpp b/src/dusk/randomizer/logic/item_pool.hpp new file mode 100644 index 0000000000..737405c051 --- /dev/null +++ b/src/dusk/randomizer/logic/item_pool.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +// Foward Declarations +namespace tphdr::logic::world +{ + class World; + using WorldPool = std::vector>; +} // namespace tphdr::logic::world + +namespace tphdr::logic::item +{ + class Item; +} + +namespace tphdr::logic::item_pool +{ + using ItemPool = std::vector; + + /** + * @brief Generates and sets the item pool of randomized items for a single world. + * + * @param world The world to generate the item pool for + */ + void GenerateItemPool(tphdr::logic::world::World* world); + + /** + * @brief Generates and sets the starting item pool for a single world. Starting items will be + * subtracted from the world's regular item pool, so be sure to call GenerateItemPool first + * + * @param world The world to generate the starting item pool for + */ + void GenerateStartingItemPool(tphdr::logic::world::World* world); + + std::map GetInitialJunkPool(); + + ItemPool GetCompleteItemPool(tphdr::logic::world::WorldPool& worlds); +} // namespace tphdr::logic::item_pool diff --git a/src/dusk/randomizer/logic/location.cpp b/src/dusk/randomizer/logic/location.cpp new file mode 100644 index 0000000000..d7fddf4bba --- /dev/null +++ b/src/dusk/randomizer/logic/location.cpp @@ -0,0 +1,144 @@ +#include "location.hpp" + +#include "world.hpp" +#include "../utility/log.hpp" + +namespace tphdr::logic::location +{ + Location::Location(const int& id, + const std::string& name, + std::unordered_set categories, + tphdr::logic::world::World* world, + tphdr::logic::item::Item* originalItem, + const bool& goalLocation, + const std::string& hintPriority): + _id(id), + _name(name), + _categories(categories), + _world(world), + _originalItem(originalItem), + _goalLocation(goalLocation), + _hintPriority(hintPriority) + { + this->_computedRequirement._type = tphdr::logic::requirement::Type::IMPOSSIBLE; + } + + int Location::GetID() const + { + return this->_id; + } + + std::string Location::GetName() const + { + return this->_name; + } + + tphdr::logic::world::World* Location::GetWorld() const + { + return this->_world; + } + + bool Location::IsGoalLocation() const + { + return this->_goalLocation; + } + + void Location::SetCurrentItem(tphdr::logic::item::Item* item) + { + LOG_TO_DEBUG("Placed " + item->GetName() + " at " + this->GetName()); + this->_currentItem = item; + } + + tphdr::logic::item::Item* Location::GetCurrentItem() const + { + return this->_currentItem; + } + + void Location::RemoveCurrentItem() + { + LOG_TO_DEBUG("Removed " + this->GetCurrentItem()->GetName() + " at " + this->GetName()); + this->_currentItem = tphdr::logic::item::Nothing.get(); + } + + bool Location::IsEmpty() const + { + return this->_currentItem == tphdr::logic::item::Nothing.get(); + } + + tphdr::logic::item::Item* Location::GetOriginalItem() const + { + return this->_originalItem; + } + + tphdr::logic::item::Item* Location::GetTrackedItem() const + { + return this->_trackedItem; + } + + void Location::SetKnownVanillaItem(const bool& hasKnownVanillaItem) + { + this->_hasKnownVanillaItem = hasKnownVanillaItem; + } + + bool Location::HasKnownVanillaItem() const + { + return this->_hasKnownVanillaItem; + } + + void Location::SetProgression(const bool& progression) + { + this->_progression = progression; + LOG_TO_DEBUG(this->GetName() + " progression status set to " + (progression ? " true" : "false")); + } + + bool Location::IsProgression() const + { + return this->_progression; + } + + void Location::SetHinted(const bool& hinted) + { + this->_hinted = hinted; + } + + bool Location::IsHinted() const + { + return this->_hinted; + } + + void Location::AddLocationAccess(tphdr::logic::area::LocationAccess* locAcc) + { + this->_locationAccessList.push_back(locAcc); + } + + std::list Location::GetAccessList() const + { + return this->_locationAccessList; + } + + void Location::AddForbiddenItem(tphdr::logic::item::Item* forbiddenItem) + { + this->_forbiddenItems.insert(forbiddenItem); + LOG_TO_DEBUG(forbiddenItem->GetName() + " is forbidden from being placed at " + this->GetName()); + } + + const std::unordered_set& Location::GetForbiddenItems() + { + return this->_forbiddenItems; + } + + void Location::SetComputedRequirement(const tphdr::logic::requirement::Requirement& computedRequirement) + { + this->_computedRequirement = computedRequirement; + } + + tphdr::logic::requirement::Requirement Location::GetComputedRequirement() + { + return this->_computedRequirement; + } + + void Location::SetRegisteredLocationCategories(std::unordered_set* registeredLocationCategories) + { + this->_registeredLocationCategories = registeredLocationCategories; + } +} // namespace tphdr::logic::location diff --git a/src/dusk/randomizer/logic/location.hpp b/src/dusk/randomizer/logic/location.hpp new file mode 100644 index 0000000000..4f4e7260b9 --- /dev/null +++ b/src/dusk/randomizer/logic/location.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include "item.hpp" +#include "requirement.hpp" + +#include +#include +#include + +namespace tphdr::logic::world +{ + class World; +} + +namespace tphdr::logic::area +{ + class LocationAccess; +} + +namespace tphdr::logic::location +{ + class Location + { + public: + Location(const int& id, + const std::string& name, + std::unordered_set categories, + tphdr::logic::world::World* world, + tphdr::logic::item::Item* originalItem, + const bool& goalLocation, + const std::string& hintPriority); + + int GetID() const; + std::string GetName() const; + tphdr::logic::world::World* GetWorld() const; + bool IsGoalLocation() const; + void SetCurrentItem(tphdr::logic::item::Item* currentItem); + tphdr::logic::item::Item* GetCurrentItem() const; + void RemoveCurrentItem(); + bool IsEmpty() const; + tphdr::logic::item::Item* GetOriginalItem() const; + tphdr::logic::item::Item* GetTrackedItem() const; + void SetKnownVanillaItem(const bool& hasKnownVanillaItem); + bool HasKnownVanillaItem() const; + void SetProgression(const bool& progression); + bool IsProgression() const; + void SetHinted(const bool& hinted); + bool IsHinted() const; + void AddLocationAccess(tphdr::logic::area::LocationAccess* locAcc); + std::list GetAccessList() const; + void AddForbiddenItem(tphdr::logic::item::Item* forbiddenItem); + const std::unordered_set& GetForbiddenItems(); + void SetComputedRequirement(const tphdr::logic::requirement::Requirement& computedRequirement); + tphdr::logic::requirement::Requirement GetComputedRequirement(); + void SetRegisteredLocationCategories(std::unordered_set* registeredLocationCategories); + + /** + * @brief Checks to see if the location has all the passed in categories. If a passed in category was never registred, + * a std::runtime_error will be thrown. + * @param categoryNames paramater pack of string representations of category names + * @returns true if all passed in categories are present, false otherwise + */ + template + bool HasCategories(Types... categoryNames) const + { + for (const auto& categoryName : {categoryNames...}) + { + if (this->_registeredLocationCategories != nullptr && + !this->_registeredLocationCategories->contains(categoryName)) + { + throw std::runtime_error(std::string("Category \"") + categoryName + "\" is not used by any locations"); + } + if (!this->_categories.contains(categoryName)) + { + return false; + } + } + + return true; + } + + private: + int _id = -1; + std::string _name = ""; + std::unordered_set _categories = {}; + tphdr::logic::world::World* _world; + tphdr::logic::item::Item* _originalItem = tphdr::logic::item::Nothing.get(); + bool _goalLocation = false; + tphdr::logic::item::Item* _currentItem = tphdr::logic::item::Nothing.get(); + bool _hasKnownVanillaItem = false; + std::list _locationAccessList = {}; + bool _progression = true; // Set as false later if applicable + bool _hinted = false; + std::string _hintPriority = "Never"; + std::unordered_set _forbiddenItems = {}; + tphdr::logic::requirement::Requirement _computedRequirement; + /** + * @brief _registeredLocationCategories is the set of all categories that are processed after reading locations.yaml. + * This structure is held in the World class and every location in that world has a pointer to it. + * We can't call it from the world directly since the function we want to use it in is templated in this class. + */ + std::unordered_set* _registeredLocationCategories = nullptr; + + // Potential tracker stuff + tphdr::logic::item::Item* _trackedItem = tphdr::logic::item::Nothing.get(); + }; + + using LocationPool = std::vector; +} // namespace tphdr::logic::location diff --git a/src/dusk/randomizer/logic/plandomizer.cpp b/src/dusk/randomizer/logic/plandomizer.cpp new file mode 100644 index 0000000000..b76222a9d3 --- /dev/null +++ b/src/dusk/randomizer/logic/plandomizer.cpp @@ -0,0 +1,113 @@ +#include "plandomizer.hpp" + +#include "world.hpp" + +#include "../utility/yaml.hpp" +#include "../utility/file.hpp" +#include "../utility/log.hpp" + +namespace tphdr::logic::plandomizer +{ + void LoadPlandomizerData(tphdr::logic::world::WorldPool& worlds, const fspath& filepath, const bool& ignoreErrors /*false*/) + { + // Verify the file exists before trying to open it + // TODO: TRY CATCH HERE + tphdr::utility::file::Verify(filepath); + + auto plandoTree = LoadYAML(filepath); + + for (auto& world : worlds) + { + int worldId = world->GetID(); + std::string worldStr = "World " + std::to_string(world->GetID()); + if (plandoTree[worldStr]) + { + const auto& worldNode = plandoTree[worldStr]; + if (worldNode["Locations"]) + { + const auto& locations = worldNode["Locations"]; + if (!locations.IsMap()) + { + if (ignoreErrors) + { + return; + } + + throw std::runtime_error("Plandomizer file locations for " + worldStr + + " is not a map. Please check your syntax before trying again."); + } + + for (const auto& locationNode : locations) + { + // If the location object has children instead of a value, then parse the item name and potential + // world id from those children. If no world id is given, the current world will be used. + std::string itemName; + if (locationNode.second.IsMap()) + { + if (locationNode.second["Item"]) + { + itemName = locationNode.second["Item"].as(); + } + else + { + throw std::runtime_error("Plandomizer Error: Missing key \"item\" in node:\n" + + YAML::Dump(locationNode)); + } + + if (locationNode.second["World"]) + { + worldId = locationNode.second["World"].as(); + if (worldId < 1 || worldId > worlds.size()) + { + std::string errorMsg = "Plandomizer Error: Bad World ID \"" + std::to_string(worldId) + + "\"\nOnly " + std::to_string(worlds.size()) + + " worlds are being generated."; + throw std::runtime_error(errorMsg); + } + } + } + // Otherwise treat the value as an item for the same world as the location + else + { + itemName = locationNode.second.as(); + } + + const std::string locationName = locationNode.first.as(); + auto location = world->GetLocation(locationName); + + auto& itemWorld = worlds.at(worldId - 1); + auto item = itemWorld->GetItem(itemName); + + world->AddPlandomizedLocation(location, item); + } + } + + if (worldNode["Entrances"]) + { + const auto& entrances = worldNode["Entrances"]; + if (!entrances.IsMap()) + { + if (ignoreErrors) + { + return; + } + + throw std::runtime_error("Plandomizer file entrances for " + worldStr + + " is not a map. Please check your syntax before trying again."); + } + + for (const auto& entranceNode : entrances) + { + auto entranceName = entranceNode.first.as(); + auto targetName = entranceNode.second.as(); + + auto entrance = world->GetEntrance(entranceName); + auto target = world->GetEntrance(targetName); + + world->AddPlandomizedEntrance(entrance, target); + } + } + } + } + } +} // namespace tphdr::logic::plandomizer diff --git a/src/dusk/randomizer/logic/plandomizer.hpp b/src/dusk/randomizer/logic/plandomizer.hpp new file mode 100644 index 0000000000..53fcffa323 --- /dev/null +++ b/src/dusk/randomizer/logic/plandomizer.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +using fspath = std::filesystem::path; + +// Forward Declarations +namespace tphdr::logic::world +{ + class World; + using WorldPool = std::vector>; +} // namespace tphdr::logic::world + +namespace tphdr::logic::plandomizer +{ + void LoadPlandomizerData(tphdr::logic::world::WorldPool& worlds, const fspath& filepath, const bool& ignoreErrors = false); +} \ No newline at end of file diff --git a/src/dusk/randomizer/logic/requirement.cpp b/src/dusk/randomizer/logic/requirement.cpp new file mode 100644 index 0000000000..75a8865f41 --- /dev/null +++ b/src/dusk/randomizer/logic/requirement.cpp @@ -0,0 +1,622 @@ +#include "requirement.hpp" + +#include "search.hpp" +#include "world.hpp" +#include "../utility/container.hpp" +#include "../utility/log.hpp" +#include "../utility/string.hpp" + +#include +#include +#include + +namespace tphdr::logic::requirement +{ + namespace FormTime + { + const std::vector ALL_FORM_TIMES = {HUMAN_DAY, HUMAN_NIGHT, WOLF_DAY, WOLF_NIGHT}; + const std::vector ALL_FORM_TIMES_AND_TWILIGHT = {HUMAN_DAY, HUMAN_NIGHT, WOLF_DAY, WOLF_NIGHT, TWILIGHT}; + const std::vector ALL_FORM_AND_DAY_TIMES = {HUMAN_DAY, HUMAN_NIGHT, WOLF_DAY, WOLF_NIGHT, DAY, NIGHT}; + + std::string to_string(const int& formTime) + { + std::string formTimeStr = ""; + if (formTime & HUMAN_DAY) + formTimeStr += " Human_Day"; + if (formTime & HUMAN_NIGHT) + formTimeStr += " Human_Night"; + if (formTime & WOLF_DAY) + formTimeStr += " Wolf_Day"; + if (formTime & WOLF_NIGHT) + formTimeStr += " Wolf_Night"; + if (formTime & TWILIGHT) + formTimeStr += " Twilight"; + return formTimeStr; + } + } // namespace FormTime + + const extern Requirement NO_REQUIREMENT = Requirement{Type::NOTHING, {}}; + const extern Requirement IMPOSSIBLE_REQUIREMENT = Requirement{Type::IMPOSSIBLE, {}}; + + std::string Requirement::to_string() const + { + std::string reqStr = ""; + tphdr::logic::item::Item* item; + Requirement nestedReq; + int count; + int eventIndex; + int macroIndex; + switch (this->_type) + { + case Type::NOTHING: + return "Nothing"; + + case Type::IMPOSSIBLE: + return "Impossible (Please discover an entrance first)"; + + case Type::OR: + for (const auto& arg : this->_args) + { + nestedReq = std::get(arg); + if (nestedReq._type == Type::AND || nestedReq._type == Type::OR) + { + reqStr += "("; + reqStr += nestedReq.to_string(); + reqStr += ")"; + } + else + { + reqStr += nestedReq.to_string(); + } + reqStr += " or "; + } + // pop off the last " or " + for (auto i = 0; i < 4; i++) + { + reqStr.pop_back(); + } + return reqStr; + + case Type::AND: + for (const auto& arg : this->_args) + { + nestedReq = std::get(arg); + if (nestedReq._type == Type::AND || nestedReq._type == Type::OR) + { + reqStr += "("; + reqStr += nestedReq.to_string(); + reqStr += ")"; + } + else + { + reqStr += nestedReq.to_string(); + } + reqStr += " and "; + } + // pop off the last " and " + for (auto i = 0; i < 5; i++) + { + reqStr.pop_back(); + } + return reqStr; + + case Type::ITEM: + item = std::get(this->_args[0]); + return item->GetName(); + + case Type::COUNT: + count = std::get(this->_args[0]); + item = std::get(this->_args[1]); + return "count(" + item->GetName() + ", " + std::to_string(count) + ")"; + + case Type::EVENT: + eventIndex = std::get(this->_args[0]); + return "'Event_" + std::to_string(eventIndex) + "'"; + + case Type::MACRO: + macroIndex = std::get(this->_args[0]); + return "'Macro_" + std::to_string(macroIndex) + "'"; + + case Type::DAY: + return "Day"; + + case Type::NIGHT: + return "Night"; + + case Type::HUMAN_LINK: + return "Human Link"; + + case Type::WOLF_LINK: + return "Wolf Link"; + + case Type::TWILIGHT: + return "Twilight"; + + case Type::GOLDEN_BUGS: + count = std::get(this->_args[0]); + return "golden_bugs(" + std::to_string(count) + ")"; + default: + return reqStr; + } + return reqStr; + } + + Requirement ParseRequirementString(const std::string& reqStr, + tphdr::logic::world::World* world, + const bool& forceLogic /* = false */) + { + Requirement req; + std::string logicStr(reqStr); + // First, we make sure that the expression has no missing or extra parenthesis + // and that the nesting level at the beginning is the same at the end. + // + // Logic expressions are split up via spaces, but we only want to evaluate the parts of + // the expression at the highest nesting level for the string that was passed in. + // (We'll recursively call the function later to evaluate deeper levels.) So we replace + // all the spaces on the highest nesting level with an arbitrarily chosen delimeter that shouldn't appear anywhere + // in a logic statement (in req case: '+'). + int nestingLevel = 1; + constexpr char delimeter = '+'; + for (auto& ch : logicStr) + { + if (ch == '(') + { + nestingLevel++; + } + else if (ch == ')') + { + nestingLevel--; + } + + if (nestingLevel == 1 && ch == ' ') + { + ch = delimeter; + } + } + + // If the nesting level isn't the same as what we started with, then the logic + // expression is invalid. + if (nestingLevel != 1) + { + throw std::runtime_error("Extra or missing parenthesis within expression: \"" + reqStr + "\""); + } + + // Next we split up the expression by the delimeter in the previous step + size_t pos = 0; + std::vector splitLogicStr = {}; + while ((pos = logicStr.find(delimeter)) != std::string::npos) + { + // When parsing setting checks, take the entire expression + // and the three components individually + auto& chBefore = logicStr[pos - 1]; + auto& chAfter = logicStr[pos + 1]; + if (chBefore != '!' && chAfter != '!' && chBefore != '=' && chAfter != '=') + { + splitLogicStr.push_back(logicStr.substr(0, pos)); + logicStr.erase(0, pos + 1); + } + else + { + logicStr.erase(logicStr.begin() + pos); + } + } + splitLogicStr.push_back(logicStr); + + // Once we have the different parts of our expression, we can use the number + // of parts we have to determine what kind of expression it is. + + // If we only have one part... + if (splitLogicStr.size() == 1) + { + std::string argStr = splitLogicStr[0]; + std::ranges::replace(argStr, '_', ' '); + // First, see if we have nothing + if (argStr == "Nothing") + { + req._type = tphdr::logic::requirement::Type::NOTHING; + return req; + } + + // Then Human Link... + if (argStr == "Human Link") + { + req._type = tphdr::logic::requirement::Type::HUMAN_LINK; + return req; + } + + // Then Wolf Link... + if (argStr == "Wolf Link") + { + req._type = tphdr::logic::requirement::Type::WOLF_LINK; + return req; + } + + // Then Twilight... + if (argStr == "Twilight") + { + req._type = tphdr::logic::requirement::Type::TWILIGHT; + return req; + } + + // Then an event... + if (argStr[0] == '\'') + { + req._type = tphdr::logic::requirement::Type::EVENT; + std::string eventName(argStr.begin() + 1, argStr.end() - 1); // Remove quotes + int eventId = world->GetEventIndex(eventName); + + req._args.emplace_back(eventId); + return req; + } + + // NOTE: Checking macros *MUST* come before checking items. Some macros use the exact same name as an item + // and we want the macro to be used in req case instead of just the item + + // Then a macro... + if (world->GetMacroIndex(argStr) != -1) + { + req._type = tphdr::logic::requirement::Type::MACRO; + req._args.emplace_back(world->GetMacroIndex(argStr)); + return req; + } + + // Then an item... + if (world->GetItem(argStr, true) != nullptr) + { + auto item = world->GetItem(argStr); + req._type = tphdr::logic::requirement::Type::ITEM; + req._args.emplace_back(item); + return req; + } + + // Then a setting... + else if (tphdr::utility::str::Contains(argStr, "!=", "==")) + { + bool equalComparison = tphdr::utility::str::Contains(argStr, "=="); + bool notEqualComparison = tphdr::utility::str::Contains(argStr, "!="); + + // Split up the comparison using the second comparison character (which will always be '=') + auto compPos = argStr.rfind('='); + std::string optionName(argStr.begin() + (compPos + 1), argStr.end()); + std::string settingName(argStr.begin(), argStr.begin() + (compPos - 1)); + + // Check using the appropriate comparison function + bool result = false; + if (equalComparison) + { + result = world->Setting(settingName) == optionName.c_str(); + } + else if (notEqualComparison) + { + result = world->Setting(settingName) != optionName.c_str(); + } + + if (result == true) + { + req._type = tphdr::logic::requirement::Type::NOTHING; + } + else + { + req._type = tphdr::logic::requirement::Type::IMPOSSIBLE; + } + return req; + } + // Then a count... + else if (argStr.find("count") != std::string::npos) + { + req._type = tphdr::logic::requirement::Type::COUNT; + // Since a count has two arguments (a number and an item), we have + // to split up the string in the parenthesis into those arguments. + + // Get rid of parenthesis + std::string countArgs(argStr.begin() + argStr.find('(') + 1, argStr.end() - 1); + // Erase any spaces + // countArgs.erase(std::remove(countArgs.begin(), countArgs.end(), ' '), countArgs.end()); + + // Split up the arguments + pos = 0; + splitLogicStr = {}; + while ((pos = countArgs.find(", ")) != std::string::npos) + { + splitLogicStr.push_back(countArgs.substr(0, pos)); + countArgs.erase(0, pos + 2); + } + splitLogicStr.push_back(countArgs); + + // Get the arguments + auto& itemName = splitLogicStr[0]; + int count = std::stoi(splitLogicStr[1]); + auto item = world->GetItem(itemName); + req._args.emplace_back(count); + req._args.emplace_back(item); + return req; + } + + // Then Day... + if (argStr == "Day") + { + req._type = tphdr::logic::requirement::Type::DAY; + return req; + } + + // Then Night... + if (argStr == "Night") + { + req._type = tphdr::logic::requirement::Type::NIGHT; + return req; + } + + // And finally a health check + // else if (argStr.find("health") != std::string::npos) + // { + // req._type = tphdr::logic::requirement::Type::HEALTH; + // std::string numHeartsStr(argStr.begin() + argStr.find('(') + 1, argStr.end() - 1); + // int numHearts = std::stoi(numHeartsStr); + // req._args.emplace_back(numHearts); + // return req; + // } + + // Check Impossible down here since it's very unlikely + else if (argStr == "Impossible") + { + req._type = tphdr::logic::requirement::Type::IMPOSSIBLE; + return req; + } + + // Check golden bugs last since it's least likely + else if (argStr.find("golden bugs") != std::string::npos) + { + req._type = tphdr::logic::requirement::Type::GOLDEN_BUGS; + // Get rid of parenthesis + std::string countArg(argStr.begin() + argStr.find('(') + 1, argStr.end() - 1); + int count = std::stoi(countArg); + req._args.emplace_back(count); + return req; + } + + throw std::runtime_error("Unrecognized logic symbol: \"" + reqStr + "\""); + } + + // If our expression has two parts, then we don't know what that is + if (splitLogicStr.size() == 2) + { + throw std::runtime_error("Unrecognized 2 part expression: " + reqStr); + } + + // If we have more than two parts to our expression, then we have either "and" + // or "or". + bool andType = tphdr::utility::container::ElementInContainer(splitLogicStr, "and"); + bool orType = tphdr::utility::container::ElementInContainer(splitLogicStr, "or"); + + // If we have both of them, there's a problem with the logic expression + if (andType && orType) + { + throw std::runtime_error("\"and\" & \"or\" in same nesting level when parsing \"" + reqStr + "\""); + } + + if (andType || orType) + { + // Set the appropriate type + if (andType) + { + req._type = tphdr::logic::requirement::Type::AND; + } + else + { + req._type = tphdr::logic::requirement::Type::OR; + } + + // Once we know the type, we can erase the "and"s or "or"s and are left with just the deeper + // expressions to be logically operated on. + tphdr::utility::container::FilterAndEraseFromVector(splitLogicStr, + [](const std::string& arg) + { return arg == "and" || arg == "or"; }); + + // For each deeper expression, parse it and add it as an argument to the + // Requirement + for (auto& reqStr : splitLogicStr) + { + // Get rid of parenthesis surrounding each deeper expression + if (reqStr[0] == '(') + { + reqStr = reqStr.substr(1, reqStr.length() - 2); + } + req._args.push_back(ParseRequirementString(reqStr, world, forceLogic)); + } + } + + if (req._type != tphdr::logic::requirement::Type::INVALID) + { + return req; + } + // If we've reached req point, we weren't able to determine a logical operator within the expression + throw std::runtime_error("Could not determine logical operator type from expression: \"" + reqStr + "\""); + return req; + } + + bool EvaluateRequirementAtFormTime(const tphdr::logic::requirement::Requirement& req, + tphdr::logic::search::Search* search, + const int& formTime, + tphdr::logic::world::World* world) + { + tphdr::logic::item::Item* item; + int count; + int eventIndex; + int macroIndex; + switch (req._type) + { + case Type::NOTHING: + return true; + + case Type::IMPOSSIBLE: + return false; + + case Type::OR: + return std::any_of( + req._args.begin(), + req._args.end(), + [&](const auto& arg) + { return EvaluateRequirementAtFormTime(std::get(arg), search, formTime, world); }); + + case Type::AND: + return std::all_of( + req._args.begin(), + req._args.end(), + [&](const auto& arg) + { return EvaluateRequirementAtFormTime(std::get(arg), search, formTime, world); }); + + case Type::ITEM: + item = std::get(req._args[0]); + return search->_ownedItems.contains(item); + + case Type::COUNT: + count = std::get(req._args[0]); + item = std::get(req._args[1]); + return search->_ownedItems.count(item) >= count; + + case Type::EVENT: + eventIndex = std::get(req._args[0]); + return search->_ownedEvents.contains(eventIndex); + + case Type::MACRO: + macroIndex = std::get(req._args[0]); + return EvaluateRequirementAtFormTime(world->GetMacro(macroIndex), search, formTime, world); + + case Type::DAY: + return formTime & FormTime::DAY; + + case Type::NIGHT: + return formTime & FormTime::NIGHT; + + case Type::HUMAN_LINK: + return formTime & FormTime::HUMAN; + + case Type::WOLF_LINK: + return formTime & FormTime::WOLF; + + case Type::TWILIGHT: + return formTime & FormTime::TWILIGHT; + + case Type::GOLDEN_BUGS: + count = std::get(req._args[0]); + return std::count_if(search->_ownedItems.begin(), + search->_ownedItems.end(), + [](const auto& item) { return item->IsGoldenBug(); }) >= count; + default: + return false; + } + return false; + } + + EvalSuccess EvaluateEventRequirement(tphdr::logic::search::Search* search, tphdr::logic::area::EventAccess* event) + { + auto& formTime = search->_areaFormTime[event->GetArea()]; + if (EvaluateRequirementAtFormTime(event->GetRequirement(), search, formTime, event->GetArea()->GetWorld())) + { + return EvalSuccess::COMPLETE; + } + return EvalSuccess::NONE; + } + + EvalSuccess EvaluateExitRequirement(tphdr::logic::search::Search* search, tphdr::logic::entrance::Entrance* exit) + { + // Some exits in the middle of entrance shuffling will not have a connected area. Ignore these + if (exit->GetConnectedArea() == nullptr) + { + return EvalSuccess::UNNECESSARY; + } + + // If the exit is currently disabled, don't try it + if (exit->IsDisabled()) + { + return EvalSuccess::NONE; + } + + auto& exitFormTimeCache = exit->GetWorld()->GetExitTimeFormCache(); + auto parentArea = exit->GetParentArea(); + auto connectedArea = exit->GetConnectedArea(); + auto parentAreaFormTime = search->_areaFormTime[parentArea]; + auto& connectedAreaFormTime = search->_areaFormTime[connectedArea]; + auto potentialExitFormTimes = (exitFormTimeCache.contains(exit) ? exitFormTimeCache[exit] : FormTime::ALL); + + // LOG_TO_DEBUG("Trying " + connectedArea->GetName()); + + auto connectedAreaTwilightCleared = connectedArea->TwilightCleared(search); + if (!connectedAreaTwilightCleared) + { + // LOG_TO_DEBUG("Added Twilight"); + parentAreaFormTime |= FormTime::TWILIGHT; + potentialExitFormTimes |= FormTime::TWILIGHT; + } + + // Calculate the potential form times that we could spread to the connected area. These are the form times + // which the connected area does not have that the parent area has, and that the exit can potentially pass on + // to the connected area + auto potentialFormTimeSpread = ~connectedAreaFormTime & (parentAreaFormTime & potentialExitFormTimes); + + // LOG_TO_DEBUG("Potential spreads: " + FormTime::to_string(potentialFormTimeSpread)); + + // If there's no potential to spread FormTime, then return early + if (potentialFormTimeSpread == FormTime::NONE) + { + // LOG_TO_DEBUG("No potential formtime spread"); + return EvalSuccess::NONE; + } + + // Check each form time individually and spread the ones which succeed. If any of them pass, set the evaluation success + // to partial. + auto evalSuccess = EvalSuccess::NONE; + const auto& formTimes = connectedAreaTwilightCleared ? FormTime::ALL_FORM_TIMES : FormTime::ALL_FORM_TIMES_AND_TWILIGHT; + for (const auto& formTime : formTimes) + { + if (formTime & potentialFormTimeSpread) + { + if (EvaluateRequirementAtFormTime(exit->GetRequirement(), search, formTime, exit->GetWorld())) + { + if (!connectedAreaTwilightCleared) + { + if (~connectedAreaFormTime & FormTime::TWILIGHT) + { + // LOG_TO_DEBUG("Spread Twilight to " + connectedArea->GetName()); + connectedAreaFormTime |= FormTime::TWILIGHT; + evalSuccess = EvalSuccess::PARTIAL; + } + } + else if (formTime != FormTime::TWILIGHT) + { + // LOG_TO_DEBUG("Spread" + FormTime::to_string(formTime) + " to " + connectedArea->GetName()); + connectedAreaFormTime |= formTime; + evalSuccess = EvalSuccess::PARTIAL; + } + } + } + else + { + // LOG_TO_DEBUG(FormTime::to_string(formTime) + " is not a potential timespread."); + } + } + + if (evalSuccess != EvalSuccess::NONE) + { + search->ExpandFormTimes(connectedArea); + // If the connected area now has complete access, then we mark a complete success instead of just a partial one + } + + if (connectedAreaTwilightCleared && ((connectedAreaFormTime & potentialExitFormTimes) == potentialExitFormTimes)) + { + evalSuccess = EvalSuccess::COMPLETE; + } + + return evalSuccess; + } + + EvalSuccess EvaluateLocationRequirement(tphdr::logic::search::Search* search, tphdr::logic::area::LocationAccess* locAccess) + { + auto& formTime = search->_areaFormTime[locAccess->GetArea()]; + if (EvaluateRequirementAtFormTime(locAccess->GetRequirement(), search, formTime, locAccess->GetArea()->GetWorld())) + { + return EvalSuccess::COMPLETE; + } + return EvalSuccess::NONE; + } +} // namespace tphdr::logic::requirement diff --git a/src/dusk/randomizer/logic/requirement.hpp b/src/dusk/randomizer/logic/requirement.hpp new file mode 100644 index 0000000000..9838f137c4 --- /dev/null +++ b/src/dusk/randomizer/logic/requirement.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include +#include + +// Forward declarations +namespace tphdr::logic::item +{ + class Item; +} + +namespace tphdr::logic::entrance +{ + class Entrance; +} + +namespace tphdr::logic::area +{ + class EventAccess; + class LocationAccess; +} // namespace tphdr::logic::area + +namespace tphdr::logic::world +{ + class World; +} + +namespace tphdr::logic::search +{ + class Search; +} + +namespace tphdr::logic::requirement +{ + enum class Type + { + INVALID, + NOTHING, + IMPOSSIBLE, + OR, + AND, + ITEM, + COUNT, + EVENT, + MACRO, + DAY, + NIGHT, + HUMAN_LINK, + WOLF_LINK, + TWILIGHT, + GOLDEN_BUGS, + }; + + enum class EvalSuccess + { + NONE, + PARTIAL, + COMPLETE, + UNNECESSARY, + }; + + // FormTime is a set of flags that cover all the possible cases of human-wolf/day-night combinations that are needed + // for logic to work + namespace FormTime + { + enum + { + NONE = 0b0000, + HUMAN_DAY = 0b0001, + HUMAN_NIGHT = 0b0010, + WOLF_DAY = 0b0100, + WOLF_NIGHT = 0b1000, + HUMAN = HUMAN_DAY | HUMAN_NIGHT, + WOLF = WOLF_DAY | WOLF_NIGHT, + DAY = HUMAN_DAY | WOLF_DAY, + NIGHT = HUMAN_NIGHT | WOLF_NIGHT, + ALL = 0b1111, + TWILIGHT = 0b10000, + }; + + extern const std::vector ALL_FORM_TIMES; + extern const std::vector ALL_FORM_TIMES_AND_TWILIGHT; + extern const std::vector ALL_FORM_AND_DAY_TIMES; + + std::string to_string(const int& formTime); + }; // namespace FormTime + + struct Requirement; + struct Requirement + { + using Argument = std::variant; + Type _type = Type::INVALID; + std::vector _args; + + std::string to_string() const; + }; + + Requirement ParseRequirementString(const std::string& reqStr, + tphdr::logic::world::World* world, + const bool& forceLogic = false); + + bool EvaluateRequirementAtFormTime(const tphdr::logic::requirement::Requirement& req, + tphdr::logic::search::Search* search, + const int& formTime, + tphdr::logic::world::World*); + EvalSuccess EvaluateEventRequirement(tphdr::logic::search::Search* search, tphdr::logic::area::EventAccess* event); + EvalSuccess EvaluateExitRequirement(tphdr::logic::search::Search* search, tphdr::logic::entrance::Entrance* exit); + EvalSuccess EvaluateLocationRequirement(tphdr::logic::search::Search* search, + tphdr::logic::area::LocationAccess* locAccess); + + const extern Requirement NO_REQUIREMENT; + const extern Requirement IMPOSSIBLE_REQUIREMENT; +} // namespace tphdr::logic::requirement \ No newline at end of file diff --git a/src/dusk/randomizer/logic/search.cpp b/src/dusk/randomizer/logic/search.cpp new file mode 100644 index 0000000000..7c3b483038 --- /dev/null +++ b/src/dusk/randomizer/logic/search.cpp @@ -0,0 +1,650 @@ +#include "search.hpp" + +#include "world.hpp" +#include "../utility/general.hpp" +#include "../utility/platform.hpp" + +#include + +namespace tphdr::logic::search +{ + Search::Search(const SearchMode& searchMode, + tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items /* = {} */, + const int& worldToSearch /* = -1 */): + _searchMode(searchMode), _worlds(worlds) + { + // Set the items we should already own + this->_ownedItems.insert(items.begin(), items.end()); + + // Add starting inventory items for each world + for (const auto& world : *(this->_worlds)) + { + if (worldToSearch == -1 || world->GetID() == worldToSearch) + { + const auto& startingInventory = world->GetStartingItemPool(); + this->_ownedItems.insert(startingInventory.begin(), startingInventory.end()); + } + } + + // Set search starting properties and add each world's root exits to _exitsToTry + for (const auto& world : *(this->_worlds)) + { + if (worldToSearch == -1 || world->GetID() == worldToSearch) + { + auto root = world->GetRootArea(); + this->_visitedAreas.emplace(root); + world->SetSearchStartingProperties(this); + for (const auto& exit : root->GetExits()) + { // Don't add target exits if we're doing a sphere zero search + if (!exit->IsDisabled() && (this->_searchMode != SearchMode::SPHERE_ZERO || !exit->IsTarget())) + { + this->_exitsToTry.emplace_back(exit); + } + } + } + } + } + + void Search::SearchWorlds() + { + // Get all locations which fit criteria to test on each iteration + std::list itemLocations = {}; + for (const auto& world : *(this->_worlds)) + { + for (const auto& [areaName, area] : world->GetAreaTable()) + { + for (const auto& locAccess : area->GetLocations()) + { + // Only add locations that aren't empty, unless we're searching with one of the modes below + if (!locAccess->GetLocation()->IsEmpty() || + tphdr::utility::general::IsAnyOf(this->_searchMode, + SearchMode::ACCESSIBLE_LOCATIONS, + SearchMode::ALL_LOCATIONS_REACHABLE, + SearchMode::SPHERE_ZERO, + SearchMode::TRACKER_SPHERES)) + { + itemLocations.emplace_back(locAccess); + } + } + } + } + + // Main Searching Loop + // Keep iterating while new things are being found, but if the search is beatable and we're either generating the + // playthrough or checking for beatability, exit early. + this->_newThingsFound = true; + while ( + this->_newThingsFound && + !(this->_isBeatable && + tphdr::utility::general::IsAnyOf(this->_searchMode, SearchMode::GENERATE_PLAYTHROUGH, SearchMode::GAME_BEATABLE))) + { + // Keep track of making logical progress. We want to keep iterating as long as we're finding new things on each + // iteration + this->_newThingsFound = false; + + // Add an empty sphere if we're generating the playthrough or tracker spheres + if (tphdr::utility::general::IsAnyOf(this->_searchMode, + SearchMode::GENERATE_PLAYTHROUGH, + SearchMode::TRACKER_SPHERES)) + { + this->_playthroughSpheres.push_back({}); + this->_entranceSpheres.push_back({}); + } + + // Process Events and Exits at least once. If we're calculating spheres, then keep repeating these until nothing + // new is found. + do + { + this->_newThingsFound = false; + this->ProcessEvents(); + this->ProcessExits(); + } while (this->_newThingsFound && tphdr::utility::general::IsAnyOf(this->_searchMode, + SearchMode::GENERATE_PLAYTHROUGH, + SearchMode::TRACKER_SPHERES)); + + this->ProcessLocations(itemLocations); + this->_sphereNum += 1; + } + } + + void Search::ProcessEvents() + { + for (const auto& event : this->_eventsToTry) + { + // Ignore the event if we've already found it, or we're not searching its world at the moment + if (this->_ownedEvents.contains(event->GetEventIndex()) || + (this->_worldToSearch != -1 && event->GetArea()->GetWorld()->GetID() != this->_worldToSearch)) + { + continue; + } + + if (tphdr::logic::requirement::EvaluateEventRequirement(this, event) == + tphdr::logic::requirement::EvalSuccess::COMPLETE) + { + this->_newThingsFound = true; + this->_ownedEvents.insert(event->GetEventIndex()); + } + } + } + + void Search::ProcessExits() + { + for (const auto& exit : this->_exitsToTry) + { + // Ignore the exit if we've already completed it, or we're not searching its world at the moment + if (this->_successfulExits.contains(exit) || + (this->_worldToSearch != -1 && this->_worldToSearch != exit->GetWorld()->GetID())) + { + continue; + } + + // If the exit is unnecessary, we'll just consider it successful and move on + auto evalSuccess = tphdr::logic::requirement::EvaluateExitRequirement(this, exit); + if (evalSuccess == tphdr::logic::requirement::EvalSuccess::UNNECESSARY) + { + this->_successfulExits.insert(exit); + } + else if (tphdr::utility::general::IsAnyOf(evalSuccess, + tphdr::logic::requirement::EvalSuccess::COMPLETE, + tphdr::logic::requirement::EvalSuccess::PARTIAL)) + { + this->AddExitToEntranceSpheres(exit); + if (evalSuccess == tphdr::logic::requirement::EvalSuccess::COMPLETE) + { + this->_successfulExits.insert(exit); + } + this->_newThingsFound = true; + + // If this exit's connected region hasn't been explored yet, then explore it + if (!this->_visitedAreas.contains(exit->GetConnectedArea())) + { + this->_visitedAreas.insert(exit->GetConnectedArea()); + this->Explore(exit->GetConnectedArea()); + } + } + } + } + + void Search::ProcessLocations(std::list& itemLocations) + { + std::list accessibleThisIteration = {}; + // Loop through all possible item locations for this search + for (const auto& locAccess : itemLocations) + { + auto location = locAccess->GetLocation(); + auto world = location->GetWorld(); + + // If we've already visited this location, or have *not* visited this area, or aren't searching this world, + // then ignore the location this time + if (this->_visitedLocations.contains(location) || !this->_visitedAreas.contains(locAccess->GetArea()) || + (this->_worldToSearch != -1 && world->GetID() != this->_worldToSearch)) + { + continue; + } + + // If the location's requirement is met + if (tphdr::logic::requirement::EvaluateLocationRequirement(this, locAccess) == + tphdr::logic::requirement::EvalSuccess::COMPLETE) + { + this->_visitedLocations.insert(location); + this->_newThingsFound = true; + // If we're calculating spheres, then process this location later for accurate sphere calculation. Otherwise + // process it now for slightly faster searching + if (tphdr::utility::general::IsAnyOf(this->_searchMode, + SearchMode::GENERATE_PLAYTHROUGH, + SearchMode::TRACKER_SPHERES)) + { + accessibleThisIteration.push_back(location); + } + else + { + this->ProcessLocation(location); + } + } + } + + for (const auto& location : accessibleThisIteration) + { + this->ProcessLocation(location); + if (this->_isBeatable) + { + return; + } + } + } + + void Search::ProcessLocation(tphdr::logic::location::Location* location) + { + // Don't return if we aren't collecting items + if (!this->_collectItems) + { + return; + } + + // Add the tracked item if we're doing tracker sphere tracking + if (this->_searchMode == SearchMode::TRACKER_SPHERES) + { + this->_ownedItems.insert(location->GetTrackedItem()); + } + // Otherwise add the current item as usual + else + { + this->_ownedItems.insert(location->GetCurrentItem()); + } + + // If we just added the shadow crystal, expand timeforms for all areas we've visited so far + if (location->GetCurrentItem()->IsShadowCrystal()) + { + for (auto& area : this->_visitedAreas) + { + if (area->GetWorld()->GetID() == location->GetWorld()->GetID()) + { + this->ExpandFormTimes(area); + } + } + } + + // If we're generating spheres and the location has a major item, add the location to the last sphere + if (this->_searchMode == SearchMode::TRACKER_SPHERES || + (this->_searchMode == SearchMode::GENERATE_PLAYTHROUGH && location->GetCurrentItem()->IsMajor())) + { + this->_playthroughSpheres.back().push_back(location); + } + + // If we're generating the playthrough or just checking for beatability, then we can stop searching early if we've + // found all world's game winning items + if (tphdr::utility::general::IsAnyOf(this->_searchMode, SearchMode::GENERATE_PLAYTHROUGH, SearchMode::GAME_BEATABLE) && + location->GetCurrentItem()->IsGameWinningItem()) + { + if (std::count_if(this->_ownedItems.begin(), + this->_ownedItems.end(), + [](const auto& item) { return item->IsGameWinningItem(); }) == this->_worlds->size()) + { + if (this->_searchMode == SearchMode::GENERATE_PLAYTHROUGH) + { + auto& lastSphere = this->_playthroughSpheres.back(); + lastSphere.erase( + std::remove_if(lastSphere.begin(), + lastSphere.end(), + [](const auto& location) { return !location->GetCurrentItem()->IsGameWinningItem(); }), + lastSphere.end()); + } + this->_isBeatable = true; + } + } + } + + void Search::Explore(tphdr::logic::area::Area* area) + { + for (const auto& event : area->GetEvents()) + { + this->_eventsToTry.push_back(event); + } + + for (const auto& exit : area->GetExits()) + { + auto evalSuccess = tphdr::logic::requirement::EvaluateExitRequirement(this, exit); + switch (evalSuccess) + { + case tphdr::logic::requirement::EvalSuccess::COMPLETE: + this->_successfulExits.insert(exit); + this->AddExitToEntranceSpheres(exit); + if (!this->_visitedAreas.contains(exit->GetConnectedArea())) + { + this->_visitedAreas.insert(exit->GetConnectedArea()); + this->Explore(exit->GetConnectedArea()); + } + case tphdr::logic::requirement::EvalSuccess::PARTIAL: + this->_exitsToTry.push_back(exit); + this->AddExitToEntranceSpheres(exit); + if (!this->_visitedAreas.contains(exit->GetConnectedArea())) + { + this->_visitedAreas.insert(exit->GetConnectedArea()); + this->Explore(exit->GetConnectedArea()); + } + case tphdr::logic::requirement::EvalSuccess::NONE: + this->_exitsToTry.push_back(exit); + case tphdr::logic::requirement::EvalSuccess::UNNECESSARY: + this->_foundDisconnectedExit = true; + } + } + } + + void Search::ExpandFormTimes(tphdr::logic::area::Area* area) + { + using namespace tphdr::logic::requirement; + + auto& areaFormTime = this->_areaFormTime[area]; + auto twilightCleared = area->TwilightCleared(this); + + auto shadowCrystal = area->GetWorld()->GetShadowCrystal(); + // Check if we can add additional form times to the area + if (area->CanChangeTime() && area->CanTransform() && this->_ownedItems.contains(shadowCrystal) && twilightCleared) + { + // LOG_TO_DEBUG("Spread All to " + area->GetName()); + areaFormTime |= FormTime::ALL; + } + // This might look backwards at first glance, but spreading formtime by the form spreads both day and night for the form + else if (area->CanChangeTime() && twilightCleared) + { + if (areaFormTime & FormTime::WOLF) + { + // LOG_TO_DEBUG("Spread Day/Night to " + area->GetName()); + areaFormTime |= FormTime::WOLF; + } + else if (areaFormTime & FormTime::HUMAN) + { + // LOG_TO_DEBUG("Spread Day/Night to " + area->GetName()); + areaFormTime |= FormTime::HUMAN; + } + } + // Same as above except with spreading time spreads the form + else if (area->CanTransform() && this->_ownedItems.contains(shadowCrystal) && twilightCleared) + { + if (areaFormTime & FormTime::NIGHT) + { + // LOG_TO_DEBUG("Spread Human/Wolf to " + area->GetName()); + areaFormTime |= FormTime::NIGHT; + } + + if (areaFormTime & FormTime::DAY) + { + // LOG_TO_DEBUG("Spread Human/Wolf to " + area->GetName()); + areaFormTime |= FormTime::DAY; + } + } + } + + void Search::AddExitToEntranceSpheres(tphdr::logic::entrance::Entrance* exit) + { + if (tphdr::utility::general::IsAnyOf(this->_searchMode, + SearchMode::GENERATE_PLAYTHROUGH, + SearchMode::TRACKER_SPHERES) && + exit->IsShuffled()) + { + if (!this->_playthroughEntrances.contains(exit)) + { + this->_entranceSpheres.back().push_back(exit); + this->_playthroughEntrances.insert(exit); + if (!exit->IsDecoupled() && exit->GetReplaces()->GetReverse()) + { + this->_playthroughEntrances.insert(exit->GetReplaces()->GetReverse()); + } + } + } + } + + void Search::RemoveEmptySpheres() + { + // Get rid of any empty spheres in both the item playthrough and entrance playthrough + // based only on if the item playthrough has empty spheres. Both the playthroughs + // will have the same number of spheres, so we only need to conditionally + // check one of them. + auto itemItr = this->_playthroughSpheres.begin(); + auto entranceItr = this->_entranceSpheres.begin(); + while (itemItr != this->_playthroughSpheres.end()) + { + if (itemItr->empty() && entranceItr->empty()) + { + itemItr = this->_playthroughSpheres.erase(itemItr); + entranceItr = this->_entranceSpheres.erase(entranceItr); + } + else + { + itemItr++; // Only incremement if we don't erase + entranceItr++; + } + } + } + + void Search::DumpWorldGraph(const int& worldNum /* = 0 */) + { + auto& world = this->_worlds->at(worldNum); + std::cout << "Now dumping search graph for world " << worldNum << std::endl; + std::ofstream worldGraph; + std::string filepath = "World.gv"; + worldGraph.open(filepath); + worldGraph << "digraph {\n\tcenter=true;\n"; + for (const auto& [areaName, area] : world->GetAreaTable()) + { + auto color = this->_visitedAreas.contains(area.get()) ? "black" : "red"; + std::string formTimeStr = ":
"; + auto& areaFormTime = this->_areaFormTime[area.get()]; + if (areaFormTime & tphdr::logic::requirement::FormTime::HUMAN) + { + formTimeStr += " Human"; + } + if (areaFormTime & tphdr::logic::requirement::FormTime::WOLF) + { + formTimeStr += " Wolf"; + } + if (areaFormTime & tphdr::logic::requirement::FormTime::DAY) + { + formTimeStr += " Day"; + } + if (areaFormTime & tphdr::logic::requirement::FormTime::NIGHT) + { + formTimeStr += " Night"; + } + if (areaFormTime & tphdr::logic::requirement::FormTime::TWILIGHT) + { + formTimeStr += " Twilight"; + } + + worldGraph << "\t\"" << areaName << "\"[label=<" << areaName << formTimeStr << "> shape=\"plain\" fontcolor=\"" + << color << "\"];\n"; + + // Make edge connections defined by events + for (const auto& event : area->GetEvents()) + { + auto color = this->_ownedEvents.contains(event->GetEventIndex()) ? "blue" : "red"; + auto eventName = world->GetEventName(event->GetEventIndex()); + worldGraph << "\t\"" << eventName << "\"[label=<" << eventName << "> shape=\"plain\" fontcolor=\"" << color + << "\"];"; + worldGraph << "\t\"" << areaName << "\" -> \"" << eventName << "\"[dir=forward color=\"" << color << "\"]"; + } + + // Make edge connections defined by exits + for (const auto& exit : area->GetExits()) + { + if (exit->GetConnectedArea()) + { + auto color = this->_successfulExits.contains(exit) ? "black" : "red"; + worldGraph << "\t\"" << areaName << "\" -> \"" << exit->GetConnectedArea()->GetName() + << "\"[dir=forward color=\"" << color << "\"]"; + } + } + + // Make edge connections between areas and their locations + for (const auto& locAccess : area->GetLocations()) + { + auto location = locAccess->GetLocation(); + auto color = this->_visitedLocations.contains(location) ? "black" : "red"; + worldGraph << "\t\"" << location->GetName() << "\"[label=<" << location->GetName() << ":
" + << location->GetCurrentItem()->GetName() << "> shape=\"plain\" fontcolor=\"" << color << "\"];"; + worldGraph << "\t\"" << areaName << "\" -> \"" << location->GetName() << "\"[dir=forward color=\"" << color + << "\"]"; + } + } + + worldGraph << "}"; + worldGraph.close(); + } + + std::optional VerifyLogic(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items /* = {} */) + { + // Run an all locations reachable search + auto search = Search::AllLocationsReachable(worlds, items); + search.SearchWorlds(); + + for (const auto& world : *worlds) + { + // If all locations should be reachable, make sure they're all reachable + if (world->Setting("Logic Rules") == "All Locations Reachable") + { + auto numlocationsReached = + std::count_if(search._visitedLocations.begin(), + search._visitedLocations.end(), + [&](const auto& location) { return location->GetWorld() == world.get(); }); + auto allLocations = world->GetAllLocations(/*includeNonItemLocations = */ true); + + if (numlocationsReached != allLocations.size()) + { + std::string errorMsg = "Not all locations reachable! Missing locations:\n"; + // Gather all the missing locations + std::vector unreachedLocations = {}; + for (const auto& location : allLocations) + { + if (!search._visitedLocations.contains(location)) + { + unreachedLocations.push_back(location); + } + } + // Only print the first 5 so we don't clog the error message + for (auto i = 0; i < unreachedLocations.size(); i++) + { + errorMsg += "- " + unreachedLocations[i]->GetName() + "\n"; + if (i == 4 && i != unreachedLocations.size()) + { + errorMsg += "(" + std::to_string(unreachedLocations.size() - i) + " more)"; + break; + } + } + return errorMsg; + } + } + } + + return std::nullopt; + } + + void GeneratePlaythrough(tphdr::logic::world::WorldPool* worlds) + { + LOG_TO_DEBUG("Generating Playthrough"); + // Generate Initial Playthrough + auto playthroughSearch = Search::Playthrough(worlds); + playthroughSearch.SearchWorlds(); + + auto& playthroughSpheres = playthroughSearch._playthroughSpheres; + + // Keep track of all locations we temporaily take items away from so we can give them back after playthrough calculation + std::unordered_map tempEmptyLocations = {}; + // Keep track of all the locations that appear in the playthrough + std::unordered_set playthroughLocationsSet = {}; + for (const auto& sphere : playthroughSpheres) + { + for (const auto& location : sphere) + { + playthroughLocationsSet.insert(location); + } + } + + // Remove all items from locations that are not part of the playthrough set + for (const auto& world : *worlds) + { + for (const auto& location : world->GetAllLocations()) + { + if (!playthroughLocationsSet.contains(location)) + { + tempEmptyLocations[location] = location->GetCurrentItem(); + location->RemoveCurrentItem(); + } + } + } + + tphdr::utility::platform::Log("Paring down playthrough"); + // Pare down the playthrough in reverse order so we're paring it down from highest to lowest sphere. + // This way, lower sphere items will be prioritized for the playthrough + playthroughSpheres.reverse(); + for (const auto& sphere : playthroughSpheres) + { + for (auto& location : sphere) + { + auto itemAtLocation = location->GetCurrentItem(); + location->RemoveCurrentItem(); + + // If the game is beatable, temporarily take this item away and erase the location from the playthrough + // locations + if (GameBeatable(worlds)) + { + tempEmptyLocations[location] = itemAtLocation; + playthroughLocationsSet.erase(location); + } + else + { + location->SetCurrentItem(itemAtLocation); + } + } + } + + // Generate a new playthrough search incase some spheres were flattened by the previous generation having access + // to extra items + auto newSearch = Search::Playthrough(worlds); + newSearch.SearchWorlds(); + + // Now do the same process for entrances to pare down the entrance playthrough + auto& entranceSpheres = newSearch._entranceSpheres; + std::unordered_map nonRequiredEntrances = {}; + + for (auto& sphere : entranceSpheres) + { + auto sphereCopy = sphere; + for (const auto& entrance : sphereCopy) + { + auto connectedArea = entrance->Disconnect(); + if (GameBeatable(worlds)) + { + // If the game is still beatable then this entrance is not required + sphere.erase(std::remove(sphere.begin(), sphere.end(), entrance), sphere.end()); + nonRequiredEntrances[entrance] = connectedArea; + } + else + { + // If the entrance is required, reconnect it + entrance->Connect(connectedArea); + } + } + } + + // Reconnect all non-required entrances + for (auto& [entrance, connectedArea] : nonRequiredEntrances) + { + entrance->Connect(connectedArea); + } + + // Give items back their locations + for (auto& [location, item] : tempEmptyLocations) + { + location->SetCurrentItem(item); + } + + // Erase all locations not in the playthrough locations set + for (auto& sphere : newSearch._playthroughSpheres) + { + auto sphereCopy = sphere; + for (const auto& location : sphereCopy) + { + if (!playthroughLocationsSet.contains(location)) + { + sphere.erase(std::remove(sphere.begin(), sphere.end(), location), sphere.end()); + } + } + } + + // Remove any empty spheres + newSearch.RemoveEmptySpheres(); + + worlds->at(0)->SetPlaythroughSpheres(newSearch._playthroughSpheres); + worlds->at(0)->SetEntranceSpheres(newSearch._entranceSpheres); + } + + bool GameBeatable(tphdr::logic::world::WorldPool* worlds, const tphdr::logic::item_pool::ItemPool& items /* = {} */) + { + auto search = Search::Beatable(worlds, items); + search.SearchWorlds(); + return search._isBeatable; + } + +} // namespace tphdr::logic::search diff --git a/src/dusk/randomizer/logic/search.hpp b/src/dusk/randomizer/logic/search.hpp new file mode 100644 index 0000000000..1ee247c23d --- /dev/null +++ b/src/dusk/randomizer/logic/search.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include "item_pool.hpp" +#include "../utility/log.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// Forward Declarations (we have a lot here) +namespace tphdr::logic::world +{ + class World; + using WorldPool = std::vector>; +} // namespace tphdr::logic::world + +namespace tphdr::logic::item +{ + class Item; +} + +namespace tphdr::logic::location +{ + class Location; +} + +namespace tphdr::logic::area +{ + class EventAccess; + class LocationAccess; + class Area; +} // namespace tphdr::logic::area + +namespace tphdr::logic::entrance +{ + class Entrance; +} + +namespace tphdr::logic::search +{ + enum class SearchMode + { + ACCESSIBLE_LOCATIONS, + GAME_BEATABLE, + ALL_LOCATIONS_REACHABLE, + GENERATE_PLAYTHROUGH, + SPHERE_ZERO, + TRACKER_SPHERES + }; + + class Search + { + public: + Search(const SearchMode& searchMode, + tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}, + const int& worldToSearch = -1); + + static auto Accessible(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}, + const int& worldToSearch = -1) + { + return Search(SearchMode::ACCESSIBLE_LOCATIONS, worlds, items, worldToSearch); + } + + static auto AllLocationsReachable(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}, + const int& worldToSearch = -1) + { + return Search(SearchMode::ALL_LOCATIONS_REACHABLE, worlds, items, worldToSearch); + } + + static auto Playthrough(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}, + const int& worldToSearch = -1) + { + return Search(SearchMode::GENERATE_PLAYTHROUGH, worlds, items, worldToSearch); + } + + static auto Beatable(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}, + const int& worldToSearch = -1) + { + return Search(SearchMode::GAME_BEATABLE, worlds, items, worldToSearch); + } + + static auto SphereZero(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}, + const int& worldToSearch = -1) + { + return Search(SearchMode::SPHERE_ZERO, worlds, items, worldToSearch); + } + + void SearchWorlds(); + + /** + * @brief Loop through and see if there are any events that are now accessible. Add them to the ownedEvents list if + * they are. + * + */ + void ProcessEvents(); + void ProcessExits(); + void ProcessLocations(std::list& itemLocations); + void ProcessLocation(tphdr::logic::location::Location* location); + void Explore(tphdr::logic::area::Area* area); + void ExpandFormTimes(tphdr::logic::area::Area* area); + + void AddExitToEntranceSpheres(tphdr::logic::entrance::Entrance*); + void RemoveEmptySpheres(); + + /** + * @brief Will dump a file which can be turned into a visual graph using graphviz + * https://graphviz.org/download/ + * Use this command to generate the graph: "dot -Tsvg -o world.svg" + * Then, open world.svg in a browser and CTRL + F to find the area of interest + */ + void DumpWorldGraph(const int& world = 0); + + SearchMode _searchMode; + tphdr::logic::world::WorldPool* _worlds; + int _worldToSearch = -1; + + // Search variables + int _sphereNum = 0; + bool _newThingsFound = true; + bool _isBeatable = false; + bool _collectItems = true; + std::unordered_set _ownedEvents; + std::unordered_multiset _ownedItems; + + std::list _eventsToTry; + std::list _exitsToTry; + std::unordered_set _visitedLocations; + std::unordered_set _visitedAreas; + std::unordered_set _successfulExits; + std::unordered_set _playthroughEntrances; + bool _foundDisconnectedExit = false; + + std::list> _playthroughSpheres; + std::list> _entranceSpheres; + + std::unordered_map _areaFormTime; + }; + + /** + * @brief Verifies that necessary logic for all worlds is satisfied. + * + * @param worlds The worlds to verify logic for + * @param items The pool of items that haven't been placed yet + * + * @return An optional value that holds a string explaining why the logic was not satisfied if validation failed + */ + std::optional VerifyLogic(tphdr::logic::world::WorldPool* worlds, + const tphdr::logic::item_pool::ItemPool& items = {}); + void GeneratePlaythrough(tphdr::logic::world::WorldPool* worlds); + bool GameBeatable(tphdr::logic::world::WorldPool* worlds, const tphdr::logic::item_pool::ItemPool& items = {}); +} // namespace tphdr::logic::search diff --git a/src/dusk/randomizer/logic/spoiler_log.cpp b/src/dusk/randomizer/logic/spoiler_log.cpp new file mode 100644 index 0000000000..bb906da194 --- /dev/null +++ b/src/dusk/randomizer/logic/spoiler_log.cpp @@ -0,0 +1,247 @@ +#include "spoiler_log.hpp" + +#include "entrance_shuffle.hpp" +#include "../utility/file.hpp" +#include "../utility/platform.hpp" +#include "../utility/yaml.hpp" + +#include +#include +#include + +namespace tphdr::logic::spoiler_log +{ + std::string SpoilerFormatLocation(tphdr::logic::location::Location* location, const size_t& longestNameLength) + { + auto numSpaces = longestNameLength - location->GetName().length(); + std::string spaces(numSpaces, ' '); + + return location->GetName() + ": " + spaces + location->GetCurrentItem()->GetName(); + } + + std::string SpoilerFormatEntrance(tphdr::logic::entrance::Entrance* entrance, const size_t& longestNameLength) + { + auto numSpaces = longestNameLength - entrance->GetOriginalName().length(); + std::string spaces(numSpaces, ' '); + auto replacement = entrance->GetReplaces(); + auto parent = replacement->GetParentArea()->GetName(); + auto connected = replacement->GetOriginalConnectedArea()->GetName(); + + return entrance->GetOriginalName() + ": " + spaces + connected + " from " + parent; + } + + void LogBasicInfo(std::ofstream& log, tphdr::seedgen::config::Config& config, tphdr::logic::world::WorldPool& worlds) + { + log << "Twilight Princess HD Randomizer Version: " << "1.0.0" << std::endl; + log << "Seed: " << config.GetSeed() << std::endl; + + // TODO: Setting string + + log << "Hash: " << config.GetHash() << std::endl; + } + + void LogSettings(std::ofstream& log, tphdr::seedgen::config::Config& config, tphdr::logic::world::WorldPool& worlds) + { + log << std::endl << "# Settings" << std::endl; + log << YAML::Dump(config.SettingsToYaml()) << std::endl; + } + + void GenerateSpoilerLog(tphdr::logic::world::WorldPool& worlds, tphdr::seedgen::config::Config& config) + { + tphdr::utility::platform::Log("Generating Spoiler Log"); + + // Create logs folder if it doesn't exist + if (!tphdr::utility::file::dirExists(LOGS_PATH)) + { + tphdr::utility::file::create_directories(LOGS_PATH); + } + + std::string filepath = std::string(LOGS_PATH) + config.GetHash() + " Spoiler Log.txt"; + std::ofstream spoilerLog; + spoilerLog.open(filepath); + + LogBasicInfo(spoilerLog, config, worlds); + + // Gather worlds with starting inventories + std::list worldswithStartingInventories = {}; + for (const auto& world : worlds) + { + if (!world->GetStartingItemPool().empty()) + { + worldswithStartingInventories.push_back(world.get()); + } + } + // Print starting inventories if there are any + if (!worldswithStartingInventories.empty()) + { + spoilerLog << std::endl << "Starting Inventory:" << std::endl; + for (const auto& world : worldswithStartingInventories) + { + spoilerLog << " World " << world->GetID() << ":" << std::endl; + for (const auto& item : world->GetStartingItemPool()) + { + spoilerLog << " - " << item->GetName() << std::endl; + } + } + } + + // TODO: Print required dungeons + + // Get name lengths for pretty formatting + size_t longestNameLength = 0; + for (const auto& sphere : worlds.at(0)->GetPlaythroughSpheres()) + { + for (const auto& location : sphere) + { + longestNameLength = std::max(location->GetName().length(), longestNameLength); + } + } + + // Print playthrough + int sphereNum = 0; + spoilerLog << std::endl << "Playthrough:" << std::endl; + for (auto& sphere : worlds.at(0)->GetPlaythroughSpheres()) + { + sphereNum += 1; + spoilerLog << " Sphere " << sphereNum << ":" << std::endl; + sphere.sort([](const auto& a, const auto& b) { return a->GetName()[0] < b->GetName()[0]; }); + for (const auto& location : sphere) + { + spoilerLog << " " << SpoilerFormatLocation(location, longestNameLength) << std::endl; + } + } + + // Get name lengths for pretty formatting + longestNameLength = 0; + for (const auto& sphere : worlds.at(0)->GetEntranceSpheres()) + { + for (const auto& entrance : sphere) + { + longestNameLength = std::max(entrance->GetOriginalName().length(), longestNameLength); + } + } + + // Print entrance playthrough + sphereNum = 0; + if (longestNameLength != 0) + { + spoilerLog << std::endl << "Entrance Playthrough:" << std::endl; + } + for (auto& sphere : worlds.at(0)->GetEntranceSpheres()) + { + sphereNum += 1; + if (sphere.empty()) + { + continue; + } + spoilerLog << " Sphere " << sphereNum << ":" << std::endl; + sphere.sort([](auto& e1, auto& e2) { return e1->GetID() < e2->GetID(); }); + for (const auto& entrance : sphere) + { + spoilerLog << " " << SpoilerFormatEntrance(entrance, longestNameLength) << std::endl; + } + } + + // Recalculate longest name length for all locations + longestNameLength = 0; + for (const auto& world : worlds) + { + for (const auto& location : world->GetAllLocations()) + { + longestNameLength = std::max(location->GetName().length(), longestNameLength); + } + } + + // Print All Locations + spoilerLog << std::endl << "All Locations:" << std::endl; + for (const auto& world : worlds) + { + spoilerLog << " World " << world->GetID() << ":" << std::endl; + for (const auto& location : world->GetAllLocations()) + { + spoilerLog << " " << SpoilerFormatLocation(location, longestNameLength) << std::endl; + } + } + + // Recalculate longest name length for all shuffled entrances + longestNameLength = 0; + for (const auto& world : worlds) + { + for (const auto& entrance : world->GetShuffledEntrances()) + { + longestNameLength = std::max(entrance->GetOriginalName().length(), longestNameLength); + } + } + // Print all randomized entrances + if (longestNameLength != 0) + { + spoilerLog << std::endl << "All Entrances:" << std::endl; + } + for (const auto& world : worlds) + { + auto entrances = world->GetShuffledEntrances(); + if (!entrances.empty()) + { + spoilerLog << " World " << world->GetID() << ":" << std::endl; + // Create entrance pools to easily separate the entrances by type + auto entrancePools = tphdr::logic::entrance_shuffle::CreateEntrancePools(world.get()); + auto mixedPools = world->GetSettings().GetMixedEntrancePools(); + for (auto& [entranceType, entrancePool] : entrancePools) + { + auto typeStr = tphdr::logic::entrance::TypeToStr(entranceType); + // If this is a mixed pool, display the types it mixed + if (typeStr.starts_with("Mixed Pool")) + { + typeStr += " ("; + auto& pool = mixedPools.front(); + for (const auto& type : pool) + { + typeStr += type + " + "; + } + typeStr.erase(typeStr.end() - 3, typeStr.end()); // Remove the last " + " + typeStr += ")"; + mixedPools.pop_front(); + } + spoilerLog << " " << typeStr << ":" << std::endl; + std::sort(entrancePool.begin(), + entrancePool.end(), + [](auto& e1, auto& e2) { return e1->GetID() < e2->GetID(); }); + for (const auto& entrance : entrancePool) + { + // Ignore entrances that are impossible + if (entrance->GetRequirement()._type == tphdr::logic::requirement::Type::IMPOSSIBLE) + { + continue; + } + spoilerLog << " " << SpoilerFormatEntrance(entrance, longestNameLength) << std::endl; + } + } + } + } + + // TODO: Hints + + // Log Settings + LogSettings(spoilerLog, config, worlds); + + spoilerLog.close(); + + tphdr::utility::platform::Log("Wrote spoiler log to " + filepath); + } + + void GenerateAntiSpoilerLog(tphdr::logic::world::WorldPool& worlds, tphdr::seedgen::config::Config& config) + { + // Create logs folder if it doesn't exist + if (!tphdr::utility::file::dirExists(LOGS_PATH)) + { + tphdr::utility::file::create_directories(LOGS_PATH); + } + + std::string filepath = std::string(LOGS_PATH) + config.GetHash() + " Anti-Spoiler Log.txt"; + std::ofstream antiSpoilerLog; + antiSpoilerLog.open(filepath); + + LogBasicInfo(antiSpoilerLog, config, worlds); + LogSettings(antiSpoilerLog, config, worlds); + } +} // namespace tphdr::logic::spoiler_log diff --git a/src/dusk/randomizer/logic/spoiler_log.hpp b/src/dusk/randomizer/logic/spoiler_log.hpp new file mode 100644 index 0000000000..0a446c7ac4 --- /dev/null +++ b/src/dusk/randomizer/logic/spoiler_log.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "world.hpp" +#include "../seedgen/config.hpp" + +namespace tphdr::logic::spoiler_log +{ + void GenerateSpoilerLog(tphdr::logic::world::WorldPool& worlds, tphdr::seedgen::config::Config& config); + void GenerateAntiSpoilerLog(tphdr::logic::world::WorldPool& worlds, tphdr::seedgen::config::Config& config); +} // namespace tphdr::logic::spoiler_log diff --git a/src/dusk/randomizer/logic/world.cpp b/src/dusk/randomizer/logic/world.cpp new file mode 100644 index 0000000000..11237bc3a0 --- /dev/null +++ b/src/dusk/randomizer/logic/world.cpp @@ -0,0 +1,1174 @@ +#include "world.hpp" + + +#include "search.hpp" +#include "../utility/exception.hpp" +#include "../utility/file.hpp" +#include "../utility/general.hpp" +#include "../utility/log.hpp" +#include "../utility/platform.hpp" +#include "../utility/random.hpp" +#include "../utility/string.hpp" +#include "../utility/yaml.hpp" + +#include +#include +#include + +namespace tphdr::logic::world +{ + int World::_eventIdCounter = 0; + + World::World(const int& id) + { + this->_id = id; + } + + int World::GetID() const + { + return this->_id; + } + void World::SetSettings(const tphdr::seedgen::settings::Settings& settings) + { + _settings = settings; + } + const tphdr::seedgen::settings::Settings& World::GetSettings() const + { + return this->_settings; + } + void World::SetWorlds(WorldPool* worlds) + { + _worlds = worlds; + } + + void World::ResolveRandomSettings() + { + for (auto& [name, setting] : this->_settings.GetMap()) + { + setting.ResolveIfRandom(); + } + } + + void World::ResolveConflictingSettings() + { + // If Bonks Do Damage is On and the Damage Multiplier is OHKO and Eldin or Lanayru Twilight are not cleared, then + // this creates a logically impossible scenario. We can't guarantee repeatable access to a bottled fairy in twilight + // unless the player starts with the shadow crystal in their inventory. Turn off Bonks Do Damage in this case. + bool bonksDoDamage = this->Setting("Bonks Do Damage") == "On"; + bool ohko = this->Setting("Damage Multiplier") == "OHKO"; + bool eldinTwilightNotCleared = this->Setting("Eldin Twilight Cleared") == "Off"; + bool lanayruTwilightNotCleared = this->Setting("Lanayru Twilight Cleared") == "Off"; + if (bonksDoDamage && ohko && (eldinTwilightNotCleared || lanayruTwilightNotCleared)) + { + this->Setting("Bonks Do Damage").SetCurrentOption("Off"); + LOG_TO_DEBUG("Changing Bonks Do Damage to Off"); + } + + // If we're starting as wolf link, the prologue has to be skipped + if (this->Setting("Starting Form") == "Wolf" && this->Setting("Skip Prologue") == "Off") + { + this->Setting("Skip Prologue").SetCurrentOption("On"); + LOG_TO_DEBUG("Turning off Prologue due to Wolf Start"); + } + } + + void World::Build() + { + tphdr::utility::platform::Log(std::string("Building World ") + std::to_string(this->GetID())); + this->BuildItemTable(); + this->BuildLocationTable(); + this->LoadLogicMacros(); + this->LoadWorldGraph(); + // TODO: Verify Hint Data + this->GenerateItemPools(); + } + + void World::BuildItemTable() + { + LOG_TO_DEBUG("Building Item Table for World " + std::to_string(this->GetID())); + // Check if we can open the file before parsing + auto filepath = DATA_PATH "items.yaml"; + tphdr::utility::file::Verify(filepath); + + auto itemDataTree = LoadYAML(filepath); + // Process all nodes of the yaml file. Each node contains one item + for (const auto& itemNode : itemDataTree) + { + // Check to make sure all required fields are present + YAMLVerifyFields(itemNode, "Name", "Importance", "Id"); + + // Required Fields + auto id = itemNode["Id"].as(); + auto name = itemNode["Name"].as(); + auto importanceStr = itemNode["Importance"].as(); + auto importance = tphdr::logic::item::ImportanceFromStr(importanceStr); + if (importance == tphdr::logic::item::Importance::INVALID) + { + throw std::runtime_error(std::string("Unknown importance \"") + importanceStr + "\" from item node:\n" + + YAML::Dump(itemNode)); + } + + LOG_TO_DEBUG("Processing new item " + name + "\tid: " + std::to_string(id)); + + // Optional fields + auto gameWinningItem = itemNode["Game Winning Item"].as(false); + auto dungeonSmallKey = itemNode["Dungeon Small Key"].as(""); + auto dungeonBigKey = itemNode["Dungeon Big Key"].as(""); + auto dungeonCompass = itemNode["Dungeon Compass"].as(""); + auto dungeonMap = itemNode["Dungeon Map"].as(""); + + // Make the item and insert it into the item table + auto item = std::make_unique(id, + name, + this, + importance, + gameWinningItem, + dungeonSmallKey != "", + dungeonBigKey != "", + dungeonCompass != "", + dungeonMap != ""); + + this->_itemTable.try_emplace(name, std::move(item)); + + // Assign dungeon items to dungeons + auto curItem = this->GetItem(name); + if (dungeonSmallKey != "") + { + this->GetDungeon(dungeonSmallKey)->SetSmallKey(curItem); + } + else if (dungeonBigKey != "") + { + this->GetDungeon(dungeonBigKey)->SetBigKey(curItem); + } + else if (dungeonCompass != "") + { + this->GetDungeon(dungeonCompass)->SetCompass(curItem); + } + else if (dungeonMap != "") + { + this->GetDungeon(dungeonMap)->SetDungeonMap(curItem); + } + } + } + + void World::BuildLocationTable() + { + LOG_TO_DEBUG("Building Location Table for World " + std::to_string(this->GetID())); + // check if we can open the file before parsing because exceptions won't work on console + auto filepath = DATA_PATH "locations.yaml"; + tphdr::utility::file::Verify(filepath); + + auto locationDataTree = LoadYAML(filepath); + // Process all nodes of the yaml file. Each node contains one location + int locationIdCounter = 0; + for (const auto& locationNode : locationDataTree) + { + // Check to make sure all required fields are present + YAMLVerifyFields(locationNode, "Name", "Categories"); + + // Required Fields + auto name = locationNode["Name"].as(); + std::unordered_set categories = {}; + for (const auto& category : locationNode["Categories"]) + { + categories.insert(category.as()); + // Add the category to the registered location categories for this world. + // When checking a locations categories, we can check to make sure the category + // is in here to make sure proper categories are being checked. + this->_registeredLocationCategories.insert(category.as()); + } + + // Optional Fields + auto originalItemName = locationNode["Original Item"].as("Nothing"); + + // If the original item is a twilight tear for a twilight section that's been cleared, then don't include the + // location + if ((originalItemName == "Faron Twilight Tear" && this->Setting("Faron Twilight Cleared") == "On") || + (originalItemName == "Eldin Twilight Tear" && this->Setting("Eldin Twilight Cleared") == "On") || + (originalItemName == "Lanayru Twilight Tear" && this->Setting("Lanayru Twilight Cleared") == "On")) + { + LOG_TO_DEBUG("Removing " + name + " because it's corresponding twilight is already cleared"); + this->_intentionallyRemovedLocations.insert(name); + continue; + } + + auto originalItem = this->GetItem(originalItemName); + auto goalLocation = locationNode["Goal Location"].as(false); + auto hintPriority = locationNode["Hint Priority"].as("Never"); + + auto location = std::make_unique(locationIdCounter++, + name, + categories, + this, + originalItem, + goalLocation, + hintPriority); + + LOG_TO_DEBUG("Processing new location " + name + "\tid: " + std::to_string(locationIdCounter - 1) + + "\toriginal item: " + originalItemName); + + location->SetRegisteredLocationCategories(&this->_registeredLocationCategories); + + this->_locationTable.emplace(name, std::move(location)); + } + } + + void World::LoadLogicMacros() + { + LOG_TO_DEBUG("Loading Macros for World " + std::to_string(this->GetID())); + // check if we can open the file before parsing + auto filepath = DATA_PATH "macros.yaml"; + tphdr::utility::file::Verify(filepath); + + auto macrosDataTree = LoadYAML(filepath); + + // Process all nodes of the yaml file. Each node contains one macro + int macroIdCounter = 0; + for (const auto& macroNode : macrosDataTree) + { + auto macroName = macroNode.first.as(); + auto macroReqStr = macroNode.second.as(); + + // Process the macro + this->_macros[macroIdCounter] = tphdr::logic::requirement::ParseRequirementString(macroReqStr, + this, + /*forceLogic = */ true); + + // Store it + this->_macroIndexes[macroName] = macroIdCounter; + LOG_TO_DEBUG("\"" + macroName + "\" assigned macro index of " + std::to_string(macroIdCounter)); + macroIdCounter += 1; + } + } + + void World::LoadWorldGraph() + { + LOG_TO_DEBUG("Loading world graph for World " + std::to_string(this->GetID())); + + std::unordered_set definedEvents = {}; + std::unordered_set definedAreas = {}; + + // I don't know if directory iterator works on console so all logic files are manually specified here for now + std::string folder = DATA_PATH "world/"; + std::list files = { + "Root.yaml", + "overworld/Ordona Province.yaml", + "overworld/Faron Province.yaml", + "overworld/Eldin Province.yaml", + "overworld/Lanayru Province.yaml", + "overworld/Gerudo Desert.yaml", + "overworld/Snowpeak Province.yaml", + "dungeons/Forest Temple.yaml", + "dungeons/Goron Mines.yaml", + "dungeons/Lakebed Temple.yaml", + "dungeons/Arbiters Grounds.yaml", + "dungeons/Snowpeak Ruins.yaml", + "dungeons/Temple of Time.yaml", + "dungeons/City in the Sky.yaml", + "dungeons/Palace of Twilight.yaml", + "dungeons/Hyrule Castle.yaml", + }; + + // Loop through and process all files + for (const auto& file : files) + { + auto filepath = folder + file; + tphdr::utility::file::Verify(filepath); + + auto worldDataTree = LoadYAML(filepath); + for (const auto& areaNode : worldDataTree) + { + YAMLVerifyFields(areaNode, "Name"); + + // Required Fields + auto areaName = areaNode["Name"].as(); + + // Optional Fields + auto mapSector = areaNode["Map Sector"].as(""); + auto region = areaNode["Region"].as(""); + auto twilight = areaNode["Twilight"].as(""); + auto dungeonStartArea = areaNode["Dungeon Start Area"].as(false); + auto canWarp = areaNode["Can Warp"].as(false); + auto canChangeTime = areaNode["Can Change Time"].as(false); + auto canTransformStr = areaNode["Can Transform"].as("Always"); + + // Copy our events map so we can add autogenerated events to it + std::map eventNodes = {}; + if (areaNode["Events"]) + { + for (const auto& eventNode : areaNode["Events"]) + { + auto eventName = eventNode.first.as(); + auto eventReqStr = eventNode.second.as(); + eventNodes.emplace(eventName, eventReqStr); + } + } + + // Add an event for accessing this area + eventNodes.emplace("Can Access " + areaName, "Nothing"); + + // If we can warp, add the Can Warp event + if (canWarp) + { + eventNodes.emplace("Can Warp", "Nothing"); + } + + // If this area unlocks a map sector, add the event for the map sector + if (mapSector != "") + { + eventNodes.emplace(mapSector + " Map Sector", "Nothing"); + } + + // Create and get the area object now so we can pass it to all the other things + // which need a pointer to it + auto area = this->GetArea(areaName, /*createIfNotFound = */ true); + definedAreas.emplace(areaName); + + // Set if the area can change time + area->SetCanChangeTime(canChangeTime); + + // If this area is in a dungeon, check and set the dungeon start area + if (this->_dungeons.contains(region)) + { + auto dungeon = this->GetDungeon(region); + if (dungeonStartArea) + { + dungeon->SetStartingArea(area); + } + } + + // Set hint region stuff + if (region != "") + { + area->SetHardAssignedRegion(region); + area->AddHintRegion(region); + } + + // Set the transform status + // Check to make sure a valid string is used for Can Transform + const std::unordered_set validTransformStatuses = {"Always", "If Transform Anywhere", "Never"}; + if (!validTransformStatuses.contains(canTransformStr)) + { + throw std::runtime_error("Unknown Can Transform Status \"" + canTransformStr + "\" in area \"" + areaName + + "\"."); + } + + auto canTransform = canTransformStr == "Always" || + (this->Setting("Transform Anywhere") == "On" && canTransformStr == "If Transform Anywhere"); + + area->SetCanTransform(canTransform); + + // Set the completed twilight macro index if necessary + if (twilight != "") + { + auto twilightMacro = "Can Complete " + twilight + " Twilight"; + auto canCompleteTwilightMacroIndex = this->GetMacroIndex(twilightMacro); + + if (canCompleteTwilightMacroIndex == -1) + { + throw std::runtime_error('\"' + twilightMacro + + "\" is not a macro that exists when trying to set twilight macro for " + + area->GetName()); + } + + // Only bother with this if the setting for clearing this twilight is off + if (this->Setting(twilight + " Twilight Cleared") == "Off") + { + area->SetTwilightCompletedMacroIndex(canCompleteTwilightMacroIndex); + } + } + + // Lists of events, locations, and exits that we pass along to the area object + std::list> events = {}; + std::list> locations = {}; + std::list> exits = {}; + + // Process events + for (const auto& [eventName, eventReqStr] : eventNodes) + { + // Parse the requirement string + auto eventReq = tphdr::logic::requirement::ParseRequirementString(eventReqStr, this); + + // Create the EventAccess wrapper and put it into the list of events for this area + auto eventIndex = this->GetEventIndex(eventName); + auto event = std::make_unique(eventReq, area, eventIndex); + events.emplace_back(std::move(event)); + definedEvents.emplace(eventIndex); + } + + // Process locations + if (areaNode["Locations"]) + { + for (const auto& locationNode : areaNode["Locations"]) + { + // Get location name and requirement string + auto locationName = locationNode.first.as(); + auto locationReqStr = locationNode.second.as(); + + // Ignore the location if it's been intentionally removed + if (this->_intentionallyRemovedLocations.contains(locationName)) + { + continue; + } + + auto location = this->GetLocation(locationName); + + // If this location is in a twilight section, and is not a twilit insect, add the Not_Twilight macro. + // We can't assume repeatable access to non-insect locations in twilights. + if (twilight != "" && !location->GetOriginalItem()->GetName().ends_with("Twilight Tear")) + { + locationReqStr = "Not_Twilight and (" + locationReqStr + ")"; + LOG_TO_DEBUG("Adding Not_Twilight check to requirement for " + locationName); + } + + // Parse the requirement string + auto locationReq = tphdr::logic::requirement::ParseRequirementString(locationReqStr, this); + + // Create the LocationAccess wrapper and put it into the list of locations for this area + + auto locationAccess = std::make_unique(location, locationReq, area); + locations.emplace_back(std::move(locationAccess)); + + // Also add this LocationAccess to the locations list of access points + location->AddLocationAccess(locations.back().get()); + } + } + + // Process Exits + if (areaNode["Exits"]) + { + for (const auto& exitNode : areaNode["Exits"]) + { + // Get the connected area and requirement string + auto connectedAreaName = exitNode.first.as(); + auto entranceReqStr = exitNode.second.as(); + auto connectedArea = this->GetArea(connectedAreaName, /*createIfNotFound = */ true); + + // Parse the requirement string + auto entranceReq = tphdr::logic::requirement::ParseRequirementString(entranceReqStr, this); + + // Create the Entrance object and put it into the list of exits for this area + auto entrance = + std::make_unique(area, connectedArea, entranceReq, this); + exits.emplace_back(std::move(entrance)); + } + } + + area->SetEvents(events); + area->SetLocations(locations); + area->SetExits(exits); + } + } + + // Make sure that all used events are defined + for (const auto& [eventName, eventIndex] : this->_eventIndexes) + { + if (!definedEvents.contains(eventIndex)) + { + throw std::runtime_error("Event \"" + eventName + "\" is used but never defined."); + } + } + + // Make sure all used areas are defined + for (const auto& [areaName, area] : this->_areaTable) + { + if (!definedAreas.contains(areaName)) + { + throw std::runtime_error("Area \"" + areaName + "\" is used but never defined."); + } + } + + // Pass a pointer for each exit to the entrance list for the area it connects to + for (const auto& [areaName, area] : this->_areaTable) + { + for (const auto& exit : area->GetExits()) + { + exit->GetConnectedArea()->AddEntrance(exit); + } + } + } + + void World::GenerateItemPools() + { + LOG_TO_DEBUG("Now building item pools"); + tphdr::logic::item_pool::GenerateItemPool(this); + tphdr::logic::item_pool::GenerateStartingItemPool(this); + + LOG_TO_DEBUG("Item Pool for world " + std::to_string(this->GetID()) + ":"); + for (const auto& item : this->_itemPool) + { + LOG_TO_DEBUG("- " + item->GetName()); + } + LOG_TO_DEBUG("Starting Inventory for world " + std::to_string(this->GetID()) + ":"); + for (const auto& item : this->_startingItemPool) + { + LOG_TO_DEBUG("- " + item->GetName()); + } + } + + void World::PerformPreEntranceShuffleTasks() + { + this->PlaceVanillaItems(); + this->SanitizeItemPool(); + this->PlacePlandomizerItems(); + this->SetNonProgressLocations(); + } + + void World::PlaceVanillaItems() + { + LOG_TO_DEBUG("Now placing vanilla items"); + + for (auto& [locationName, location] : this->_locationTable) + { + auto originalItem = location->GetOriginalItem(); + auto originalItemName = originalItem->GetName(); + + // Place all vanilla items + // Vanilla Small Keys + if ((this->Setting("Small Keys") == "Vanilla" && + (originalItem->IsDungeonSmallKey() || + tphdr::utility::str::Contains(originalItemName, "Ordon Pumpkin", "Ordon Cheese"))) || + // Vanilla Big Keys + (this->Setting("Big Keys") == "Vanilla" && originalItem->IsBigKey()) || + // Vanilla Maps and Compasses + (this->Setting("Maps and Compasses") == "Vanilla" && + (originalItem->IsDungeonMap() || originalItem->IsCompass())) || + // Vanilla Poe Souls + (originalItemName == "Poe Soul" && + (this->Setting("Poe Souls") == "Vanilla" || + (this->Setting("Poe Souls") == "Dungeon" && location->HasCategories("Dungeon")) || + (this->Setting("Poe Souls") == "Overworld" && location->HasCategories("Overworld")))) || + // Vanilla Golden Bugs + (this->Setting("Golden Bugs") == "Off" && location->HasCategories("Golden Bug")) || + // Sky Characters + (this->Setting("Sky Characters") == "Off" && location->HasCategories("Sky Book")) || + // NPC Gifts + (this->Setting("Gifts From NPCs") == "Off" && location->HasCategories("Npc")) || + // Shop Items + (this->Setting("Shop Items") == "Off" && location->HasCategories("Shop")) || + // Hidden Skills + (this->Setting("Hidden Skills") == "Off" && location->HasCategories("Hidden Skill")) || + // North Faron Woods Gate Key + (this->Setting("Skip Prologue") == "Off" && locationName == "Faron Mist Cave Open Chest") || + // Some locations which will always be vanilla for the time being + (tphdr::utility::str::Contains(locationName, + "Renados Letter", + "Telma Invoice", + "Wooden Statue", + "Ilia Charm", + "Ilia Memory Reward", + "Defeat Ganondorf", + "Twilit Insect", + "Twilit Bloat"))) + { + // Change bottled items to all be empty bottles. It's much easier logically to only have to worry about a single + // item as a bottle instead of all bottled items as bottles for the search algorithm. Other contents will + // replace the empty bottles after all items have been placed + if (originalItem->IsBottle()) + { + originalItem = this->GetItem("Empty Bottle"); + } + + // Don't place stamps for now + if (originalItem->IsStamp()) + { + originalItem = this->GetItem("Purple Rupee"); + } + + location->SetCurrentItem(originalItem); + location->SetKnownVanillaItem(true); + tphdr::utility::container::Erase(this->_itemPool, originalItem); + } + } + } + + void World::PlacePlandomizerItems() + { + for (auto& [location, item] : this->_plandomizerLocations) + { + if (!location->IsEmpty()) + { + throw std::runtime_error("Cannot plandomize \"" + item->GetName() + "\" at \"" + location->GetName() + + "\" because vanilla item \"" + location->GetCurrentItem()->GetName() + + "\" already exists there."); + } + location->SetCurrentItem(item); + tphdr::utility::container::Erase(this->_itemPool, item); + } + } + + void World::SetNonProgressLocations() + { + LOG_TO_DEBUG("Now setting nonprogress locations for world " + std::to_string(this->GetID())); + + // Any manually excluded locations are nonprogress + for (const auto& locationName : this->_settings.GetExcludedLocations()) + { + auto location = this->GetLocation(locationName); + location->SetProgression(false); + } + + // Some locations not being randomized can conflict with other settings. When + // the appropriate location and setting conflict, these locations should have their item + // removed and be set to nonprogress. + for (auto& [locationName, location] : this->_locationTable) + { + auto originalItem = location->GetOriginalItem(); + auto originalItemName = originalItem->GetName(); + + // If an NPC gives a key when not randomized, but keys are keysy (keys shouldn't exist) + if ((this->Setting("Gifts From NPCs") == "Off" && location->HasCategories("Npc") && + ((this->Setting("Small Keys") == "Keysy" && originalItem->IsDungeonSmallKey()) || + (this->Setting("Big Keys") == "Keysy" && originalItem->IsBigKey()) || + (this->Setting("Maps and Compasses") == "Start With" && + (originalItem->IsDungeonMap() || originalItem->IsCompass())))) || + // Sky Characters are not randomized, but City in the Sky doesn't require Sky Book Characters (Sky characters + // shouldn't exist) + (this->Setting("Sky Characters") == "Off" && this->Setting("City Does Not Require Filled Skybook") == "On" && + location->HasCategories("Sky Book")) || + // We're starting with a shop item, but shop items aren't randomized + (this->Setting("Shop Items") == "Off" && location->HasCategories("Shop") && + tphdr::utility::container::ElementInContainer(this->_startingItemPool, originalItem))) + { + location->RemoveCurrentItem(); + location->SetKnownVanillaItem(false); + location->SetProgression(false); + } + } + } + + void World::PerformPostEntranceShuffleTasks() + { + this->AssignAreaProperties(); + this->AssignGoalLocations(); + this->ChooseRequiredDungeons(); + this->SetForbiddenItems(); + } + + void World::AssignAreaProperties() + { + for (auto& [areaName, area] : this->_areaTable) + { + area->AssignHintRegionsAndDungeonLocations(); + + // Also assign dungeons their starting entrance + for (const auto& exit : area->GetExits()) + { + auto parentRegions = exit->GetParentArea()->GetHintRegions(); + auto connectedRegions = exit->GetConnectedArea()->GetHintRegions(); + if (!parentRegions.contains("None")) + { + for (auto& [dungeonName, dungeon] : this->_dungeons) + { + // If this exit leads into a dungeon and its parent area is not part of the dungeon + // then this is the entrance that leads into the dungeon + if (connectedRegions.contains(dungeonName) && !parentRegions.contains(dungeonName)) + { + dungeon->AddStartingEntrance(exit); + } + } + } + } + } + } + + void World::AssignGoalLocations() + { + std::unordered_map dungeonGoalLocations = {}; + for (const auto& [dungeonName, dungeon] : this->_dungeons) + { + dungeonGoalLocations[dungeonName] = {}; + } + // Collect all the possible goal locations for each dungeon + for (auto& [areaName, area] : this->_areaTable) + { + for (const auto& locAcc : area->GetLocations()) + { + auto location = locAcc->GetLocation(); + if (location->IsGoalLocation()) + { + for (const auto& region : area->GetHintRegions()) + { + if (dungeonGoalLocations.contains(region)) + { + dungeonGoalLocations.at(region).push_back(location); + } + } + } + } + } + + // Set a single goal location for each dungeon + for (auto& [dungeonName, dungeon] : this->_dungeons) + { + auto& possibleGoalLocations = dungeonGoalLocations.at(dungeonName); + // If a goal location becomes unreachable due to beatable only logic, then it's possible a dungeon may not be + // assigned a goal location. Dungeons without a goal location cannot be chosen as required dungeons. + if (!possibleGoalLocations.empty()) + { + dungeon->SetGoalLocation(tphdr::utility::random::RandomElement(possibleGoalLocations)); + } + else + { + LOG_TO_DEBUG("No goal location could be chosen for " + dungeonName); + } + } + } + + void World::SetForbiddenItems() + { + // Prevent small keys from appearing on bosses if the setting is on + if (this->Setting("No Small Keys on Bosses") == "On") + { + // Gather all boss locations (heart container and dungeon reward checks) + auto bossLocations = this->GetAllLocations(); + tphdr::utility::container::FilterAndEraseFromVector( + bossLocations, + [](const auto& location) + { return !tphdr::utility::str::Contains(location->GetName(), "Heart Container", "Dungeon Reward"); }); + + // Gather all small key items + tphdr::logic::item_pool::ItemPool smallKeys = {}; + for (const auto& [itemName, item] : this->_itemTable) + { + if (item->IsDungeonSmallKey() || tphdr::utility::general::IsAnyOf(itemName, + "Ordon Pumpkin", + "Ordon Cheese", + "North Faron Woods Gate Key", + "Gerudo Desert Bulblin Camp Key")) + { + smallKeys.push_back(item.get()); + } + } + + // Set the small keys as forbidden on the boss locations + for (auto& location : bossLocations) + { + for (const auto& smallKey : smallKeys) + { + location->AddForbiddenItem(smallKey); + } + } + } + } + + void World::ChooseRequiredDungeons() + { + // STUB + } + + void World::DetermineRequiredDungeons() + { + for (const auto& [dungeonName, dungeon] : this->_dungeons) + { + // To determine if a dungeon is required, we're going to disable all of its entrances and then check to see + // that the game is still beatble. If the game is not beatable with the dungeon entrances disabled, then the + // dungeon is required. + + // Disable the dungeon's starting entrances + for (auto& entrance : dungeon->GetStartingEntrances()) + { + entrance->SetDisbled(true); + } + + // Check if the game is beatable, set dungeon as required if so. If the dungeon is not required and barren + // unrequired dungeons is on, then set all the locations in the unrequired dungeon as nonprogress. + auto completeItemPool = tphdr::logic::item_pool::GetCompleteItemPool(*(this->_worlds)); + if (!tphdr::logic::search::GameBeatable(this->_worlds, completeItemPool)) + { + dungeon->SetRequired(true); + } + else if (this->Setting("Unrequired Dungeons Are Barren") == "On") + { + for (auto& location : dungeon->GetLocations()) + { + location->SetProgression(false); + } + } + + // Re-enable the dungeon's entrances + for (auto& entrance : dungeon->GetStartingEntrances()) + { + entrance->SetDisbled(false); + } + } + } + + void World::SanitizeItemPool() + { + auto junkPool = tphdr::logic::item_pool::GetInitialJunkPool(); + + // Depending on the Trap item Frequency setting, add some amount of ice traps to the pool + if (this->Setting("Trap Item Frequency") == "Few") + { + junkPool.emplace("Foolish Item", 6); + } + else if (this->Setting("Trap Item Frequency") == "Many") + { + junkPool.emplace("Foolish Item", 27); + } + else if (this->Setting("Trap Item Frequency") == "Mayhem") + { + junkPool.emplace("Foolish Item", 64); + } + else if (this->Setting("Trap Item Frequency") == "Nightmare") + { + junkPool.clear(); + junkPool.emplace("Foolish Item", 1); + } + + // Create an actual item pool from the junk items + tphdr::logic::item_pool::ItemPool mainJunkPool = {}; + for (const auto& [itemName, count] : junkPool) + { + auto item = this->GetItem(itemName); + for (auto i = 0; i < count; i++) + { + mainJunkPool.push_back(item); + } + } + + auto allItemLocations = this->GetAllLocations(); + int numEmptyLocations = std::count_if(allItemLocations.begin(), + allItemLocations.end(), + [](const auto& location) { return location->IsEmpty(); }); + + // Create a copy of the real pool we just made. When adding junk items we want to add all the items from the junk pool + // once if possible, then if there's more space left pick randomly from the full pool + auto mainJunkPoolCopy = mainJunkPool; + + // Add items until the pool's size matches the number of empty locations + while (this->_itemPool.size() < numEmptyLocations) + { + tphdr::logic::item::Item* randomJunkItem; + if (!mainJunkPool.empty()) + { + randomJunkItem = tphdr::utility::random::PopRandomElement(mainJunkPool); + } + else + { + randomJunkItem = tphdr::utility::random::RandomElement(mainJunkPoolCopy); + } + this->_itemPool.emplace_back(randomJunkItem); + LOG_TO_DEBUG("Added junk item \"" + randomJunkItem->GetName() + "\" to item pool for world " + + std::to_string(this->GetID())); + } + } + + void World::SetSearchStartingProperties(tphdr::logic::search::Search* search) const + { + // Set the root area to have all player forms and times of day (necessary for entrance rando validation) + auto root = this->GetRootArea(); + search->_areaFormTime[root] = tphdr::logic::requirement::FormTime::ALL; + } + + void World::PerformPostFillTasks() + { + this->FinalizeBottleContents(); + } + + void World::FinalizeBottleContents() + { + // Replace 3 bottles with other bottle contents we currently use. + auto bottleWithGreatFairiesTears = this->GetItem("Bottle with Great Fairies Tears"); + auto bottleWithHalfMilk = this->GetItem("Bottle with Half Milk"); + auto bottleWithLanternOil = this->GetItem("Bottle with Lantern Oil"); + auto emptyBottle = this->GetItem("Empty Bottle"); + tphdr::logic::item_pool::ItemPool bottlePool = {bottleWithGreatFairiesTears, + bottleWithHalfMilk, + bottleWithLanternOil, + emptyBottle}; + + // If npc gifts are vanilla, then set those vanilla bottles appropriately + if (this->Setting("Gifts From NPCs") == "Off") + { + for (auto& [locationName, location] : this->_locationTable) + { + auto originalItem = location->GetOriginalItem(); + if (location->HasCategories("Npc") && originalItem->IsBottle()) + { + location->SetCurrentItem(originalItem); + } + } + } + // Otherwise gather all the locations which have a bottle and replace the bottles at those locations instead + else + { + // Gather the bottle locations + tphdr::logic::location::LocationPool bottleLocations = {}; + for (auto& [locationName, location] : this->_locationTable) + { + auto originalItem = location->GetCurrentItem(); + if (originalItem->IsBottle()) + { + bottleLocations.push_back(location.get()); + } + } + + // Place the new bottle items + tphdr::utility::random::ShufflePool(bottleLocations); + for (auto& bottleLocation : bottleLocations) + { + bottleLocation->SetCurrentItem(tphdr::utility::random::PopRandomElement(bottlePool)); + } + } + } + + void World::AddPlandomizedLocation(tphdr::logic::location::Location* location, tphdr::logic::item::Item* item) + { + if (this->_plandomizerLocations.contains(location)) + { + throw std::runtime_error("Plandomizer Error: multiple entries for \"" + location->GetName() + "\" in world " + + std::to_string(this->_id)); + } + this->_plandomizerLocations[location] = item; + } + + void World::AddPlandomizedEntrance(tphdr::logic::entrance::Entrance* entrance, tphdr::logic::entrance::Entrance* target) + { + for (const auto& [plandoEntrance, plandoTarget] : this->_plandomizerEntrances) + { + if (plandoEntrance == entrance) + { + throw std::runtime_error("Plandomizer Error: multiple entries for \"" + entrance->GetOriginalName() + + "\" in world " + std::to_string(this->_id)); + } + if (plandoTarget == target) + { + throw std::runtime_error("Plandomizer Error: multiple entrances target \"" + target->GetOriginalName() + + "\" in world " + std::to_string(this->_id)); + } + } + this->_plandomizerEntrances[entrance] = target; + } + + std::unordered_map World::GetPlandomizerEntrances() + { + return this->_plandomizerEntrances; + } + + tphdr::logic::dungeon::Dungeon* World::GetDungeon(const std::string& name) + { + if (!this->_dungeons.contains(name)) + { + this->_dungeons.emplace(name, std::make_unique(name, this)); + LOG_TO_DEBUG("Added new dungeon \"" + name + "\" to world " + std::to_string(this->_id)); + } + return this->_dungeons.at(name).get(); + } + + const std::map>& World::GetDungeonTable() const + { + return this->_dungeons; + } + + tphdr::logic::item::Item* World::GetItem(const std::string& name, const bool& ignoreError /*= false*/) + { + if (name == "Nothing") + { + return tphdr::logic::item::Nothing.get(); + } + + if (!this->_itemTable.contains(name)) + { + if (!ignoreError) + { + throw std::runtime_error("Unknown item name \"" + name + "\""); + } + return nullptr; + } + return this->_itemTable.at(name).get(); + } + + tphdr::logic::item::Item* World::GetGameWinningItem() const + { + return this->_itemTable.at("Game Beatable").get(); + } + + tphdr::logic::item::Item* World::GetShadowCrystal() + { + return this->_itemTable.at("Shadow Crystal").get(); + } + + tphdr::logic::item_pool::ItemPool& World::GetItemPool() + { + return this->_itemPool; + } + + tphdr::logic::item_pool::ItemPool& World::GetStartingItemPool() + { + return this->_startingItemPool; + } + + tphdr::logic::location::Location* World::GetLocation(const std::string& name) + { + if (!this->_locationTable.contains(name)) + { + throw std::runtime_error("Unknown location name \"" + name + "\""); + } + return this->_locationTable.at(name).get(); + } + + tphdr::logic::location::LocationPool World::GetAllLocations(const bool& includeNonItemLocations /* = false */) + { + tphdr::logic::location::LocationPool locationPool = {}; + for (const auto& [locationName, location] : this->_locationTable) + { + if (includeNonItemLocations || !location->HasCategories("Non-Item Location")) + { + locationPool.emplace_back(location.get()); + } + } + return locationPool; + } + + tphdr::logic::area::Area* World::GetArea(const std::string& name, const bool& createIfNotFound /* = false */) + { + if (!this->_areaTable.contains(name)) + { + if (createIfNotFound) + { + this->_areaTable.emplace(name, std::make_unique(name, this)); + } + else + { + throw std::runtime_error("Unknown area name \"" + name + "\""); + } + } + return this->_areaTable.at(name).get(); + } + + tphdr::logic::area::Area* World::GetRootArea() const + { + return this->_areaTable.at("Root").get(); + } + + const std::map>& World::GetAreaTable() const + { + return this->_areaTable; + } + + tphdr::logic::entrance::Entrance* World::GetEntrance(const std::string& originalName) + { + auto [parentAreaName, connectedAreaName] = tphdr::logic::entrance::GetParentAndConnectedAreaNames(originalName); + auto parentArea = this->GetArea(parentAreaName); + auto connectedArea = this->GetArea(connectedAreaName); + for (const auto& exit : parentArea->GetExits()) + { + if (exit->GetOriginalConnectedArea() == connectedArea) + { + return exit; + } + } + + throw std::runtime_error("\"" + originalName + "\" is not a known connection"); + } + + int World::GetNewEntranceID() + { + return this->_entranceIdCounter++; + } + + tphdr::logic::entrance::EntrancePool World::GetShuffleableEntrances(const tphdr::logic::entrance::Type& type, + const bool& onlyPrimary /* = false */) + { + tphdr::logic::entrance::EntrancePool shuffleableEntrances = {}; + for (const auto& [areaName, area] : this->GetAreaTable()) + { + for (const auto& exit : area->GetExits()) + { + if ((type == exit->GetType() || type == tphdr::logic::entrance::Type::ALL) && + (!onlyPrimary || exit->IsPrimary()) && exit->GetType() != tphdr::logic::entrance::Type::INVALID) + { + shuffleableEntrances.push_back(exit); + } + } + } + return shuffleableEntrances; + } + + tphdr::logic::entrance::EntrancePool World::GetShuffledEntrances( + const tphdr::logic::entrance::Type& type /* = tphdr::logic::entrance::Type::ALL */, + const bool& onlyPrimary /* = false */) + { + auto entrances = this->GetShuffleableEntrances(type, onlyPrimary); + + // Remove any entrances which aren't shuffled + tphdr::utility::container::FilterAndEraseFromVector(entrances, [](const auto& e) { return !e->IsShuffled(); }); + + return entrances; + } + + std::unordered_map& World::GetExitTimeFormCache() + { + return this->_exitTimeFormCache; + } + + int World::GetMacroIndex(const std::string& macroName) const + { + if (this->_macroIndexes.contains(macroName)) + { + return this->_macroIndexes.at(macroName); + } + return -1; + } + + const tphdr::logic::requirement::Requirement& World::GetMacro(const int& macroIndex) + { + return this->_macros.at(macroIndex); + } + + int World::GetEventIndex(const std::string& eventName) + { + // Add the event if it doesn't exist yet + if (!this->_eventIndexes.contains(eventName)) + { + auto index = this->_eventIdCounter++; + this->_eventIndexes.emplace(eventName, index); + this->_eventNames.emplace(index, eventName); + LOG_TO_DEBUG("Event \"" + eventName + "\" was assigned eventIndex " + std::to_string(index)); + } + + return this->_eventIndexes.at(eventName); + } + + std::string World::GetEventName(const int& eventIndex) + { + if (!this->_eventNames.contains(eventIndex)) + { + LOG_TO_ERROR("Invalid Event Index"); + } + return this->_eventNames.at(eventIndex); + } + + tphdr::seedgen::settings::Setting& World::Setting(const std::string& settingName) + { + auto& settings = this->_settings; + // Check to make sure the setting exists + if (!settings.GetMap().contains(settingName)) + { + throw std::runtime_error("Setting \"" + settingName + "\" is not a known setting"); + } + return settings.GetMap().at(settingName); + } + + void World::SetPlaythroughSpheres(const std::list>& playthroughSpheres) + { + this->_playthroughSpheres = playthroughSpheres; + } + + std::list> World::GetPlaythroughSpheres() const + { + return this->_playthroughSpheres; + } + + void World::SetEntranceSpheres(const std::list>& entranceSpheres) + { + this->_entranceSpheres = entranceSpheres; + } + + std::list> World::GetEntranceSpheres() const + { + return this->_entranceSpheres; + } +} // namespace tphdr::logic::world diff --git a/src/dusk/randomizer/logic/world.hpp b/src/dusk/randomizer/logic/world.hpp new file mode 100644 index 0000000000..0d33a00cb1 --- /dev/null +++ b/src/dusk/randomizer/logic/world.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include "area.hpp" +#include "dungeon.hpp" +#include "item.hpp" +#include "item_pool.hpp" +#include "location.hpp" +#include "requirement.hpp" + +#include "../seedgen/settings.hpp" +#include "../utility/container.hpp" +#include "../utility/log.hpp" + +#include +#include +#include +#include + +#include + +// Forward Declarations +namespace tphdr::logic::search +{ + class Search; +} + +namespace tphdr::logic::world +{ + class World; + using WorldPool = std::vector>; + + class World + { + public: + World(const int& id); + + int GetID() const; + void SetSettings(const tphdr::seedgen::settings::Settings& settings); + const tphdr::seedgen::settings::Settings& GetSettings() const; + void SetWorlds(WorldPool* worlds); + + /** + * @brief Resolves all remaining random settings within a specific world + */ + void ResolveRandomSettings(); + + /** + * @brief Resolves settings that conflict with each other. Ideally will only resolve settings that conflict due to + * having their current option randomly chosen. + */ + void ResolveConflictingSettings(); + void Build(); + void BuildItemTable(); + void BuildLocationTable(); + void LoadLogicMacros(); + void LoadWorldGraph(); + + /** + * @brief Generate the main item pool and starting item pool for this world. + */ + void GenerateItemPools(); + + /** + * @brief Perform all tasks which must be complete before shuffling entrances. + */ + void PerformPreEntranceShuffleTasks(); + void PlaceVanillaItems(); + void PlacePlandomizerItems(); + void SetNonProgressLocations(); + + /** + * @brief Perform all tasks which require shuffled entrances to be set, but before running the main item placement + * algorithm. + */ + void PerformPostEntranceShuffleTasks(); + void AssignAreaProperties(); + void AssignGoalLocations(); + + /** + * @brief Forbid items from being in certain locations depending on settings + */ + void SetForbiddenItems(); + + /** + * @brief STUB: Would choose required dungeons ahead of placing any non-vanilla and non-plandomized items. Not really + * required unless we let users choose a specific amount of directly required dungeons + */ + void ChooseRequiredDungeons(); + + /** + * @brief Determines which dungeons are required based on placed items. Sets required dungeons as such in their + * properties. If "Unrequired Dungeons Are Barren" is "On", then unrequired dungeons will have all their locations + * progression status set to false. + */ + void DetermineRequiredDungeons(); + + /** + * @brief Adds junk to the main pool until the number of items in the pool matches the total number of + * currently empty locations. + */ + void SanitizeItemPool(); + void SetSearchStartingProperties(tphdr::logic::search::Search* search) const; + void PerformPostFillTasks(); + void FinalizeBottleContents(); + void AddPlandomizedLocation(tphdr::logic::location::Location* location, tphdr::logic::item::Item* item); + void AddPlandomizedEntrance(tphdr::logic::entrance::Entrance* entrance, tphdr::logic::entrance::Entrance* target); + std::unordered_map GetPlandomizerEntrances(); + + tphdr::logic::dungeon::Dungeon* GetDungeon(const std::string& name); + const std::map>& GetDungeonTable() const; + tphdr::logic::item::Item* GetItem(const std::string& name, const bool& ignoreError = false); + tphdr::logic::item::Item* GetShadowCrystal(); + tphdr::logic::item::Item* GetGameWinningItem() const; + tphdr::logic::item_pool::ItemPool& GetItemPool(); + tphdr::logic::item_pool::ItemPool& GetStartingItemPool(); + tphdr::logic::location::Location* GetLocation(const std::string& name); + tphdr::logic::location::LocationPool GetAllLocations(const bool& includeNonItemLocations = false); + tphdr::logic::area::Area* GetArea(const std::string& name, const bool& createIfNotFound = false); + tphdr::logic::area::Area* GetRootArea() const; + const std::map>& GetAreaTable() const; + tphdr::logic::entrance::Entrance* GetEntrance(const std::string& originalName); + int GetNewEntranceID(); + tphdr::logic::entrance::EntrancePool GetShuffleableEntrances(const tphdr::logic::entrance::Type& type, + const bool& onlyPrimary = false); + tphdr::logic::entrance::EntrancePool GetShuffledEntrances( + const tphdr::logic::entrance::Type& type = tphdr::logic::entrance::Type::ALL, + const bool& onlyPrimary = false); + std::unordered_map& GetExitTimeFormCache(); + + int GetMacroIndex(const std::string& macroName) const; + const tphdr::logic::requirement::Requirement& GetMacro(const int& macroIndex); + int GetEventIndex(const std::string& eventName); + std::string GetEventName(const int& eventIndex); + + tphdr::seedgen::settings::Setting& Setting(const std::string& settingName); + void SetPlaythroughSpheres(const std::list>& playthroughSpheres); + std::list> GetPlaythroughSpheres() const; + void SetEntranceSpheres(const std::list>& entranceSpheres); + std::list> GetEntranceSpheres() const; + + private: + int _id = -1; + + static int _eventIdCounter; // Needs to be shared for events across all worlds + int _entranceIdCounter = 0; // Specific for this world + + tphdr::seedgen::settings::Settings _settings; + std::map> _itemTable = {}; + std::map> _locationTable = {}; + std::unordered_set _intentionallyRemovedLocations = {}; + std::unordered_set _registeredLocationCategories = {}; + std::map> _areaTable = {}; + std::map> _dungeons = {}; + std::map _macros = {}; + std::unordered_map _macroIndexes = {}; + std::unordered_map _eventIndexes = {}; + std::unordered_map _eventNames = {}; + tphdr::logic::item_pool::ItemPool _itemPool = {}; + tphdr::logic::item_pool::ItemPool _startingItemPool = {}; + std::unordered_map _exitTimeFormCache = {}; + + // Playthroughs will be stored in world 0 for convenience + std::list> _playthroughSpheres = {}; + std::list> _entranceSpheres = {}; + + // Plandomizer Data + std::unordered_map _plandomizerLocations = {}; + std::unordered_map _plandomizerEntrances = {}; + + WorldPool* _worlds = nullptr; + }; +} // namespace tphdr::logic::world diff --git a/src/dusk/randomizer/randomizer.cpp b/src/dusk/randomizer/randomizer.cpp new file mode 100644 index 0000000000..318d7a6394 --- /dev/null +++ b/src/dusk/randomizer/randomizer.cpp @@ -0,0 +1,29 @@ +#include "randomizer.hpp" + +#include "logic/generate.hpp" +#include "logic/world.hpp" +#include "test/test.hpp" +#include "utility/log.hpp" + +#include + +int randomizerMain() +{ + try + { +#ifdef LOGIC_TESTS + tphdr::test::test::RunTests(); + return 0; +#else + auto worlds = tphdr::logic::generate::GenerateWorlds(); +#endif + } + catch(const std::exception& e) + { + std::cout << "============================================================" << std::endl; + std::cout << "The following exception occured: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/src/dusk/randomizer/randomizer.hpp b/src/dusk/randomizer/randomizer.hpp new file mode 100644 index 0000000000..788d40a541 --- /dev/null +++ b/src/dusk/randomizer/randomizer.hpp @@ -0,0 +1,3 @@ +#pragma once + +int randomizerMain(); \ No newline at end of file diff --git a/src/dusk/randomizer/seedgen/config.cpp b/src/dusk/randomizer/seedgen/config.cpp new file mode 100644 index 0000000000..d855ebb699 --- /dev/null +++ b/src/dusk/randomizer/seedgen/config.cpp @@ -0,0 +1,467 @@ +#include "config.hpp" + +#include "zlib-ng.h" +#include "seed.hpp" +#include "../utility/log.hpp" +#include "../utility/platform.hpp" +#include "../utility/random.hpp" +#include "../utility/yaml.hpp" + +#include + +namespace tphdr::seedgen::config +{ + + void Config::LoadFromFile(const fspath& settingsPath, + const fspath& preferencesPath, + const bool& createIfNotFound /*= true*/, + const bool& allowRewrite /*= true*/) + { + // Create files for settings/preferences if they don't exist + std::ifstream settingsFile(settingsPath); + std::ifstream preferencesFile(preferencesPath); + + if (settingsFile.is_open() == false) + { + if (createIfNotFound) + { + WriteDefaultSettings(settingsPath); + } + else + { + throw std::runtime_error("Could not open settings file at \"" + settingsPath.generic_string() + "\""); + } + } + + if (preferencesFile.is_open() == false) + { + if (createIfNotFound) + { + WriteDefaultPreferences(preferencesPath); + } + else + { + throw std::runtime_error("Could not open preferences file at \"" + preferencesPath.generic_string() + "\""); + } + } + settingsFile.close(); + preferencesFile.close(); + + this->_settingsList.clear(); + this->_settingsList.push_front(tphdr::seedgen::settings::Settings()); + auto& settings = this->_settingsList.front(); + + // Load settings info + auto settingInfoMap = tphdr::seedgen::settings::GetAllSettingsInfo(); + + // Read in settings and preferences. If we have to change anything, + // rewrite the appropriate file if allowed. + bool rewriteSettings = false; + auto settingsTree = LoadYAML(settingsPath); + + // Loop through all setting fields + for (const auto& settingNode : settingsTree) + { + const auto& settingName = settingNode.first.as(); + // Insert the setting if it's in the info map + if (settingInfoMap->contains(settingName)) + { + auto& settingInfo = settingInfoMap->at(settingName); + auto settingOption = settingNode.second.as(); + + // If the option doesn't exist, revert to default and rewrite later if necessary + if (settingInfo->GetIndexOfOption(settingOption) == -1) + { + tphdr::utility::platform::Log(std::string("Setting \"") + settingName + "\" has no option \"" + + settingOption + "\". Reverting to default \"" + + settingInfo->GetDefaultOption() + "\""); + settingOption = settingInfo->GetDefaultOption(); + rewriteSettings = true; + } + + settings.InsertSetting(settingName, tphdr::seedgen::settings::Setting(settingInfo.get(), settingOption)); + } + // Special handling for starting inventory + else if (settingName == "Starting Inventory") + { + for (const auto& inventoryNode : settingNode.second) + { + const auto& itemName = inventoryNode.first.as(); + const auto& count = inventoryNode.second.as(); + + settings.AddStartingItem(itemName, count); + } + } + // Special Handling for Excluded Locations + else if (settingName == "Excluded Locations") + { + for (const auto& locationNode : settingNode.second) + { + const auto& locationName = locationNode.as(); + settings.AddExcludedLocation(locationName); + } + } + // Special Handling for Mixed Entrance Pools + else if (settingName == "Mixed Entrance Pools") + { + for (const auto& poolNode : settingNode.second) + { + if (!poolNode.IsSequence()) + { + throw std::runtime_error("Mixed Entrance Pools is not a nested sequence of strings"); + } + settings.AddMixedPool(poolNode.as>()); + } + } + // Special handling for Seed + else if (settingName == "Seed") + { + const auto& seed = settingNode.second.as(); + this->_seed = seed; + + // If seed is empty string, generate a new one + if (this->_seed.empty()) + { + this->_seed = tphdr::seedgen::seed::GenerateSeed(); + } + } + // Special handling for Plandomizer + else if (settingName == "Plandomizer") + { + const auto& plandomizer = settingNode.second.as(false); + this->_isUsingPlandomizer = plandomizer; + } + } + + // Loop through all preference fields + bool rewritePreferences = false; + auto preferencesTree = LoadYAML(preferencesPath); + for (const auto& preferenceNode : preferencesTree) + { + const auto& preferenceName = preferenceNode.first.as(); + // Insert the preference if it's in the info map + if (settingInfoMap->contains(preferenceName)) + { + auto& preferenceInfo = settingInfoMap->at(preferenceName); + auto preferenceOption = preferenceNode.second.as(); + + // If the option doesn't exist, revert to default and rewrite later if necessary + if (preferenceInfo->GetIndexOfOption(preferenceOption) == -1) + { + tphdr::utility::platform::Log(std::string("Preference \"") + preferenceName + " has no option \"" + + preferenceOption + "\". Reverting to default \"" + + preferenceInfo->GetDefaultOption() + "\""); + preferenceOption = preferenceInfo->GetDefaultOption(); + rewriteSettings = true; + } + + settings.InsertSetting(preferenceName, + tphdr::seedgen::settings::Setting(preferenceInfo.get(), preferenceOption)); + } + else if (preferenceName == "Game Base Path") + { + const auto& gameBasePath = preferenceNode.second.as(); + this->_gameBasePath = gameBasePath; + } + else if (preferenceName == "Output Path") + { + const auto& outputPath = preferenceNode.second.as(); + this->_outputPath = outputPath; + } + else if (preferenceName == "Plandomizer Path") + { + const auto& plandomizerPath = preferenceNode.second.as(); + this->_plandomizerPath = plandomizerPath; + } + } + + // Add in any missing settings/preferences + for (auto& [settingName, settingInfo] : *settingInfoMap) + { + if (!settings.GetMap().contains(settingName)) + { + settings.InsertSetting(settingName, + tphdr::seedgen::settings::Setting(settingInfo.get(), settingInfo->GetDefaultOption())); + tphdr::utility::platform::Log(std::string("Added missing setting \"") + settingName + "\""); + if (settingInfo->GetType() == tphdr::seedgen::settings::Type::STANDARD) + { + rewriteSettings = true; + } + else if (settingInfo->GetType() == tphdr::seedgen::settings::Type::PREFERENCE) + { + rewritePreferences = true; + } + } + } + if (!settingsTree["Seed"]) + { + this->_seed = tphdr::seedgen::seed::GenerateSeed(); + tphdr::utility::platform::Log("Seed is missing. Generated new seed."); + rewriteSettings = true; + } + if (!settingsTree["Plandomizer"] || !settingsTree["Generate Spoiler Log"] || !settingsTree["Starting Inventory"] || + !settingsTree["Excluded Locations"] || !settingsTree["Mixed Entrance Pools"]) + { + rewriteSettings = true; + } + if (!preferencesTree["Game Base Path"] || !preferencesTree["Output Path"] || !preferencesTree["Plandomizer Path"]) + { + rewritePreferences = true; + } + + // Rewrite files if deemed necessary + if (allowRewrite && rewriteSettings) + { + tphdr::utility::platform::Log(std::string("Rewriting ") + settingsPath.generic_string()); + this->WriteSettingsToFile(settingsPath); + } + if (allowRewrite && rewritePreferences) + { + tphdr::utility::platform::Log(std::string("Rewriting ") + preferencesPath.generic_string()); + this->WritePreferencesToFile(preferencesPath); + } + } + + YAML::Node Config::SettingsToYaml() + { + YAML::Node out; + for (auto& settings : this->_settingsList) + { + out["Seed"] = this->_seed; + out["Plandomizer"] = this->_isUsingPlandomizer; + out["Generate Spoiler Log"] = this->_isGeneratingSpoilerLog; + + // Sort settings by id to keep relevant settings close together in the settings file + std::list sortedNames = {}; + for (auto& [settingName, setting] : settings.GetMap()) + { + sortedNames.push_back(settingName); + } + sortedNames.sort( + [&](const auto& a, const auto& b) + { return settings.GetMap().at(a).GetInfo()->GetID() < settings.GetMap().at(b).GetInfo()->GetID(); }); + + for (const auto& settingName : sortedNames) + { + auto& setting = settings.GetMap().at(settingName); + if (setting.GetInfo()->GetType() == tphdr::seedgen::settings::Type::STANDARD) + { + out[settingName] = setting.GetCurrentOption(); + } + } + + out["Starting Inventory"] = std::map(); + for (const auto& [itemName, count] : settings.GetStartingInventory()) + { + out["Starting Inventory"][itemName] = count; + } + + out["Excluded Locations"] = std::list(); + for (const auto& locationName : settings.GetExcludedLocations()) + { + out["Excluded Locations"].push_back(locationName); + } + + out["Mixed Entrance Pools"] = std::list>(); + int i = 0; + for (const auto& pool : settings.GetMixedEntrancePools()) + { + out["Mixed Entrance Pools"].push_back({}); + for (const auto& type : pool) + { + out["Mixed Entrance Pools"][i].push_back(type); + } + i += 1; + } + } + + return out; + } + + YAML::Node Config::PreferencesToYaml() + { + YAML::Node out; + for (auto& settings : this->_settingsList) + { + out["Game Base Path"] = this->_gameBasePath.generic_string(); + out["Output Path"] = this->_outputPath.generic_string(); + out["Plandomizer Path"] = this->_plandomizerPath.generic_string(); + for (auto& [settingName, setting] : settings.GetMap()) + { + if (setting.GetInfo()->GetType() == tphdr::seedgen::settings::Type::PREFERENCE) + { + out[settingName] = setting.GetCurrentOption(); + } + } + } + + return out; + } + + void Config::WriteSettingsToFile(const fspath& settingsPath) + { + std::ofstream outputFile(settingsPath); + if (outputFile.is_open() == false) + { + throw std::runtime_error("Unable to open settings file \"" + settingsPath.generic_string() + "\" for writing."); + } + + outputFile << this->SettingsToYaml(); + outputFile.close(); + } + + void Config::WritePreferencesToFile(const fspath& preferencesPath) + { + std::ofstream outputFile(preferencesPath); + if (outputFile.is_open() == false) + { + throw std::runtime_error("Unable to open preferences file \"" + preferencesPath.generic_string() + + "\" for writing."); + } + + outputFile << this->PreferencesToYaml(); + outputFile.close(); + } + + std::string Config::GetHash() + { + if (this->_hash.empty()) + { + this->_hash = tphdr::seedgen::seed::GenerateHash(); + } + + return this->_hash; + } + + int WriteDefaultSettings(const fspath& settingsPath) + { + tphdr::utility::platform::Log("Creating Default Settings"); + std::ofstream settingsFile(settingsPath); + if (settingsFile.is_open() == false) + { + LOG_TO_ERROR("Could not open file to write default settings."); + return 1; + } + + auto settingInfoMap = tphdr::seedgen::settings::GetAllSettingsInfo(); + + YAML::Node root; + root["Seed"] = tphdr::seedgen::seed::GenerateSeed(); + root["Plandomizer"] = false; + root["Generate Spoiler Log"] = true; + // TODO: root["Permalink"] = tphdr::seedgen::permalink::GeneratePermalink(); + for (const auto& [name, info] : *settingInfoMap) + { + if (info->GetType() == tphdr::seedgen::settings::Type::STANDARD) + { + root[name] = info->GetDefaultOption(); + } + } + root["Starting Inventory"] = std::map(); + root["Excluded Locations"] = std::list(); + root["Mixed Entrance Pools"] = std::list>(); + + settingsFile << root; + settingsFile.close(); + + return 0; + } + + int WriteDefaultPreferences(const fspath& preferencesPath) + { + tphdr::utility::platform::Log("Creating Default Preferences"); + std::ofstream preferencesFile(preferencesPath); + if (preferencesFile.is_open() == false) + { + LOG_TO_ERROR("Could not open file to write default preferences."); + return 1; + } + + auto settingInfoMap = tphdr::seedgen::settings::GetAllSettingsInfo(); + + YAML::Node root; + root["Game Base Path"] = ""; + root["Output Path"] = ""; + root["Plandomizer Path"] = ""; + for (const auto& [name, info] : *settingInfoMap) + { + if (info->GetType() == tphdr::seedgen::settings::Type::PREFERENCE) + { + root[name] = info->GetDefaultOption(); + } + } + preferencesFile << root; + preferencesFile.close(); + + return 0; + } + + int SeedRNG(Config& config, + const bool& resolveNonStandardRandom /* = false */, + const bool& ignoreInvalidPlandomizer /* = true */) + { + // Seed with system time incase we have to choose random preferences during seeding + auto seed = static_cast(std::random_device {}()); + tphdr::utility::random::RandomInit(seed); + + // Seed the rng using a combination of the seed and standard settings + std::string hashStr = config.GetSeed(); + for (auto& settings : config.GetSettingsList()) + { + for (auto& [settingName, setting] : settings.GetMap()) + { + if (setting.GetInfo()->GetType() == tphdr::seedgen::settings::Type::STANDARD) + { + hashStr += settingName + setting.GetCurrentOption(); + } + else if (resolveNonStandardRandom) + { + setting.ResolveIfRandom(); + } + } + + // Special handling for other settings + for (const auto& [itemName, count] : settings.GetStartingInventory()) + { + hashStr += itemName + std::to_string(count); + } + + for (const auto& locationName : settings.GetExcludedLocations()) + { + hashStr += locationName; + } + + for (const auto& pool : settings.GetMixedEntrancePools()) + { + for (const auto& type : pool) + { + hashStr += type; + } + } + } + + // Change the seed if we're using plandomizer + if (config.IsUsingPlandomizer()) + { + std::string plandomizerContents; + auto retVal = tphdr::utility::file::GetContents(config.GetPlandomizerPath(), plandomizerContents); + if (!ignoreInvalidPlandomizer && retVal != 0) + { + LOG_TO_ERROR("Could not read plandomizer file at \"" + config.GetPlandomizerPath().generic_string() + "\""); + return 1; + } + hashStr += plandomizerContents; + } + + // Change the seed if we're generating a spoiler log + if (config.IsGeneratingSpoilerLog()) + { + hashStr += "Spoiler Log: True"; + } + + const size_t integerSeed = zng_crc32(0L, reinterpret_cast(hashStr.data()), hashStr.length()); + tphdr::utility::random::RandomInit(integerSeed); + + return 0; + } +} // namespace tphdr::seedgen::config diff --git a/src/dusk/randomizer/seedgen/config.hpp b/src/dusk/randomizer/seedgen/config.hpp new file mode 100644 index 0000000000..9c28f4b1c4 --- /dev/null +++ b/src/dusk/randomizer/seedgen/config.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include + +#include "settings.hpp" +#include "../utility/path.hpp" + +// forward declaration +namespace YAML +{ + class Node; +} + +namespace tphdr::seedgen::config +{ + enum struct [[nodiscard]] ConfigError + { + NONE = 0, + COULD_NOT_OPEN, + MISSING_KEY, + DIFFERENT_FILE_VERSION, + DIFFERENT_RANDO_VERSION, + BAD_PERMALINK, + INVALID_VALUE, + MODEL_ERROR, + UNKNOWN, + COUNT + }; + + enum struct [[nodiscard]] PermalinkError + { + NONE = 0, + EMPTY, + BAD_ENCODING, + MISSING_PARTS, + INVALID_VERSION, + INCORRECT_LENGTH, + COULD_NOT_READ, + UNHANDLED_OPTION, + COULD_NOT_LOAD_LOCATIONS, + UNKNOWN, + COUNT + }; + + class Config + { + public: + Config() = default; + + fspath GetGameBasePath() const { return this->_gameBasePath; } + fspath GetOutputPath() const { return this->_outputPath; } + fspath GetPlandomizerPath() const { return this->_plandomizerPath; } + void SetSeed(const std::string& newSeed) { this->_seed = newSeed; } + std::string GetSeed() const { return this->_seed; } + auto& GetSettingsList() { return this->_settingsList; } + bool IsUsingPlandomizer() const { return this->_isUsingPlandomizer; } + bool IsGeneratingSpoilerLog() const { return this->_isGeneratingSpoilerLog; } + + void resetDefaultSettings(); + void resetDefaultPreferences(const bool& paths = false); + void LoadFromFile(const fspath& filePath, + const fspath& preferencesPath, + const bool& createNewIfNotFound = true, + const bool& allowRewrite = true); + YAML::Node SettingsToYaml(); + YAML::Node PreferencesToYaml(); + void WriteSettingsToFile(const fspath& filePath); + void WritePreferencesToFile(const fspath& preferencesPath); + void WriteToFile(const fspath& filePath, const fspath& preferencesPath) const; + + PermalinkError loadPermalink(std::string b64permalink); + std::string getPermalink(const bool& internal = false) const; + + /** + * @brief Returns the hash for the config. If the hash is an empty string, then a new one will be generated. + * + * @return The hash as a string + */ + std::string GetHash(); + + private: + fspath _gameBasePath; + fspath _outputPath; + fspath _plandomizerPath; + + std::string _seed; + std::string _hash; + std::list _settingsList; + bool _isUsingPlandomizer = false; + bool _isGeneratingSpoilerLog = true; + + // bool _converted = false; + // bool _updated = false; + // bool _configSet = false; + }; + + int WriteDefaultSettings(const fspath& filePath); + int WriteDefaultPreferences(const fspath& filePath); + + std::string ConfigErrorGetName(ConfigError err); + std::string PermalinkErrorGetName(ConfigError err); + + int SeedRNG(Config& config, const bool& resolvePreferenceRandom = false, const bool& ignoreInvalidPlandomizer = true); +} // namespace tphdr::seedgen::config diff --git a/src/dusk/randomizer/seedgen/packed_bits.hpp b/src/dusk/randomizer/seedgen/packed_bits.hpp new file mode 100644 index 0000000000..9720709d01 --- /dev/null +++ b/src/dusk/randomizer/seedgen/packed_bits.hpp @@ -0,0 +1,105 @@ +#pragma once + +// Packed Bits classes copied from the original Wind Waker Randomizer +class PackedBitsWriter +{ + public: + PackedBitsWriter() = default; + ~PackedBitsWriter() = default; + + uint8_t bits_left_in_byte = 8; + size_t current_byte = 0; + std::vector bytes = {}; + + template + void write(T value, size_t length) + { + size_t bits_to_read = 0; + while (length > 0) + { + if (length >= bits_left_in_byte) + { + bits_to_read = bits_left_in_byte; + } + else + { + bits_to_read = length; + } + + size_t mask = (1 << bits_to_read) - 1; + current_byte |= (value & mask) << (8 - bits_left_in_byte); + + bits_left_in_byte -= bits_to_read; + length -= bits_to_read; + value >>= bits_to_read; + + if (bits_left_in_byte > 0) + { + continue; + } + + flush(); + } + } + + void flush() + { + bytes.push_back(current_byte); + current_byte = 0; + bits_left_in_byte = 8; + } +}; + +class PackedBitsReader +{ + public: + PackedBitsReader(const std::vector& bytes_): bytes(bytes_) {} + ~PackedBitsReader() = default; + + size_t current_bit_index = 0; + size_t current_byte_index = 0; + std::vector bytes = {}; + + size_t read(size_t length) + { + size_t bits_read = 0; + size_t value = 0; + size_t bits_left_to_read = length; + + while (bits_read != length) + { + size_t bits_to_read = 0; + if (bits_left_to_read > 8) + { + bits_to_read = 8; + } + else + { + bits_to_read = bits_left_to_read; + } + + if (bits_to_read + current_bit_index > 8) + { + bits_to_read = 8 - current_bit_index; + } + + size_t mask = ((1 << bits_to_read) - 1) << current_bit_index; + + if (current_byte_index >= bytes.size()) + { + return static_cast(-1); + } + + size_t current_byte = bytes[current_byte_index]; + value = ((current_byte & mask) >> current_bit_index) << bits_read | value; + + current_bit_index += bits_to_read; + current_byte_index += current_bit_index >> 3; + current_bit_index %= 8; + bits_left_to_read -= bits_to_read; + bits_read += bits_to_read; + } + + return value; + } +}; diff --git a/src/dusk/randomizer/seedgen/seed.cpp b/src/dusk/randomizer/seedgen/seed.cpp new file mode 100644 index 0000000000..f547a8c189 --- /dev/null +++ b/src/dusk/randomizer/seedgen/seed.cpp @@ -0,0 +1,113 @@ +#include "seed.hpp" + +#include "../utility/random.hpp" + +#include +#include + +namespace tphdr::seedgen::seed +{ + static const std::vector nouns = { + "Aeralfos", "Agitha", "Ant", "Argorok", "Armos", "Ashei", "Auru", "BackSlice", "Bari", + "Barnes", "Beamos", "Beth", "BigBaba", "Blizzeta", "Bo", "Bokoblin", "Bombfish", "Borville", + "Bulblin", "Butterfly", "CastleTown", "Charlo", "Cheese", "Chilfos", "Chu", "Chudley", "Clawshot", + "Colin", "Coro", "Cucco", "Dangoro", "Darbus", "Darkhammer", "Darknut", "Dayfly", "DeathSword", + "DekuToad", "Dodongo", "Dragonfly", "Dynalfos", "Eldin", "Epona", "Fado", "Fairy", "Falbi", + "Fanadi", "Faron", "Freezard", "Fyer", "Ganondorf", "Gengle", "GhoulRat", "Gibdo", "Goron", + "GreatSpin", "Greengill", "Guay", "Hanch", "Hawkeye", "Helmasaur", "Hena", "Hornet", "HorseGrass", + "Hylian", "Jaggle", "Jovani", "JumpStrike", "Keese", "Kili", "Ladybug", "Lanayru", "Lantern", + "Leever", "Link", "Lizalfos", "Louise", "Luda", "Malo", "Malver", "Mantis", "Midna", + "Misha", "Moldorm", "Morpheel", "Ooccoo", "Ordona", "Pergie", "Phasmid", "Plumm", "Poe", + "Postman", "Pumpkin", "Puppet", "Purdy", "Ralis", "Reekfish", "Renado", "Rupee", "Rusl", + "Rutela", "Sage", "Sera", "Shad", "ShellBlade", "Sketch", "SkullKid", "Skulltula", "SkyBook", + "Snail", "Snowpeak", "Soal", "Soldier", "Spinner", "Stalfos", "Stallord", "Talo", "Tektite", + "Telma", "Temple", "TileWorm", "Toadpoli", "Trill", "Twilight", "Uli", "WolfLink", "Zant", + "Zelda", "Zora"}; + + static const std::vector adjectives = { + "Abnormal", "Absent", "Absolute", "Abstract", "Absurd", "Accurate", "Active", "Actual", + "Adjacent", "Aesthetic", "Aggressive", "Alert", "Alien", "Alternate", "Amazing", "Ambitious", + "Amusing", "Ancient", "Angry", "Anxious", "Apparent", "Artistic", "Astute", "Atomic", + "Atrocious", "Attractive", "Authentic", "Average", "Awful", "Awkward", "Bad", "Bashful", + "Basic", "Beautiful", "Big", "Bitter", "Bizarre", "Blue", "Bold", "Brainy", + "Brave", "Bright", "Brilliant", "Busy", "Callous", "Calm", "Capable", "Careful", + "Casual", "Cautious", "Central", "Cheap", "Cheerful", "Chemical", "Chilly", "Chronic", + "Chummy", "Circular", "Civil", "Classic", "Clean", "Clever", "Clinical", "Clumsy", + "Coastal", "Cognitive", "Coherent", "Cold", "Colorful", "Comical", "Commercial", "Common", + "Compact", "Competent", "Complete", "Complex", "Concise", "Concrete", "Confident", "Confused", + "Consistent", "Constant", "Contrary", "Cool", "Corny", "Corporate", "Correct", "Cosmic", + "Costly", "Courteous", "Cranky", "Crazy", "Creative", "Credible", "Creepy", "Criminal", + "Critical", "Curious", "Current", "Custom", "Cute", "Daily", "Damp", "Dangerous", + "Dapper", "Dark", "Deadly", "Decent", "Decisive", "Defeated", "Defensive", "Defiant", + "Delicate", "Delightful", "Desperate", "Detached", "Determined", "Different", "Difficult", "Digital", + "Diligent", "Disastrous", "Disgusted", "Distant", "Disturbed", "Divine", "Dizzy", "Dominant", + "Double", "Doubtful", "Dramatic", "Dreadful", "Droll", "Dull", "Dynamic", "Early", + "Effective", "Elated", "Elderly", "Electric", "Elegant", "Empty", "Endless", "Enormous", + "Entire", "Equal", "Essential", "Eternal", "Evil", "Excellent", "Exotic", "Expensive", + "Explicit", "Extreme", "Factual", "Faithful", "False", "Famous", "Fancy", "Fantastic", + "Fast", "Fatal", "Favorite", "Fellow", "Fierce", "Final", "Financial", "Foolish", + "Formal", "Fortified", "Fortunate", "Frantic", "Free", "Frenzied", "Fresh", "Friendly", + "Functional", "Funny", "Furious", "Future", "Galactic", "Generous", "Genial", "Gentle", + "Genuine", "Giant", "Glad", "Glass", "Glittery", "Gloomy", "Glorious", "Golden", + "Good", "Gothic", "Graceful", "Gradual", "Grand", "Great", "Grim", "Gross", + "Grumpy", "Guilty", "Handsome", "Happy", "Harmful", "Harsh", "Healthy", "Hearty", + "Heavy", "Helpful", "Historic", "Honest", "Hostile", "Huge", "Hungry", "Hyper", + "Impartial", "Implicit", "Important", "Impressive", "Indirect", "Indoor", "Infinite", "Inherent", + "Initial", "Inner", "Innocent", "Inspiring", "Instant", "Intense", "Internal", "Inventive", + "Jarring", "Jealous", "Jolly", "Joyful", "Junior", "Kind", "Kooky", "Late", + "Lazy", "Lesser", "Lethargic", "Light", "Likable", "Linear", "Linguistic", "Liquid", + "Little", "Lively", "Local", "Logical", "Lonely", "Loud", "Lovely", "Loyal", + "Lucky", "Lunar", "Mad", "Magical", "Magnetic", "Mainstream", "Majestic", "Major", + "Malicious", "Manual", "Marine", "Marvellous", "Massive", "Maximum", "Mean", "Meaningful", + "Medical", "Medieval", "Medium", "Mellow", "Mental", "Mere", "Middle", "Mighty", + "Mild", "Minimal", "Mobile", "Modest", "Monthly", "Moral", "Motionless", "Muddy", + "Mundane", "Musical", "Mutual", "Nasty", "Natural", "Nearby", "Neat", "Negative", + "Nerdy", "Nervous", "Neutral", "Nice", "Nimble", "Noble", "Noisy", "Notable", + "Objective", "Obnoxious", "Obscure", "Obvious", "Odd", "Offensive", "Official", "Okay", + "Old", "Only", "Opposite", "Optical", "Optional", "Organic", "Organized", "Outdoor", + "Painful", "Paper", "Parallel", "Past", "Patient", "Peaceful", "Peppy", "Perfect", + "Permanent", "Persistent", "Personal", "Petty", "Pink", "Plain", "Platinum", "Plausible", + "Pleasant", "Polite", "Popular", "Portable", "Positive", "Potential", "Powerful", "Practical", + "Precious", "Pretty", "Previous", "Primitive", "Private", "Probable", "Productive", "Profound", + "Prominent", "Proper", "Protective", "Public", "Pure", "Purple", "Purposeful", "Puzzled", + "Quick", "Quiet", "Quirky", "Random", "Rapid", "Rational", "Recent", "Redundant", + "Refined", "Regretful", "Regular", "Relaxed", "Relevant", "Remote", "Reserved", "Resident", + "Responsive", "Retail", "Rigid", "Rival", "Romantic", "Rotten", "Royal", "Rubber", + "Rude", "Sacred", "Sad", "Safe", "Scary", "Seasonal", "Secret", "Secured", + "Selective", "Senior", "Sensible", "Serious", "Severe", "Shady", "Shallow", "Sharp", + "Sheer", "Shiny", "Short", "Sick", "Sideways", "Silent", "Silly", "Silver", + "Similar", "Simple", "Sincere", "Skilled", "Skittish", "Sleepy", "Slow", "Small", + "Smart", "Smug", "Snazzy", "Snooty", "Solar", "Solid", "Somber", "Spare", + "Specific", "Spiteful", "Splendid", "Spooky", "Spotless", "Spry", "Square", "Stable", + "Standard", "Startled", "Static", "Steady", "Stern", "Stone", "Stylish", "Subsequent", + "Successful", "Sudden", "Suitable", "Sunny", "Super", "Supportive", "Surplus", "Suspicious", + "Sweet", "Symbolic", "Talkative", "Tall", "Tearful", "Technical", "Terrible", "Thankful", + "Thoughtful", "Thrilled", "Tidy", "Tired", "Total", "Tough", "Toxic", "Tragic", + "Tremendous", "Trivial", "Tropical", "Troubled", "Truthful", "Typical", "Ultimate", "Ultra", + "Unaware", "Uncertain", "Unfair", "Unforeseen", "Uniform", "Unique", "Unknown", "Unlawful", + "Unlikely", "Unreal", "Upbeat", "Upset", "Urban", "Useful", "Usual", "Vague", + "Valid", "Verbal", "Vertical", "Vicious", "Vigorous", "Villainous", "Virtual", "Visible", + "Vital", "Vivid", "Warm", "Weekly", "Weird", "Wholesome", "Wicked", "Wise", + "Wistful", "Witty", "Wonderful", "Wooden", "Worried", "Wrong", "Young", "Zany"}; + + std::string GenerateSeed() + { + std::string adjective1, adjective2, noun; + + adjective1 = adjectives[rand() % adjectives.size()]; + adjective2 = adjectives[rand() % adjectives.size()]; + noun = nouns[rand() % nouns.size()]; + + return adjective1 + adjective2 + noun; + } + + std::string GenerateHash() + { + std::string noun1, noun2, noun3; + noun1 = tphdr::utility::random::RandomElement(nouns); + noun2 = tphdr::utility::random::RandomElement(nouns); + noun3 = tphdr::utility::random::RandomElement(nouns); + + return noun1 + " " + noun2 + " " + noun3; + } +} // namespace tphdr::seedgen::seed diff --git a/src/dusk/randomizer/seedgen/seed.hpp b/src/dusk/randomizer/seedgen/seed.hpp new file mode 100644 index 0000000000..4b3a7a4a52 --- /dev/null +++ b/src/dusk/randomizer/seedgen/seed.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "config.hpp" + +namespace tphdr::seedgen::seed +{ + /** + * @brief Generates a random sequence of 3 words to be used as a seed. + * + * @return The sequence of words as a string + */ + std::string GenerateSeed(); + + /** + * @brief Generates a random sequence of 3 nouns to be used as a verification hash. + * + * @return The sequence of words as a string + */ + std::string GenerateHash(); + + std::string HashForConfig(const tphdr::seedgen::config::Config& config); +} // namespace tphdr::seedgen::seed diff --git a/src/dusk/randomizer/seedgen/settings.cpp b/src/dusk/randomizer/seedgen/settings.cpp new file mode 100644 index 0000000000..c726995d09 --- /dev/null +++ b/src/dusk/randomizer/seedgen/settings.cpp @@ -0,0 +1,328 @@ +#include "settings.hpp" + +#include "../utility/log.hpp" +#include "../utility/platform.hpp" +#include "../utility/container.hpp" +#include "../utility/file.hpp" +#include "../utility/random.hpp" +#include "../utility/string.hpp" +#include "../utility/yaml.hpp" + +#include +#include +#include +#include + +namespace tphdr::seedgen::settings +{ + + Type TypeFromStr(const std::string& str) + { + std::unordered_map types = {{"Standard", Type::STANDARD}, {"Preference", Type::PREFERENCE}}; + + if (!types.contains(str)) + { + return Type::INVALID; + } + + return types.at(str); + } + + SettingInfo::SettingInfo(const int& id, + const std::string& name, + const Type& type, + const std::vector& options, + const std::vector& descriptions, + const int& defaultOptionIndex, + const bool& hasRandomOption, + const int& randomOptionIndex, + const int& randomLow, + const int& randomHigh, + const bool& trackerImportant): + _id(id), + _name(name), + _type(type), + _options(options), + _descriptions(descriptions), + _defaultOptionIndex(defaultOptionIndex), + _hasRandomOption(hasRandomOption), + _randomOptionIndex(randomOptionIndex), + _randomLow(randomLow), + _randomHigh(randomHigh), + _trackerImportant(trackerImportant) + { + // The logic expression of a setting replaces spaces with underscores, + // and removes apostraphes and parenthesis + auto logicName = name; + std::replace(logicName.begin(), logicName.end(), ' ', '_'); + tphdr::utility::str::Erase(logicName, "'", ")", "("); + this->_logicName = logicName; + + // Same for logic expressions of options for this setting + for (const auto& option : options) + { + auto logicOption = option; + std::replace(logicOption.begin(), logicOption.end(), ' ', '_'); + tphdr::utility::str::Erase(logicOption, "'", ")", "("); + this->_logicOptions.push_back(logicOption); + } + } + + std::string SettingInfo::GetDefaultOption() const + { + return this->_options[this->_defaultOptionIndex]; + } + + int SettingInfo::GetIndexOfOption(const std::string& option) const + { + return tphdr::utility::container::GetIndex(this->_options, option); + } + + std::string SettingInfo::GetRandomOption() const + { + return this->_options.at(this->_randomOptionIndex); + } + + Setting::Setting(SettingInfo* info, const std::string& option): _info(info) + { + this->_currentOptionIndex = info->GetIndexOfOption(option); + } + + void Setting::SetCurrentOption(const int& newOptionIndex) + { + if (newOptionIndex >= this->GetInfo()->GetOptions().size()) + { + throw std::runtime_error(std::string("Index ") + std::to_string(newOptionIndex) + + " is out of bounds for setting \"" + this->GetInfo()->GetName() + "\""); + } + this->_currentOptionIndex = newOptionIndex; + } + + void Setting::SetCurrentOption(const std::string& optionName) + { + int optionNameIndex = this->GetInfo()->GetIndexOfOption(optionName); + if (optionNameIndex == -1) + { + throw std::runtime_error(std::string("\"") + optionName + "\" is not a valid option for setting \"" + + this->GetInfo()->GetName() + "\""); + } + this->SetCurrentOption(optionNameIndex); + } + + std::string Setting::GetCurrentOption() const + { + return this->_info->GetOptions()[this->_currentOptionIndex]; + } + + void Setting::ResolveIfRandom() + { + if (this->GetCurrentOptionIndex() == this->GetInfo()->GetRandomOptionIndex()) + { + this->_isUsingRandomOption = true; + auto randomOption = + tphdr::utility::random::Random(this->GetInfo()->GetRandomLow(), this->GetInfo()->GetRandomHigh()); + this->SetCurrentOption(randomOption); + LOG_TO_DEBUG("Chose \"" + this->GetInfo()->GetOptions()[randomOption] + " as random option for setting \"" + + this->GetInfo()->GetName()); + } + } + + bool Setting::operator==(const char* optionName) const + { + int optionNameIndex = this->GetInfo()->GetIndexOfOption(optionName); + if (optionNameIndex == -1) + { + throw std::runtime_error(std::string("\"") + optionName + "\" is not a valid option for setting \"" + + this->GetInfo()->GetName() + "\""); + } + return this->_currentOptionIndex == optionNameIndex; + } + + bool Setting::operator!=(const char* optionName) const + { + return !(*this == optionName); + } + + bool Setting::operator>=(const char* optionName) const + { + int optionNameIndex = this->GetInfo()->GetIndexOfOption(optionName); + if (optionNameIndex == -1) + { + throw std::runtime_error(std::string("\"") + optionName + "\" is not a valid option for setting \"" + + this->GetInfo()->GetName() + "\""); + } + return this->_currentOptionIndex >= optionNameIndex; + } + + void Settings::InsertSetting(const std::string& settingName, Setting setting) + { + this->_map.emplace(settingName, setting); + } + + void Settings::AddStartingItem(const std::string& itemName, const int& count /*= 1*/) + { + if (!this->_startingInventory.contains(itemName)) + { + this->_startingInventory.emplace(itemName, 0); + } + this->_startingInventory.at(itemName) += count; + } + + void Settings::AddExcludedLocation(const std::string& locationName) + { + this->_excludedLocations.insert(locationName); + } + + void Settings::AddMixedPool(const std::list& pool) + { + this->_mixedEntrancePools.push_back(pool); + } + + SettingInfoMap_t* GetAllSettingsInfo() + { + static std::unique_ptr settingInfoMap = std::make_unique(); + + // If we haven't loaded in our setting info yet, do so now + if (settingInfoMap->empty()) + { + settingInfoMap = LoadAllSettingsInfo(); + } + + return settingInfoMap.get(); + } + + std::unique_ptr LoadAllSettingsInfo() + { + std::unique_ptr settingInfoMap = std::make_unique(); + auto filepath = DATA_PATH "settings_list.yaml"; + // check if we can open the file before parsing because exceptions won't work on console + std::ifstream file(filepath); + if (!file.is_open()) + { + throw std::runtime_error(std::string("Could not open ") + filepath); + } + file.close(); + + auto settingsDataTree = LoadYAML(filepath); + + // Process all nodes of the yaml file. Each node contains one setting + int settingIdCounter = 0; + for (const auto& settingNode : settingsDataTree) + { + // Check to make sure all required fields are present + const auto requiredFields = {"Name", "Default Option", "Options"}; + for (const auto& field : requiredFields) + { + if (!settingNode[field]) + { + throw std::runtime_error(std::string("Field \"") + field + "\" is missing from settings list node:\n" + + YAML::Dump(settingNode)); + } + } + + // Required Fields + const auto& name = settingNode["Name"].as(); + const auto& defaultOption = settingNode["Default Option"].as(); + std::vector options = {}; + std::vector descriptions = {}; + for (const auto& optionNodes : settingNode["Options"]) + { + for (const auto& optionNode : optionNodes) + { + const auto& option = optionNode.first.as(); + const auto& description = optionNode.second.as(); + + // If we're specifying a range, then include all numbers in the range + if (tphdr::utility::str::Contains(option, "-")) + { + // Fill in all the options between the lower and upper bounds + auto ops = tphdr::utility::str::Split(option, '-'); + int lowerBound = std::stoi(ops[0]); + int upperBound = std::stoi(ops[1]); + for (auto i = lowerBound; i <= upperBound; i++) + { + options.push_back(std::to_string(i)); + descriptions.push_back(description); + } + } + else + { + options.push_back(option); + descriptions.push_back(description); + } + } + } + + // Calculate default option index + auto defaultOptionIndex = tphdr::utility::container::GetIndex(options, defaultOption); + if (defaultOptionIndex == -1) + { + throw std::runtime_error(std::string("Default Option \"") + defaultOption + "\" is not defined for setting \"" + + name + "\""); + } + + // Optional fields. If found, use the field value. If not found, use a default + const auto& type = TypeFromStr(settingNode["Type"] ? settingNode["Type"].as() : "Standard"); + if (type == Type::INVALID) + { + throw std::runtime_error(std::string("Unknown setting type \"") + settingNode["Type"].as() + + "\" for setting \"" + name + "\""); + } + + const auto& trackerImportant = + settingNode["Tracker Important"] ? settingNode["Tracker Important"].as() : false; + const auto& hasRandomOption = + settingNode["Autogenerate Random"] ? settingNode["Autogenerate Random"].as() : true; + const auto& randomAlias = settingNode["Random Alias"] ? settingNode["Random Alias"].as() : "Random"; + + int randomLow = 0; + int randomHigh = options.size() - 1; + if (settingNode["Random Low"]) + { + auto randomLowStr = settingNode["Random Low"].as(); + randomLow = tphdr::utility::container::GetIndex(options, randomLowStr); + if (randomLow == -1) + { + throw std::runtime_error(std::string("Random Low Option \"") + randomLowStr + + "\" is not defined for setting \"" + name + "\""); + } + } + if (settingNode["Random high"]) + { + auto randomHighStr = settingNode["Random High"].as(); + randomHigh = tphdr::utility::container::GetIndex(options, randomHighStr); + if (randomHigh == -1) + { + throw std::runtime_error(std::string("Random High Option \"") + randomHighStr + + "\" is not defined for setting \"" + name + "\""); + } + } + + // Generate the random option if it's not already there + if (hasRandomOption && tphdr::utility::container::GetIndex(options, randomAlias) != -1) + { + options.push_back(randomAlias); + descriptions.push_back("A random option will be chosen"); + } + + int randomOptionIndex = tphdr::utility::container::GetIndex(options, randomAlias); + + // Insert the data for the setting + auto info = std::make_unique(settingIdCounter++, + name, + type, + options, + descriptions, + defaultOptionIndex, + hasRandomOption, + randomOptionIndex, + randomLow, + randomHigh, + trackerImportant); + settingInfoMap->emplace(name, std::move(info)); + } + + return std::move(settingInfoMap); + } + +}; // namespace tphdr::seedgen::settings diff --git a/src/dusk/randomizer/seedgen/settings.hpp b/src/dusk/randomizer/seedgen/settings.hpp new file mode 100644 index 0000000000..668088fb51 --- /dev/null +++ b/src/dusk/randomizer/seedgen/settings.hpp @@ -0,0 +1,213 @@ +#pragma once + +#include "../utility/container.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tphdr::seedgen::settings +{ + class SettingInfo; + using SettingInfoMap_t = std::unordered_map>; + + /** + * @brief Enum for different types of settings. + * + * Standard settings will affect the rng used for seed generation. + * Preference settings will not affect the rng used for seed generation. + */ + enum Type + { + INVALID = 0, + STANDARD, + PREFERENCE, + }; + + /** + * @brief Takes a string representation of a Type and returns the + * associated enum value. + * + * @param str The string representation of a Type. + * @return The associated enum value for the passed in type. + */ + Type TypeFromStr(const std::string& str); + + /** + * @brief SettingInfo holds static info about a setting. + * + * Data should only ever be set once when reading from settings.yaml + * and creating appropriate info entries. + * + */ + class SettingInfo + { + public: + SettingInfo(const int& id, + const std::string& name, + const Type& type, + const std::vector& options, + const std::vector& descriptions, + const int& defaultOptionIndex, + const bool& hasRandomOption, + const int& randomOptionIndex, + const int& randomLow, + const int& randomHigh, + const bool& trackerImportant); + + int GetID() const { return this->_id; } + + /** + * @brief Returns the setting's name as displayed in a UI. + */ + std::string GetName() const { return this->_name; } + + /** + * @brief Returns the type of the setting. + */ + Type GetType() const { return this->_type; } + + /** + * @brief Returns a vector of strings of the setting's available options. + */ + std::vector GetOptions() const { return this->_options; } + + /** + * @brief Returns a vector of strings of the setting's options' descriptions. + */ + std::vector GetDescriptions() const { return this->_descriptions; } + + /** + * @brief Returns the index of the default option in the options vector for the setting. + */ + int GetDefaultOptionIndex() const { return this->_defaultOptionIndex; } + + /** + * @brief Returns the string representation of the default option for the setting. + */ + std::string GetDefaultOption() const; + int GetIndexOfOption(const std::string& option) const; + bool HasRandomOption() const { return this->_hasRandomOption; } + int GetRandomOptionIndex() const { return this->_randomOptionIndex; } + std::string GetRandomOption() const; + int GetRandomLow() const { return this->_randomLow; } + int GetRandomHigh() const { return this->_randomHigh; } + bool TrackerImportant() const { return this->_trackerImportant; } + + private: + int _id = -1; + std::string _name = ""; + Type _type = INVALID; + std::vector _options = {}; + std::vector _descriptions = {}; + int _defaultOptionIndex = 0; + bool _hasRandomOption = true; + int _randomOptionIndex = 0; // The index of this setting's random option + int _randomLow = 0; // Lower bound when choosing a random option + int _randomHigh = 0; // Upper bound when choosing a random option + bool _trackerImportant = false; // Whether or not this setting can affect trackers + + // Variables that hold the setting's name and options when being checked + // in a logical requirement string. + std::string _logicName = ""; + std::vector _logicOptions = {}; + }; + + /** + * @brief Setting holds the data for a single setting in a specific world. + * + * A setting's current option index can change depending on certain + * circumstances (i.e. the user changes it, or it conflicts with another setting) + */ + class Setting + { + public: + Setting(SettingInfo* info, const std::string& option); + + void SetCurrentOption(const std::string& newOption); + void SetCurrentOption(const int& newOptionIndex); + std::string GetCurrentOption() const; + const int& GetCurrentOptionIndex() const { return this->_currentOptionIndex; } + const bool& IsUsingRandomOption() const { return this->_isUsingRandomOption; } + SettingInfo* GetInfo() const { return this->_info; } + const std::string& GetCustomOption() const { return this->_customOption; } + void SetCustomOption(const std::string& newCustomOption); + void ResolveIfRandom(); + + bool operator==(const char* optionName) const; + bool operator!=(const char* optionName) const; + bool operator>=(const char* optionName) const; + + template + bool IsAnyOf(Types... optionNames) + { + // Check to make sure all listed options exist + for (const auto& optionName : {optionNames...}) + { + if (!tphdr::utility::container::ElementInContainer(this->GetInfo()->GetOptions(), optionName)) + { + throw std::runtime_error("\"" + std::string(optionName) + "\" is not a known option for setting \"" + + this->GetInfo()->GetName() + "\""); + } + } + + // Check if any of the options are the current one + for (const auto& optionName : {optionNames...}) + { + if (optionName == this->GetCurrentOption()) + { + return true; + } + } + return false; + } + + private: + int _currentOptionIndex = -1; + bool _isUsingRandomOption = false; + SettingInfo* _info = nullptr; + std::string _customOption = ""; // For things like hex color strings + }; + + /** + * @brief Settings holds all of the settings for a specific world. + * + */ + class Settings + { + public: + Settings() = default; + + void InsertSetting(const std::string& settingName, Setting setting); + void AddStartingItem(const std::string& itemName, const int& count = 1); + void AddExcludedLocation(const std::string& locationName); + void AddMixedPool(const std::list& pool); + std::map& GetMap() { return this->_map; } + const std::map& GetStartingInventory() const { return this->_startingInventory; } + const std::set& GetExcludedLocations() const { return this->_excludedLocations; } + const std::list>& GetMixedEntrancePools() const { return this->_mixedEntrancePools; } + + private: + std::map _map = {}; + std::map _startingInventory = {}; + std::set _excludedLocations = {}; + std::list> _mixedEntrancePools = {}; + }; + + /** + * @brief Gets the map of each setting name to its info + * @return pointer to the setting info map + */ + SettingInfoMap_t* GetAllSettingsInfo(); + + /** + * @brief Reads settings_list.yaml and loads in all setting data + */ + std::unique_ptr LoadAllSettingsInfo(); + +}; // namespace tphdr::seedgen::settings diff --git a/src/dusk/randomizer/test/test.cpp b/src/dusk/randomizer/test/test.cpp new file mode 100644 index 0000000000..f43214ef19 --- /dev/null +++ b/src/dusk/randomizer/test/test.cpp @@ -0,0 +1,42 @@ +#include "test.hpp" + +#include "../logic/generate.hpp" +#include "../logic/world.hpp" +#include "../utility/string.hpp" + +#include +#include + +namespace tphdr::test::test +{ + void RunTests() + { + for (const auto& entry : std::filesystem::recursive_directory_iterator(DATA_PATH "tests/logic")) + { + if (entry.path().generic_string().ends_with("settings.yaml")) + { + auto pathFolders = tphdr::utility::str::Split(entry.path().generic_string(), '/'); + auto& testName = pathFolders[pathFolders.size() - 2]; + std::filesystem::remove(SETTINGS_PATH); + std::filesystem::copy_file(entry, SETTINGS_PATH); + + std::cout << "Testing " << testName << std::endl; + + try { + tphdr::logic::generate::GenerateWorlds(); + } + catch(const std::exception& e) { + std::cout << "Test \"" << testName << "\" failed! Failed settings saved to " << SETTINGS_PATH << std::endl; + std::cout << "Error Message:" << std::endl; + throw e; + } + + std::filesystem::remove(SETTINGS_PATH); + } + } + // Remove test preferences + std::filesystem::remove(PREFERENCES_PATH); + + std::cout << "All Settings Tests passed" << std::endl; + } +} // namespace tphdr::test::test diff --git a/src/dusk/randomizer/test/test.hpp b/src/dusk/randomizer/test/test.hpp new file mode 100644 index 0000000000..d2b2d04386 --- /dev/null +++ b/src/dusk/randomizer/test/test.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace tphdr::test::test +{ + void RunTests(); +} // namespace tphdr::test::test diff --git a/src/dusk/randomizer/utility/color.cpp b/src/dusk/randomizer/utility/color.cpp new file mode 100644 index 0000000000..8d86910774 --- /dev/null +++ b/src/dusk/randomizer/utility/color.cpp @@ -0,0 +1,277 @@ +#include "../utility/color.hpp" +#include "../utility/string.hpp" + +#include + +HSV RGBToHSV(const double& r, const double& g, const double& b) { + double min, max, delta; + + HSV out; + + min = r < g ? r : g; + min = min < b ? min : b; + + max = r > g ? r : g; + max = max > b ? max : b; + + out.V = max; + delta = max - min; + + if(max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash + out.S = (delta / max); // s + } else { + // if max is 0, then r = g = b = 0 + // s = 0, h is undefined + out.S = 0.0; + } + + if (delta < 0.001) { + out.H = 0.0; + } else { + if (r >= max) { + out.H = (g - b) / delta; // between yellow & magenta + } else if (g >= max) { + out.H = 2.0 + (b - r) / delta; // between cyan & yellow + } else { + out.H = 4.0 + (r - g) / delta; // between magenta & cyan + } + + out.H *= 60.0; // degrees + + if(out.H < 0.0) { + out.H += 360.0; + } + } + return out; +} + +HSV RGBToHSV(RGBA color) { + return RGBToHSV(color.R, color.G, color.B); +} + +RGBA HSVToRGB(const HSV& hsv) { + double hh, p, q, t, ff; + long i; + RGBA out; + + if(hsv.S <= 0.0) { + out.R = hsv.V; + out.G = hsv.V; + out.B = hsv.V; + return out; + } + hh = hsv.H; + if (hh >= 360.0) hh = 0.0; + hh /= 60.0; + i = (long)hh; + ff = hh - i; + p = hsv.V * (1.0 - hsv.S); + q = hsv.V * (1.0 - (hsv.S * ff)); + t = hsv.V * (1.0 - (hsv.S * (1.0 - ff))); + + switch(i) { + case 0: + out.R = hsv.V; + out.G = t; + out.B = p; + break; + case 1: + out.R = q; + out.G = hsv.V; + out.B = p; + break; + case 2: + out.R = p; + out.G = hsv.V; + out.B = t; + break; + case 3: + out.R = p; + out.G = q; + out.B = hsv.V; + break; + case 4: + out.R = t; + out.G = p; + out.B = hsv.V; + break; + case 5: + default: + out.R = hsv.V; + out.G = p; + out.B = q; + break; + } + return out; +} + +HSV color16BitToHSV(const uint16_t& color) { + double r = (color & 0xF800) >> 11; + double g = (color & 0x07E0) >> 5; + double b = (color & 0x001F); + return RGBToHSV(r / 31.0, g / 63.0, b / 31.0); +} + +uint16_t colorHSVTo16Bit(const HSV& hsv) { + auto colorRGB = HSVToRGB(hsv); + + uint16_t color565 = 0; + color565 |= uint16_t(round(colorRGB.R * 31.0)) << 11; + color565 |= uint16_t(round(colorRGB.G * 63.0)) << 5; + color565 |= uint16_t(round(colorRGB.B * 31.0)); + + return color565; +} + +uint16_t hexColorStrTo16Bit(const std::string& hexColor) { + auto hex = std::stoi(hexColor, nullptr, 16); + + double r = ((hex & 0xFF0000) >> 16) / 255.0f; + double g = ((hex & 0x00FF00) >> 8) / 255.0f; + double b = (hex & 0x0000FF) / 255.0f; + + auto colorHSV = RGBToHSV(r, g, b); + return colorHSVTo16Bit(colorHSV); +} + +RGBA hexColorStrToRGB(const std::string& hexColor) { + auto hex = std::stoi(hexColor, nullptr, 16); + + double r = ((hex & 0xFF0000) >> 16) / 255.0f; + double g = ((hex & 0x00FF00) >> 8) / 255.0f; + double b = (hex & 0x0000FF) / 255.0f; + + return RGBA(r, g, b, 1); +} + +std::string RGBToHexColorStr(const RGBA& color) { + + int c = 0; + + c |= int(color.R * 255) << 16; + c |= int(color.G * 255) << 8; + c |= int(color.B * 255); + + return tphdr::utility::str::intToHex(c, 6, false); +} + +bool isValidHexColor(const std::string& hexColor) { + return hexColor.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && hexColor.length() == 6; +} + +// Takes 16-bit base, replacement, and current colors. +// Outputs what the new 16-bit color in place of the current color should +// be based on the difference between the base and replacement colors +uint16_t colorExchange(const uint16_t& baseColor, const uint16_t& replacementColor, const uint16_t& curColor) { + + // Translate 16-bit colors into HSV color space + auto baseColorHSV = color16BitToHSV(baseColor); + auto replacementColorHSV = color16BitToHSV(replacementColor); + auto curColorHSV = color16BitToHSV(curColor); + + // Calculate difference between base and replacement colors + double sChange = replacementColorHSV.S - baseColorHSV.S; + double vChange = replacementColorHSV.V - baseColorHSV.V; + + // Prevent issues when recoloring black/white/grey parts of a texture where the base color is not black/white/grey. + if (curColorHSV.S == 0.0) { + curColorHSV.S = baseColorHSV.S; + } + + // Create new color from current color based on difference between base and replacement + HSV newColorHSV; + newColorHSV.H = replacementColorHSV.H; + newColorHSV.S = curColorHSV.S + sChange; + newColorHSV.V = curColorHSV.V + vChange; + + newColorHSV.S = std::max(0.0, std::min(1.0, newColorHSV.S)); + newColorHSV.V = std::max(0.0, std::min(1.0, newColorHSV.V)); + + return colorHSVTo16Bit(newColorHSV); +} + +std::string HSVShiftColor(const std::string& hexColor, const int& hShift, const int& vShift) { + auto colorRGB = hexColorStrToRGB(hexColor); + auto colorHSV = RGBToHSV(colorRGB); + int h = colorHSV.H; + int s = round(colorHSV.S * 100); + int v = round(colorHSV.V * 100); + + h += hShift; + h %= 360; + + auto origV = v; + v += vShift; + if (v < 0) { + v = 0; + } + if (v > 100) { + v = 100; + } + if (v < 30 && origV >= 30) { + v = 30; + } + if (v > 90 and origV <= 90) { + v = 90; + } + + auto vDiff = v - origV; + + // Instead of shifting saturation separately, we simply make it relative to the value shift. + // As value increases we want saturation to decrease and vice versa. + // This is because bright colors look bad if they are too saturated, and dark colors look bland if they aren't saturated enough. + auto origS = s; + if (origS < 15 && vShift > 0) { + // For colors that were originally very unsaturated, we want saturation to increase regardless of which direction value is shifting in. + if (origV < 30) { + // Very dark, nearly black. Needs extra saturation for the change to be noticeable. + s += (vShift * 2); + } else { + // Not that dark, probably grey or whitish. + s += vShift; + } + } else { + s -= vDiff; + } + + if (s < 0) { + s = 0; + } + if (s > 100) { + s = 100; + } + if (s < 5 && origS >= 5) { + s = 5; + } + if (s > 80 && origS <= 80) { + s = 80; + } + + auto newColorHSV = HSV(h, s / 100.0f, v / 100.0f); + auto newColorRGB = HSVToRGB(newColorHSV); + return RGBToHexColorStr(newColorRGB); +} + +std::pair get_random_h_and_v_shifts_for_custom_color(const std::string& hexColor) { + auto colorRGB = hexColorStrToRGB(hexColor); + auto colorHSV = RGBToHSV(colorRGB); + + int s = round(colorHSV.S * 100); + int v = round(colorHSV.V * 100); + + int minVShift = -40; + int maxVShift = 40; + + if (s < 10) { + // For very unsaturated colors, we want to limit the range of value + // randomization to exclude results that wouldn't change anything anyway. + // This effectively stops white and black from having a 50% chance to not change at all. + minVShift = std::max(-40, 0-v); + maxVShift = std::min(40, 100-v); + } + + auto hShift = rand() % 360; + auto vShift = (rand() % (maxVShift - minVShift)) + minVShift; + + return {hShift, vShift}; +} diff --git a/src/dusk/randomizer/utility/color.hpp b/src/dusk/randomizer/utility/color.hpp new file mode 100644 index 0000000000..fbff111bcd --- /dev/null +++ b/src/dusk/randomizer/utility/color.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "../utility/common.hpp" + +template requires std::is_arithmetic_v +struct RGBA { + T R = 0; + T G = 0; + T B = 0; + T A = std::numeric_limits::max(); + + RGBA() = default; + + RGBA(const T& val, const T& alpha) : + R(val), + G(val), + B(val), + A(alpha) + {} + + RGBA(const T& r_, const T& g_, const T& b_ , const T& a_) : + R(r_), + G(g_), + B(b_), + A(a_) + {} +}; + +using RGBA8 = RGBA; + +template +bool readRGBA(std::istream& in, const std::streamoff& offset, RGBA& out) { + in.seekg(offset, std::ios::beg); + + if(!in.read(reinterpret_cast(&out.R), sizeof(out.R))) return false; + if(!in.read(reinterpret_cast(&out.G), sizeof(out.G))) return false; + if(!in.read(reinterpret_cast(&out.B), sizeof(out.B))) return false; + if(!in.read(reinterpret_cast(&out.A), sizeof(out.A))) return false; + + if constexpr (sizeof(T) > 1) { + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.R); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.G); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.B); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.A); + } + + return true; +} + +template +void writeRGBA(std::ostream& out, const RGBA& color) { + if constexpr (sizeof(T) > 1) { + T R_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, color.R); + T G_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, color.G); + T B_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, color.B); + T A_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, color.A); + + out.write(reinterpret_cast(&R_BE), sizeof(R_BE)); + out.write(reinterpret_cast(&G_BE), sizeof(G_BE)); + out.write(reinterpret_cast(&B_BE), sizeof(B_BE)); + out.write(reinterpret_cast(&A_BE), sizeof(A_BE)); + return; + } + else { + out.write(reinterpret_cast(&color.R), sizeof(color.R)); + out.write(reinterpret_cast(&color.G), sizeof(color.G)); + out.write(reinterpret_cast(&color.B), sizeof(color.B)); + out.write(reinterpret_cast(&color.A), sizeof(color.A)); + } + + return; +} + +struct HSV { + double H = 0; + double S = 0; + double V = 0; + + HSV() = default; + + HSV(const double& h_, const double& s_, const double& v_) : + H(h_), + S(s_), + V(v_) + {} +}; + +HSV RGBToHSV(const double& r, const double& g, const double& b); + +HSV RGBToHSV(RGBA color); + +RGBA HSVToRGB(const HSV& hsv); + +HSV color16BitToHSV(const uint16_t& color); + +uint16_t colorHSVTo16Bit(const HSV& hsv); + +RGBA hexColorStrToRGB(const std::string& hexColor); + +std::string RGBToHexColorStr(const RGBA& color); + +uint16_t hexColorStrTo16Bit(const std::string& hexColor); + +bool isValidHexColor(const std::string& hexColor); + +uint16_t colorExchange(const uint16_t& baseColor, const uint16_t& replacementColor, const uint16_t& curColor); + +std::string HSVShiftColor(const std::string& hexColor, const int& hShift, const int& vShift); + +std::pair get_random_h_and_v_shifts_for_custom_color(const std::string& hexColor); diff --git a/src/dusk/randomizer/utility/common.cpp b/src/dusk/randomizer/utility/common.cpp new file mode 100644 index 0000000000..4a0210b5ad --- /dev/null +++ b/src/dusk/randomizer/utility/common.cpp @@ -0,0 +1,47 @@ +#include "common.hpp" + +std::string readNullTerminatedStr(std::istream& in, const unsigned int& offset) { + in.seekg(offset, std::ios::beg); + + std::string ret; + char character = '\0'; + do { + if (!in.read(&character, sizeof(char))) { + ret.clear(); + return ret; + } + ret += character; + } while (character != '\0'); + + return ret; +} + +std::u16string readNullTerminatedWStr(std::istream& in, const unsigned int offset) { + in.seekg(offset, std::ios::beg); + + std::u16string ret; + char16_t character = u'\0'; + do { + if (!in.read(reinterpret_cast(&character), sizeof(char16_t))) { + ret.clear(); + return ret; + } + ret += character; + } while (character != u'\0'); + + return ret; +} + + +size_t padToLen(std::ostream& out, const unsigned int& len, const char pad) { + if (len == 0) return 0; //don't pad to no alignment (also cant % by 0) + + size_t padLen = len - (static_cast(out.tellp()) % len); + if (padLen == len) return 0; //doesnt write any padding, return length 0 + + for (size_t i = 0; i < padLen; i++) { + out.write(&pad, 1); + } + + return padLen; //return number of bytes written +} diff --git a/src/dusk/randomizer/utility/common.hpp b/src/dusk/randomizer/utility/common.hpp new file mode 100644 index 0000000000..b625c33a9a --- /dev/null +++ b/src/dusk/randomizer/utility/common.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include +#include +#include +#include + +#include "../utility/endian.hpp" + + +template requires std::is_arithmetic_v +struct vec2 { + T X; + T Y; + + vec2() = default; + vec2(const T& val) : + X(val), + Y(val) + {} +}; + +template requires std::is_arithmetic_v +struct vec3 { + T X; + T Y; + T Z; + + vec3() = default; + vec3(const T& val) : + X(val), + Y(val), + Z(val) + {} + vec3(const T& x_, const T& y_, const T& z_) : + X(x_), + Y(y_), + Z(z_) + {} +}; + +template requires std::is_arithmetic_v +struct vec4 { + T A; + T B; + T C; + T D; + + vec4() = default; + vec4(const T& val) : + A(val), + B(val), + C(val), + D(val) + {} +}; + + +template +bool readVec2(std::istream& in, const std::streamoff offset, vec2& out) { + in.seekg(offset, std::ios::beg); + + if (!in.read(reinterpret_cast(&out.X), sizeof(out.X))) return false; + if (!in.read(reinterpret_cast(&out.Y), sizeof(out.Y))) return false; + + if constexpr (sizeof(T) > 1) { + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.X); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.Y); + } + + return true; +} + +template +void writeVec2(std::ostream& out, const vec2& vec) { + if constexpr (sizeof(T) > 1) { + T X_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.X); + T Y_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.Y); + + out.write(reinterpret_cast(&X_BE), sizeof(X_BE)); + out.write(reinterpret_cast(&Y_BE), sizeof(Y_BE)); + return; + } + else { + out.write(reinterpret_cast(&vec.X), sizeof(vec.X)); + out.write(reinterpret_cast(&vec.Y), sizeof(vec.Y)); + return; + } +} + + +template +bool readVec3(std::istream& in, const std::streamoff offset, vec3& out) { + in.seekg(offset, std::ios::beg); + + if (!in.read(reinterpret_cast(&out.X), sizeof(out.X))) return false; + if (!in.read(reinterpret_cast(&out.Y), sizeof(out.Y))) return false; + if (!in.read(reinterpret_cast(&out.Z), sizeof(out.Z))) return false; + + if constexpr (sizeof(T) > 1) { + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.X); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.Y); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.Z); + } + + return true; +} + +template +void writeVec3(std::ostream& out, const vec3& vec) { + if constexpr (sizeof(T) > 1) { + T X_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.X); + T Y_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.Y); + T Z_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.Z); + + out.write(reinterpret_cast(&X_BE), sizeof(X_BE)); + out.write(reinterpret_cast(&Y_BE), sizeof(Y_BE)); + out.write(reinterpret_cast(&Z_BE), sizeof(Z_BE)); + return; + } + else { + out.write(reinterpret_cast(&vec.X), sizeof(vec.X)); + out.write(reinterpret_cast(&vec.Y), sizeof(vec.Y)); + out.write(reinterpret_cast(&vec.Z), sizeof(vec.Z)); + return; + } +} + + +template +bool readVec4(std::istream& in, const std::streamoff offset, vec4& out) { + in.seekg(offset, std::ios::beg); + + if (!in.read(reinterpret_cast(&out.A), sizeof(out.A))) return false; + if (!in.read(reinterpret_cast(&out.B), sizeof(out.B))) return false; + if (!in.read(reinterpret_cast(&out.C), sizeof(out.C))) return false; + if (!in.read(reinterpret_cast(&out.D), sizeof(out.D))) return false; + + if constexpr (sizeof(T) > 1) { + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.A); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.B); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.C); + Utility::Endian::toPlatform_inplace(Utility::Endian::Type::Big, out.D); + } + + return true; +} + +template +void writeVec4(std::ostream& out, const vec4& vec) { + if constexpr (sizeof(T) > 1) { + T A_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.A); + T B_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.B); + T C_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.C); + T D_BE = Utility::Endian::toPlatform(Utility::Endian::Type::Big, vec.D); + + out.write(reinterpret_cast(&A_BE), sizeof(A_BE)); + out.write(reinterpret_cast(&B_BE), sizeof(B_BE)); + out.write(reinterpret_cast(&C_BE), sizeof(C_BE)); + out.write(reinterpret_cast(&D_BE), sizeof(D_BE)); + return; + } + else { + out.write(reinterpret_cast(&vec.A), sizeof(vec.A)); + out.write(reinterpret_cast(&vec.B), sizeof(vec.B)); + out.write(reinterpret_cast(&vec.C), sizeof(vec.C)); + out.write(reinterpret_cast(&vec.D), sizeof(vec.D)); + return; + } +} + +std::string readNullTerminatedStr(std::istream& in, const unsigned int& offset); + +std::u16string readNullTerminatedWStr(std::istream& in, const unsigned int offset); + +template requires requires { + requires std::is_enum_v; + error_enum::NONE; + error_enum::REACHED_EOF; + error_enum::UNEXPECTED_VALUE; +} +error_enum readPadding(std::istream& in, const unsigned int& len, const char* val = nullptr) { + if (in.tellg() % len != 0) { + const size_t& padding_size = len - (static_cast(in.tellg()) % len); + + std::string padding(padding_size, '\0'); + if (!in.read(&padding[0], static_cast(padding_size))) return error_enum::REACHED_EOF; + + if(val != nullptr) { + for (const char& character : padding) { + if (character != *val) return error_enum::UNEXPECTED_VALUE; + } + } + } + + return error_enum::NONE; +} + +size_t padToLen(std::ostream& out, const unsigned int& len, const char pad = '\x00'); diff --git a/src/dusk/randomizer/utility/container.hpp b/src/dusk/randomizer/utility/container.hpp new file mode 100644 index 0000000000..076e996c7c --- /dev/null +++ b/src/dusk/randomizer/utility/container.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +namespace tphdr::utility::container +{ + template + int GetIndex(const Container& container, const T& element) + { + auto it = std::find(container.begin(), container.end(), element); + if (it == container.end()) + { + return -1; + } + return std::distance(container.begin(), it); + } + + template + bool ElementInContainer(const Container& container, const T& element) + { + auto it = std::find(container.begin(), container.end(), element); + if (it == container.end()) + { + return false; + } + return true; + } + + template + std::vector FilterFromVector(std::vector& vector, Predicate pred, bool eraseAfterFilter = false) + { + std::vector filteredPool = {}; + std::copy_if(vector.begin(), vector.end(), std::back_inserter(filteredPool), pred); + + if (eraseAfterFilter) + { + std::erase_if(vector, pred); + } + + return filteredPool; + } + + template + std::vector FilterAndEraseFromVector(std::vector& vector, Predicate pred) + { + return FilterFromVector(vector, pred, true); + } + + /** + * @brief Erases a number of elements a container. If there are no more of the specified element to erase, then nothing + * happens. + * + * @param container The container to erase elements from + * @param element The value of the element to erase from the container + * @param numberToErase The number of elements of the specified value to erase (default 1) + */ + template + void Erase(Container& container, T element, int numberToErase = 1) + { + for (int i = 0; i < numberToErase; i++) + { + auto itr = std::find(container.begin(), container.end(), element); + if (itr != container.end()) + { + container.erase(itr); + } + } + } +} // namespace tphdr::utility::container diff --git a/src/dusk/randomizer/utility/endian.cpp b/src/dusk/randomizer/utility/endian.cpp new file mode 100644 index 0000000000..8f226a5dae --- /dev/null +++ b/src/dusk/randomizer/utility/endian.cpp @@ -0,0 +1,103 @@ +#include "../utility/endian.hpp" + +#ifndef __cpp_lib_endian + #include "../utility/platform.hpp" +#endif + +namespace Utility::Endian +{ + + #ifdef __cpp_lib_endian + #pragma message("Using C++20 endianness") + #else + #pragma message("Using runtime endian check") + + Type getEndian() { + static const uint16_t TestVal = 0x0001; + static const uint8_t tester = *reinterpret_cast(&TestVal); + + if(tester == 0x00) { + return Type::Big; + } + else if (tester == 0x01) { + return Type::Little; + } + else { + tphdr::utility::platform::Log("Warning: Could not determine endianness!"); + tphdr::utility::platform::Log("Using little endian as default"); + return Type::Little; + } + } + #endif + + uint64_t byteswap(const uint64_t& value) + { + return ((value & 0xFF00000000000000) >> 56) | + ((value & 0x00FF000000000000) >> 40) | + ((value & 0x0000FF0000000000) >> 24) | + ((value & 0x000000FF00000000) >> 8) | + ((value & 0x00000000FF000000) << 8) | + ((value & 0x0000000000FF0000) << 24) | + ((value & 0x000000000000FF00) << 40) | + ((value & 0x00000000000000FF) << 56); + } + + uint32_t byteswap(const uint32_t& value) + { + return ((value & 0xFF000000) >> 24) | + ((value & 0x00FF0000) >> 8) | + ((value & 0x0000FF00) << 8) | + ((value & 0x000000FF) << 24); + } + + uint32_t byteswap24(const uint32_t& value) + { + return ((value & 0x00FF0000) >> 16) | + ((value & 0x0000FF00)) | + ((value & 0x000000FF) << 16); + } + + uint16_t byteswap(const uint16_t& value) + { + return ((value & 0xFF00) >> 8) | ((value & 0x00FF) << 8); + } + + int64_t byteswap(const int64_t& value) + { + return std::bit_cast(byteswap(std::bit_cast(value))); + } + + int32_t byteswap(const int32_t& value) + { + return std::bit_cast(byteswap(std::bit_cast(value))); + } + + int16_t byteswap(const int16_t& value) + { + return std::bit_cast(byteswap(std::bit_cast(value))); + } + + float byteswap(const float& value) + { + return std::bit_cast(byteswap(std::bit_cast(value))); + } + + double byteswap(const double& value) + { + return std::bit_cast(byteswap(std::bit_cast(value))); + } + + char16_t byteswap(const char16_t& value) + { + return std::bit_cast(byteswap(std::bit_cast(value))); + } + + std::u16string byteswap(const std::u16string& value) { + std::u16string result = value; + for(char16_t& character : result) { + character = byteswap(character); + } + + return result; + } +} diff --git a/src/dusk/randomizer/utility/endian.hpp b/src/dusk/randomizer/utility/endian.hpp new file mode 100644 index 0000000000..944c4869d4 --- /dev/null +++ b/src/dusk/randomizer/utility/endian.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include + + + +namespace Utility::Endian +{ + enum struct Type { + Big = 0, + Little = 1 + }; + +#ifdef __cpp_lib_endian + //use the c++20 api if possible + constexpr Type target = std::endian::native == std::endian::big ? Type::Big : Type::Little; + constexpr inline bool isBE() { return target == Type::Big; } +#else + //do a runtime check otherwise + Type getEndian(); + const Type target = getEndian(); + inline bool isBE() { return target == Type::Big; } +#endif + + uint64_t byteswap(const uint64_t& value); + + uint32_t byteswap(const uint32_t& value); + + uint32_t byteswap24(const uint32_t& value); //used in FST files + + uint16_t byteswap(const uint16_t& value); + + int64_t byteswap(const int64_t& value); + + int32_t byteswap(const int32_t& value); + + int16_t byteswap(const int16_t& value); + + [[deprecated("Platform may silently set NaN bits, bit_cast to uint32_t first if possible.")]] float byteswap(const float& value); + + [[deprecated("Platform may silently set NaN bits, bit_cast to uint64_t first if possible.")]] double byteswap(const double& value); + + char16_t byteswap(const char16_t& value); + + std::u16string byteswap(const std::u16string& value); + + template + concept CanByteswap = sizeof(T) > 1; + + template requires CanByteswap && (!std::is_enum_v) + constexpr T toPlatform(const Type& src, const T& value) { + if (src != target) return byteswap(value); + return value; + } + + //for enums + template> requires CanByteswap && std::is_enum_v + constexpr T toPlatform(const Type& src, const T& value) { + if (src != target) return static_cast(byteswap(static_cast(value))); + return value; + } + + //doesn't work for enums + template requires CanByteswap && (!std::is_enum_v) + constexpr void toPlatform_inplace(const Type& src, T& value) { + if (src != target) value = byteswap(value); + } + + //for enums + template> requires CanByteswap && std::is_enum_v + constexpr void toPlatform_inplace(const Type& src, T& value) { + if (src != target) value = static_cast(byteswap(static_cast(value))); + } +} diff --git a/src/dusk/randomizer/utility/exception.hpp b/src/dusk/randomizer/utility/exception.hpp new file mode 100644 index 0000000000..30fb844c5d --- /dev/null +++ b/src/dusk/randomizer/utility/exception.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "../utility/log.hpp" + +#define RUNTIME_ERROR(msg) std::runtime_error(std::string(msg) + " on line " TOSTRING(__LINE__) " of " __FILENAME__) diff --git a/src/dusk/randomizer/utility/file.cpp b/src/dusk/randomizer/utility/file.cpp new file mode 100644 index 0000000000..572b3c8a3a --- /dev/null +++ b/src/dusk/randomizer/utility/file.cpp @@ -0,0 +1,210 @@ +#include "../utility/file.hpp" +#include "../utility/log.hpp" +#include "../utility/path.hpp" +#include "../utility/platform.hpp" +#include "../utility/thread_local.hpp" + +#include +#include +#include + +#if defined(QT_GUI) && defined(EMBED_DATA) +#include +#include +#endif + +namespace tphdr::utility::file +{ + bool isRoot(const fspath& fsPath) + { + static const std::regex rootFilesystem(R"(^fs:\/vol\/[^\/:]+\/?$)"); + + const std::string path = fsPath.string(); + + if (path.size() >= 2 && path.ends_with(":")) + return true; + if (path.size() >= 3 && path.ends_with(":/")) + return true; + if (std::regex_match(path, rootFilesystem)) + return true; + + return false; + }; + + static constexpr int FILE_BUF_SIZE = 25 * 1024 * 1024; + class AlignedBufferWrapper + { + private: + alignas(0x40) char buffer[FILE_BUF_SIZE]; + + public: + char* getBuffer() { return buffer; } + }; + static ThreadLocal buf; + + bool copy_file(const fspath& from, const fspath& to) + { + tphdr::utility::platform::Log("Copying " + Utility::toUtf8String(to)); +#ifdef DEVKITPRO + // use a buffer to speed up file copying + + std::ifstream src(from, std::ios::binary); + std::ofstream dst(to, std::ios::binary); + if (!src.is_open()) + { + ErrorLog::getInstance().log("Failed to open " + from.string()); + return false; + } + if (!dst.is_open()) + { + ErrorLog::getInstance().log("Failed to open " + to.string()); + return false; + } + + while (src) + { + src.read(buf.get().getBuffer(), FILE_BUF_SIZE); + dst.write(buf.get().getBuffer(), src.gcount()); + } + return true; +#else +// GNU on windows currently has a bug where you can't copy over a file that already exists +// even if you pass std::filesystem::copy_options::overwrite_existing. So delete the copy location +// file in this case +#if defined(WIN32) && defined(__GNUG__) + std::filesystem::remove(to); +#endif + return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); +#endif + } + + bool copy(const fspath& from, const fspath& to) + { +#ifdef DEVKITPRO + // based on https://github.com/emiyl/dumpling/blob/12935ede46e9720fdec915cdb430d10eb7df54a7/source/app/dumping.cpp#L208 + + DIR* dirHandle; + if ((dirHandle = opendir(from.string().c_str())) == nullptr) + { + ErrorLog::getInstance().log("Couldn't open directory to copy files from: " + to.string()); + return false; + } + + tphdr::utility::file::create_directories(to); + + // Loop over directory contents + struct dirent* dirEntry; + while ((dirEntry = readdir(dirHandle)) != nullptr) + { + const std::string entrySrcPath = from / dirEntry->d_name; + const std::string entryDstPath = to / dirEntry->d_name; + + // Use lstat since readdir returns DT_REG for symlinks + struct stat fileStat; + if (lstat(entrySrcPath.c_str(), &fileStat) != 0) + { + ErrorLog::getInstance().log("Couldn't check what type this file/folder was: " + entrySrcPath); + return false; + } + + if (S_ISLNK(fileStat.st_mode)) + { + continue; + } + else if (S_ISREG(fileStat.st_mode)) + { + // Copy file + if (!copy_file(entrySrcPath, entryDstPath)) + { + ErrorLog::getInstance().log("Failed to copy file: " + entrySrcPath); + closedir(dirHandle); + return false; + } + } + else if (S_ISDIR(fileStat.st_mode)) + { + // Ignore root and parent folder entries + if (std::strncmp(dirEntry->d_name, ".", 1) == 0 || std::strncmp(dirEntry->d_name, "..", 2) == 0) + continue; + + // Copy all the files in this subdirectory + if (!copy(entrySrcPath, entryDstPath)) + { + ErrorLog::getInstance().log("Failed to copy dir: " + entrySrcPath); + closedir(dirHandle); + return false; + } + } + } + + closedir(dirHandle); +#else + std::filesystem::copy(from, to, std::filesystem::copy_options::recursive); +#endif + + return true; + } + + // Short function for getting the string data from a file + int GetContents(const fspath& filename, std::string& fileContents, bool resourceFile /*= false*/) + { + if (resourceFile) + { +// If this is a resource file and the data has been embedded, then load it from +// the embedded resources file +#if defined(QT_GUI) && defined(EMBED_DATA) + QResource file(Utility::toQString(filename)); + if (!file.isValid()) + { + return 1; + } + + QByteArray data = file.uncompressedData(); + if (data.isNull()) + { + return 1; + } + + fileContents = data.toStdString(); + return 0; +#endif + } + + // Otherwise load it normally + auto ss = std::stringstream {}; + if (const auto err = GetContents(filename, ss); err != 0) + return err; + fileContents = ss.str(); + return 0; + } + + // Short function for getting the string data from a file + int GetContents(const fspath& filename, std::stringstream& fileContents) + { + // Otherwise load it normally + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + LOG_TO_ERROR("Unable to open file \"" + Utility::toUtf8String(filename) + "\""); + return 1; + } + + while (file) + { + file.read(buf.get().getBuffer(), FILE_BUF_SIZE); + fileContents.write(buf.get().getBuffer(), file.gcount()); + } + + return 0; + } + + void Verify(const fspath& filename) + { + std::ifstream file(filename); + if (!file.is_open()) + { + throw std::runtime_error("Could not open " + Utility::toUtf8String(filename)); + } + file.close(); + } +} // namespace tphdr::utility::file diff --git a/src/dusk/randomizer/utility/file.hpp b/src/dusk/randomizer/utility/file.hpp new file mode 100644 index 0000000000..1ac83d610a --- /dev/null +++ b/src/dusk/randomizer/utility/file.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include + +#include "../utility/path.hpp" + +#ifdef DEVKITPRO + #include + #include +#endif + +namespace tphdr::utility::file +{ + //std::filesystem is partially broken on Wii U, these are cross-platform replacements + + inline std::ostream& seek(std::ostream& stream, const std::streamoff& off, const std::ios::seekdir& way = std::ios::beg) { + //#ifdef DEVKITPRO + //Wii U crashes if you seek past eof, most other platforms extend the file + //Handle writing the extra padding manually + + switch(way) { + case std::ios::cur: + { + const std::streamoff& cur = stream.tellp(); + if(off > 0) { + stream.seekp(0, std::ios::end); + if(stream.tellp() < (cur + off)) { + const std::string buffer((cur + off) - stream.tellp(), '\0'); + stream.write(&buffer[0], buffer.size()); + } + } + else if ((cur + off) < 0) { + //can't seek before start of file, seek to beginning as failsafe + return stream.seekp(0, std::ios::beg); + } + return stream.seekp(cur + off, std::ios::beg); + } + case std::ios::end: + //BUG: seek to std::ios::end doesn't seem to work on MLC, find workaround? (relevant uses are currently replaced) + { + stream.seekp(0, std::ios::end); + if(off > 0) { + const std::string buffer(off, '\0'); + stream.write(&buffer[0], buffer.size()); + } + else if((-off) > stream.tellp()) { + //Can't seek before start of file, seek to beginning as failsafe + return stream.seekp(0, std::ios::beg); + } + return stream.seekp(off, std::ios::end); + } + case std::ios::beg: + [[fallthrough]]; + default: + { + if(off < 0) { + //can't seek before start of file, seek to beginning as failsafe + stream.seekp(0, std::ios::beg); + } + stream.seekp(0, std::ios::end); + if(stream.tellp() < off) { + const std::string buffer(off - stream.tellp(), '\0'); + stream.write(&buffer[0], buffer.size()); + } + return stream.seekp(off, std::ios::beg); + } + } + //#else + // return stream.seekp(off, way); + //#endif + } + + //from https://github.com/emiyl/dumpling/blob/5dc5131243385050e45339779e75a2eaad31f1e4/source/app/filesystem.cpp#L177 + bool isRoot(const fspath& fsPath); + + //from https://github.com/emiyl/dumpling/blob/5dc5131243385050e45339779e75a2eaad31f1e4/source/app/filesystem.cpp#L193 + inline bool dirExists(const fspath& fsPath) { + #ifdef DEVKITPRO + static struct stat existStat; + if (isRoot(fsPath)) return true; + if (lstat(fsPath.string().c_str(), &existStat) == 0 && S_ISDIR(existStat.st_mode)) return true; + return false; + #else + return std::filesystem::is_directory(fsPath); + #endif + } + + inline bool create_directories(const fspath& fsPath) { + #ifdef DEVKITPRO + std::string temp = fsPath.string(); + if(temp.back() == '/') temp.pop_back(); + for(size_t i = 0; i < temp.size(); i++) { + if(temp[i] == '/') { + const std::string& sub = temp.substr(0, i); + if (!dirExists(sub)) { + mkdir(sub.c_str(), ACCESSPERMS); + } + } + } + mkdir(temp.c_str(), ACCESSPERMS); + #else + std::filesystem::create_directories(fsPath); + #endif + + return true; + } + + bool copy_file(const fspath& from, const fspath& to); + + bool copy(const fspath& from, const fspath& to); + + int GetContents(const fspath& filename, std::string& fileContents, bool resourceFile = false); + + int GetContents(const fspath& filename, std::stringstream& fileContents); + + void Verify(const fspath& filename); +} diff --git a/src/dusk/randomizer/utility/general.hpp b/src/dusk/randomizer/utility/general.hpp new file mode 100644 index 0000000000..e1edba4068 --- /dev/null +++ b/src/dusk/randomizer/utility/general.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace tphdr::utility::general +{ + template + bool IsAnyOf(First&& first, T&&... t) + { + return ((first == t) || ...); + } +} // namespace tphdr::utility::general \ No newline at end of file diff --git a/src/dusk/randomizer/utility/log.cpp b/src/dusk/randomizer/utility/log.cpp new file mode 100644 index 0000000000..7290db9b94 --- /dev/null +++ b/src/dusk/randomizer/utility/log.cpp @@ -0,0 +1,101 @@ +#include "../utility/log.hpp" +#include "../utility/time.hpp" + +#define RANDOMIZER_VERSION "1.0.0" + +namespace tphdr::utility::log +{ + LogInfo::LogInfo() {} + + LogInfo::~LogInfo() {} + + LogInfo& LogInfo::getInstance() + { + static LogInfo s_Instance; + return s_Instance; + } + + const tphdr::seedgen::config::Config& LogInfo::getConfig() + { + return getInstance().config; + } + + const std::string& LogInfo::getSeedHash() + { + return getInstance().seedHash; + } + + ErrorLog::ErrorLog() + { +#ifdef WRITE_ERROR_LOG + output.open(LOG_PATH); + output << "Program opened " << tphdr::utility::time::ProgramTime::getDateStr(); // time string ends with \n + output << "Twilight Princess HD Randomizer Version " << RANDOMIZER_VERSION << std::endl; + output << std::endl << std::endl; +#endif + } + + ErrorLog::~ErrorLog() + { +#ifdef WRITE_ERROR_LOG + output.close(); +#endif + } + + ErrorLog& ErrorLog::getInstance() + { + static ErrorLog s_Instance; + return s_Instance; + } + + void ErrorLog::log(const std::string& msg, const bool& timestamp) + { +#ifdef WRITE_ERROR_LOG + if (timestamp) + output << "[" << tphdr::utility::time::ProgramTime::getTimeStr() << "] "; + output << msg << std::endl; +#endif + lastErrors.push_front(msg); + } + + std::string ErrorLog::getLastErrors() const + { + std::string retStr = ""; + for (auto& error : lastErrors) + { + retStr += error + "\n"; + } + return retStr; + } + + void ErrorLog::clearLastErrors() + { + lastErrors.clear(); + } + + DebugLog::DebugLog() + { + output.open(LOG_PATH); + output << "Program opened " << tphdr::utility::time::ProgramTime::getDateStr(); // time string ends with \n + output << "Twilight Princess HD Randomizer Version " << RANDOMIZER_VERSION << std::endl; + output << std::endl << std::endl; + } + + DebugLog::~DebugLog() + { + output.close(); + } + + DebugLog& DebugLog::getInstance() + { + static DebugLog s_Instance; + return s_Instance; + } + + void DebugLog::log(const std::string& msg, const bool& timestamp) + { + if (timestamp) + output << "[" << tphdr::utility::time::ProgramTime::getTimeStr() << "] "; + output << msg << std::endl; + } +} // namespace tphdr::utility::log diff --git a/src/dusk/randomizer/utility/log.hpp b/src/dusk/randomizer/utility/log.hpp new file mode 100644 index 0000000000..696204aa2e --- /dev/null +++ b/src/dusk/randomizer/utility/log.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include + +#include "../seedgen/config.hpp" +#include "../utility/path.hpp" + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define __FILENAME__ (&__FILE__[SOURCE_PATH_SIZE]) + +namespace tphdr::utility::log +{ + class LogInfo + { + private: + tphdr::seedgen::config::Config config; + std::string seedHash; + + LogInfo(); + ~LogInfo(); + + static LogInfo& getInstance(); + + public: + LogInfo(const LogInfo&) = delete; + LogInfo& operator=(const LogInfo&) = delete; + + static void setConfig(const tphdr::seedgen::config::Config& config_) { getInstance().config = config_; } + static void setSeedHash(const std::string& seedHash_) { getInstance().seedHash = seedHash_; } + static const tphdr::seedgen::config::Config& getConfig(); + static const std::string& getSeedHash(); + }; + + class ErrorLog + { + private: + static constexpr size_t MAX_ERRORS = 5; + + std::ofstream output; + std::list lastErrors; + + ErrorLog(); + ~ErrorLog(); + + public: + const fspath LOG_PATH = Utility::get_app_save_path() / "Error Log.txt"; + + ErrorLog(const ErrorLog&) = delete; + ErrorLog& operator=(const ErrorLog&) = delete; + + static ErrorLog& getInstance(); + void log(const std::string& msg, const bool& timestamp = true); + std::string getLastErrors() const; + void clearLastErrors(); + }; + +#define LOG_ERR_AND_RETURN(error) \ + { \ + ErrorLog::getInstance().log(std::string("Encountered " #error " on line " TOSTRING(__LINE__) " of ") + __FILENAME__); \ + return error; \ + } + +#define LOG_AND_RETURN_IF_ERR(func) \ + { \ + if (const auto error = func; error != decltype(error)::NONE) \ + { \ + ErrorLog::getInstance().log(std::string("Encountered error on line " TOSTRING(__LINE__) " of ") + __FILENAME__); \ + return error; \ + } \ + } + +#define LOG_ERR_AND_RETURN_BOOL(error) \ + { \ + ErrorLog::getInstance().log(std::string("Encountered " #error " on line " TOSTRING(__LINE__) " of ") + __FILENAME__); \ + return false; \ + } + +#define LOG_AND_RETURN_BOOL_IF_ERR(func) \ + { \ + if (const auto error = func; error != decltype(error)::NONE) \ + { \ + ErrorLog::getInstance().log(std::string("Encountered error on line " TOSTRING(__LINE__) " of ") + __FILENAME__); \ + return false; \ + } \ + } + + class DebugLog + { + private: + std::ofstream output; + + DebugLog(); + ~DebugLog(); + + public: + const fspath LOG_PATH = Utility::get_app_save_path() / "Debug Log.txt"; + + DebugLog(const DebugLog&) = delete; + DebugLog& operator=(const DebugLog&) = delete; + + static DebugLog& getInstance(); + void log(const std::string& msg, const bool& timestamp = true); + }; +} // namespace tphdr::utility::log + +#ifdef RANDO_DEBUG +#define LOG_TO_DEBUG(message) \ + tphdr::utility::log::DebugLog::getInstance().log(std::string("Message on line " TOSTRING(__LINE__) " of ") + \ + __FILENAME__ + std::string(": " + std::string(message))); +#else +#define LOG_TO_DEBUG(message) +#endif + +#define LOG_TO_ERROR(message) \ + tphdr::utility::log::ErrorLog::getInstance().log(std::string("Message on line " TOSTRING(__LINE__) " of ") + \ + __FILENAME__ + std::string(": " + std::string(message))); diff --git a/src/dusk/randomizer/utility/math.hpp b/src/dusk/randomizer/utility/math.hpp new file mode 100644 index 0000000000..ac48964ee1 --- /dev/null +++ b/src/dusk/randomizer/utility/math.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +template requires std::is_arithmetic_v +T roundUp(const T& val, const T& multiple) { + if(val % multiple == 0) return val; + return val + multiple - (val % multiple); +} diff --git a/src/dusk/randomizer/utility/path.cpp b/src/dusk/randomizer/utility/path.cpp new file mode 100644 index 0000000000..ae672b2901 --- /dev/null +++ b/src/dusk/randomizer/utility/path.cpp @@ -0,0 +1,71 @@ +#include "path.hpp" +#include "file.hpp" + +#if defined(QT_GUI) + #if defined(__APPLE__) + #include + #else + #include + #endif +#endif + +namespace Utility { + fspath get_data_path() { + #if defined(QT_GUI) + #if defined(EMBED_DATA) + return ":/"; + #else + return fromQString(QCoreApplication::applicationDirPath()) / "data/"; + #endif + #elif defined(DEVKITPRO) + return "/vol/content/"; + #elif defined(APPLE) + return "../../../data/"; + #else + return "./data/"; + #endif + } + + fspath get_app_save_path() { + fspath path; + #if defined(__APPLE__) && defined(QT_GUI) + path = fromQString(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + #elif defined(QT_GUI) + path = fromQString(QCoreApplication::applicationDirPath()); + #elif defined(DEVKITPRO) + return "/vol/save/"; + #else + return "./"; + #endif + + if (!std::filesystem::is_directory(path)) + { + tphdr::utility::file::create_directories(path); + } + + return path; + } + + fspath get_logs_path() { + const fspath path = get_app_save_path() / "logs/"; + + if (!std::filesystem::is_directory(path)) + { + tphdr::utility::file::create_directories(path); + } + + return path; + } + + fspath get_temp_dir() { + // could get the OS-provided temp folder with Qt but it might be harder to find and debug should we use it for anything + const fspath path = get_app_save_path() / "temp/"; + + if (!std::filesystem::is_directory(path)) + { + tphdr::utility::file::create_directories(path); + } + + return path; + } +} diff --git a/src/dusk/randomizer/utility/path.hpp b/src/dusk/randomizer/utility/path.hpp new file mode 100644 index 0000000000..cf8fa666ae --- /dev/null +++ b/src/dusk/randomizer/utility/path.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#ifdef QT_GUI + #include +#endif + +using fspath = std::filesystem::path; + +namespace Utility { + fspath get_data_path(); + fspath get_app_save_path(); + fspath get_logs_path(); + fspath get_temp_dir(); + + // On Windows, fspath.string() will throw an exception if the character requires some kind of Unicode/non-ANSI representation + // using .u8string() fixes this, but a lot of the randomizer still expects std::string which is not implicitly convertible + // std::string should still properly store a UTF-8 string, so this wrapper does that conversion + inline std::string toUtf8String(const fspath& path) { + const std::u8string& pathStr = path.u8string(); + return std::string(pathStr.begin(), pathStr.end()); + } + + #ifdef QT_GUI + // Wrapper for path -> QString + // Use a wide string type to cover Windows where paths are UTF-16 encoded (and hopefully still be fine on other platforms) + // Also use the "generic" version with '/' separators because the Windows '\' breaks some paths + inline QString toQString(const fspath& path) { return QString::fromStdU32String(path.generic_u32string()); } + inline fspath fromQString(const QString& path) { return path.toStdU32String(); } + #endif +} diff --git a/src/dusk/randomizer/utility/platform.cpp b/src/dusk/randomizer/utility/platform.cpp new file mode 100644 index 0000000000..e47ccb7b81 --- /dev/null +++ b/src/dusk/randomizer/utility/platform.cpp @@ -0,0 +1,263 @@ +#include "../utility/platform.hpp" +#include "../utility/log.hpp" + +#include +#include + +#ifdef PLATFORM_DKP +#include + +#include + +#define PRINTF_BUFFER_LENGTH 2048 + +static bool mochaOpen = false; +static bool MLCMounted = false; +static bool USBMounted = false; +static bool DiscMounted = false; +#endif + +static std::mutex printMut; + +#ifdef PLATFORM_DKP +static bool flushVolume(const std::string& vol) +{ + const FSAClientHandle handle = FSAAddClient(NULL); + if (handle < 0) + { + return false; + } + + if (FSAFlushVolume(handle, vol.c_str()) != FS_ERROR_OK) + { + return false; + } + + if (FSADelClient(handle) != FS_ERROR_OK) + { + return false; + } + + return true; +} + +bool initMocha() +{ + tphdr::utility::platform::Log("Starting libmocha..."); + + if (const MochaUtilsStatus status = Mocha_InitLibrary(); status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Mocha_InitLibrary() failed, error ") + Mocha_GetStatusStr(status)); + return false; + } + + tphdr::utility::platform::Log("Mocha initialized"); + return true; +} + +void closeMocha() +{ + if (MLCMounted) + { + if (!flushVolume("/vol/storage_mlc01")) + { // maybe check if we wrote to MLC + ErrorLog::getInstance().log("Could not flush MLC"); + } + if (const MochaUtilsStatus status = Mocha_UnmountFS("storage_mlc01"); status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Error unmounting MLC: ") + Mocha_GetStatusStr(status)); + } + MLCMounted = false; + } + + if (USBMounted) + { + if (!flushVolume("/vol/storage_usb01")) + { // maybe check if we wrote to USB + ErrorLog::getInstance().log("Could not flush USB"); + } + if (const MochaUtilsStatus status = Mocha_UnmountFS("storage_usb01"); status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Error unmounting USB: ") + Mocha_GetStatusStr(status)); + } + USBMounted = false; + } + + if (DiscMounted) + { + if (const MochaUtilsStatus status = Mocha_UnmountFS("storage_odd_content"); status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Error unmounting disc: ") + Mocha_GetStatusStr(status)); + } + DiscMounted = false; + } + + if (const MochaUtilsStatus status = Mocha_DeInitLibrary(); status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Mocha_DeinitLibrary() failed, error ") + Mocha_GetStatusStr(status)); + } + + return; +} + +namespace utility +{ + bool mountDeviceAndConvertPath(fspath& path) + { + if (path.string().starts_with("/vol/storage_mlc01")) + { + if (!MLCMounted) + { + tphdr::utility::platform::Log("Attempting to mount MLC"); + if (const MochaUtilsStatus status = Mocha_MountFS("storage_mlc01", nullptr, "/vol/storage_mlc01"); + status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Failed to mount MLC: ") + Mocha_GetStatusStr(status)); + return false; + } + + MLCMounted = true; + } + } + else if (path.string().starts_with("/vol/storage_usb01")) + { + if (!USBMounted) + { + tphdr::utility::platform::Log("Attempting to mount USB"); + if (const MochaUtilsStatus status = Mocha_MountFS("storage_usb01", nullptr, "/vol/storage_usb01"); + status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Failed to mount USB: ") + Mocha_GetStatusStr(status)); + return false; + } + + USBMounted = true; + } + } + else if (path.string().starts_with("/vol/storage_odd")) + { + if (!DiscMounted) + { + tphdr::utility::platform::Log("Attempting to mount disc"); + if (const MochaUtilsStatus status = Mocha_MountFS("storage_odd03", "/dev/odd03", "/vol/storage_odd_content"); + status != MOCHA_RESULT_SUCCESS) + { + ErrorLog::getInstance().log(std::string("Failed to mount disc: ") + Mocha_GetStatusStr(status)); + return false; + } + + DiscMounted = true; + } + } + else + { + return false; + } + + // https://github.com/emiyl/dumpling/blob/9290dad8f8d91cc3ef4c4b9602898d244a2a1454/source/app/filesystem.cpp#L130 + std::string working_path = path.string().substr(5); + if (const auto& driveEnd = working_path.find_first_of('/'); driveEnd != std::string::npos) + { + // Return mount path + the path after it + working_path.replace(driveEnd, 1, ":/", 2); + } + else + { + // Return just the mount path + working_path.append(":"); + } + path = working_path; + + return true; + } +} // namespace utility +#endif + +namespace tphdr::utility::platform +{ + void Log(const std::string& str) + { + std::unique_lock lock(printMut); +#ifdef PLATFORM_DKP + LogConsoleWrite(str.c_str()); + + if (ProcIsForeground()) + { + LogConsoleDraw(); + } +#else +#ifndef LOGIC_TESTS + printf("%s\n", str.c_str()); + fflush(stdout); // vscode debug console works better with this +#endif +#endif + lock.unlock(); + } + + bool Init() + { +#ifdef PLATFORM_DKP + ProcInit(); + ConsoleScreenInit(); + + initHomeMenu(); + initEnergySaver(); + + setHomeMenuEnable(false); + setDim(false); + setAPD(false); + + if (!initMocha()) + { + ErrorLog::getInstance().log("Failed to init libmocha"); + return false; + } + mochaOpen = true; +#endif + return true; + } + + bool IsRunning() + { +#ifdef PLATFORM_DKP + return ProcIsRunning(); +#else + return true; // not sure if it's worth doing anything for this +#endif + } + + void waitForPlatformStop() + { +#ifdef PLATFORM_DKP // only need to wait on console + while (IsRunning()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(33)); // Check ~30 times a second + } +#endif + } + + void Shutdown() + { +#ifdef PLATFORM_DKP + if (mochaOpen) + { + closeMocha(); + mochaOpen = false; + } + + resetHomeMenu(); + resetEnergySaver(); + + if (IsRunning()) + { + ProcExit(); + } + waitForPlatformStop(); +#endif + } +} // namespace tphdr::utility::platform diff --git a/src/dusk/randomizer/utility/platform.hpp b/src/dusk/randomizer/utility/platform.hpp new file mode 100644 index 0000000000..7e0a7977af --- /dev/null +++ b/src/dusk/randomizer/utility/platform.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#ifdef DEVKITPRO + #define PLATFORM_DKP + + #include "../utility/path.hpp" +#elif defined(_MSC_VER) + #define PLATFORM_MSVC +#elif defined(__GNUC__) || defined(__GNUG__) + #define PLATFORM_GCC +#elif defined(__clang__) + #define PLATFORM_CLANG +#else + #error UNKNOWN PLATFORM +#endif + +namespace tphdr::utility::platform +{ + void Log(const std::string& str); + + bool Init(); + + bool IsRunning(); + + void waitForPlatformStop(); + + void Shutdown(); + +#ifdef DEVKITPRO + bool mountDeviceAndConvertPath(fspath& path); +#endif +} diff --git a/src/dusk/randomizer/utility/random.cpp b/src/dusk/randomizer/utility/random.cpp new file mode 100644 index 0000000000..64be6b9c1f --- /dev/null +++ b/src/dusk/randomizer/utility/random.cpp @@ -0,0 +1,40 @@ +#include "../utility/random.hpp" + +namespace tphdr::utility::random +{ + static bool init = false; + static std::mt19937_64 generator; + + // Initialize with seed specified + void RandomInit(size_t seed) + { + init = true; + generator = std::mt19937_64 {seed}; + } + + // Returns a random integer in range [min, max-1] + uint32_t Random(int min, int max) + { + if (!init) + { + // No seed given, get a random number from device to seed + const auto seed = static_cast(std::random_device {}()); + RandomInit(seed); + } + + auto number = generator(); + return min + (number % (max - min)); + } + + // Returns a random floating point number in [0.0, 1.0] + double RandomDouble() + { + auto number = generator(); + return (double)number / (double)generator.max(); + } + + std::mt19937_64& GetGenerator() + { + return generator; + } +} // namespace tphdr::utility::random diff --git a/src/dusk/randomizer/utility/random.hpp b/src/dusk/randomizer/utility/random.hpp new file mode 100644 index 0000000000..ce55c213ae --- /dev/null +++ b/src/dusk/randomizer/utility/random.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace tphdr::utility::random +{ + void RandomInit(size_t seed); + uint32_t Random(int min, int max); + double RandomDouble(); + std::mt19937_64& GetGenerator(); + + /** + * @brief Will get and erase a random element out of a vector + * + * @param vector The vector to get a random element from + * @return a random element from the vector + */ + template + T PopRandomElement(std::vector& vector) + { + const auto idx = Random(0, vector.size()); + T selected = vector[idx]; + vector.erase(vector.begin() + idx); + return selected; + } + + template + auto& RandomElement(Container& container) + { + return container[Random(0, std::size(container))]; + } + template + const auto& RandomElement(const Container& container) + { + return container[Random(0, std::size(container))]; + } + + // Shuffle items within a vector or array + template + void ShufflePool(std::vector& vector) + { + for (std::size_t i = 0; i + 1 < vector.size(); i++) + { + std::swap(vector[i], vector[Random(i, vector.size())]); + } + } + template + void ShufflePool(std::array& arr) + { + for (std::size_t i = 0; i + 1 < arr.size(); i++) + { + std::swap(arr[i], arr[Random(i, arr.size())]); + } + } +} // namespace tphdr::utility::random diff --git a/src/dusk/randomizer/utility/string.cpp b/src/dusk/randomizer/utility/string.cpp new file mode 100644 index 0000000000..20797227bb --- /dev/null +++ b/src/dusk/randomizer/utility/string.cpp @@ -0,0 +1,52 @@ +#include "../utility/string.hpp" + +#include +#include +#include +#include +#include + +namespace tphdr::utility::str { + //can't use codecvt on Wii U, deprecated in c++17 and g++ hates it + //Borrowed from https://docs.microsoft.com/en-us/cpp/standard-library/codecvt-class?view=msvc-170#out + std::string toUTF8(const std::u16string& str) { + if(str.empty()) return ""; + + std::string ret; + ret.resize(str.size()); + char* pszNext; + const char16_t* pwszNext; + std::mbstate_t state = {0}; // zero-initialization represents the initial conversion state for mbstate_t + std::locale loc("C"); + int res = std::use_facet>(loc).out(state, str.c_str(), &str[str.size()], pwszNext, + &ret[0], &ret[ret.size()], pszNext); + + if(res == std::codecvt_base::error) return ""; + return ret; + } + + std::u16string toUTF16(const std::string& str) + { + if(str.empty()) return u""; + + std::u16string ret; + ret.resize(str.size()); + const char* pszNext; + char16_t* pwszNext; + std::mbstate_t state = {0}; // zero-initialization represents the initial conversion state for mbstate_t + std::locale loc("C"); + int res = std::use_facet>(loc).in(state, str.c_str(), &str[str.size()], pszNext, + &ret[0], &ret[ret.size()], pwszNext); + + if(res == std::codecvt_base::error) return u""; + + // Remove extra null terminators that may have been created from multi-byte + // UTF-8 characters + while(ret.size() > 0 && ret[ret.size() - 1] == u'\0') + { + ret.pop_back(); + } + + return ret; + } +} diff --git a/src/dusk/randomizer/utility/string.hpp b/src/dusk/randomizer/utility/string.hpp new file mode 100644 index 0000000000..19954f26c1 --- /dev/null +++ b/src/dusk/randomizer/utility/string.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tphdr::utility::str { + std::string toUTF8(const std::u16string& str); + + std::u16string toUTF16(const std::string& str); + + template + concept StringType = std::derived_from>; + + template requires StringType + std::vector Split(const T& string, const typename T::value_type delim) { + std::vector ret; + T tail = string; + auto index = tail.find_first_of(delim); + + while (index != T::npos) { + ret.push_back(tail.substr(0, index)); + tail = tail.substr(index + 1); + index = tail.find_first_of(delim); + } + ret.push_back(tail); //add anything after last line break + + return ret; + } + + template requires StringType + T Merge(const std::vector& lines, const typename T::value_type separator) { + T ret; + for (const T& segment : lines) { + ret += segment + separator; + } + + return ret; + } + + template requires StringType + T assureNullTermination(const T& string) { + if(!string.empty() && string.back() == typename T::value_type(0)) return string; + + return string + typename T::value_type(0); + } + + template requires std::integral + std::string intToHex(const T& i, const bool& base = true) + { + std::stringstream stream; + stream << std::hex << (base ? std::showbase : std::noshowbase) << i; + return stream.str(); + } + + template requires std::integral + std::string intToHex(const T& i, const std::streamsize& width, const bool& base = true) + { + std::stringstream stream; + stream << std::hex << (base ? std::showbase : std::noshowbase) << std::setfill('0') << std::setw(width) << i; + return stream.str(); + } + + /** + * @brief Checks to see if any of the passed in substrings are within a string + * + * @param str The string to check for substrings + * @param substrs Paramater Pack of strings to test against the first argument + * + * @return true if any of the passed in substrings are found within the string, false otherwise + */ + template + bool Contains(const std::string& str, Types... substrs) + { + for (const auto& substr : {substrs...}) + { + if (str.find(substr) != std::string::npos) + { + return true; + } + } + return false; + } + + //wrapper for a constexpr string, for use in other templates + template + struct StringLiteral { + constexpr StringLiteral(const char (&str)[N]) { + std::copy_n(str, N, value); + } + + constexpr operator std::string_view() const { + return std::string_view(value, N - 1); //leave out null terminator + } + + char value[N]; + }; + + template + void Erase(std::string& s, Types... tokens) + { + for (const auto& token : {tokens...}) + { + while (tphdr::utility::str::Contains(s, token)) + { + s.erase(s.find(token), strlen(token)); + } + } + } +} diff --git a/src/dusk/randomizer/utility/text.cpp b/src/dusk/randomizer/utility/text.cpp new file mode 100644 index 0000000000..b6e556d390 --- /dev/null +++ b/src/dusk/randomizer/utility/text.cpp @@ -0,0 +1,178 @@ +// #include "../utility/text.hpp" +// #include "../utility/string.hpp" +// #include + +// namespace Text { + +// std::array supported_languages = {"English", "Spanish", "French"}; + +// static std::unordered_map nameToColor = { +// {Text::Color::NONE, TEXT_COLOR_DEFAULT}, +// {Text::Color::RED, TEXT_COLOR_RED}, +// {Text::Color::GREEN, TEXT_COLOR_GREEN}, +// {Text::Color::BLUE, TEXT_COLOR_BLUE}, +// {Text::Color::YELLOW, TEXT_COLOR_YELLOW}, +// {Text::Color::CYAN, TEXT_COLOR_CYAN}, +// {Text::Color::MAGENTA, TEXT_COLOR_MAGENTA}, +// {Text::Color::GRAY, TEXT_COLOR_GRAY}, +// {Text::Color::ORANGE, TEXT_COLOR_ORANGE}, +// }; + +// std::u16string apply_name_color(std::u16string str, const Color& color) +// { +// // Return the raw text (bars included) +// if (color == Color::RAW) +// { +// return str; +// } +// // If there are no '|'s then just return with the color surrounding the whole string +// if (str.find('|') == std::string::npos) +// { +// auto textColor = nameToColor[color]; +// return textColor + str + TEXT_COLOR_DEFAULT; +// } + +// // Alternate between the text color and default incase there are multiple +// // pairs of bars +// auto textColor = nameToColor[color]; +// bool insertColor = false; +// for (size_t pos = 0; pos < str.length(); pos++) +// { +// if (str[pos] == '|') +// { +// insertColor = !insertColor; +// str.erase(pos, 1); +// str.insert(pos, insertColor ? textColor : TEXT_COLOR_DEFAULT); +// } +// } + +// return str; +// } + +// std::u16string word_wrap_string(const std::u16string& string, const size_t& max_line_len) { +// size_t index_in_str = 0; +// std::u16string wordwrapped_str; +// std::u16string current_word; +// size_t curr_word_len = 0; +// size_t len_curr_line = 0; + +// while (index_in_str < string.length()) { //length is weird because its utf-16 +// char16_t character = string[index_in_str]; + +// if (character == u'\x0E') { //need to parse the commands, only implementing a few necessary ones for now (will break with other commands) +// std::u16string substr; +// size_t code_len = 0; +// if (string[index_in_str + 1] == u'\x00') { +// if (string[index_in_str + 2] == u'\x03') { //color command +// if (string[index_in_str + 4] == u'\xFFFF') { //text color white, weird length +// code_len = 10; +// } +// else { +// code_len = 5; +// } +// } +// } +// else if (string[index_in_str + 1] == u'\x01') { //all implemented commands in this group have length 4 +// code_len = 4; +// } +// else if (string[index_in_str + 1] == u'\x02') { //all implemented commands in this group have length 4 +// code_len = 4; +// } +// else if (string[index_in_str + 1] == u'\x03') { //all implemented commands in this group have length 4 +// code_len = 4; +// } +// else if (string[index_in_str + 1] == u'\x04') { //all implemented commands in this group have length 4. Only used for Ho Ho sound +// code_len = 4; +// } + +// substr = string.substr(index_in_str, code_len); +// current_word += substr; +// index_in_str += code_len; +// } +// else if (character == u'\n') { +// wordwrapped_str += current_word; +// wordwrapped_str += character; +// len_curr_line = 0; +// current_word = u""; +// curr_word_len = 0; +// index_in_str += 1; +// } +// else if (character == u' ') { +// wordwrapped_str += current_word; +// wordwrapped_str += character; +// len_curr_line += curr_word_len + 1; +// current_word = u""; +// curr_word_len = 0; +// index_in_str += 1; +// } +// else { +// current_word += character; +// curr_word_len += 1; +// index_in_str += 1; + +// if (len_curr_line + curr_word_len > max_line_len) { +// wordwrapped_str += u'\n'; +// len_curr_line = 0; + +// if (curr_word_len > max_line_len) { +// wordwrapped_str += current_word + u'\n'; +// current_word = u""; +// } +// } +// } +// } +// wordwrapped_str += current_word; + +// return wordwrapped_str; +// } + +// std::string pad_str_4_lines(const std::string& string) +// { +// std::vector lines = tphdr::utility::str::Split(string, '\n'); + +// unsigned int padding_lines_needed = (4 - lines.size() % 4) % 4; +// for (unsigned int i = 0; i < padding_lines_needed; i++) +// { +// lines.push_back(""); +// } + +// return tphdr::utility::str::Merge(lines, '\n'); +// } + +// std::u16string pad_str_4_lines(const std::u16string& string) +// { +// std::vector lines = tphdr::utility::str::Split(string, u'\n'); + +// unsigned int padding_lines_needed = (4 - lines.size() % 4) % 4; +// for (unsigned int i = 0; i < padding_lines_needed; i++) +// { +// lines.push_back(u""); +// } + +// return tphdr::utility::str::erge(lines, u'\n'); +// } + +// Gender string_to_gender(const std::string& str) +// { +// std::unordered_map strToGender = { +// {"Male", Gender::MALE}, +// {"Female", Gender::FEMALE} +// }; + +// if (strToGender.contains(str)) +// { +// return strToGender.at(str); +// } + +// return Gender::NONE; +// } + +// Plurality string_to_plurality(const std::string& str) +// { +// if (str == "Plural") return Plurality::PLURAL; +// return Plurality::SINGULAR; +// } +// }; // namespace Text diff --git a/src/dusk/randomizer/utility/text.hpp b/src/dusk/randomizer/utility/text.hpp new file mode 100644 index 0000000000..3efaa78e51 --- /dev/null +++ b/src/dusk/randomizer/utility/text.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +namespace Text + { + enum struct Type + { + STANDARD = 0, + PRETTY, + CRYPTIC, + }; + + enum struct Color + { + RAW = 0, + NONE, + RED, + GREEN, + BLUE, + YELLOW, + CYAN, + MAGENTA, + GRAY, + ORANGE, + }; + + enum struct Gender + { + NONE = 0, + MALE, + FEMALE, + }; + + enum struct Plurality + { + SINGULAR, + PLURAL, + }; + + struct Translation + { + std::map types; + Gender gender; + Plurality plurality; + }; + + extern std::array supported_languages; + + std::u16string apply_name_color(std::u16string str, const Color& color); + std::u16string word_wrap_string(const std::u16string& string, const size_t& max_line_len); //IMPROVEMENT: use font data to do this "properly" + std::string pad_str_4_lines(const std::string& string); + std::u16string pad_str_4_lines(const std::u16string& string); + + Gender string_to_gender(const std::string& str); + Plurality string_to_plurality(const std::string& str); +}; // namespace Text diff --git a/src/dusk/randomizer/utility/thread_local.hpp b/src/dusk/randomizer/utility/thread_local.hpp new file mode 100644 index 0000000000..5a5e3814ec --- /dev/null +++ b/src/dusk/randomizer/utility/thread_local.hpp @@ -0,0 +1,41 @@ +#pragma once + +#ifdef DEVKITPRO +#include +#include +#include +#endif + +enum struct DataIDs : uint32_t { +#ifdef DEVKITPRO + FILE_OP_BUFFER = OS_THREAD_SPECIFIC_0 +#else + FILE_OP_BUFFER = 0 +#endif +}; + +template +class ThreadLocal { +private: +#ifdef DEVKITPRO //TODO: somehow unregister data on all threads during destruct? + std::list data; +#else + inline static thread_local T data; +#endif + +public: + T& get() { + #ifdef DEVKITPRO + const OSThreadSpecificID& id = static_cast(ID); + if(OSGetThreadSpecific(id) == nullptr) { + data.emplace_back(); + OSSetThreadSpecific(id, &data.back()); + } + return *reinterpret_cast(OSGetThreadSpecific(id)); + #else + return data; + #endif + } + + ThreadLocal() = default; +}; diff --git a/src/dusk/randomizer/utility/time.cpp b/src/dusk/randomizer/utility/time.cpp new file mode 100644 index 0000000000..b98e44113a --- /dev/null +++ b/src/dusk/randomizer/utility/time.cpp @@ -0,0 +1,79 @@ +#include "../utility/time.hpp" + +using namespace std::chrono; +using namespace std::literals::chrono_literals; + +namespace tphdr::utility::time +{ + ProgramTime::ProgramTime(): openTime(Clock_t::now()) {} + + ProgramTime& ProgramTime::getInstance() + { + static ProgramTime s_Instance; + return s_Instance; + } + + ProgramTime::TimePoint_t ProgramTime::getOpenedTime() + { + return getInstance().openTime; + } + + ProgramTime::Duration_t ProgramTime::getElapsedTime() + { + return Clock_t::now() - getOpenedTime(); + } +} // namespace tphdr::utility::time +#if __has_include() && !defined(__APPLE__) +#include +namespace tphdr::utility::time +{ + std::string ProgramTime::getDateStr() + { + return std::format("{0:%a, %b %d, %Y, %I:%M:%S %p%n}", round(getOpenedTime())); + } + + std::string ProgramTime::getTimeStr() + { + return std::format("{:%T}", round(getElapsedTime())); + } +} // namespace tphdr::utility::time +#else +#include +#include +namespace tphdr::utility::time +{ + std::string ProgramTime::getDateStr() + { + const time_t point = Clock_t::to_time_t(ProgramTime::getOpenedTime()); + + static std::mutex localtimeMut; // std::ctime is not thread safe + std::unique_lock lock(localtimeMut); + return std::ctime(&point); // time string ends with \n + } + + std::string ProgramTime::getTimeStr() + { + Duration_t duration = getElapsedTime(); + std::stringstream ret; + ret << std::setfill('0'); + + const hours hr = duration_cast(duration); + ret << std::setw(2) << hr.count() << ":"; + duration -= hr; + const minutes min = duration_cast(duration); + ret << std::setw(2) << min.count() << ":"; + duration -= min; + const seconds sec = duration_cast(duration); + ret << std::setw(2) << sec.count() << "."; + duration -= sec; + const milliseconds ms = duration_cast(duration); + ret << std::setw(3) << ms.count(); + + return ret.str(); + } +} // namespace tphdr::utility::time +#endif +namespace tphdr::utility::time +{ + static const ProgramTime& temp = ProgramTime::getInstance(); // inaccessible global to create instance when program starts +}; // namespace tphdr::utility::time diff --git a/src/dusk/randomizer/utility/time.hpp b/src/dusk/randomizer/utility/time.hpp new file mode 100644 index 0000000000..eaa0008ef7 --- /dev/null +++ b/src/dusk/randomizer/utility/time.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include "../utility/log.hpp" +#include "../utility/string.hpp" +#include "../utility/platform.hpp" + +namespace tphdr::utility::time +{ + template + concept DurationType = std::same_as>; + + template + requires DurationType + class Timer + { + public: + typename Clock::duration getElapsed() const + { + end = Clock::now(); + return end - begin; + } + + protected: + typename Clock::time_point begin; + typename Clock::time_point end; + + void start() { begin = Clock::now(); } + + void stop() + { + end = Clock::now(); + duration = end - begin; + } + + void print() const + { + std::stringstream message; + message << stem << (stem.back() == ' ' ? "" : " ") << std::chrono::duration_cast(duration); + + tphdr::utility::platform::Log(message.str()); + LOG_TO_DEBUG(message.str() + '\n'); + } + + private: + typename Clock::duration duration; + + static constexpr std::string_view stem = Message; + }; + + template + requires DurationType + class ScopedTimer: public Timer + { + public: + ScopedTimer() { Timer::start(); } + + ~ScopedTimer() + { + Timer::stop(); + Timer::print(); + } + }; + + class ProgramTime + { + private: + using Clock_t = std::chrono::system_clock; + using TimePoint_t = Clock_t::time_point; + using Duration_t = Clock_t::duration; + + const TimePoint_t openTime; + static TimePoint_t getOpenedTime(); + static Duration_t getElapsedTime(); + + ProgramTime(); + ~ProgramTime() = default; + + public: + ProgramTime(const ProgramTime&) = delete; + ProgramTime& operator=(const ProgramTime&) = delete; + + static ProgramTime& getInstance(); + static std::string getTimeStr(); + static std::string getDateStr(); + }; + +} // namespace tphdr::utility::time diff --git a/src/dusk/randomizer/utility/yaml.hpp b/src/dusk/randomizer/utility/yaml.hpp new file mode 100644 index 0000000000..ced86b5d15 --- /dev/null +++ b/src/dusk/randomizer/utility/yaml.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "yaml-cpp/yaml.h" + +#include "../utility/file.hpp" +#include "../utility/log.hpp" +#include "../utility/path.hpp" + +// this wrapper is here to avoid path encoding issues +// removes any possible path -> string oddities or the need to open the file manually +inline YAML::Node LoadYAML(const fspath& path, const bool& resourceFile = false) { + std::string file; + if (tphdr::utility::file::GetContents(path, file, resourceFile) != 0) { + throw YAML::BadFile( + path.string()); // exception is bad (unhandled) but it matches the old behavior + } + + return YAML::Load(file); +} + +template +void YAMLVerifyFields(const YAML::Node& node, Fields... requiredFields) { + for (const auto& field : {requiredFields...}) { + if (!node[field]) { + throw std::runtime_error(std::string("Field \"") + field + + "\" is missing from node:\n" + YAML::Dump(node)); + } + } +} \ No newline at end of file diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 9cb7e41764..b9554fa09c 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -61,6 +61,10 @@ #include "cxxopts.hpp" #include "dusk/config.hpp" +#if RANDOMIZER_ONLY +#include "dusk/randomizer/randomizer.hpp" +#endif + // --- GLOBALS --- s8 mDoMain::developmentMode = -1; OSTime mDoMain::sPowerOnTime; @@ -279,6 +283,12 @@ static const char* CalculateConfigPath() { // PC ENTRY POINT // ========================================================================= int game_main(int argc, char* argv[]) { + + #if RANDOMIZER_ONLY + randomizerMain(); + exit(0); + #endif + dusk::registerSettings(); dusk::config::FinishRegistration();