diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index 9595aa79f0..c27db9594d 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -88,6 +88,49 @@ jobs: ref: 'master' path: 'Arduino-Source/Packages' + - name: Install Python for pinned map preparation + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Prepare approved FRLG map resources + id: map-resources + uses: dayfine/PokemonSimulation/integrations/arduino-source/verify-map-packs@906956e94fc912ae9adb0e44845da780f3bc7e4c + with: + layout: resources + operation: prepare + consumer-directory: Arduino-Source + + - name: Checkout authored checkpoint simulator + if: startsWith(inputs.os, 'ubuntu') && inputs.run-tests + uses: actions/checkout@v7 + with: + repository: dayfine/PokemonSimulation + ref: 8cb831caf8bfe1f06b1aec9a4a7e55e6e09b0604 + path: AuthoredStorySource + + - name: Check identical authored Parcel JSON and replay its scenarios + if: startsWith(inputs.os, 'ubuntu') && inputs.run-tests + env: + PYTHONPATH: AuthoredStorySource/src + run: | + python3 - <<'PYTHON' + from pathlib import Path + from pokesim.story.replay_binding import load_binding + from pokesim.story.replay_cases import read_cases, check_case + from pokesim.story.checkpoint_replay import run + source = Path('AuthoredStorySource') + folder = source / 'data/story/parcel-pilot' + native = Path('Arduino-Source/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData') + assert (native / 'frlg-parcel-00.05.plan.json').read_bytes() == (folder / 'frlg-parcel-00.05.plan.json').read_bytes() + binding = load_binding(folder / 'authored-binding.json', source) + cases = read_cases(folder / 'cases.json') + for case in cases: + check_case(case, run(binding, case['input'])) + print(case['id'] + ': authored JSON replay passed') + assert len(cases) == 7 + PYTHON + - name: Install Qt uses: jurplel/install-qt-action@v4 with: @@ -204,6 +247,8 @@ jobs: # every file missed, compiling took 10-40% longer than before on Linux and macOS, and # about twice as long on Windows. CCACHE_DEPEND: 1 + POKESIM_TOOLS_SOURCE: ${{ steps.map-resources.outputs.tools-source }} + POKESIM_SOURCE_CACHE: ${{ steps.map-resources.outputs.source-cache }} run: | cd Arduino-Source/SerialPrograms mkdir bin @@ -265,7 +310,9 @@ jobs: uses: actions/checkout@v7 if: inputs.run-tests with: - repository: 'PokemonAutomation/CommandLineTests' + # Complete fork fixtures include pending FRLG image submissions. + repository: 'dayfine/CommandLineTests' + ref: 'd150a6c547434110a920f95ba7857b93d5bb9222' path: 'CommandLineTests' # The unit tests assume the downloadable resources they need are already downloaded diff --git a/.gitignore b/.gitignore index ef6f409ae9..a6b04ddffd 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ Packages/* # Local agent/linter environments. .venv-code-health/ __pycache__/ + +# External PokemonSimulation tools/data cache; prepared maps live in ignored Packages. +.pokesim-map-source/ diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index 73729ebde9..bd7c93a134 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -227,10 +227,14 @@ add_library(SerialProgramsLib STATIC ${CORE_LIBRARY_SOURCES} ${LIBRARY_SOURCES}) include(cmake/EmbedTextFiles.cmake) pa_embed_text_files(SerialProgramsLib Source/Pokemon/Navigation/TestMaps Pokemon/Navigation/Pokemon_Navigation_Tests.h PokemonAutomation::Pokemon navigation_test_map_json) -pa_embed_text_files(SerialProgramsLib Source/VideoGames/PokemonFRLG/Overworld/Maps +include(cmake/PokesimMapResources.cmake) +pa_frlg_map_resources(FRLG_MAP_RESOURCES) +pa_embed_text_files(SerialProgramsLib "${FRLG_MAP_RESOURCES}" VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h PokemonAutomation::NintendoSwitch::PokemonFRLG overworld_map_json) pa_embed_text_files(SerialProgramsLib Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.h PokemonAutomation::NintendoSwitch::PokemonFRLG autostory_battle_json) +pa_embed_text_files(SerialProgramsLib Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData + VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h PokemonAutomation::NintendoSwitch::PokemonFRLG autostory_checkpoint_json) # Create binary target diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp index c1a89a6176..af82fde0ea 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp @@ -73,6 +73,7 @@ class PlaceNameMatcher : public OCR::SmallDictionaryMatcher{ {"cerulean-city", "Cerulean City"}, {"route-24", "Route 24"}, {"route-25", "Route 25"}, + {"route-10", "Route 10"}, {"vermilion-city", "Vermilion City"}, }){ JsonArray names; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h index cc07c4b7b3..f693d02977 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h @@ -33,7 +33,7 @@ ImageFloatBox save_card_title_box(); // The place on the save card: "pallet-town", "viridian-city", // "viridian-forest", "pewter-city", "route-1" to "route-4", "cerulean-city", -// "route-24", "route-25", "vermilion-city"; empty if it can't be read, as +// "route-10", "route-24", "route-25", "vermilion-city"; empty if it can't be read, as // in a language the reader has no names for. std::string read_save_card_location(Logger& logger, Language language, const ImageViewRGB32& screen); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json deleted file mode 100644 index e1539f229f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "name": "CeruleanCave_1F", - "source": { - "decomp_file": "data/maps/CeruleanCave_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################", - "#######~~~##~#########wwwwwwwww#~~##~~##", - "#######~~~.~~#########wwwwwwwww#~~.~~~##", - "#######~~##~~#wwwwwwwwwwwwwwwww#~~~~~~##", - "##.####~~##~~#wwwwwwwwww#~~~#ww##.######", - "##~#~~~~~~~~~#wwwwwwwwww#~~~#wwwwwwwwww#", - "#~~#~~~~##~~~#www#~~~~~~~~~~#wwwwwwwwww#", - "#.~#~~~~##~~~#www#~~~~~~~~~~#wwwwwwwwww#", - "#~~#~~~~##~~~#www#~~~~~~~~~~~~~~~~~#www#", - "####~~~~##.###www#~~~######~~~#~~~~#www#", - "##~#~~~~#www##www#~~~##~####~~.~~~~#www#", - "##~~~~~~#www##www##.###~.~##~~~~~~~#www#", - "##~~~~~~#wwwwwwww#~~~~#~~~##~~~#####www#", - "##~~~~~~#wwwwwwww#~~#~##.###~~~###~~~~~#", - "##~~##~~#wwwwwwww#~~~~~~~~~#~~~#~~~~~~~#", - "##~~#.~~#www#~~~~##~~~~~~###~~~#~~~~~###", - "##~~~~~~#~~~#~~~~####~~~####~~~#~~~#####", - "##~~~~~~#~~~#~~~~~~~~~~~~~~~~~~#~~~#####", - "#####.###~~~##.#~~~~~~~~~~~~~~~#~~~#####", - "#####~~~~~~~~~~#~~~~~~~~~~~~~~~#~~~#####", - "#####~~~~~~~~~~#~~~~~~~~~~~~~~~#~~~#####", - "#####~~~~~~~~~~#################~.~#####", - "########################################" - ], - "elevation": [ - "0000000000000000000000000000000000000000", - "0000000333003000000000111111111033003300", - "0000000333333000000000111111111033333300", - "0000000330033011111111111111111033333300", - "0030000330033011111111110444011003000000", - "0030333333333011111111110444011111111110", - "0330333300333011104444444444011111111110", - "0330333300333011104444444444011111111110", - "0330333300333011104444444444444444401110", - "0000333300300011104440000004440444401110", - "0030333301110011104440030000444444401110", - "0033333301110011100000033300444444401110", - "0033333301111111103333033300444000001110", - "0033333301111111103303003000444000333330", - "0033003301111111103333333330444033333330", - "0033033301110444400333333000444033333000", - "0033333303330444400003330000444033300000", - "0033333303330444444444444444444033300000", - "0000030003330000444444444444444033300000", - "0000033333333330444444444444444033300000", - "0000033333333330444444444444444033300000", - "0000033333333330000000000000000033300000", - "0000000000000000000000000000000000000000" - ], - "points": { - "entrance": [33, 21], - "warp_0": [33, 21], - "warp_1": [34, 2], - "warp_2": [1, 7], - "warp_3": [10, 2], - "warp_4": [5, 15], - "warp_5": [30, 10], - "warp_6": [24, 11], - "warp_7": [2, 4] - }, - "warps": [ - { - "x": 33, - "y": 21, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_7" - }, - { - "x": 34, - "y": 2, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_0" - }, - { - "x": 1, - "y": 7, - "enter": "step_on", - "to_map": "CeruleanCave_B1F", - "to_point": "warp_0" - }, - { - "x": 10, - "y": 2, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_1" - }, - { - "x": 5, - "y": 15, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_2" - }, - { - "x": 30, - "y": 10, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_3" - }, - { - "x": 24, - "y": 11, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_4" - }, - { - "x": 2, - "y": 4, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_5" - } - ], - "objects": [ - { - "name": "item_ball", - "kind": "item", - "x": 7, - "y": 3, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_1F_NUGGET" - }, - { - "name": "item_ball", - "kind": "item", - "x": 11, - "y": 16, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_1F_FULL_RESTORE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 25, - "y": 5, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_1F_MAX_ELIXIR" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 7, - "y": 21, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 13, - "y": 21, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 5, - "y": 20, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 14, - "y": 20, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 11, - "y": 21, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 9, - "y": 18, - "hide_flag": "FLAG_TEMP_17" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json deleted file mode 100644 index fa72ccc628..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "name": "CeruleanCave_2F", - "source": { - "decomp_file": "data/maps/CeruleanCave_2F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################", - "########################################", - "########################################", - "####~~~~####~~##~~~~~#~~~~~~~~~~~~######", - "#######~~~~~~.#~~###~#~#########~.######", - "####~~####~####~##~#~#~~~~~~~~~#########", - "####~.~~~#~~~~~~~~~###########~~~~######", - "#######~############~~~~~~~~~####~######", - "####~#~~~~~~~~~~~~~~~#######~~~~#~######", - "####~#################~~#~.###~##~######", - "####~#~~~~#~~~~~~~~~~~~.#~~~#~~~#~######", - "####~#~##~#~#####~#######~#####~#~######", - "####~#~~#~#~~~~~#~~~~~~~#~~~~~#~#~######", - "####~#~~#~#####~#######~#####~#~########", - "####~#~.#~#~~~~~#~~~~~#~~~~~#~#~#~######", - "####~####~#~#####~###~#~#~#~#~#~#~######", - "####~#~~~~#~~~~~~~~~#~#~#~#~#~#~#~######", - "####~#~####~#########~#~#~#~###~#~######", - "####~#~~~~#~~~~~#~~~~~#~#~#~~~~~#~######", - "####~############~#####~#~#######~######", - "####~~~~~~~~~~~~~~#~~~~~#~~~~~~~~~######", - "########################################", - "########################################" - ], - "points": { - "warp_0": [33, 4], - "warp_1": [13, 4], - "warp_2": [7, 14], - "warp_3": [26, 9], - "warp_4": [23, 10], - "warp_5": [5, 6] - }, - "warps": [ - { - "x": 33, - "y": 4, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_1" - }, - { - "x": 13, - "y": 4, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_3" - }, - { - "x": 7, - "y": 14, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_4" - }, - { - "x": 26, - "y": 9, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_5" - }, - { - "x": 23, - "y": 10, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_6" - }, - { - "x": 5, - "y": 6, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_7" - } - ], - "objects": [ - { - "name": "item_ball", - "kind": "item", - "x": 9, - "y": 18, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_2F_PP_UP" - }, - { - "name": "item_ball", - "kind": "item", - "x": 29, - "y": 16, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_2F_ULTRA_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 33, - "y": 12, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_2F_FULL_RESTORE" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 33, - "y": 10, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 33, - "y": 9, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 25, - "y": 11, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 28, - "y": 20, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 30, - "y": 20, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 9, - "y": 13, - "hide_flag": "FLAG_TEMP_17" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 23, - "y": 16, - "hide_flag": "FLAG_TEMP_18" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 13, - "y": 6, - "hide_flag": "FLAG_TEMP_19" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 4, - "y": 12, - "hide_flag": "FLAG_TEMP_1A" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 10, - "y": 20, - "hide_flag": "FLAG_TEMP_1B" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json deleted file mode 100644 index b06ee61a5f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "name": "CeruleanCave_B1F", - "source": { - "decomp_file": "data/maps/CeruleanCave_B1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################", - "##~~~~~~~~~~~~~~##~~~~~############~~~~#", - "##~~~~~~~~~~~~~~#~~~~~~~~#######~~~~~~~#", - "##~~~~~~~~~~~~~~#~~####~~~~~#######~~~~#", - "##~~~~~~#####.###~~####~~~~~#######~~~~#", - "##~~~~~~#~~~~~~~~~~########~#######~#~~#", - "##~~##~~##~~~~~~~~#########~~~~~~~~~~~~#", - "##~~#.~~###~~~~~~##########~~~~~~~~~~~~#", - "##~~~~~~#wwwww##~~~~~~~~~~~~~~~~#~~~####", - "#########wwwww##~~~~~~~~~~~~~~~~#~~~####", - "#www############~~~~~~~~~~~~~~~~#~~~####", - "#www##############.########~###~#~~~####", - "#www#~#~#~########~~~~~~~~#~###~#~~~####", - "#www#~~~~~########~~~~~~~~#~~~~~#~~~####", - "#www#~~~~~########~~~~~~~~#~~~~~#~~~####", - "#www###.###wwww#~~~~~~#~~~#~###~#~~~####", - "#wwwwwwwwwwwwww#~~~~~~#~~~#~###~#~~~####", - "#wwwwwwwwwwwwww#~~~~~~#~~~#~~~~~#~~~####", - "#wwwwwwwwwwwwww#~~~~~~#~~~#~~~~~#~~~####", - "######wwwwwwwww##.##.##~~~###.###~~~####", - "######wwwwwwwwwwww#~~~~~~~###~~~~~~~####", - "######wwwwwwwwwwww##~~~~~####~~~~~~~####", - "########################################" - ], - "elevation": [ - "0000000000000000000000000000000000000000", - "0033333333333333003333300000000000033330", - "0033333333333333033333333000000033333330", - "0033333333333333033000033333000000033330", - "0033333300000300033000033333000000033330", - "0033333303333333333000000003000000030330", - "0033003300333333330000000003333333333330", - "0033033300033333300000000003333333333330", - "0033333301111100444444444444444403330000", - "0000000001111100444444444444444403330000", - "0111000000000000444444444444444403330000", - "0111000000000000000000000004000403330000", - "0111030303000000003333333304000403330000", - "0111033333000000003333333304444403330000", - "0111033333000000003333333304444403330000", - "0111000300011110444444033304000403330000", - "0111111111111110444444033304000403330000", - "0111111111111110444444033304444403330000", - "0111111111111110404444033304444403330000", - "0000001111111110030000033300000003330000", - "0000001111111111110333333300033333330000", - "0000001111111111110033333000033333330000", - "0000000000000000000000000000000000000000" - ], - "points": { - "mewtwo": [7, 12], - "warp_0": [5, 7] - }, - "warps": [ - { - "x": 5, - "y": 7, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "item_ball", - "kind": "item", - "x": 31, - "y": 9, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_B1F_ULTRA_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 32, - "y": 2, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_B1F_MAX_REVIVE" - }, - { - "name": "mewtwo", - "kind": "npc", - "x": 7, - "y": 12, - "hide_flag": "FLAG_HIDE_MEWTWO" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 37, - "y": 1, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 38, - "y": 2, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 35, - "y": 1, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 37, - "y": 4, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 35, - "y": 5, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 2, - "y": 2, - "hide_flag": "FLAG_TEMP_17" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 4, - "y": 1, - "hide_flag": "FLAG_TEMP_18" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 6, - "y": 1, - "hide_flag": "FLAG_TEMP_19" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 3, - "y": 4, - "hide_flag": "FLAG_TEMP_1A" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json deleted file mode 100644 index 9334a3aa83..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json +++ /dev/null @@ -1,294 +0,0 @@ -{ - "name": "CeruleanCity", - "source": { - "decomp_file": "data/maps/CeruleanCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################wwwww#...#...#wwwwwwwwwwwwwwwwww", - "################wwwww#...#vvv#wwwwwwwwwwwwwwwwww", - "####wwwwwwwwwwwwwwwww#...#...#wwwwwwwwwwwwwwwwww", - "####wwwwwwwwwwwwwwwww#...#...#wwwwwwwwwwwwwwwwww", - "####wwwwwwwwwwwwwwwww#...#...###################", - "####www###############...#...#.........#########", - "####www#.............#...#...#.........#########", - "####www#.............#...#...#.........#########", - "####www##...##########.......#...##...##########", - "####www###############......#######...##########", - "####www###############......#######...##########", - "####www###D######D####......##D####......#######", - "#.##www#..........................#......#######", - "....www#..........................#......#######", - "....www#..........................#......#######", - "....www#.....#######..............#......#######", - "wwwwwww#...vv############.........#......#......", - "wwwwwww#.....##D#########.........#......#######", - "wwwwwww#............#####vvv#######.............", - "########............SSD##...#######.............", - "............................#######vvv#..#######", - "...........................S..#D#.....#..#......", - "......................................#..#######", - "#######...............................#..#######", - "#######.....####......................#..#######", - "#######....#####....S.................#..#######", - "#######....#####.....###############..#..#######", - "#######....#####.....###############..#..#######", - "#######....S#..#.....##D###SSD######..#..#######", - "#######...............................#..#######", - "#######...............................#..#######", - "#######...............................#..#######", - "###################S######.############..#######", - "#######..................................#######", - "#######..................................#######", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#........#....###############" - ], - "elevation": [ - "000000000000000011111033303330100000000000000000", - "000000000000000011111033300000100000000000000000", - "000011111111111111111033303330100000000000000000", - "000011111111111111111033303330111111111111111111", - "000011111111111111111033303330000000000000000000", - "000011100000000000000033303330333333333000000000", - "000011103333333333333033303330333333333000000000", - "000011103333333333333033303330333333333000000000", - "000011100333000000000033333330333003330000000000", - "000011100000000000000033333300000003330000000000", - "000011100000000000000033333300000003330000000000", - "000011100000000000000033333300000003333330000000", - "030011103333333333333333333333333303333330000000", - "333311103333333333333333333333333303333330000000", - "333311103333333333333333333333333303333330000000", - "333311103333300000003333333333333303333330000000", - "111111103330000000000000033333333303333330333333", - "111111103333300000000000033333333303333330000000", - "111111103333333333330000000000000003333333333333", - "000000003333333333330000033300000003333333333333", - "333333333333333333333333333300000000000330000000", - "333333333333333333333333333033000333330330333333", - "333333333333333333333333333333333333330330000000", - "000000033333333333333333333333333333330330000000", - "000000033333000033333333333333333333330330000000", - "000000033330000033330333333333333333330330000000", - "000000033330000033333000000000000000330330000000", - "000000033330000033333000000000000000330330000000", - "000000033330033033333000000000000000330330000000", - "000000033333333333333333333333333333330330000000", - "000000033333333333333333333333333333330330000000", - "000000033333333333333333333333333333330330000000", - "000000000000000000000000003000000000000330000000", - "000000033333333333333333333333333333333330000000", - "000000033333333333333333333333333333333330000000", - "000000000000000333303333333303333000000000000000", - "000000000000000333300000000003333000000000000000", - "000000000000000333303333333303333000000000000000", - "000000000000000333303333333303333000000000000000", - "000000000000000333303333333303333000000000000000" - ], - "points": { - "warp_1": [30, 11], - "warp_3": [22, 19], - "warp_4": [31, 21], - "warp_6": [29, 28], - "warp_7": [1, 12], - "warp_9": [31, 8] - }, - "warps": [ - { - "x": 10, - "y": 11, - "enter": "walk_into" - }, - { - "x": 30, - "y": 11, - "enter": "walk_into", - "to_map": "CeruleanCity_House2", - "to_point": "warp_1" - }, - { - "x": 15, - "y": 17, - "enter": "walk_into" - }, - { - "x": 22, - "y": 19, - "enter": "walk_into", - "to_map": "CeruleanCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 31, - "y": 21, - "enter": "walk_into", - "to_map": "CeruleanCity_Gym", - "to_point": "warp_1" - }, - { - "x": 13, - "y": 28, - "enter": "cave_entrance" - }, - { - "x": 29, - "y": 28, - "enter": "walk_into", - "to_map": "CeruleanCity_Mart", - "to_point": "warp_1" - }, - { - "x": 1, - "y": 12, - "enter": "cave_entrance", - "to_map": "CeruleanCave_1F", - "to_point": "warp_0" - }, - { - "x": 10, - "y": 8, - "enter": "walk_south" - }, - { - "x": 31, - "y": 8, - "enter": "walk_south", - "to_map": "CeruleanCity_House2", - "to_point": "warp_3" - }, - { - "x": 31, - "y": 9, - "enter": "walk_into", - "to_map": "CeruleanCity_House2", - "to_point": "warp_3" - }, - { - "x": 14, - "y": 28, - "enter": "cave_entrance" - }, - { - "x": 23, - "y": 28, - "enter": "walk_into" - }, - { - "x": 17, - "y": 11, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "policeman", - "kind": "npc", - "x": 31, - "y": 12 - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 33, - "y": 6, - "hide_flag": "FLAG_HIDE_CERULEAN_ROCKET" - }, - { - "name": "little_boy", - "kind": "npc", - "x": 16, - "y": 21, - "wander": { - "x0": 16, - "y0": 18, - "x1": 16, - "y1": 24 - } - }, - { - "name": "balding_man", - "kind": "npc", - "x": 9, - "y": 23, - "wander": { - "x0": 8, - "y0": 22, - "x1": 10, - "y1": 24 - } - }, - { - "name": "slowbro", - "kind": "npc", - "x": 32, - "y": 29 - }, - { - "name": "lass", - "kind": "npc", - "x": 33, - "y": 29 - }, - { - "name": "youngster", - "kind": "npc", - "x": 34, - "y": 22 - }, - { - "name": "blue", - "kind": "npc", - "x": 22, - "y": 0, - "hide_flag": "FLAG_HIDE_CERULEAN_RIVAL" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 26, - "y": 32, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "woman_1", - "kind": "npc", - "x": 12, - "y": 30 - }, - { - "name": "cooltrainer_m", - "kind": "npc", - "x": 1, - "y": 13, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_GUARD" - } - ], - "flag_sets": { - "after_hall_of_fame": [ - "FLAG_HIDE_CERULEAN_CAVE_GUARD", - "FLAG_HIDE_CERULEAN_RIVAL", - "FLAG_HIDE_CERULEAN_ROCKET" - ] - }, - "connections": [ - { - "side": "north", - "to_map": "Route24", - "offset": 12 - }, - { - "side": "south", - "to_map": "Route5", - "offset": 0 - }, - { - "side": "west", - "to_map": "Route4", - "offset": 10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json deleted file mode 100644 index 519944eecb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "name": "CeruleanCity_Gym", - "source": { - "decomp_file": "data/maps/CeruleanCity_Gym/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#################", - "#####w#w.w#w#####", - "#################", - "#...............#", - "#......www......#", - "#.wwwwwwwwwwwww.#", - "#.wwwww...wwwww.#", - "#.ww.........ww.#", - "#.wwwww...ww.ww.#", - "#.wwwwwwwwww.ww.#", - "#.ww.........ww.#", - "#.ww.wwww.wwwww.#", - "#.ww.wwww.wwwww.#", - "#.ww......wwwww.#", - "#.wwwwwww.wwwww.#", - "#.wwwwwww.wwwww.#", - "#.wwww.....wwww.#", - "#.wwwwS...Swwww.#", - "#...............#", - "#################" - ], - "elevation": [ - "00000000000000000", - "00000000000000000", - "00000000000000000", - "03333333333333330", - "03333331113333330", - "03111111111111130", - "03111113331111130", - "03113333333331130", - "03111113331131130", - "03111111111131130", - "03113333333331130", - "03113111131111130", - "03113111130111130", - "03113333331111130", - "03111111131111130", - "03111111131111130", - "03111133333111130", - "03111103330111130", - "03333333333333330", - "00000000000000000" - ], - "points": { - "misty_front": [8, 7], - "warp_1": [8, 18] - }, - "warps": [ - { - "x": 7, - "y": 18, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_4" - }, - { - "x": 8, - "y": 18, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_4" - }, - { - "x": 9, - "y": 18, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_4" - } - ], - "objects": [ - { - "name": "swimmer_m_water", - "kind": "npc", - "x": 10, - "y": 12, - "facing": "west", - "trainer": { - "sight": 1 - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 4, - "y": 7, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "misty", - "kind": "npc", - "x": 8, - "y": 6 - }, - { - "name": "gym_guy", - "kind": "npc", - "x": 7, - "y": 16 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json deleted file mode 100644 index 2abad6df50..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "CeruleanCity_House2", - "source": { - "decomp_file": "data/maps/CeruleanCity_House2/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "##########", - "####.#####", - "........##", - ".........#", - "...##.....", - "...##.....", - "..........", - "#.......##", - "##########" - ], - "points": { - "warp_1": [3, 7], - "warp_3": [4, 1] - }, - "warps": [ - { - "x": 2, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_1" - }, - { - "x": 3, - "y": 7, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_1" - }, - { - "x": 4, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_1" - }, - { - "x": 4, - "y": 1, - "enter": "cave_entrance", - "to_map": "CeruleanCity", - "to_point": "warp_9" - } - ], - "objects": [ - { - "name": "hiker", - "kind": "npc", - "x": 1, - "y": 2 - }, - { - "name": "lass", - "kind": "npc", - "x": 7, - "y": 6, - "wander": { - "x0": 6, - "y0": 5, - "x1": 8, - "y1": 7 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json deleted file mode 100644 index db4c9defee..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "CeruleanCity_Mart", - "source": { - "decomp_file": "data/maps/CeruleanCity_Mart/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "##.#......#", - "...#.......", - "####...##.#", - ".......##.#", - ".##....##.#", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "00303333330", - "33003333333", - "00003330030", - "33333330030", - "30033330030", - "33333333333", - "00000000000" - ], - "points": { - "clerk_front": [4, 3], - "warp_1": [4, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_6" - }, - { - "x": 4, - "y": 7, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_6" - }, - { - "x": 5, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_6" - } - ], - "objects": [ - { - "name": "clerk", - "kind": "npc", - "x": 2, - "y": 3 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 9, - "y": 2, - "wander": { - "x0": 5, - "y0": 2, - "x1": 10, - "y1": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 1, - "y": 7 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json deleted file mode 100644 index 28d628941f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "name": "CeruleanCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/CeruleanCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "pc": [11, 2], - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_3" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_3" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_3" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "rocker", - "kind": "npc", - "x": 5, - "y": 4, - "wander": { - "x0": 4, - "y0": 4, - "x1": 6, - "y1": 4 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 4, - "y": 8, - "wander": { - "x0": 3, - "y0": 8, - "x1": 5, - "y1": 8 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 3, - "y": 3 - }, - { - "name": "0", - "kind": "npc", - "x": 2, - "y": 1, - "hide_flag": "FLAG_HIDE_POSTGAME_GOSSIPERS" - }, - { - "name": "0", - "kind": "npc", - "x": 3, - "y": 1, - "hide_flag": "FLAG_HIDE_POSTGAME_GOSSIPERS" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json deleted file mode 100644 index cf3836faa8..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json +++ /dev/null @@ -1,229 +0,0 @@ -{ - "name": "MtMoon_1F", - "source": { - "decomp_file": "data/maps/MtMoon_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################################################", - "################################################", - "##~~~~~~~~~#########~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~#######~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~#####~~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~#####~~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~.~~~~~~~####~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~~~~~~~~~~~~~~~~~~~~~~#~~~~##", - "##~~~~~~~~~~~###~~~~~~~~~~~~~~~~~~~~~~~~####~~##", - "##~~~~~~~~~~~###~~~~~~~~~~~~~~~~~~~~~~~~~##~~~##", - "##~~~~~~~~~~~###~~~~###############~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~~###############~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~~###############~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~~#####~~~~~####~~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~.####~~~~~~~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~###~~~~####~~~~~~~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~###~~~~###~~~~~~~~.~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "###########~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "###########~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "###########~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "##~~~~#################~~~~###~~~~~~~~~###~~~~##", - "##~~~~~################~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###############~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###~~~~~~~~~~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###~~~~~~~~S~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###~~~~~~~~~~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~##########~~~###~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~##########~~~###~~~###~~~~##", - "##~~~~~~~~~~~~##~~~~##########~~~###~~~###~~~~##", - "##~~~~~~~~~~~####~~~#####~~~~~~~~###~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~######~~~~~~~###~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~######~~~~~~~###~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~#####~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~####~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~####~.~####~~~~~~~~~~~~~~~~~~~~~~##", - "################################################", - "################################################" - ], - "points": { - "warp_0": [5, 6], - "warp_1": [19, 14], - "warp_2": [31, 16], - "warp_3": [18, 37] - }, - "warps": [ - { - "x": 5, - "y": 6, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_0" - }, - { - "x": 19, - "y": 14, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_1" - }, - { - "x": 31, - "y": 16, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_2" - }, - { - "x": 18, - "y": 37, - "enter": "walk_south", - "to_map": "Route4", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "lass", - "kind": "npc", - "x": 20, - "y": 26, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 36, - "y": 30, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "scientist", - "kind": "npc", - "x": 30, - "y": 35, - "trainer": { - "sight": 4, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "lass", - "kind": "npc", - "x": 33, - "y": 4, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 7, - "y": 26, - "facing": "south", - "trainer": { - "sight": 4 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 13, - "y": 17, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 7, - "y": 10, - "wander": { - "x0": 5, - "y0": 8, - "x1": 9, - "y1": 12 - }, - "trainer": { - "sight": 1, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 2, - "y": 22, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_PARALYZE_HEAL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 11, - "y": 35, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_TM09" - }, - { - "name": "item_ball", - "kind": "item", - "x": 26, - "y": 32, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_POTION" - }, - { - "name": "item_ball", - "kind": "item", - "x": 42, - "y": 35, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_RARE_CANDY" - }, - { - "name": "item_ball", - "kind": "item", - "x": 44, - "y": 21, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_ESCAPE_ROPE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 3, - "y": 2, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_MOON_STONE" - }, - { - "name": "balding_man", - "kind": "npc", - "x": 42, - "y": 7 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json deleted file mode 100644 index 448aa61f4d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "name": "MtMoon_B1F", - "source": { - "decomp_file": "data/maps/MtMoon_B1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#################################################", - "#################################################", - "##################~~~~~~~~###############~~~~~###", - "##~.~~~##########~~~~~~~~#############~~~~~~~####", - "##~~~~~#########~~~~~~~~~.~##########~~.~~~~~.~##", - "##~~~~~#########~.~~~~~~~~~#########~~~~~~~~~~~##", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~###########################################", - "##~~~~~##########################################", - "##~~~#~##########################################", - "##~~~~###########################################", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~~~~~~~~~~~~~~~~~~~#########################", - "##~~~~~~~~~~~~~~~~~~~~~~#########################", - "##~~~~~~~~~~~~~~~~~~~~.~#########################", - "#################################################", - "#################################################", - "#########################################~~.~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "##########################################~~~####", - "##########################################~~~####", - "##########################################~~~####", - "##########################~~~~~~~~~~~####~~~~####", - "#########################~~~~~~~~~~~~~~~~~~~~####", - "########################~~.~~~~~~~~~~~~~~~~~~####", - "#################################################", - "#################################################", - "#################################################" - ], - "points": { - "warp_0": [3, 3], - "warp_1": [25, 4], - "warp_2": [43, 21], - "warp_3": [22, 18], - "warp_4": [17, 5], - "warp_5": [26, 36], - "warp_6": [39, 4], - "warp_7": [45, 4] - }, - "warps": [ - { - "x": 3, - "y": 3, - "enter": "step_on", - "to_map": "MtMoon_1F", - "to_point": "warp_0" - }, - { - "x": 25, - "y": 4, - "enter": "step_on", - "to_map": "MtMoon_1F", - "to_point": "warp_1" - }, - { - "x": 43, - "y": 21, - "enter": "step_on", - "to_map": "MtMoon_1F", - "to_point": "warp_2" - }, - { - "x": 22, - "y": 18, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_0" - }, - { - "x": 17, - "y": 5, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_1" - }, - { - "x": 26, - "y": 36, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_2" - }, - { - "x": 39, - "y": 4, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_3" - }, - { - "x": 45, - "y": 4, - "enter": "step_on", - "to_map": "Route4", - "to_point": "warp_1" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json deleted file mode 100644 index 33c98997eb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "name": "MtMoon_B2F", - "source": { - "decomp_file": "data/maps/MtMoon_B2F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################################################", - "################################################", - "################################################", - "###~~~~~~~~~~~~~~~~~############################", - "###~~~~~~~~~~~~~~~~~############################", - "###~~~~~~~~~~~~~~~~~###~~~~######~~~~###########", - "###~~~~~~~~~~~~~~~~~###~~~~#~~~~#~~~~#~~~~######", - "###~~########~~########~~~~#~~~~##~~##~~~~######", - "##~~~~~~#8888~~88888###~~~~#~~~~~~~~~~~~~~######", - "##~~~#~~#~~~~~~~~~~~###~~~~#~~~~~~~~~~~~~~######", - "##~~~.#~#~~~~~~~~~~~###~~~~#~~##~~~~~~~~~~######", - "##~~~~~~#~~~~~~~~~~~###~~~~#~~#.~~~~~~~#~~######", - "##~~~~~######~~###~~###~~~~#~~~~~~~~~~~~~~######", - "#########~~~~~~~~#~~###~~~~#~~~~~~~~~~~~~~######", - "#vvvvvv#~~~~~~~~~#~~###~~~~88888888##8888888888#", - "########~~~~~~~~~######~~~~~~~~~~~~##~~~~~~~~~~#", - "########~~~~~~~~~~~~###~~~~~~~~~~~~##~~~~~~~~~~#", - "############~~~########~~~~~~~~~~~~##~~~~~~~~~~#", - "#######vvvv#~~~#vvvvvv#~~~~~~##~~#####~~##~~~~~#", - "############~~~########~~~~~~#~~~~~~~~~~~#~~~~~#", - "############~~~########~~#~~~#~~~~~~~~~~~#~~~~~#", - "########~~~~~~~~~~~~~##~~.~#~#~~~~~~~~~~~#~~~~~#", - "########~~~~~~~~~~~~~##~~~#~~#~~~~~~~~~~~#~~~~~#", - "########~~~~~##############################~~~~#", - "########~~~~~#vvvvvvvvvvvvvvvvvvvvvvvvvvvv#~~~~#", - "########~~~~~##############################~~~~#", - "########~~~~~###############~~~~###########~~~~#", - "########~~~~~##~~~~~~~~~~~~#~~~~#~~##~~~~~~~~~~#", - "########~~~~~###~~~~~~~~~~~##~~##~~##~~~~~~~~~~#", - "########~~~~~##~~~~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~##~~#~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~##~#.~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~####~~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~########################~~~~~######", - "########~~~~~#vvvvvvvvvvvvvvvvvvvvvv#~~~~~######", - "########~~~~~########################~~~~~######", - "########~~~~~########################~~~~~######", - "########~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~######", - "########~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~######", - "################################################" - ], - "points": { - "warp_0": [25, 21], - "warp_1": [31, 11], - "warp_2": [17, 31], - "warp_3": [5, 10] - }, - "regions": { - "wild": [ - [9, 8], - [10, 8], - [11, 8], - [12, 8], - [15, 8], - [16, 8], - [17, 8], - [18, 8], - [19, 8], - [27, 14], - [28, 14], - [29, 14], - [30, 14], - [31, 14], - [32, 14], - [33, 14], - [34, 14], - [37, 14], - [38, 14], - [39, 14], - [40, 14], - [41, 14], - [42, 14], - [43, 14], - [44, 14], - [45, 14], - [46, 14] - ] - }, - "warps": [ - { - "x": 25, - "y": 21, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_3" - }, - { - "x": 31, - "y": 11, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_4" - }, - { - "x": 17, - "y": 31, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_5" - }, - { - "x": 5, - "y": 10, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_6" - } - ], - "objects": [ - { - "name": "fossil", - "kind": "npc", - "x": 13, - "y": 7, - "hide_flag": "FLAG_HIDE_DOME_FOSSIL" - }, - { - "name": "fossil", - "kind": "npc", - "x": 14, - "y": 7, - "hide_flag": "FLAG_HIDE_HELIX_FOSSIL" - }, - { - "name": "scientist", - "kind": "npc", - "x": 13, - "y": 11 - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 37, - "y": 21, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 12, - "y": 20, - "facing": "south", - "trainer": { - "sight": 2 - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 35, - "y": 12, - "facing": "north", - "trainer": { - "sight": 4 - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 18, - "y": 27, - "facing": "south", - "trainer": { - "sight": 4 - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "item_ball", - "kind": "item", - "x": 30, - "y": 26, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_STAR_PIECE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 35, - "y": 5, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_TM46" - }, - { - "name": "item_ball", - "kind": "item", - "x": 24, - "y": 6, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_REVIVE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 3, - "y": 11, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_ANTIDOTE" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json deleted file mode 100644 index 63540ac440..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "name": "PalletTown", - "source": { - "decomp_file": "data/maps/PalletTown/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "############..##########", - "############..##########", - "##....................##", - "##....................##", - "##...#####....#####...##", - "##...#####....#####...##", - "##...#####....#####...##", - "##..S#D###...S#D###...##", - "##....................##", - "##....................##", - "##...........#######..##", - "##...####S...#######..##", - "##...........#######..##", - "##...........###D###..##", - "##...S................##", - "##....................##", - "##...........###S##...##", - "##.....wwww...........##", - "##.....wwww...........##", - "##.....wwww...........##" - ], - "elevation": [ - "000000000000330000000000", - "000000000000330000000000", - "003333333333333333333300", - "003333333333333333333300", - "003330000033330000033300", - "003330000033330000033300", - "003330000033330000033300", - "003300000033300000033300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333300000003300", - "003330000033300000003300", - "003333333333300000003300", - "003333333333300000003300", - "003330333333333333333300", - "003333333333333333333300", - "003333333333300000033300", - "003333311113333333333300", - "003333311113333333333300", - "003333311113333333333300" - ], - "points": { - "oaks_lab_front": [16, 14], - "warp_0": [6, 7], - "warp_1": [15, 7], - "warp_2": [16, 13] - }, - "warps": [ - { - "x": 6, - "y": 7, - "enter": "walk_into", - "to_map": "PalletTown_PlayersHouse_1F", - "to_point": "warp_1" - }, - { - "x": 15, - "y": 7, - "enter": "walk_into", - "to_map": "PalletTown_RivalsHouse", - "to_point": "warp_0" - }, - { - "x": 16, - "y": 13, - "enter": "walk_into", - "to_map": "PalletTown_ProfessorOaksLab", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "woman_1", - "kind": "npc", - "x": 3, - "y": 10, - "wander": { - "x0": 2, - "y0": 6, - "x1": 4, - "y1": 14 - } - }, - { - "name": "fat_man", - "kind": "npc", - "x": 13, - "y": 17, - "wander": { - "x0": 7, - "y0": 15, - "x1": 19, - "y1": 19 - } - }, - { - "name": "prof_oak", - "kind": "npc", - "x": 10, - "y": 8, - "hide_flag": "FLAG_HIDE_OAK_IN_PALLET_TOWN" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "Route1", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json deleted file mode 100644 index bc171fed17..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "PalletTown_PlayersHouse_1F", - "source": { - "decomp_file": "data/maps/PalletTown_PlayersHouse_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "#############", - "#..........##", - "#............", - "#.....##.....", - "#.....##.....", - "#............", - "##..........#", - "#............", - "#############" - ], - "points": { - "warp_1": [4, 8], - "warp_2": [10, 2] - }, - "warps": [ - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_0" - }, - { - "x": 4, - "y": 8, - "enter": "walk_south", - "to_map": "PalletTown", - "to_point": "warp_0" - }, - { - "x": 10, - "y": 2, - "enter": "walk_east", - "to_map": "PalletTown_PlayersHouse_2F", - "to_point": "warp_0" - }, - { - "x": 3, - "y": 9, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "mom", - "kind": "npc", - "x": 8, - "y": 4 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json deleted file mode 100644 index 7ca500d896..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "PalletTown_PlayersHouse_2F", - "source": { - "decomp_file": "data/maps/PalletTown_PlayersHouse_2F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "############", - "###########S", - "#.......##..", - "#.......##..", - "#.2...#.....", - "#.8...#.....", - "#.#.........", - "#...........", - "#..........." - ], - "elevation": [ - "000000000000", - "000000000000", - "033333330033", - "033333330033", - "033333033333", - "003033033333", - "040403333333", - "003033333333", - "033333333333" - ], - "points": { - "start": [6, 6], - "warp_0": [10, 2] - }, - "warps": [ - { - "x": 10, - "y": 2, - "enter": "walk_west", - "to_map": "PalletTown_PlayersHouse_1F", - "to_point": "warp_2" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json deleted file mode 100644 index 724be58581..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "name": "PalletTown_ProfessorOaksLab", - "source": { - "decomp_file": "data/maps/PalletTown_ProfessorOaksLab/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "######SS#####", - ".............", - "#............", - "###.....###..", - ".##..........", - ".............", - ".............", - "#####...#####", - ".............", - ".............", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_0": [6, 12] - }, - "warps": [ - { - "x": 6, - "y": 12, - "enter": "walk_south", - "to_map": "PalletTown", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 12, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_2" - }, - { - "x": 5, - "y": 12, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "scientist", - "kind": "npc", - "x": 3, - "y": 11 - }, - { - "name": "worker_f", - "kind": "npc", - "x": 2, - "y": 10, - "wander": { - "x0": 2, - "y0": 6, - "x1": 2, - "y1": 13 - } - }, - { - "name": "scientist", - "kind": "npc", - "x": 11, - "y": 10 - }, - { - "name": "prof_oak", - "kind": "npc", - "x": 6, - "y": 3, - "hide_flag": "FLAG_HIDE_OAK_IN_HIS_LAB" - }, - { - "name": "item_ball", - "kind": "item", - "x": 8, - "y": 4, - "hide_flag": "FLAG_HIDE_BULBASAUR_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 9, - "y": 4, - "hide_flag": "FLAG_HIDE_SQUIRTLE_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 10, - "y": 4, - "hide_flag": "FLAG_HIDE_CHARMANDER_BALL" - }, - { - "name": "blue", - "kind": "npc", - "x": 5, - "y": 4, - "hide_flag": "FLAG_HIDE_RIVAL_IN_LAB" - }, - { - "name": "pokedex", - "kind": "npc", - "x": 4, - "y": 1, - "hide_flag": "FLAG_HIDE_POKEDEX" - }, - { - "name": "pokedex", - "kind": "npc", - "x": 5, - "y": 1, - "hide_flag": "FLAG_HIDE_POKEDEX" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json deleted file mode 100644 index 641924ae26..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "PalletTown_RivalsHouse", - "source": { - "decomp_file": "data/maps/PalletTown_RivalsHouse/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "#############", - ".............", - ".............", - "......##.....", - "......##.....", - ".............", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_0": [4, 8] - }, - "warps": [ - { - "x": 4, - "y": 8, - "enter": "walk_south", - "to_map": "PalletTown", - "to_point": "warp_1" - }, - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_1" - }, - { - "x": 3, - "y": 8, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "daisy", - "kind": "npc", - "x": 10, - "y": 6, - "wander": { - "x0": 9, - "y0": 3, - "x1": 11, - "y1": 9 - } - }, - { - "name": "town_map", - "kind": "npc", - "x": 6, - "y": 4, - "hide_flag": "FLAG_HIDE_TOWN_MAP" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json deleted file mode 100644 index 845d8ae7dc..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "name": "PewterCity", - "source": { - "decomp_file": "data/maps/PewterCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################################################", - "#######################....#####################", - "####........#################.........##########", - "####........#################.........##########", - "####........#############D######......##########", - "####........###########...............##########", - "#######.....#####.#####..#######........########", - "####vvv..vvvvvvv...Svvvvvvvvvvvvvvvvv...########", - "####....................................########", - "####............................#####...########", - "######..........................#####...########", - "######..........................#D###...########", - "######..................................########", - "######...##.#######.##..................########", - "######...#############..................########", - "######...##.#######.##..................########", - "######...##S..#D#...##....####..........########", - "######...............#....######........########", - "######...............#....SSD###........########", - "######...............#.................S########", - "######################..........................", - "######..........................................", - "######.......#########..........................", - "######.......#########....##########............", - "######.......#########....##########....########", - "######.......##SSD####....#####S#####...########", - "######....................#.........#...########", - "######....................#.........#...########", - "######..#####.............#.........#...########", - "######..#####.............#.........#...########", - "######..#D###.......S.....#vvvv.vvvv#...########", - "######..................................########", - "######..................................########", - "######..................................########", - "######..................................########", - "######..................................########", - "####################....########################", - "####################....########################", - "####################....########################", - "####################....########################" - ], - "points": { - "gym_front": [15, 17], - "warp_0": [17, 6], - "warp_1": [25, 4], - "warp_2": [15, 16], - "warp_3": [28, 18], - "warp_5": [17, 25] - }, - "warps": [ - { - "x": 17, - "y": 6, - "enter": "cave_entrance", - "to_map": "PewterCity_Museum_1F", - "to_point": "warp_1" - }, - { - "x": 25, - "y": 4, - "enter": "walk_into", - "to_map": "PewterCity_Museum_1F", - "to_point": "warp_3" - }, - { - "x": 15, - "y": 16, - "enter": "walk_into", - "to_map": "PewterCity_Gym", - "to_point": "warp_1" - }, - { - "x": 28, - "y": 18, - "enter": "walk_into", - "to_map": "PewterCity_Mart", - "to_point": "warp_1" - }, - { - "x": 33, - "y": 11, - "enter": "walk_into" - }, - { - "x": 17, - "y": 25, - "enter": "walk_into", - "to_map": "PewterCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 9, - "y": 30, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "lass", - "kind": "npc", - "x": 6, - "y": 15 - }, - { - "name": "man", - "kind": "npc", - "x": 33, - "y": 17, - "hide_flag": "FLAG_HIDE_PEWTER_MUSEUM_GUIDE" - }, - { - "name": "fat_man", - "kind": "npc", - "x": 21, - "y": 28 - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 33, - "y": 27, - "wander": { - "x0": 31, - "y0": 27, - "x1": 35, - "y1": 27 - } - }, - { - "name": "boy", - "kind": "npc", - "x": 42, - "y": 20, - "hide_flag": "FLAG_HIDE_PEWTER_CITY_GYM_GUIDE" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 30, - "y": 5, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "scientist", - "kind": "npc", - "x": 46, - "y": 20, - "hide_flag": "FLAG_HIDE_PEWTER_CITY_RUNNING_SHOES_GUY" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "Route2", - "offset": 12 - }, - { - "side": "east", - "to_map": "Route3", - "offset": 10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json deleted file mode 100644 index 5f5f46c50b..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "name": "PewterCity_Gym", - "source": { - "decomp_file": "data/maps/PewterCity_Gym/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "####.....####", - "#############", - "#....#.#....#", - "#...........#", - "#...#...#...#", - "#...........#", - "#..#.#.#.#..#", - "##.........##", - "#...#...#...#", - "##.........##", - "#...........#", - "#...S...S...#", - "#...........#", - "#...........#", - "#############" - ], - "points": { - "brock_front": [6, 6], - "warp_1": [6, 14] - }, - "warps": [ - { - "x": 5, - "y": 14, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_2" - }, - { - "x": 6, - "y": 14, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 14, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "brock", - "kind": "npc", - "x": 6, - "y": 5 - }, - { - "name": "camper", - "kind": "npc", - "x": 3, - "y": 8, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "gym_guy", - "kind": "npc", - "x": 7, - "y": 12 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json deleted file mode 100644 index 29f7f0d2cd..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "PewterCity_Mart", - "source": { - "decomp_file": "data/maps/PewterCity_Mart/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "##.#......#", - "...#.......", - "####...##.#", - ".......##.#", - ".##....##.#", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "00303333330", - "33003333333", - "00003330030", - "33333330030", - "30033330030", - "33333333333", - "00000000000" - ], - "points": { - "warp_1": [4, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_3" - }, - { - "x": 4, - "y": 7, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_3" - }, - { - "x": 5, - "y": 7, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_3" - } - ], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 8, - "y": 2 - }, - { - "name": "boy", - "kind": "npc", - "x": 6, - "y": 4 - }, - { - "name": "clerk", - "kind": "npc", - "x": 2, - "y": 3 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json deleted file mode 100644 index ee0947b080..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "name": "PewterCity_Museum_1F", - "source": { - "decomp_file": "data/maps/PewterCity_Museum_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "############################", - "############################", - "...............#............", - "...............#......#....#", - "...####........#...........#", - "...............#............", - "...........#...#...........#", - "...####..###...###.........#", - ".........###.....#..........", - "#........###....##......##..", - "############################" - ], - "points": { - "warp_1": [14, 9], - "warp_3": [21, 9] - }, - "warps": [ - { - "x": 13, - "y": 9, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_0" - }, - { - "x": 14, - "y": 9, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_0" - }, - { - "x": 15, - "y": 9, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_0" - }, - { - "x": 21, - "y": 9, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_1" - }, - { - "x": 22, - "y": 9, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_1" - }, - { - "x": 8, - "y": 8, - "enter": "walk_east" - } - ], - "objects": [ - { - "name": "worker_m", - "kind": "npc", - "x": 16, - "y": 5 - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 2, - "y": 4 - }, - { - "name": "scientist", - "kind": "npc", - "x": 21, - "y": 3 - }, - { - "name": "old_amber", - "kind": "npc", - "x": 22, - "y": 3, - "hide_flag": "FLAG_HIDE_OLD_AMBER" - }, - { - "name": "scientist", - "kind": "npc", - "x": 26, - "y": 4 - }, - { - "name": "scientist", - "kind": "npc", - "x": 26, - "y": 7 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json deleted file mode 100644 index 87ce82cab4..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "name": "PewterCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/PewterCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_5" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_5" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_5" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "jigglypuff", - "kind": "npc", - "x": 2, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 4, - "y": 8 - }, - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "youngster", - "kind": "npc", - "x": 1, - "y": 2 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 13, - "y": 2 - }, - { - "name": "gba_kid", - "kind": "npc", - "x": 10, - "y": 7 - }, - { - "name": "gba_kid", - "kind": "npc", - "x": 14, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json deleted file mode 100644 index c48c746b72..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "Route1", - "source": { - "decomp_file": "data/maps/Route1/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "##########....##########", - "##########....##########", - "##....................##", - "##....................##", - "##......##............##", - "##vvvvvv##vvvvvv......##", - "##......##~~~~~~~~~~~~##", - "##......##~~~~~~~~~~~~##", - "##......##~~~~~~~~~~~~##", - "##......##~~~~~~~~~~~~##", - "##vvvvvv##~~~~~~~~~~~~##", - "##......##............##", - "##....................##", - "##..............~~~~~~##", - "##..............~~~~~~##", - "####vvvvvv######~~~~~~##", - "####......######~~~~~~##", - "##..............~~~~~~##", - "##....................##", - "##....................##", - "##vv.vvvv..vvvvvvvvvvv##", - "##....................##", - "##....................##", - "##....................##", - "##..........~~~~~~....##", - "############~~~~~~....##", - "############~~~~~~vvvv##", - "##..........~~~~~~....##", - "##..........~~~~~~....##", - "##....................##", - "##....................##", - "##vvvv...Svvvvvvvvvvvv##", - "##..~~~~~~~......~~~~~##", - "##..~~~~~~~......~~~~~##", - "##~~~~~~~......~~~~~..##", - "##~~~~~~~...~~.~~~~~..##", - "############~~##########", - "##.........#~~#.......##", - "############~~##########", - "############~~##########" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "clerk", - "kind": "npc", - "x": 6, - "y": 28, - "wander": { - "x0": 6, - "y0": 27, - "x1": 6, - "y1": 29 - } - }, - { - "name": "boy", - "kind": "npc", - "x": 19, - "y": 16, - "wander": { - "x0": 18, - "y0": 16, - "x1": 20, - "y1": 16 - } - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "ViridianCity", - "offset": -12 - }, - { - "side": "south", - "to_map": "PalletTown", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json deleted file mode 100644 index 706949faeb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "name": "Route2", - "source": { - "decomp_file": "data/maps/Route2/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########....############", - "########....############", - "##~~~~~~~...############", - "##~~~~~~~...#.........##", - "##~~~~~~~...#.........##", - "##~~~~~~~...#.........##", - "##~~~~~~~...#########.##", - "##~~~~~~~...#########.##", - "##~~~~~~~...#########.##", - "##..........#########.##", - "##..........#########.##", - "##..........#####.###.##", - "##..........#.S.......##", - "####....###.#.........##", - "###########...........##", - "##########............##", - "##############........##", - "##############........##", - "##############........##", - "##############........##", - "##############........##", - "###################...##", - "#################D#...##", - "##############........##", - "################......##", - "################......##", - "##################.#####", - "################......##", - "################......##", - "################......##", - "################......##", - "################......##", - "################vvv.vv##", - "################......##", - "################......##", - "################......##", - "################......##", - "################vvv.vv##", - "################......##", - "################......##", - "################......##", - "#################....###", - "########################", - "########################", - "########################", - "##........##############", - "##################..####", - "#################....###", - "################......##", - "################......##", - "###.####.#######......##", - "#####..#########vvv.vv##", - "####....########......##", - "##............##......##", - "##............##......##", - "##vvvvvvvvv...##......##", - "##....~~~~~~~.##......##", - "##....~~~~~~~.##vv.vvv##", - "##....~~~~~~~.##......##", - "##....~~~~~~~.##......##", - "##....~~~~~~~.##......##", - "##............##......##", - "##....................##", - "##....##########......##", - "##....##########......##", - "##....##########......##", - "##....##########......##", - "##............##......##", - "##............##......##", - "##....................##", - "##vvvvvvv..vvv..vv.vvv##", - "##............##......##", - "####..........##......##", - "####...S......##......##", - "####..........##......##", - "####..........##......##", - "####..........##......##", - "####..........##......##", - "#######.......##########", - "#######.....############" - ], - "points": { - "warp_0": [5, 13], - "warp_2": [5, 51] - }, - "warps": [ - { - "x": 5, - "y": 13, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_3" - }, - { - "x": 6, - "y": 13, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_3" - }, - { - "x": 5, - "y": 51, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_1" - }, - { - "x": 17, - "y": 11, - "enter": "cave_entrance" - }, - { - "x": 17, - "y": 22, - "enter": "walk_into" - }, - { - "x": 18, - "y": 46, - "enter": "cave_entrance" - }, - { - "x": 18, - "y": 41, - "enter": "walk_south" - }, - { - "x": 19, - "y": 41, - "enter": "walk_south" - }, - { - "x": 19, - "y": 46, - "enter": "cave_entrance" - }, - { - "x": 6, - "y": 51, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "cut_tree", - "kind": "npc", - "x": 16, - "y": 62, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 15, - "y": 69, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 11, - "y": 13, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 18, - "y": 26, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "item_ball", - "kind": "item", - "x": 17, - "y": 54, - "hide_flag": "FLAG_HIDE_ROUTE2_ETHER" - }, - { - "name": "item_ball", - "kind": "item", - "x": 17, - "y": 64, - "hide_flag": "FLAG_HIDE_ROUTE2_PARALYZE_HEAL" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "PewterCity", - "offset": -12 - }, - { - "side": "south", - "to_map": "ViridianCity", - "offset": -12 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json deleted file mode 100644 index be2d5629b2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "name": "Route22", - "source": { - "decomp_file": "data/maps/Route22/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#############################################...", - "###########################################.#...", - "##################################........#.###.", - "##################################vvvv.vvv#...##", - "######################....................#.....", - "########D######...........................######", - "#####.......###.........................##......", - "###...........#.............######......##......", - "###...........#vvvvvvvwwwwww######vv..vv##......", - "###vvvvvvv..vv#~~~~~~~wwwwww######~~~~~~##......", - "###...........#~~~~~~~wwwwww######~~~~~~##....##", - "###...........#~~~~~~~wwwwww######~~~~~~##....##", - "####...S#######~~~~~~~......######~~~~~~##....##", - "####...........~~~~~~~......######~~~~~~##....##", - "####........................######............##", - "####vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv..vvvvv##", - "####..........................................##", - "####..........................................##", - "####..........................................##", - "################################################", - "################################################", - "################################################", - "################################################", - "################################################" - ], - "elevation": [ - "000000000000000000000000000000000000000000000333", - "000000000000000000000000000000000000000000030333", - "000000000000000000000000000000000033333333030003", - "000000000000000000000000000000000000003000033300", - "000000000000000000000033333333333333333333033333", - "000000000000000333333333333333333333333333000000", - "000003333333000333333333333333333333333300333333", - "000333333333330333333333333300000033333300333333", - "000333333333330000000011111100000000330000333333", - "000000000033000333333311111100000033333300333333", - "000333333333330333333311111100000033333300333300", - "000333333333330333333311111100000033333300333300", - "000033300000000333333333333300000033333300333300", - "000033333333333333333333333300000033333300333300", - "000033333333333333333333333300000033333333333300", - "000000000000000000000000000000000000000330000000", - "000033333333333333333333333333333333333333333300", - "000033333333333333333333333333333333333333333300", - "000033333333333333333333333333333333333333333300", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000" - ], - "points": {}, - "warps": [ - { - "x": 8, - "y": 5, - "enter": "walk_into" - }, - { - "x": 9, - "y": 5, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "blue", - "kind": "npc", - "x": 25, - "y": 4, - "hide_flag": "FLAG_HIDE_ROUTE_22_RIVAL" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "east", - "to_map": "ViridianCity", - "offset": -10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json deleted file mode 100644 index c8402d903e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "name": "Route24", - "source": { - "decomp_file": "data/maps/Route24/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################", - "########################", - "########################", - "####.........>...<......", - "####.........>...<......", - "####.........>...<......", - "###vvvv..vvvv#...#vvvvvv", - "###.....................", - "###.....................", - "###.....................", - "###..........###########", - "###..........#..........", - "###..........#...#######", - "###..........#...#######", - "###..........#...#######", - "###..........#...#######", - "###...www#...#...#######", - "###...www#...#...#######", - "###...www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww" - ], - "elevation": [ - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000033333333303330333333", - "000033333333303330333333", - "000033333333303330333333", - "000000033000003330000000", - "000333333333333333333333", - "000333333333333333333333", - "000333333333333333333333", - "000333333333300000000000", - "000333333333303333333333", - "000333333333303330000000", - "000333333333303330000000", - "000333333333303330000000", - "000333333333303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330111111", - "000333111033303330100000", - "000333111033303330100000", - "000333111033303330100000", - "000333111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "man", - "kind": "npc", - "x": 12, - "y": 15, - "hide_flag": "FLAG_HIDE_NUGGET_BRIDGE_ROCKET" - }, - { - "name": "camper", - "kind": "npc", - "x": 12, - "y": 19, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 10, - "y": 22, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 12, - "y": 25, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 10, - "y": 28, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 12, - "y": 31, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 5, - "y": 21, - "facing": "north", - "trainer": { - "sight": 5 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 11, - "y": 4, - "hide_flag": "FLAG_HIDE_ROUTE24_TM45" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "CeruleanCity", - "offset": -12 - }, - { - "side": "east", - "to_map": "Route25", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json deleted file mode 100644 index 29dcb02843..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "name": "Route25", - "source": { - "decomp_file": "data/maps/Route25/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################################################", - "########################################################################", - "############...#...#.....#.....#..........##############################", - ".~~~~~~~~......#...#.....#.....#..........####################wwww######", - ".~~~~~~~~................###.#............######S##D##########wwww######", - "................#.#.#....#...#.....#.........................#wwww######", - "vvvvvvvvvv##..##....#.##.#.........#.........................#wwwww#####", - "................#...#....#......##...........................#wwwwwww###", - ".............#..##..#...........#............................#wwwwwwwwww", - ".............#......#.#.........#..........wwwwww.....wwwwww.#wwwwwwwwww", - "###########################################wwwwww.....wwwwww.#wwwwwwwwww", - "...........................................wwwwww.....wwwwww.#wwwwwwwwww", - "#########################################....................#wwwwwwwwww", - "##############################################################wwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww" - ], - "elevation": [ - "000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000000333033303333303333303333333333000000000000000000000000000000", - "333333333333333033303333303333303333333333000000000000000000001111000000", - "333333333333333333333333300030333333333333000000000000000000001001000000", - "333333333333333303030333303330333330333333333333333333333333301001000000", - "000000000000330033330300303333333330333333333333333333333333301001100000", - "333333333333333303330333303333330033333333333333333333333333301000111000", - "333333333333303300330333333333330333333333333333333333333333301000001111", - "333333333333303333330303333333330333333333311111133333111111301000000000", - "000000000000000000000000000000000000000000011111133333111111301000000000", - "333333333333333333333333333333333333333333311111133333111111301000000000", - "000000000000000000000000000000000000000003333333333333333333301000000000", - "000000000000000000000000000000000000000000000000000000000000001000000000", - "000000000000000000000000000000000000000001111111111111111111111000000000", - "000000000000000000000000000000000000000001100000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000" - ], - "points": { - "warp_0": [51, 4] - }, - "warps": [ - { - "x": 51, - "y": 4, - "enter": "walk_into", - "to_map": "Route25_SeaCottage", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "hiker", - "kind": "npc", - "x": 11, - "y": 4, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 18, - "y": 2, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 17, - "y": 7, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 22, - "y": 4, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 22, - "y": 8, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 27, - "y": 9, - "facing": "north", - "trainer": { - "sight": 3 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 28, - "y": 4, - "facing": "south", - "trainer": { - "sight": 3 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 36, - "y": 4, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "lass", - "kind": "npc", - "x": 42, - "y": 5, - "facing": "south", - "trainer": { - "sight": 3 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 26, - "y": 2, - "hide_flag": "FLAG_HIDE_ROUTE25_TM43" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 30, - "y": 3, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "beauty", - "kind": "npc", - "x": 49, - "y": 10 - }, - { - "name": "man", - "kind": "npc", - "x": 49, - "y": 11 - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "west", - "to_map": "Route24", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json deleted file mode 100644 index 0ec5259806..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "Route25_SeaCottage", - "source": { - "decomp_file": "data/maps/Route25_SeaCottage/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###############", - "###############", - "#.##########..#", - "###D######D#.##", - "..............#", - "....##........#", - "..............#", - "#.............#", - "#..............", - "###..........##", - "###############" - ], - "points": { - "warp_1": [7, 9] - }, - "warps": [ - { - "x": 6, - "y": 9, - "enter": "walk_into", - "to_map": "Route25", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 9, - "enter": "walk_south", - "to_map": "Route25", - "to_point": "warp_0" - }, - { - "x": 8, - "y": 9, - "enter": "walk_into", - "to_map": "Route25", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "bill", - "kind": "npc", - "x": 7, - "y": 5, - "hide_flag": "FLAG_HIDE_BILL_HUMAN_SEA_COTTAGE" - }, - { - "name": "clefairy", - "kind": "npc", - "x": 10, - "y": 6, - "hide_flag": "FLAG_HIDE_BILL_CLEFAIRY" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json deleted file mode 100644 index 021edbb11d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "Route2_ViridianForest_NorthEntrance", - "source": { - "decomp_file": "data/maps/Route2_ViridianForest_NorthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###############", - "#######.#######", - "##...#...#...##", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#.............#", - "###############" - ], - "points": { - "warp_1": [7, 10], - "warp_3": [7, 1] - }, - "warps": [ - { - "x": 6, - "y": 10, - "enter": "walk_into", - "to_map": "ViridianForest", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 10, - "enter": "walk_south", - "to_map": "ViridianForest", - "to_point": "warp_2" - }, - { - "x": 8, - "y": 10, - "enter": "walk_into", - "to_map": "ViridianForest", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 1, - "enter": "cave_entrance", - "to_map": "Route2", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 5, - "y": 4 - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 4, - "y": 7 - }, - { - "name": "cooltrainer_f", - "kind": "npc", - "x": 10, - "y": 5 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json deleted file mode 100644 index 3b00abc4eb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "Route2_ViridianForest_SouthEntrance", - "source": { - "decomp_file": "data/maps/Route2_ViridianForest_SouthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###############", - "#######.#######", - "##...#...#...##", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#.............#", - "###############" - ], - "points": { - "warp_1": [7, 10], - "warp_3": [7, 1] - }, - "warps": [ - { - "x": 6, - "y": 10, - "enter": "walk_into", - "to_map": "Route2", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 10, - "enter": "walk_south", - "to_map": "Route2", - "to_point": "warp_2" - }, - { - "x": 8, - "y": 10, - "enter": "walk_into", - "to_map": "Route2", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 1, - "enter": "cave_entrance", - "to_map": "ViridianForest", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "woman_2", - "kind": "npc", - "x": 10, - "y": 6 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 4, - "y": 7, - "wander": { - "x0": 3, - "y0": 6, - "x1": 5, - "y1": 8 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json deleted file mode 100644 index 91a4f7fc9c..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "name": "Route3", - "source": { - "decomp_file": "data/maps/Route3/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "####################################################################........########", - "####################################################################........########", - "####################################################################........########", - "#############......................#################################.......#########", - "###########..........................###############################.....###########", - "###########..........................######................#########.....###########", - "###########.........###.......##.....#####...................#######.....###########", - "###########vvv..vvvv###vvvvvvv##vvv..#####...................#######.....###########", - "#######.............###.......##.....#####vvvv..#######vvvvv.#######vvv..###########", - "....................###.......##................#######......#######.....###########", - "....................###.......###...............#######......#####.......~~~~#######", - ".........###........###.......###~~~~~~~........#######...............##S~~~~~~#####", - "..##########vvvv..vv###vvvvvvv###~~~~~~~~~~~~~~########................~~~~~~~~#####", - "############........###.......###~~~~~~~~~~~~~~########................~~~~~~~~#####", - "############.................######~~~~~~~~~~~~########...............~~~~~~~~~#####", - "##############...............######~~~~~~~~~~~~##########.............~~~~~~~~~#####", - "#######################################~~~~~~~~##########.............~~~~~~########", - "####################################################################################", - "####################################################################################", - "####################################################################################" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 70, - "y": 13 - }, - { - "name": "lass", - "kind": "npc", - "x": 40, - "y": 11, - "wander": { - "x0": 40, - "y0": 10, - "x1": 40, - "y1": 12 - }, - "trainer": { - "sight": 3, - "looks": [ - "north", - "south" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 32, - "y": 6, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 30, - "y": 3, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 25, - "y": 4, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 29, - "y": 10, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 19, - "y": 9, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 12, - "y": 6, - "facing": "east", - "trainer": { - "sight": 3 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 17, - "y": 4, - "facing": "south", - "trainer": { - "sight": 2 - } - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "Route4", - "offset": 60 - }, - { - "side": "west", - "to_map": "PewterCity", - "offset": -10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json deleted file mode 100644 index 5c8b219633..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "name": "Route4", - "source": { - "decomp_file": "data/maps/Route4/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "############################################################################################################", - "##########.....#############################################################################################", - "###########################################..<......>.#########.<.....<###############...........###########", - "###########################################..<......>..>.....<..<.....<.....................................", - "####...#..#################################..<......>..>.....<..<.....<.....................................", - "###..#....SSD##.###.############.########....#vv..vv#..>.....<..<.....#vvvvvvvvvvvvvvvvvvvvvvvvvv###########", - "###..................##########........................>.....<..<................................#wwwwwwwwww", - "###vvvvv..........S..##########...S....................>.....<..#v..vvvvvvvvvvvvvvvvvvvvvvvvvvvvv#wwwwwwwwww", - "###..................#########.........................>.....<...................................#wwwwwwwwww", - "###............vvvvvvv########vvvvvvvvvvv...vvvvvvvvvvv#.....#v#......................####vvvvvvv###########", - "###.#.................########.................................#vvvvv..####vvvvvvvvvvv####..................", - "###vvvvvvvvv........#.########.........................................####~~~~~~~~~~~####..................", - "###............vvvvvvv###################..............................####~~~~~~~~~~~####..................", - "######.#..............###########################......................####~~~~~~~~~~~~~##.......###########", - "########.............############################......................####~~~~~~~~~~~~~##vv...vv###########", - "########vvv.......###############################vv..vvvvvvvvv..vvvvv######~~~~~~~~~~~~~##.......###########", - "########........#################################....................######~~~~~~~~~~~~.##.......###########", - "########........#################################....................######~~~~~~~~~~~...........###########", - "########.....vvv########################################.............#################.........#############", - "########........############################################################################################" - ], - "elevation": [ - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000033333000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000033033333303000000000303333300000000000000003333333333300000000000", - "000000000000000000000000000000000000000000033033333303303333303303333303333333333333333333333333333333333333", - "000033303300000000000000000000000000000000033033333303303333303303333303333333333333333333333333333333333333", - "000330333300000300030000000000003000000003333000330003303333303303333300000000000000000000000000000000000000", - "000333333333333333333000000000033333333333333333333333303333303303333333333333333333333333333333301111111111", - "000000003333333333033000000000033303333333333333333333303333303300330000000000000000000000000000001111111111", - "000333333333333333333000000000333333333333333333333333303333303333333333333333333333333333333333301111111111", - "000333333333333000000000000000000000000003330000000000003333300033333333333333333333330000000000000000000000", - "000303333333333333333300000000333333333333333333333333333333333000000330000000000000000000333333333333333333", - "000000000000333333330300000000333333333333333333333333333333333333333330000333333333330000333333333333333333", - "000333333333333000000000000000000000000003333333333333333333333333333330000333333333330000333333333333333333", - "000000303333333333333300000000000000000000000000033333333333333333333330000333333333333300333333300000000000", - "000000003333333333333000000000000000000000000000033333333333333333333330000333333333333300003330000000000000", - "000000000003333333000000000000000000000000000000000330000000003300000000000333333333333300333333300000000000", - "000000003333333300000000000000000000000000000000033333333333333333333000000333333333333300333333300000000000", - "000000003333333300000000000000000000000000000000033333333333333333333000000333333333333333333333300000000000", - "000000003333300000000000000000000000000000000000000000003333333333333000000000000000003333333330000000000000", - "000000003333333300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ], - "points": { - "warp_0": [19, 5], - "warp_1": [32, 5], - "warp_2": [12, 5] - }, - "warps": [ - { - "x": 19, - "y": 5, - "enter": "cave_entrance", - "to_map": "MtMoon_1F", - "to_point": "warp_3" - }, - { - "x": 32, - "y": 5, - "enter": "cave_entrance", - "to_map": "MtMoon_B1F", - "to_point": "warp_7" - }, - { - "x": 12, - "y": 5, - "enter": "walk_into", - "to_map": "Route4_PokemonCenter_1F", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "woman_1", - "kind": "npc", - "x": 9, - "y": 8, - "wander": { - "x0": 8, - "y0": 7, - "x1": 10, - "y1": 9 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 75, - "y": 3, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 67, - "y": 5, - "hide_flag": "FLAG_HIDE_ROUTE4_TM05" - }, - { - "name": "boy", - "kind": "npc", - "x": 15, - "y": 14, - "wander": { - "x0": 14, - "y0": 14, - "x1": 16, - "y1": 14 - } - }, - { - "name": "black_belt", - "kind": "npc", - "x": 47, - "y": 3 - }, - { - "name": "black_belt", - "kind": "npc", - "x": 50, - "y": 3 - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "Route3", - "offset": -60 - }, - { - "side": "east", - "to_map": "CeruleanCity", - "offset": -10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json deleted file mode 100644 index c3fe2d586c..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "name": "Route4_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/Route4_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "salesman_front": [2, 3], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "Route4", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "Route4", - "to_point": "warp_2" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "Route4", - "to_point": "warp_2" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "balding_man", - "kind": "npc", - "x": 1, - "y": 3 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "boy", - "kind": "npc", - "x": 5, - "y": 4, - "wander": { - "x0": 4, - "y0": 3, - "x1": 6, - "y1": 5 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 14, - "y": 4 - }, - { - "name": "clipboard", - "kind": "npc", - "x": 12, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json deleted file mode 100644 index 1b4d929c1d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "Route5", - "source": { - "decomp_file": "data/maps/Route5/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#.#####..#....###############", - "###############....#.#####..#....###############", - "###############....#.#####..#....###############", - "###############....#.##D##..#....###############", - "###############....#........#....###############", - "###############....vvvvvvvvvv....###############", - "###############..................###############", - "###############...............##################", - "###############...............##################", - "###############...............#D################", - "###############.......#....#....S###############", - "###############.......######.....###############", - "################################################", - "##############........######......##############", - "################################################", - "################################################", - "########################..######################", - "#######################....#####################" - ], - "points": { - "warp_0": [31, 31] - }, - "warps": [ - { - "x": 31, - "y": 31, - "enter": "walk_into", - "to_map": "UndergroundPath_NorthEntrance", - "to_point": "warp_1" - }, - { - "x": 23, - "y": 25, - "enter": "walk_into" - }, - { - "x": 24, - "y": 32, - "enter": "walk_south" - }, - { - "x": 25, - "y": 32, - "enter": "walk_south" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "CeruleanCity", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json deleted file mode 100644 index 4ce8f0cda2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "name": "Route6", - "source": { - "decomp_file": "data/maps/Route6/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################", - "########################", - "########################", - "########################", - "########################", - "##.......###..###.....##", - "##.......#......#.....##", - "##vvv.vvvv......vvv.vv##", - "##....................##", - "##....................##", - "##....................##", - "##vvvvvvv.vvvvvvvv######", - "##................######", - "##................#D####", - "##....................##", - "##~~~~...~~~~~~......S##", - "##~~~~...~~~~~~~~~~~~~##", - "##~~~~...~~~~~~~~~~~~~##", - "##~~~~...~~~~~~~~~~~~~##", - "##~~~~..............~~##", - "##~~~~..............~~##", - "##~~~~..............~~##", - "##~~~~~~~~~~~~~~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##..................~~##", - "##..................~~##", - "##..................~~##", - "#######......~~~~#######", - "##....#......~~~~#....##", - "#######......~~~~#######", - "#######......~~~~#######", - "##########...###########", - "######...#...#....######", - "########.#...###########", - "########.#...###########", - "wwwwwwwww#...###########", - "wwwwwwwww#...###########" - ], - "elevation": [ - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "003333333000330003333300", - "003333333033333303333300", - "000003000033333300030000", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "000000000300000000000000", - "003333333333333333000000", - "003333333333333333000000", - "003333333333333333333300", - "003333333333333333333000", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "001111111111111333333300", - "001111111111111333333300", - "001111111111111333333300", - "001111111111111333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "000000033333333330000000", - "003333033333333330333300", - "000000033333333330000000", - "000000033333333330000000", - "000000000033300000000000", - "000000333033303333000000", - "000000003033300000000000", - "000000003033300000000000", - "111111111033300000000000", - "000000001033300000000000" - ], - "points": { - "warp_0": [19, 13] - }, - "warps": [ - { - "x": 19, - "y": 13, - "enter": "walk_into", - "to_map": "UndergroundPath_SouthEntrance", - "to_point": "warp_1" - }, - { - "x": 12, - "y": 5, - "enter": "cave_entrance" - }, - { - "x": 13, - "y": 5, - "enter": "cave_entrance" - } - ], - "objects": [ - { - "name": "bug_catcher", - "kind": "npc", - "x": 3, - "y": 16, - "facing": "east", - "trainer": { - "sight": 5 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 12, - "y": 21 - }, - { - "name": "picnicker", - "kind": "npc", - "x": 13, - "y": 21 - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 20, - "y": 25, - "facing": "west", - "trainer": { - "sight": 3 - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 13, - "y": 32, - "facing": "west", - "trainer": { - "sight": 3 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 13, - "y": 33, - "facing": "west", - "trainer": { - "sight": 3 - } - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "VermilionCity", - "offset": -12 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json deleted file mode 100644 index ef4b147d64..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "name": "SSAnne_1F_Corridor", - "source": { - "decomp_file": "data/maps/SSAnne_1F_Corridor/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - ".................#####.........", - ".................##D##.........", - ".................#...#.........", - ".................#...#.........", - ".................#...#.........", - ".................#...#.........", - "#.################...##########", - "##################...##########", - "###...........................#", - "###...........................#", - "#.............................#", - "#...######################....#", - "#...######################....#", - "#...######################....#", - "#...######################....#", - "#...######################....#", - "#...########################..#", - "#...########################..#", - "#...########################..#", - ".###.##########################", - "###############################" - ], - "elevation": [ - "0000000000000000000000000000000", - "0000000000000000000000000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0003333333333333333333333333330", - "0003333333333333333333333333330", - "0333333333333333333333333333330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000000330", - "0333000000000000000000000000330", - "0000000000000000000000000000000", - "0000000000000000000000000000000" - ], - "points": { - "warp_0": [3, 8], - "warp_2": [19, 1], - "warp_3": [20, 0] - }, - "warps": [ - { - "x": 3, - "y": 8, - "enter": "walk_west", - "to_map": "SSAnne_2F_Corridor", - "to_point": "warp_0" - }, - { - "x": 2, - "y": 18, - "enter": "walk_south" - }, - { - "x": 19, - "y": 1, - "enter": "walk_into", - "to_map": "SSAnne_Exterior", - "to_point": "warp_2" - }, - { - "x": 20, - "y": 0, - "enter": "walk_into", - "to_map": "SSAnne_Exterior", - "to_point": "warp_3" - }, - { - "x": 3, - "y": 20, - "enter": "walk_into" - }, - { - "x": 28, - "y": 17, - "enter": "walk_west" - }, - { - "x": 5, - "y": 10, - "enter": "walk_south" - }, - { - "x": 8, - "y": 10, - "enter": "walk_south" - }, - { - "x": 11, - "y": 10, - "enter": "walk_south" - }, - { - "x": 14, - "y": 10, - "enter": "walk_south" - }, - { - "x": 17, - "y": 10, - "enter": "walk_south" - }, - { - "x": 23, - "y": 10, - "enter": "walk_south" - }, - { - "x": 20, - "y": 10, - "enter": "walk_south" - } - ], - "objects": [ - { - "name": "sailor", - "kind": "npc", - "x": 18, - "y": 8 - }, - { - "name": "worker_m", - "kind": "npc", - "x": 12, - "y": 9 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json deleted file mode 100644 index 9f74e4ce17..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "name": "SSAnne_2F_Corridor", - "source": { - "decomp_file": "data/maps/SSAnne_2F_Corridor/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "##################################", - "##################################", - "#..###########################.###", - "#..###########################.###", - "#....#########################...#", - "#....#########################...#", - "#....#########################...#", - "#....#########################...#", - "#....#######################.....#", - "#....#######################.....#", - "#....#D###D###D###D###D###D#.....#", - "###..............................#", - "###..............................#", - "###..............................#", - "#................................#", - "##################################" - ], - "points": { - "warp_0": [2, 2], - "warp_2": [30, 2] - }, - "warps": [ - { - "x": 2, - "y": 2, - "enter": "walk_east", - "to_map": "SSAnne_1F_Corridor", - "to_point": "warp_0" - }, - { - "x": 3, - "y": 12, - "enter": "walk_west" - }, - { - "x": 30, - "y": 2, - "enter": "walk_east", - "to_map": "SSAnne_CaptainsOffice", - "to_point": "warp_0" - }, - { - "x": 6, - "y": 10, - "enter": "walk_into" - }, - { - "x": 10, - "y": 10, - "enter": "walk_into" - }, - { - "x": 14, - "y": 10, - "enter": "walk_into" - }, - { - "x": 18, - "y": 10, - "enter": "walk_into" - }, - { - "x": 22, - "y": 10, - "enter": "walk_into" - }, - { - "x": 26, - "y": 10, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "blue", - "kind": "npc", - "x": 30, - "y": 2, - "hide_flag": "FLAG_HIDE_SS_ANNE_RIVAL" - }, - { - "name": "sailor", - "kind": "npc", - "x": 2, - "y": 7, - "wander": { - "x0": 2, - "y0": 5, - "x1": 2, - "y1": 9 - } - } - ], - "flag_sets": { - "new_game": [ - "FLAG_HIDE_SS_ANNE_RIVAL" - ] - }, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json deleted file mode 100644 index 03a230a27e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "SSAnne_CaptainsOffice", - "source": { - "decomp_file": "data/maps/SSAnne_CaptainsOffice/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#########", - "#########", - "#......##", - "####.#..#", - "####....#", - "#.......#", - "###.....#", - "###.....#", - "###.....#", - "#########" - ], - "points": { - "warp_0": [3, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_west", - "to_map": "SSAnne_2F_Corridor", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "captain", - "kind": "npc", - "x": 5, - "y": 4 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json deleted file mode 100644 index 20658451d2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "name": "SSAnne_Exterior", - "source": { - "decomp_file": "data/maps/SSAnne_Exterior/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "wwwwwwwwww##wwwwwwwwwwwwwwwwww#####wwwwwwwwww#########################", - "wwwwwwwwww##wwwwwwwwwwwwwwwwww#####wwwwwwwwww#########################", - "wwww##wwwwwwwwwwwwwwwwwwwwwwww#####wwwwwwwwww##........###.###########", - "wwww##wwwwwwwwwwwwww####wwwwww#####wwwwwwwww###........###.###########", - "##wwwwwwww##ww##ww######www##w#####w##wwww#####............###########", - "##wwwww##w##########www#########.#######ww##wwwwwwwwwwwww..###########", - "wwww##w####w##ww##wwwww####ww##...##ww####wwwwwwwwwwwwwww..###########", - "wwww####w##wwwwwwwwwwwwwwwwwwww...wwwwww##wwwwwwwwwwwwwww..###########", - "www##w##wwwwwwwwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwwwwwwwwww..###########", - "w####wwwwwwwwwwwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwwwwwwwwww..###########", - "w##wwwwwwwwwwwwwwwwwwww##wwwwww#.#wwwwww##wwwwwwwwwwwwwww..###########", - "ww##wwwwwwwwwwwwwwwww####ww#####.###ww######wwwwwwwwwwwww..###########", - "ww##wwwwwwwwwwwwwwwww##ww#######.#######ww##wwwwwwwwwwwww..###########", - "##wwwwwwwwwwwwwwwwww##www##www##.##w##wwwwww##wwwwwwwwwww..###########", - "##wwwwwwwwwwwwwwww####wwwwwwww##.##wwwwwwwww##wwwwwwwwwww..###########", - "w##wwwwwwwwwwwwwww##wwwwwwwwww######wwwwwwwww##wwwwwwwwww..###########", - "w##wwwwwwwwwwwwww##wwwwwwwwwwwwwwwwwwwwwwwwww##ww##wwwwww..###########", - "ww##wwwwwwwwww##w##wwwwwwwwwwwwwwwwwwwwwwwwwwww####wwwwww..###########", - "ww##w##wwwww######wwwwwwwwwwwwwwwwwwwwwwwwwwwww##w##wwwww..###########", - "www####w##ww##ww##wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##wwwww..###########", - "www##w######wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwww##ww##wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##wwww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww####ww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##ww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##ww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww####..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww#################", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww###############", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww################" - ], - "elevation": [ - "1111111111001111111111111111110000011111111110000000000000000000000000", - "1111111111001111111111111111110000011111111110000000000000000000000011", - "1111001111111111111111111111110000011111111110033333333000300000000000", - "1111001111111111111100001111110000011111111100033333333000300000000000", - "0011111111001100110000001110010000010011110000033333333333300000000000", - "0011111001000000000011100000000030000000110011111111111113300000000000", - "1111001000010011001111100001100333001100001111111111111113300000000011", - "1111000010011111111111111111111333111111001111111111111113300000000000", - "1110010011111111111111111111111333111111111111111111111113300000000000", - "1000011111111111111111111111111333111111111111111111111113300000000000", - "1001111111111111111111100111111030111111001111111111111113300000000000", - "1100111111111111111110000110000030001100000011111111111113300000000000", - "1100111111111111111110011000000030000000110011111111111113300000000000", - "0011111111111111111100111001111030110011111100111111111113300000000000", - "0011111111111111110000111110110030011011111100111111111113300000000000", - "1001111111111111110011111011110000011110111110011111111113300000000000", - "1001111111111111100111101111111111111111110110011001111113300000000000", - "1100111111111100100111011111111111111111111101100001111113300000000000", - "1100100111110000001110111111111111111111111111100100111113300000000000", - "1110000100110011001111111111111111111111111111011100111113300000000000", - "1110010000001111111011111111111111111111111111101111001113300000000000", - "1111110011001111101111111111111111111111111111110111001113300000000000", - "1111111111111111011111111111111111111111111111111110011113300000000000", - "1111111111111011111111111111111111111111111111111010000113300000000000", - "1111111111111111111111111111111111111111111111111111100113300000000000", - "1111111111111111111111111111111111111111111111111101001113300000000000", - "1111111111111111111111111111111111111111111111111110001113300000000000", - "1111111111111111111111111111111111111111111111111111100113300000000000", - "1111111111111111111111111111111111111111111111111111100003300000000000", - "1111111111111111111111111111111111111111111111111111000000000000000000", - "1111111111111111111111111111111111111111111111111111110000000000000000", - "1111111111111111111111111111111111111111111111111111110000000000000000" - ], - "points": { - "warp_1": [32, 5], - "warp_2": [32, 14], - "warp_3": [33, 15] - }, - "warps": [ - { - "x": 31, - "y": 5, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_0" - }, - { - "x": 32, - "y": 5, - "enter": "walk_north", - "to_map": "VermilionCity", - "to_point": "warp_1" - }, - { - "x": 32, - "y": 14, - "enter": "walk_south", - "to_map": "SSAnne_1F_Corridor", - "to_point": "warp_2" - }, - { - "x": 33, - "y": 15, - "enter": "walk_into", - "to_map": "SSAnne_1F_Corridor", - "to_point": "warp_3" - }, - { - "x": 33, - "y": 5, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "ss_anne", - "kind": "npc", - "x": 30, - "y": 16, - "hide_flag": "FLAG_HIDE_SS_ANNE" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json deleted file mode 100644 index 7ae2377b26..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "UndergroundPath_NorthEntrance", - "source": { - "decomp_file": "data/maps/UndergroundPath_NorthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#############", - "#############", - "#...........#", - "..##.##..##..", - "..#..##...#..", - "..#..##...#..", - "..##.....##..", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_1": [6, 8], - "warp_3": [7, 4] - }, - "warps": [ - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "Route5", - "to_point": "warp_0" - }, - { - "x": 6, - "y": 8, - "enter": "walk_south", - "to_map": "Route5", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 8, - "enter": "walk_into", - "to_map": "Route5", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 4, - "enter": "walk_west", - "to_map": "UndergroundPath_NorthSouthTunnel", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "little_girl", - "kind": "npc", - "x": 5, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json deleted file mode 100644 index 72752cc45a..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "name": "UndergroundPath_NorthSouthTunnel", - "source": { - "decomp_file": "data/maps/UndergroundPath_NorthSouthTunnel/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########", - "########", - "#....###", - "#....###", - "#....###", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "###....#", - "###....#", - "###....#", - "########" - ], - "points": { - "warp_0": [4, 3], - "warp_1": [3, 60] - }, - "warps": [ - { - "x": 4, - "y": 3, - "enter": "walk_east", - "to_map": "UndergroundPath_NorthEntrance", - "to_point": "warp_3" - }, - { - "x": 3, - "y": 60, - "enter": "walk_west", - "to_map": "UndergroundPath_SouthEntrance", - "to_point": "warp_3" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json deleted file mode 100644 index 000f7e215e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "UndergroundPath_SouthEntrance", - "source": { - "decomp_file": "data/maps/UndergroundPath_SouthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#############", - "#############", - "#...........#", - "..##.##..##..", - "..#..##...#..", - "..#..##...#..", - "..##.....##..", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_1": [6, 8], - "warp_3": [7, 4] - }, - "warps": [ - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "Route6", - "to_point": "warp_0" - }, - { - "x": 6, - "y": 8, - "enter": "walk_south", - "to_map": "Route6", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 8, - "enter": "walk_into", - "to_map": "Route6", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 4, - "enter": "walk_west", - "to_map": "UndergroundPath_NorthSouthTunnel", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "woman_2", - "kind": "npc", - "x": 5, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json deleted file mode 100644 index 3e6026ea80..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "name": "VermilionCity", - "source": { - "decomp_file": "data/maps/VermilionCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "wwwwwwwwwwwwwwwwwwwww#...#######################", - "wwwwwwwwwwwwwwwwwwwww#...#######################", - "wwwwww####wwwwwwwwwww#...#######################", - "wwww######www#####www#...#######################", - "wwww##ww##############...#######################", - "www##www##############...#######################", - "www##www#D###SSD######...########S##############", - "ww##wwww.................................#######", - "ww##wwww.................................#######", - "w##wwwww.................................#######", - "w##wwwwwwwwwww...........................#######", - "w##wwwwwwwwwww...........................#######", - "w##wwwwwwwwwwwwwwwww.....................#######", - "w##wwwwwwwwwwwwwwwww.....................#######", - "w##wwwwwwww.....wwww.....................#######", - "ww##wwww...#####..####.....####..........#######", - "ww##wwww...#####..####.....####..........##.....", - "www##www..S#D###..#D##.....SSD###############S##", - "www##www..........................S.............", - "ww##wwww........................................", - "ww##wwww........................................", - "ww##wwww.......................www.....wwwwwwwww", - "ww##wwww...#######.........####www.....wwwwwwwww", - "www##www...#######..wwww...####www64.64wwwwwwwww", - "www##www##S########.wwww...#D##www.....ww#######", - "www##www.....#D#....wwww.......wwww...www#######", - "www##www............wwwwwwwwwwwwwww...www#######", - "ww##wwww............wwwwwwwwwwwwwww...www#######", - "ww##wwww............ww................www#######", - "ww##wwwwwwwwwwwwwwwwww................www##wwwww", - "ww##wwwwwwwwwwwwwwwwww................www##wwwww", - "www##wwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwww##wwwww", - "www##wwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwww##wwwww", - "wwwww##wwwwwwwwwwwww##...####wwwwwwwwwww##wwwwww", - "wwwww##wwwwwwwww######...########wwww##w##wwwwww", - "wwwwww##wwwwww######ww###wwww####wwww##w##wwwwww", - "wwwwww####wwww##wwwwww###wwwwwwww##wwwww##wwwwww", - "wwwwwwww######wwwwwwww.#.wwwwwwww########wwwwwww", - "wwwwwwwwww####wwwwwwww.#.wwwwwwwwww######wwwwwww", - "wwwwwwwwwwwwwwwwwwwwww.#.wwwwwwwwwwwwwwwwwwwwwww" - ], - "elevation": [ - "000000000000000000001033300000000000000000000000", - "000000000000000000001033300000000000000000000000", - "000001000011111111111033300000000000000000000000", - "000100000011100000111033300000000000000000000000", - "000100110000000000000033300000000000000000000000", - "011001110000000000000033300000000000000000000000", - "011001110000000000000033300000000000000000000000", - "010011113333333333333333333333333333333330000000", - "010011113333333333333333333333333333333330000000", - "000111113333333333333333333333333333333330000000", - "000111111111113333333333333333333333333330000000", - "000111111111113333333333333333333333333330000000", - "000111111111111111113333333333333333333330000000", - "000111111111111111113333333333333333333330000000", - "000111111111111111113333333333333333333330000000", - "010011113330000033000033333000033333333330000000", - "010011113330000033000033333000033333333330033333", - "001001113300000033000033333000000000000000000000", - "001001113333333333333333333333333303333333333333", - "000011113333333333333333333333333333333333333333", - "000011113333333333333333333333333333333333333333", - "000011113333333333333333333333311133333111111111", - "000011113330000000333333333000011133333111111111", - "001001113330000000331111333000011133333111111111", - "001001110000000000031111333000011133333110000000", - "001001113333300033331111333333311113331110000000", - "011001113333333333331111111111111113331110000000", - "010011113333333333331111111111111113331110000000", - "010011113333333333331133333333333333331110000000", - "010011111111111111111133333333333333331110011111", - "010011111111111111111133333333333333331110011000", - "011001111111111111111133311111111111111110010000", - "001001111111111111111133311111111111111110010000", - "001110011111111111110033300001111111111100110000", - "000110011111111100000033300000000111100100110000", - "000011001111110000000000000110000111100100110000", - "000001000011110011111100011111111001111100100000", - "000001110000001110001100011000001000000001100000", - "000000001100001000000100010000000010000001000000", - "000000000000000000000100010000000000000000000000" - ], - "points": { - "warp_0": [22, 34], - "warp_1": [23, 34], - "warp_2": [24, 34], - "warp_4": [15, 6], - "warp_9": [14, 25] - }, - "warps": [ - { - "x": 22, - "y": 34, - "enter": "walk_south", - "to_map": "SSAnne_Exterior", - "to_point": "warp_1" - }, - { - "x": 23, - "y": 34, - "enter": "walk_south", - "to_map": "SSAnne_Exterior", - "to_point": "warp_1" - }, - { - "x": 24, - "y": 34, - "enter": "walk_south", - "to_map": "SSAnne_Exterior", - "to_point": "warp_1" - }, - { - "x": 9, - "y": 6, - "enter": "walk_into" - }, - { - "x": 15, - "y": 6, - "enter": "walk_into", - "to_map": "VermilionCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 12, - "y": 17, - "enter": "walk_into" - }, - { - "x": 19, - "y": 17, - "enter": "walk_into" - }, - { - "x": 29, - "y": 17, - "enter": "walk_into" - }, - { - "x": 28, - "y": 24, - "enter": "walk_into" - }, - { - "x": 14, - "y": 25, - "enter": "walk_into", - "to_map": "VermilionCity_Gym", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "woman_1", - "kind": "npc", - "x": 22, - "y": 11, - "wander": { - "x0": 18, - "y0": 11, - "x1": 26, - "y1": 11 - } - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 17, - "y": 9 - }, - { - "name": "old_man_2", - "kind": "npc", - "x": 36, - "y": 10 - }, - { - "name": "machop", - "kind": "npc", - "x": 35, - "y": 11, - "wander": { - "x0": 33, - "y0": 9, - "x1": 37, - "y1": 13 - } - }, - { - "name": "sailor", - "kind": "npc", - "x": 31, - "y": 30, - "wander": { - "x0": 27, - "y0": 30, - "x1": 35, - "y1": 30 - } - }, - { - "name": "sailor", - "kind": "npc", - "x": 24, - "y": 33 - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 19, - "y": 24, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "scientist", - "kind": "npc", - "x": 25, - "y": 7, - "hide_flag": "FLAG_HIDE_VERMILION_CITY_OAKS_AIDE" - } - ], - "flag_sets": { - "new_game": [ - "FLAG_HIDE_VERMILION_CITY_OAKS_AIDE" - ] - }, - "connections": [ - { - "side": "north", - "to_map": "Route6", - "offset": 12 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json deleted file mode 100644 index 2ed0d496e1..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "name": "VermilionCity_Gym", - "source": { - "decomp_file": "data/maps/VermilionCity_Gym/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "#..#...#..#", - "#.........#", - "#.........#", - "#.........#", - "####...####", - "####...####", - "...........", - "...........", - ".#.#.#.#.#.", - "...........", - ".#.#.#.#.#.", - "...........", - ".#.#.#.#.#.", - "...........", - "...........", - "...S...S...", - "...........", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "03303330330", - "03333333330", - "03333333330", - "03333333330", - "00000000000", - "00000000000", - "33333333333", - "33333333333", - "30303030303", - "33333333333", - "30303030303", - "33333333333", - "30303030303", - "33333333333", - "33333333333", - "33303330333", - "33333333333", - "33333333333", - "00000000000" - ], - "points": { - "warp_1": [5, 19] - }, - "warps": [ - { - "x": 4, - "y": 19, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_9" - }, - { - "x": 5, - "y": 19, - "enter": "walk_south", - "to_map": "VermilionCity", - "to_point": "warp_9" - }, - { - "x": 6, - "y": 19, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_9" - } - ], - "objects": [ - { - "name": "lt_surge", - "kind": "npc", - "x": 5, - "y": 2 - }, - { - "name": "balding_man", - "kind": "npc", - "x": 2, - "y": 11, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "sailor", - "kind": "npc", - "x": 8, - "y": 13, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "gym_guy", - "kind": "npc", - "x": 4, - "y": 17 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 7, - "y": 8, - "facing": "west", - "trainer": { - "sight": 3 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json deleted file mode 100644 index 5e53a3ba51..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "name": "VermilionCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/VermilionCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_4" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "VermilionCity", - "to_point": "warp_4" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_4" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "man", - "kind": "npc", - "x": 4, - "y": 8, - "wander": { - "x0": 3, - "y0": 8, - "x1": 5, - "y1": 8 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "youngster", - "kind": "npc", - "x": 14, - "y": 4, - "wander": { - "x0": 14, - "y0": 3, - "x1": 14, - "y1": 5 - } - }, - { - "name": "cooltrainer_f", - "kind": "npc", - "x": 6, - "y": 4 - }, - { - "name": "0", - "kind": "npc", - "x": 2, - "y": 1, - "hide_flag": "FLAG_HIDE_FAME_CHECKER_LT_SURGE_JOURNAL" - }, - { - "name": "0", - "kind": "npc", - "x": 3, - "y": 1, - "hide_flag": "FLAG_HIDE_FAME_CHECKER_LT_SURGE_JOURNAL" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json deleted file mode 100644 index 5bf976082a..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "name": "ViridianCity", - "source": { - "decomp_file": "data/maps/ViridianCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###################.....########################", - "###################....S########################", - "###################.....########################", - "###################.....########################", - "###################.....####..............######", - "########..................................######", - "########.##########.......................######", - "########.###########.............######...######", - "########.##########..............######...######", - "########.###########...######....######...######", - "########.##########.....#####...S..#D#....######", - "########.###########...##D###.............######", - "########.##########........##vvvvvvvvvvvvv######", - "########.###########......................######", - "########.##########.......................######", - "######...###########.........###################", - ".........##########.S..######.............######", - "........................#####.....####....######", - ".......................##D###.....####....######", - "..................................SSD#....######", - "#######...................................######", - "#######...................................######", - "#######...................................######", - "###########.............#####.............######", - "###########.##..........#####.............######", - "#######.....##..........#####.............######", - "#######....wwwwww.......SSD##.............######", - "#######....wwwwww.........................######", - "#######....wwwwww.........................######", - "#######....wwwwww.........................######", - "#######vvvvwwwwwwvvv...vvvvvvvvvvvvvvvvvvv######", - "#######.............S.....................######", - "#######...................................######", - "#######...................................######", - "##############............................######", - "##############............................######", - "######################....######################", - "######################....######################", - "######################....######################", - "######################....######################" - ], - "elevation": [ - "000000000000000000033333000000000000000000000000", - "000000000000000000033330000000000000000000000000", - "000000000000000000033333000000000000000000000000", - "000000000000000000033333000000000000000000000000", - "000000000000000000033333000033333333333333000000", - "000000003333333333333333333333333333333333000000", - "000000003000000000033333333333333333333333000000", - "000000003000000000003333333333333000000333000000", - "000000003000000000033333333333333000000333000000", - "000000003000000000003330000003333000000333000000", - "000000003000000000033333000003330330003333000000", - "000000003000000000003330000003333333333333000000", - "000000003000000000033333333000000000000000000000", - "000000003000000000003333333333333333333333000000", - "000000003000000000033333333333333333333333000000", - "000000333000000000003333333330000000000000000000", - "333333333000000000030330000003333333333333000000", - "333333333333333333333333000003333300003333000000", - "333333333333333333333330000003333300003333000000", - "333333333333333333333333333333333300003333000000", - "000000033333333333333333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000000003333333333333000003333333333333000000", - "000000000003003333333333000003333333333333000000", - "000000033333003333333333000003333333333333000000", - "000000033331111113333333000003333333333333000000", - "000000033331111113333333333333333333333333000000", - "000000033331111113333333333333333333333333000000", - "000000033331111113333333333333333333333333000000", - "000000000001111110003330000000000000000000000000", - "000000033333333333330333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000000000003333333333333333333333333333000000", - "000000000000003333333333333333333333333333000000", - "000000000000000000000033330000000000000000000000", - "000000000000000000000033330000000000000000000000", - "000000000000000000000033330000000000000000000000", - "000000000000000000000033330000000000000000000000" - ], - "points": { - "warp_0": [26, 26], - "warp_4": [36, 19] - }, - "warps": [ - { - "x": 26, - "y": 26, - "enter": "walk_into", - "to_map": "ViridianCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 25, - "y": 11, - "enter": "walk_into" - }, - { - "x": 36, - "y": 10, - "enter": "walk_into" - }, - { - "x": 25, - "y": 18, - "enter": "walk_into" - }, - { - "x": 36, - "y": 19, - "enter": "walk_into", - "to_map": "ViridianCity_Mart", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "fat_man", - "kind": "npc", - "x": 8, - "y": 26 - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 11, - "y": 24, - "hide_flag": "FLAG_TEMP_11" - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 34, - "y": 11 - }, - { - "name": "var_0", - "kind": "npc", - "x": 21, - "y": 6 - }, - { - "name": "woman_3", - "kind": "npc", - "x": 20, - "y": 12 - }, - { - "name": "youngster", - "kind": "npc", - "x": 33, - "y": 26, - "wander": { - "x0": 33, - "y0": 24, - "x1": 33, - "y1": 28 - } - }, - { - "name": "boy", - "kind": "npc", - "x": 16, - "y": 22, - "wander": { - "x0": 15, - "y0": 20, - "x1": 17, - "y1": 24 - } - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 18, - "y": 5, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "item_ball", - "kind": "item", - "x": 17, - "y": 5, - "hide_flag": "FLAG_HIDE_VIRIDIAN_CITY_POTION" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "Route2", - "offset": 12 - }, - { - "side": "south", - "to_map": "Route1", - "offset": 12 - }, - { - "side": "west", - "to_map": "Route22", - "offset": 10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json deleted file mode 100644 index 9b3e8509a4..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "name": "ViridianCity_Mart", - "source": { - "decomp_file": "data/maps/ViridianCity_Mart/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "##.#......#", - "...#.......", - "####...##.#", - ".......##.#", - ".##....##.#", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "00303333330", - "33003333333", - "00003330030", - "33333330030", - "30033330030", - "33333333333", - "00000000000" - ], - "points": { - "clerk_front": [4, 3], - "warp_1": [4, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_4" - }, - { - "x": 4, - "y": 7, - "enter": "walk_south", - "to_map": "ViridianCity", - "to_point": "warp_4" - }, - { - "x": 5, - "y": 7, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_4" - } - ], - "objects": [ - { - "name": "clerk", - "kind": "npc", - "x": 2, - "y": 3 - }, - { - "name": "youngster", - "kind": "npc", - "x": 6, - "y": 2, - "wander": { - "x0": 5, - "y0": 1, - "x1": 7, - "y1": 3 - } - }, - { - "name": "woman_1", - "kind": "npc", - "x": 9, - "y": 5, - "wander": { - "x0": 9, - "y0": 4, - "x1": 9, - "y1": 6 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json deleted file mode 100644 index e0341f1440..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "name": "ViridianCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/ViridianCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "ViridianCity", - "to_point": "warp_0" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_0" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "boy", - "kind": "npc", - "x": 4, - "y": 7 - }, - { - "name": "youngster", - "kind": "npc", - "x": 2, - "y": 3, - "wander": { - "x0": 1, - "y0": 2, - "x1": 3, - "y1": 4 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json deleted file mode 100644 index a6e51104b3..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json +++ /dev/null @@ -1,224 +0,0 @@ -{ - "name": "ViridianForest", - "source": { - "decomp_file": "data/maps/ViridianForest/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "###########.............###........................###", - "###########.............###........................###", - "###########.............###........................###", - "#..#####..#.............###.....###############....###", - "####...####.............###.....###############....###", - "####...####.............###.....###############....###", - "####...####.....###.....###.....###############....###", - "####..S####~~~~~###~~~~~###~~~~~###############....###", - "###~~~~~###~~~~~###~~~~~###~~~~~###############....###", - "###~~~~~###~~~~~###~~~~~###~~~~~###############....###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.............###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.............###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.............###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.......#########", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~........#########", - "###~~~~~###~~~~~###~~~~~###~~~~~~######......#########", - "###~~~~~###~~~~~###~~~~~###...~~~######......#########", - "###~~~~~###~~~~~###.....###......######......#########", - "###~~~~~###~~~~~###..............######......#########", - "###~~~~~###~~~~~#####............######......#########", - "###~~~~~###~~~~~#####............######............###", - "###~~~~~###~~~~~~~#####################....S.......###", - "###~~~~~...~~~~~~~#####################............###", - "###...............#####################............###", - "###......S........#####################~~~~........###", - "###...............#####################~~~~~###....###", - "###.....................###############~~~~~###....###", - "###.....................###############~~~~~###....###", - "###.....................###############~~~~~###....###", - "##################......###############~~~~~###....###", - "##################......###############~~~~~###....###", - "#######################################~~~~~###....###", - "#######################################~~~~~###....###", - "#######################################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~######~~~~........###", - "############~~~~~~~~~~~~~~~~S..~~######~~~.........###", - "############~~~~~~~~~~~~~~~~...~~######~~~.........###", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "###~~~~~~~~~..~~~~~~~~~~~~~~###~~~~~~~~~~~...~~~~~~###", - "###~~~~~~~~~..~~~~~~~~~~~~~~###~~~~~~~~~~~...~~~~~~###", - "###~~~~~~~~~...~~~~~~~~~~~~.###.~~~~~~~~~...~~~~~~~###", - "###~~~~~~~~~....~~~~~~~~~~.......~~~~~~~....~~~~~~~###", - "###~~~~~~~~............................S....~~~~~~~###", - "###~~~~~~~~....................S...........~~~~~~~~###", - "###~~~~~~~~................................~~~~~~~~###", - "###########################......#####################", - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "######################################################" - ], - "points": { - "warp_0": [29, 62], - "warp_2": [5, 9] - }, - "warps": [ - { - "x": 29, - "y": 62, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_3" - }, - { - "x": 28, - "y": 62, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_3" - }, - { - "x": 5, - "y": 9, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_1" - }, - { - "x": 6, - "y": 9, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_1" - }, - { - "x": 30, - "y": 62, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_3" - }, - { - "x": 4, - "y": 9, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 29, - "y": 58 - }, - { - "name": "boy", - "kind": "npc", - "x": 45, - "y": 58 - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 47, - "y": 45, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 47, - "y": 29, - "facing": "west", - "trainer": { - "sight": 4 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 7, - "y": 22, - "facing": "west", - "trainer": { - "sight": 4 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 5, - "y": 41, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_POKE_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 40, - "y": 21, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_ANTIDOTE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 21, - "y": 34, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_POTION" - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 43, - "y": 6, - "trainer": { - "sight": 1, - "looks": [ - "south", - "north" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 16, - "y": 5, - "facing": "north", - "trainer": { - "sight": 1 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 49, - "y": 60, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_POTION_2" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp new file mode 100644 index 0000000000..976d1a0c85 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp @@ -0,0 +1,68 @@ +/* Overworld Late Battle + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "PokemonFRLG_OverworldWalk.h" +#include "PokemonFRLG_OverworldLateBattle.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +const std::vector& wild_battle_lags(){ + static const std::vector LAGS{ + {Pokemon::ScreenEvent::BATTLE_STARTED, OverworldWalkSteps::LATE_WILD_BATTLE_DELAY}, + }; + return LAGS; +} + +int tile_reached(Milliseconds since_hold, Milliseconds tile_time, int tiles){ + if (tile_time.count() <= 0){ + return tiles; + } + if (since_hold.count() < 0){ + return 0; + } + const int begun = (int)((since_hold + tile_time / 2) / tile_time) + 1; + return std::clamp(begun, 0, tiles); +} + +LateStop place_late_stop( + const RouteStep& step, Milliseconds tile_time, + const Pokemon::ScreenEventLog& log, WallClock hold_start, WallClock now +){ + LateStop out; + out.verdict = Pokemon::classify_interrupt(log, {hold_start, now}, {}); + if (out.verdict.kind != Pokemon::InterruptKind::WILD_ENCOUNTER){ + return out; + } + const std::optional pressed = Pokemon::interrupt_step_time(out.verdict, wild_battle_lags()); + if (!pressed){ + return out; + } + const Milliseconds since_hold = std::chrono::duration_cast(*pressed - hold_start); + out.walked = walked_near(step, tile_reached(since_hold, tile_time, step.tiles), tile_time); + return out; +} + +std::string late_stop_line(const RouteStep& step, const LateStop& stop, WallClock hold_start){ + std::string out = std::format( + "Route: seen after the hold of {}: {}", + to_string(step), Pokemon::describe(stop.verdict, hold_start) + ); + if (stop.walked){ + return out + std::format("; {} to {} tiles in.", stop.walked->min, stop.walked->max); + } + return out + "; not placed."; +} + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h new file mode 100644 index 0000000000..b2e7634965 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h @@ -0,0 +1,75 @@ +/* Overworld Late Battle + * + * From: https://github.com/PokemonAutomation/ + * + * Where a wild battle seen only after an open leg's hold stopped the + * player. The walker records the leg's screens as timed events + * (Pokemon::ScreenEventLog) through the hold and the watch after it. The + * shared classifier (Pokemon::classify_interrupt()) says what stopped the + * player, and a wild battle is placed by when its first timed event + * showed, not by when the watcher that cleared it fired: that is after the + * hold, and the leg could have stopped anywhere along it. + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_OverworldLateBattle_H +#define PokemonAutomation_PokemonFRLG_OverworldLateBattle_H + +#include +#include +#include +#include "Common/Cpp/Time.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_Interrupts.h" +#include "Pokemon/AutoStory/Pokemon_ScreenEvents.h" +#include "PokemonFRLG_OverworldReanchor.h" +#include "PokemonFRLG_OverworldRoute.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// The events of a wild battle FRLG has timed from its step: its text box +// ("Wild appeared!", BATTLE_STARTED), LATE_WILD_BATTLE_DELAY +// after the step on Route 1 (console B, 2026-11-01, run 7). The flashes +// are what the walker missed, and the wipe isn't timed on the console yet. +const std::vector& wild_battle_lags(); + +// The tile of a leg (from 1) that a step begun `since_hold` into its hold +// reaches: the steps begin a tile apart from the hold's start. At most +// `tiles`. +int tile_reached(Milliseconds since_hold, Milliseconds tile_time, int tiles); + +// What stopped an open leg after its hold, and where. +struct LateStop{ + Pokemon::InterruptVerdict verdict; + // The tiles of the leg the player may be on. Set for a wild battle + // whose step the log times. + std::optional walked; +}; + +// For `step`, an open leg held from `hold_start` at `tile_time` a tile, +// after which something was seen and cleared by `now`: what `log` shows +// stopped the player there, and for a wild battle, the tile its step +// reached (tile_reached()), give or take walked_near()'s slack. +LateStop place_late_stop( + const RouteStep& step, Milliseconds tile_time, + const Pokemon::ScreenEventLog& log, WallClock hold_start, WallClock now +); + +// "Route: seen after the hold of : wild encounter: +5035 ms battle +// started rose; 5 to 7 tiles in.", times from `hold_start`. +std::string late_stop_line(const RouteStep& step, const LateStop& stop, WallClock hold_start); + + +// Registers the late battle unit tests, on the sim. add_tests_OverworldSim() +// calls it. +void add_tests_OverworldLateBattle(UnitTestDatabase& database); + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp new file mode 100644 index 0000000000..d6b6b4e46b --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp @@ -0,0 +1,194 @@ +/* Overworld Late Battle Tests + * + * From: https://github.com/PokemonAutomation/ + * + * A wild battle seen only after an open leg's hold, on the sim: the walker + * must place the player within a few tiles of where the battle stopped + * them, find them again and finish the route. + * + */ + +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/TestRunners/UnitTest.h" +#include "PokemonFRLG_OverworldLateBattle.h" +#include "PokemonFRLG_OverworldMaps.h" +#include "PokemonFRLG_OverworldReanchor.h" +#include "PokemonFRLG_OverworldSim.h" +#include "PokemonFRLG_OverworldWalk.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +using namespace std::chrono_literals; + +struct LateBattleInput{ + std::string name; + // The leg's step (from 1) the battle starts on. + int battle_step = 1; +}; + +struct LateBattleExpectation{ + // The most the walker's bound on the player's tile may span, in tiles. + int max_spread = 0; +}; + +struct LateBattleCase{ + LateBattleInput input; + LateBattleExpectation expectation; +}; + +const std::vector& maps(){ + static const std::vector MAPS = overworld_maps(); + return MAPS; +} + +// The bound run_route() logged for the stop: " stopped to +// tiles in; ...". +std::optional logged_bound(const std::vector& log){ + for (const std::string& line : log){ + const size_t at = line.find(" stopped "); + WalkedTiles bound; + if (at != std::string::npos && std::sscanf(line.c_str() + at, " stopped %d to %d tiles in", &bound.min, &bound.max) == 2){ + return bound; + } + } + return std::nullopt; +} + +// A walk on the sim: how it ended, and what the walker and the sim logged. +struct LateBattleRun{ + std::string failure; + SimState state; + std::vector log; +}; + +std::string describe(const LateBattleRun& run){ + std::string out = std::format("\n ended on {}, failure: \"{}\"", to_string(run.state.position), run.failure); + for (const std::string& line : run.log){ + out += "\n " + line; + } + for (const std::string& line : run.state.log){ + out += "\n " + line; + } + return out; +} + +const MapPosition LEG_START{"Route10", {5, 10}}; +const MapPosition LEG_END{"Route10", {16, 10}}; + +// "east 11" from LEG_START through Route 10's grass, with a battle on +// `battle_step` whose flashes are missed. +LateBattleRun walk_with_battle(int battle_step, SimWalkerRules rules){ + RouteStep east; + east.direction = Direction::EAST; + east.tiles = 11; + east.wild = true; + east.facing_after = Direction::EAST; + east.end = LEG_END; + SimScript script; + script.encounters = {{.grass_step = battle_step, .seen_late = true}}; + script.timing.battle_opening = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY; + script.timing.battle_screen = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY + 800ms; + OverworldSim sim(maps(), LEG_START, Direction::EAST, script); + OverworldSimWalker walker(sim, rules); + const RoutePlanning planning{.maps = &maps(), .costs = default_route_costs(), .start = LEG_START, .label = "Sim"}; + LateBattleRun run; + run.failure = walker.run({east}, planning); + run.state = sim.state(); + run.log = walker.log(); + return run; +} + +std::string bound_text(const std::optional& bound){ + return bound ? std::format("{} to {}", bound->min, bound->max) : std::string("no bound"); +} + +// Console B, FRLG 00.27 run 2, 2026-11-09 (~/FRLGLab/captures/ +// 2026-11-09-0027-route10-legs.log): on the open leg "east 11" across +// Route 10, wild battles (Voltorb) showed only after the hold. The walker +// took the player to be anywhere on the leg ("stopped 0 to 11 tiles in") +// and walked "east 12 (to wall)" to find them, into a second battle. Here +// the leg crosses Route 10's grass on row 10, the battle's flashes are +// missed, its text box ("Wild appeared!") shows +// LATE_WILD_BATTLE_DELAY after its step and its HP boxes 800 ms after +// that, as in Mt. Moon (console B, 2026-11-03, row 00.13). After running, +// the walker must bound the player to a few tiles around the battle's. +class Test_OverworldLateBattle : public UnitTest{ +public: + explicit Test_OverworldLateBattle(LateBattleCase test_case) + : UnitTest("PokemonFRLG::OverworldLateBattle - " + test_case.input.name) + , m_case(std::move(test_case)) + {} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + try{ + return run_case(); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } + +private: + UnitTestResult run_case() const{ + const int battle = m_case.input.battle_step; + const LateBattleRun now = walk_with_battle(battle, {}); + if (!now.failure.empty() || !(now.state.position == LEG_END) || !now.state.violations.empty()){ + return "Expected the battle run from and " + to_string(LEG_END) + " reached:" + describe(now); + } + const std::optional bound = logged_bound(now.log); + if (!bound || bound->min > battle || bound->max < battle || bound->max - bound->min > m_case.expectation.max_spread){ + return std::format( + "Expected the player placed within {} tiles around tile {} of the leg, got {}:{}", + m_case.expectation.max_spread, battle, bound_text(bound), describe(now) + ); + } + SimWalkerRules unplaced; + unplaced.place_late_battle = false; + const LateBattleRun old = walk_with_battle(battle, unplaced); + const std::optional whole = logged_bound(old.log); + if (!whole || whole->min != 0 || whole->max != 11){ + return "Expected the whole leg, 0 to 11 tiles, without the rule, as on the console, got " + bound_text(whole) + ":" + describe(old); + } + return true; + } + + LateBattleCase m_case; +}; + +} // namespace + + +const LateBattleCase LATE_BATTLE_CASES[] = { + { + .input = {.name = "a battle on step 3 of an open leg, seen after the hold", .battle_step = 3}, + .expectation = {.max_spread = 2}, + }, + { + .input = {.name = "a battle on step 6 of an open leg, seen after the hold", .battle_step = 6}, + .expectation = {.max_spread = 2}, + }, + { + .input = {.name = "a battle on step 10, its HP boxes after the leg's watch", .battle_step = 10}, + .expectation = {.max_spread = 2}, + }, +}; + +void add_tests_OverworldLateBattle(UnitTestDatabase& database){ + for (const LateBattleCase& item : LATE_BATTLE_CASES){ + database.add(item); + } +} + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp index dc8541ed63..2735ec7ae4 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp @@ -71,6 +71,13 @@ const char* const MAP_NAMES[] = { "SSAnne_CaptainsOffice", // Vermilion Gym, past the tree Cut fells. "VermilionCity_Gym", + // The BIKE VOUCHER and the BICYCLE. + "VermilionCity_PokemonFanClub", + "CeruleanCity_BikeShop", + // East over Route 9 to Route 10's Pokemon Center. + "Route9", + "Route10", + "Route10_PokemonCenter_1F", "CeruleanCave_1F", "CeruleanCave_2F", "CeruleanCave_B1F", diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h index 7fa58f230e..5f0b05d189 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h @@ -11,8 +11,8 @@ * Center, and the S.S. Anne's corridors to the Captain's office; and the * three floors of Cerulean Cave, where Mewtwo waits on B1F. * - * Each map is a JSON file, Maps/.json next to this file, generated by - * devtools/maps/frlg_maps.py and built into the program by CMake + * Each map is a pinned PokemonSimulation JSON resource, prepared under + * Packages/Resources/Pokemon/Maps/frlg and built into the program by CMake * (pa_embed_text_files() in cmake/EmbedTextFiles.cmake), so the program and * its tests read the same copy. The schema is Pokemon::MapData's * (Pokemon/Navigation/Pokemon_MapData.h). A warp leads to point diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp index 62e20d0528..588b63c357 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp @@ -6,6 +6,7 @@ #include #include +#include "Pokemon/Navigation/Pokemon_WallRecovery.h" #include "PokemonFRLG_OverworldReanchor.h" namespace PokemonAutomation{ @@ -126,15 +127,6 @@ struct Hypothesis{ bool operator==(const Hypothesis& other) const = default; }; -bool converged(const std::vector& positions){ - for (const Hypothesis& position : positions){ - if (!(position.at == positions[0].at)){ - return false; - } - } - return true; -} - // Whether a person stands, or may wander, on a tile `wall` passes after // `from`. The step rules don't have people, but one there stops the player // short of the wall. @@ -183,41 +175,33 @@ std::optional, RouteStep>> hold_all( return std::make_pair(std::move(ends), hold); } -int total_tiles(const std::vector& legs){ - int total = 0; - for (const RouteStep& leg : legs){ - total += leg.tiles; - } - return total; -} +using Found = Pokemon::WallAnchor; -struct Found{ - std::vector holds; - MapPosition at; +// Runtime world alternatives and movement mode, separate from the policy type. +struct ReanchorContext{ + const std::vector>& worlds; + bool surfing = false; }; -// Every way of exactly `depth` holds that brings all of `positions` to one -// tile, in a fixed order. -void search( - const std::vector>& worlds, const std::vector& positions, bool surfing, - size_t depth, std::vector& holds, std::vector& found -){ - for (Direction direction : Pokemon::ALL_DIRECTIONS){ - auto next = hold_all(worlds, positions, surfing, direction); - if (!next || next->first == positions || next->second.tiles == 0){ - continue; - } - holds.push_back(next->second); - if (converged(next->first)){ - if (depth == 1){ - found.push_back(Found{holds, next->first[0].at}); - } - }else if (depth > 1){ - search(worlds, next->first, surfing, depth - 1, holds, found); +class ReanchorPolicy{ +public: + using Hypothesis = PokemonFRLG::Hypothesis; + using Position = MapPosition; + using Hold = RouteStep; + + static Position position(const Hypothesis& hypothesis){ return hypothesis.at; } + static int tiles(const Hold& hold){ return hold.tiles; } + static std::optional> hold( + const ReanchorContext& context, const std::vector& positions, Direction direction + ){ + auto next = hold_all(context.worlds, positions, context.surfing, direction); + if (!next){ + return std::nullopt; } - holds.pop_back(); + return Pokemon::WallRecoveryStep{std::move(next->first), std::move(next->second)}; } -} + +}; // Whether `route`, planned from `at` on other maps, walks the same on // `world`: each leg to a wall stops at its end, each open leg gets there. @@ -257,18 +241,9 @@ bool walks_same_everywhere( std::vector find_player( const std::vector>& worlds, const std::vector& positions, bool surfing ){ - std::vector found; - if (converged(positions)){ - found.push_back(Found{{}, positions[0].at}); - } - for (size_t depth = 1; found.empty() && depth <= MAX_HOLDS; depth++){ - std::vector holds; - search(worlds, positions, surfing, depth, holds, found); - std::stable_sort(found.begin(), found.end(), [](const Found& a, const Found& b){ - return total_tiles(a.holds) < total_tiles(b.holds); - }); - } - return found; + return Pokemon::find_wall_anchors( + positions, ReanchorContext{.worlds = worlds, .surfing = surfing}, {.max_holds = MAX_HOLDS} + ); } } // namespace diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp index b6953fedbe..8aac65097e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp @@ -6,14 +6,18 @@ #include #include +#include +#include #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTools/ImageDiff.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/Async/InferenceSession.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "VideoGames/PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_OverworldLateBattle.h" #include "PokemonFRLG_OverworldRecoveryWalk.h" namespace PokemonAutomation{ @@ -41,13 +45,20 @@ Pokemon::LegSpec leg_spec(const RouteStep& step, MoveSpeed speed){ return leg; } -// One step of a route, for a player facing `facing`. If an open leg -// stopped part-way, returns how far the timing puts the player along it. -std::optional walk_step( +// How one step's walk went: if an open leg stopped part-way, how far the +// timing puts the player along it; and when its hold started. +struct StepWalk{ + std::optional stopped; + WallClock hold_start; +}; + +// One step of a route, for a player facing `facing`. +StepWalk walk_step( ConsoleHandle& console, ProControllerContext& context, const RouteStep& step, std::optional facing, const OverworldInterruptSet& interrupts, MoveSpeed speed ){ + StepWalk out{std::nullopt, current_time()}; switch (step.action){ case RouteAction::WALK:{ // Turning first, with a tap, makes the hold start from a standstill @@ -59,19 +70,23 @@ std::optional walk_step( if (const std::string note = speed_note(step, speed); !note.empty()){ console.log(note); } + context.wait_for_all_requests(); + out.hold_start = current_time(); const Pokemon::LegResult result = Pokemon::walk_leg( console, context, leg_mover(step, speed), step.wild ? interrupts.wild : interrupts.other, leg_spec(step, speed), MAX_INTERRUPTIONS_PER_LEG ); if (result.stopped){ - return walked_near(step, result.tiles, tile_time(step, speed)); + out.stopped = walked_near(step, result.tiles, tile_time(step, speed)); + return out; } if (step.warps){ wait_out_warp(console, context, step); static_assert(AFTER_WARP_BATTLE_WATCH >= AFTER_WARP); if (run_from_battle_after_warp(console, context)){ console.log("Route: a wild battle, not the warp to " + step.end.map + "; the player is on " + to_string(step) + " before the warp.", COLOR_ORANGE); - return walked_before_warp(step); + out.stopped = walked_before_warp(step); + return out; } } break; @@ -83,9 +98,54 @@ std::optional walk_step( pbf_press_dpad(context, dpad(step.direction), FACE_HOLD, STEP_RELEASE); break; } - return std::nullopt; + return out; } +// An open leg's screens, recorded with the interrupts' observer from +// before its hold until the watch after it ends, alongside the walker's +// own watchers. Records nothing for other steps, or with no observer. +class LegRecorder{ +public: + LegRecorder( + ConsoleHandle& console, ProControllerContext& context, + const OverworldInterruptSet& interrupts, const RouteStep& step + ) + : m_observer(records(interrupts, step) ? interrupts.make_observer() : nullptr) + , m_watcher(m_observer ? std::make_unique(*m_observer) : nullptr) + , m_session(context, console, callbacks()) + {} + + // Where what was seen after `step`'s hold, from `hold_start`, stopped + // the player (place_late_stop()); nothing if not recorded or not placed. + std::optional place( + ConsoleHandle& console, const RouteStep& step, Milliseconds tile_time, WallClock hold_start + ) const{ + if (!m_watcher){ + return std::nullopt; + } + const LateStop stop = place_late_stop(step, tile_time, m_watcher->log(), hold_start, current_time()); + console.log(late_stop_line(step, stop, hold_start), COLOR_ORANGE); + return stop.walked; + } + +private: + static bool records(const OverworldInterruptSet& interrupts, const RouteStep& step){ + const bool open_leg = step.action == RouteAction::WALK && !step.to_wall && !step.warps; + return open_leg && interrupts.make_observer; + } + std::vector callbacks() const{ + if (!m_watcher){ + return {}; + } + return {*m_watcher}; + } + + std::unique_ptr m_observer; + std::unique_ptr m_watcher; + // With no watcher it runs nothing. + InferenceSession m_session; +}; + // walk_step(), then a watch for what its last step set off. Not DONE if // the player may have stopped short of the step's end: an open leg // stopped, or something seen after the hold, which may have stopped it @@ -104,8 +164,10 @@ StepOutcome walk_and_watch( } clear_open_message(console, context, step.wild ? interrupts.wild : interrupts.other, step); check_overworld(console, context, step); - if (const std::optional walked = walk_step(console, context, step, facing, interrupts, speed)){ - return StepOutcome{.end = StepEnd::STOPPED, .walked = walked}; + const LegRecorder recorder(console, context, interrupts, step); + const StepWalk walked = walk_step(console, context, step, facing, interrupts, speed); + if (walked.stopped){ + return StepOutcome{.end = StepEnd::STOPPED, .walked = walked.stopped}; } if (step.action == RouteAction::FACE){ return StepOutcome{}; @@ -118,7 +180,10 @@ StepOutcome walk_and_watch( } // After a warp it was checked for already. const bool done = late == 0 || step.warps || step.action != RouteAction::WALK; - return StepOutcome{.end = done ? StepEnd::DONE : StepEnd::LATE, .walked = std::nullopt}; + if (done){ + return StepOutcome{}; + } + return StepOutcome{.end = StepEnd::LATE, .walked = recorder.place(console, step, tile_time(step, speed), walked.hold_start)}; } // Holds on along `step`, stopped part-way off the grass, until its warp's diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp index 93fdda5aaf..3395eda537 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp @@ -10,6 +10,7 @@ #include "Common/Cpp/Exceptions.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "Pokemon/Navigation/Pokemon_LegWalker.h" +#include "PokemonFRLG_OverworldLateBattle.h" #include "PokemonFRLG_OverworldReanchor.h" #include "PokemonFRLG_OverworldWalk.h" #include "PokemonFRLG_OverworldSim.h" @@ -44,6 +45,50 @@ const char* sim_screen_name(SimScreen screen){ } +WallClock sim_wall_clock(Milliseconds time){ + return WallClock() + time; +} + +namespace{ + +Pokemon::ScreenEvents observed_events(SimScreen screen){ + switch (screen){ + case SimScreen::MESSAGE: + return {Pokemon::ScreenEvent::DIALOG_BOX}; + case SimScreen::BATTLE_WIPE: + case SimScreen::FADE: + return {Pokemon::ScreenEvent::FADE_BLACK}; + case SimScreen::BATTLE_OPENING: + case SimScreen::BATTLE: + return {Pokemon::ScreenEvent::BATTLE_STARTED}; + case SimScreen::OVERWORLD: + case SimScreen::START_MENU: + case SimScreen::BATTLE_FLASH: + break; + } + return {}; +} + +} // namespace + +Pokemon::ScreenEventLog screen_event_log(const SimState& state, Milliseconds from){ + Pokemon::ScreenEventLog log; + SimScreen screen = state.shown.empty() ? state.screen : state.shown.front().screen; + size_t next = 0; + for (; next < state.shown.size() && state.shown[next].at <= from; next++){ + screen = state.shown[next].screen; + } + Milliseconds at = from; + log.record(observed_events(screen), sim_wall_clock(at)); + for (; next < state.shown.size(); next++){ + at = std::max(at, state.shown[next].at); + log.record(observed_events(state.shown[next].screen), sim_wall_clock(at)); + } + log.record(observed_events(state.screen), sim_wall_clock(std::max(at, state.time))); + return log; +} + + namespace{ // A short press: TURN_TAP and TURN_SETTLE. @@ -105,7 +150,7 @@ OverworldSim::OverworldSim(std::vector maps, MapPosition start, Directi } m_player = PlayerState{start.point, elevation_at(map(), start.point), false}; m_state.facing = facing; - m_state.screen = m_script.start_menu_open ? SimScreen::START_MENU : SimScreen::OVERWORLD; + show(m_script.start_menu_open ? SimScreen::START_MENU : SimScreen::OVERWORLD, m_now); update_view(); } @@ -135,11 +180,17 @@ void OverworldSim::schedule(Milliseconds at, SimScreen screen, bool unfreeze){ void OverworldSim::advance_to(Milliseconds time){ while (!m_pending.empty() && m_pending.front().at <= time){ - m_state.screen = m_pending.front().screen; + show(m_pending.front().screen, m_pending.front().at); m_frozen = m_frozen && !m_pending.front().unfreeze; m_pending.erase(m_pending.begin()); } m_now = std::max(m_now, time); + m_state.time = m_now; +} + +void OverworldSim::show(SimScreen screen, Milliseconds at){ + m_state.screen = screen; + m_state.shown.push_back(SimShown{at, screen}); } @@ -453,7 +504,7 @@ SimBattleEnd OverworldSim::handle_battle(SimBattleChoice choice){ return SimBattleEnd::CANT_RUN; } advance_to(m_now + BATTLE_TIME); - m_state.screen = SimScreen::OVERWORLD; + show(SimScreen::OVERWORLD, m_now); m_frozen = false; m_battle_pending = false; if (m_trainer){ @@ -471,7 +522,7 @@ bool OverworldSim::close_message(){ return false; } advance_to(m_now + MESSAGE_CLOSE); - m_state.screen = SimScreen::OVERWORLD; + show(SimScreen::OVERWORLD, m_now); if (!m_trainer_speaking){ m_frozen = false; return false; @@ -656,6 +707,7 @@ std::optional OverworldSimWalker::walk_step(const RouteStep& step, calls.name = [&](size_t index){ return ints[index].name; }; calls.log = [&](const std::string& message){ m_log.push_back("Leg: " + message); }; calls.fail = [&](const std::string& message){ fail(message); }; + m_leg_start = m_sim.state().time; const Pokemon::LegResult result = Pokemon::walk_leg_with(leg, calls, MAX_INTERRUPTIONS_PER_LEG); if (result.stopped){ return walked_near(step, result.tiles, TILE_TIME); @@ -694,7 +746,24 @@ StepOutcome OverworldSimWalker::walk(const RouteStep& step, std::optional OverworldSimWalker::late_stop(const RouteStep& step){ + if (step.to_wall || !m_rules.place_late_battle){ + return std::nullopt; + } + const WallClock start = sim_wall_clock(m_leg_start); + const LateStop stop = place_late_stop( + step, TILE_TIME, screen_event_log(m_sim.state(), m_leg_start), start, sim_wall_clock(m_sim.state().time) + ); + m_log.push_back(late_stop_line(step, stop, start)); + return stop.walked; } // Whether the screen shows a battle on its way: its wipe's black, or its diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h index b27b6f1057..9483c2e289 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h @@ -36,6 +36,7 @@ #include #include "Common/Cpp/Time.h" #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_ScreenEvents.h" #include "PokemonFRLG_OverworldRecovery.h" #include "PokemonFRLG_OverworldRoute.h" #include "PokemonFRLG_OverworldWalk.h" @@ -117,11 +118,21 @@ struct SimScript{ SimTiming timing; }; +// A screen the sim showed, and when, from its start. +struct SimShown{ + Milliseconds at{0}; + SimScreen screen = SimScreen::OVERWORLD; +}; + // The player and the screen, and what went wrong that the game would show. struct SimState{ MapPosition position; Direction facing = Direction::SOUTH; SimScreen screen = SimScreen::OVERWORLD; + // The sim's clock, from its start. + Milliseconds time{0}; + // Each screen as it showed, oldest first, from `screen` at the start. + std::vector shown; // Whether black shows below the map: the player stands near the bottom // of a map with nothing joined to it (a small room or a gate). bool black_below = false; @@ -136,6 +147,17 @@ struct SimState{ std::vector log; }; +// A time on the sim's clock as a WallClock, for the shared judges. +WallClock sim_wall_clock(Milliseconds time); + +// The screens `state` showed from `from` until its time, as the walker's +// screen observer (FrlgScreenObserver) reports them: a message's box, a +// battle's text box with or without its HP boxes, and the black of a +// battle's wipe or a warp's fade. A battle's flashes are no uniform +// screen, and black below a small room, which the observer's battle text +// box detector may take for a box, isn't modelled. +Pokemon::ScreenEventLog screen_event_log(const SimState& state, Milliseconds from); + // A watcher that fired: which (an index into the watchers asked for), and // how long after the hold or watch started. struct SimSeen{ @@ -208,6 +230,7 @@ class OverworldSim{ }; void advance_to(Milliseconds time); + void show(SimScreen screen, Milliseconds at); void schedule(Milliseconds at, SimScreen screen, bool unfreeze); bool sees(SimWatcher watcher, SimScreen screen) const; std::optional first_seen(SimWatcher watcher, Milliseconds from, Milliseconds to) const; @@ -300,6 +323,11 @@ struct SimWalkerRules{ // Cerulean City onto Nugget Bridge ended a tile short (console B, // 2026-11-03, rows 00.16 and 00.20). bool hold_over_connections = true; + // An open leg after whose hold a wild battle was seen is placed by the + // battle's first timed screen event (place_late_stop()). Without it the + // player may be anywhere on the leg: on Route 10, "east 11" was found + // again from all 12 of its tiles (console B, 2026-11-09, 00.27 run 2). + bool place_late_battle = true; }; // The console side of run_route() on an OverworldSim, as @@ -337,6 +365,7 @@ class OverworldSimWalker{ bool battle; }; StepOutcome walk(const RouteStep& step, std::optional facing); + std::optional late_stop(const RouteStep& step); std::optional walk_step(const RouteStep& step, std::optional facing); bool after_warp(const RouteStep& step, int warps_before); [[noreturn]] void fail_off_route(const RouteStep& step); @@ -358,6 +387,8 @@ class OverworldSimWalker{ std::vector m_wild; std::vector m_other; std::vector m_log; + // When the last WALK leg's first hold started, on the sim's clock. + Milliseconds m_leg_start{0}; }; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp index 2f3b3db492..f8a2c4ca80 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp @@ -21,6 +21,7 @@ #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" +#include "PokemonFRLG_OverworldLateBattle.h" #include "PokemonFRLG_OverworldMaps.h" #include "PokemonFRLG_OverworldSim.h" @@ -981,6 +982,7 @@ void add_tests_OverworldSim(UnitTestDatabase& database){ for (const auto& item : OVERWORLD_SIM_CASES){ database.add(item); } + add_tests_OverworldLateBattle(database); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h index d399c6240d..e166617c2a 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h @@ -17,11 +17,13 @@ #ifndef PokemonAutomation_PokemonFRLG_OverworldWalk_H #define PokemonAutomation_PokemonFRLG_OverworldWalk_H +#include #include #include #include #include #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_ScreenEvents.h" #include "Pokemon/Navigation/Pokemon_LegWalker.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" #include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" @@ -46,6 +48,11 @@ using OverworldInterrupts = Pokemon::LegInterrupts()> make_observer = {}; }; // A wild battle, run from, and a message (e.g. "REPEL's effect wore off..."), diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp index ccc75acb09..2bf7183092 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -53,7 +53,7 @@ #include "Programs/AutoStory/PokemonFRLG_AutoStoryRngLoop.h" #include "Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" #include "Programs/AutoStory/PokemonFRLG_AutoStoryTrashCans.h" -#include "Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h" +#include "Programs/AutoStory/Data/Json_Tests.h" #include "Programs/RngManipulation/PokemonFRLG_GiftRngAttempts_Tests.h" #include "Programs/RngManipulation/PokemonFRLG_RngPlanInputs_Tests.h" #include "Programs/RngManipulation/PokemonFRLG_RngPlanLog_Tests.h" @@ -894,6 +894,7 @@ static void add_tests_Naming(UnitTestDatabase& database){ static void add_tests_AutoStoryRows(UnitTestDatabase& database){ add_tests_AutoStoryVermilion(database); add_tests_AutoStoryVermilionGym(database); + add_tests_AutoStoryCeladon(database); } void add_tests(UnitTestDatabase& database){ @@ -933,7 +934,7 @@ void add_tests(UnitTestDatabase& database){ add_tests_OverworldSim(database); add_tests_PlayerFacingReader(database); add_tests_AutoStory(database); - add_tests_AutoStoryExport(database); + add_tests_AutoStoryJson(database); add_tests_AutoStorySimulation(database); add_tests_AutoStoryRows(database); add_tests_AutoStoryTrashCans(database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp new file mode 100644 index 0000000000..780b3a9a88 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp @@ -0,0 +1,233 @@ +/* FRLG Checkpoint JSON Parser + * From: https://github.com/PokemonAutomation/ + */ + +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" +#include "Pokemon/AutoStory/Pokemon_AuthoredCheckpointJson.h" +#include "LegacyJsonFormat.h" +#include "JsonParser.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +// Reading. Every field is required and no other is allowed, as in the +// shared codec: the record says what the table said, not what it left out. +const JsonObject& fields(const JsonValue& value, std::initializer_list keys, std::string_view source){ + const JsonObject& object = value.to_object_throw(std::string(source)); + for (const auto& [key, unused] : object){ + bool known = false; + for (std::string_view allowed : keys){ + known |= key == allowed; + } + if (!known){ + throw ParseException(std::format("{}: unsupported field {}", source, key)); + } + } + return object; +} + +void expect_string(const JsonObject& root, const std::string& key, std::string_view expected, std::string_view source){ + const std::string& actual = root.get_string_throw(key, std::string(source)); + if (actual != expected){ + throw ParseException(std::format("{}: {} is {}, not {}", source, key, actual, expected)); + } +} + +// The value of `values` that `name` names `label`. +template +Enum read_named(std::initializer_list values, const char* (*name)(Enum), const std::string& label, std::string_view source){ + for (Enum value : values){ + if (label == name(value)){ + return value; + } + } + throw ParseException(std::format("{}: unsupported value {}", source, label)); +} + +// An integer within [`low`, `high`], as the native field's type holds it. +int64_t bounded(const JsonValue& value, int64_t low, int64_t high, std::string_view source){ + const int64_t number = value.to_integer_throw(std::string(source)); + if (number < low || number > high){ + throw ParseException(std::format("{}: {} is out of range", source, number)); + } + return number; +} + +int read_int(const JsonValue& value, std::string_view source){ + return (int)bounded(value, std::numeric_limits::min(), std::numeric_limits::max(), source); +} + +uint8_t read_byte(const JsonValue& value, std::string_view source){ + return (uint8_t)bounded(value, 0, std::numeric_limits::max(), source); +} + +uint16_t read_word(const JsonValue& value, std::string_view source){ + return (uint16_t)bounded(value, 0, std::numeric_limits::max(), source); +} + +size_t read_count(const JsonValue& value, std::string_view source){ + return (size_t)bounded(value, 0, std::numeric_limits::max(), source); +} + +std::vector read_strings(const JsonValue& value, std::string_view source){ + std::vector result; + for (const JsonValue& item : value.to_array_throw(std::string(source))){ + result.push_back(item.to_string_throw(std::string(source))); + } + return result; +} + +std::optional read_stats(const JsonValue& value, std::string_view source){ + if (value.is_null()){ + return std::nullopt; + } + const JsonObject& root = fields(value, {"hp", "attack", "defense", "sp_attack", "sp_defense", "speed"}, source); + return Stats{ + .hp = read_word(root.get_value_throw("hp"), source), + .attack = read_word(root.get_value_throw("attack"), source), + .defense = read_word(root.get_value_throw("defense"), source), + .sp_attack = read_word(root.get_value_throw("sp_attack"), source), + .sp_defense = read_word(root.get_value_throw("sp_defense"), source), + .speed = read_word(root.get_value_throw("speed"), source), + }; +} + +std::vector read_moves(const JsonValue& value, std::string_view source){ + std::vector result; + for (const JsonValue& item : value.to_array_throw(std::string(source))){ + const JsonObject& row = fields(item, {"move", "pp"}, source); + result.push_back({.move = row.get_string_throw("move"), .pp = read_byte(row.get_value_throw("pp"), source)}); + } + return result; +} + +PartyMember read_member(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, {"species", "moves", "moves_through_level", "moves_read", "level", "stats"}, source); + return PartyMember{ + .species = root.get_string_throw("species"), + .moves = read_moves(root.get_value_throw("moves"), source), + .moves_through_level = read_byte(root.get_value_throw("moves_through_level"), source), + .moves_read = root.get_boolean_throw("moves_read"), + .level = read_byte(root.get_value_throw("level"), source), + .stats = read_stats(root.get_value_throw("stats"), source), + }; +} + +std::vector read_slots(const JsonValue& value, std::string_view source){ + std::vector result; + for (const JsonValue& item : value.to_array_throw(std::string(source))){ + result.push_back(read_count(item, source)); + } + return result; +} + +std::optional read_foe_out(const JsonValue& value, std::string_view source){ + if (value.is_null()){ + return std::nullopt; + } + return read_count(value, source); +} + +Supplies read_supplies(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, {"potions", "antidotes", "parlyz_heals", "pocket", "status", "hurt", "hp_red", + "hp_fill", "lead", "protected_slots", "foes", "trainer", "risk", "foe_out", "wild_foes"}, source); + Supplies result; + result.potions = read_int(root.get_value_throw("potions"), source); + result.antidotes = read_int(root.get_value_throw("antidotes"), source); + result.parlyz_heals = read_int(root.get_value_throw("parlyz_heals"), source); + result.pocket = read_strings(root.get_value_throw("pocket"), source); + result.status = read_named({StatusCondition::NONE, StatusCondition::POISON, StatusCondition::PARALYSIS, StatusCondition::OTHER}, + status_name, root.get_string_throw("status"), source); + result.hurt = root.get_boolean_throw("hurt"); + result.hp_red = root.get_boolean_throw("hp_red"); + result.hp_fill = root.get_double_throw("hp_fill"); + result.lead = read_member(root.get_value_throw("lead"), source); + result.protected_slots = read_slots(root.get_value_throw("protected_slots"), source); + result.foes = read_strings(root.get_value_throw("foes"), source); + result.trainer = root.get_string_throw("trainer"); + result.risk = read_named({BattleRisk::LOW, BattleRisk::HIGH}, battle_risk_name, root.get_string_throw("risk"), source); + result.foe_out = read_foe_out(root.get_value_throw("foe_out"), source); + result.wild_foes = read_strings(root.get_value_throw("wild_foes"), source); + return result; +} + +void check_envelope(const JsonObject& root, std::string_view source){ + expect_string(root, "format", StoryJsonInternal::FORMAT, source); + expect_string(root, "game", StoryJsonInternal::GAME, source); + expect_string(root, "status", StoryJsonInternal::STATUS, source); + expect_string(root, "state_basis", StoryJsonInternal::STATE_BASIS, source); + if (read_strings(root.get_value_throw("unbound"), source) != StoryJsonInternal::unbound_fields()){ + throw ParseException(std::format("{}: unbound is not the export's list", source)); + } + if (root.get_string_throw("source_revision").empty()){ + throw ParseException(std::format("{}: source_revision names no revision", source)); + } +} + +StoryCheckpoint apply_execution(Pokemon::AuthoredCheckpoint parsed, std::string_view source){ + const JsonObject& execution = parsed.execution; + Pokemon::require_story_json_fields(execution, {"menu"}, source); + StoryCheckpoint result; + static_cast(result) = std::move(parsed.checkpoint); + const std::string& menu = execution.get_string_throw("menu"); + if (menu == "before_pokemon"){ + result.menu = StartMenu::BEFORE_POKEMON; + }else if (menu == "before_pokedex"){ + result.menu = StartMenu::BEFORE_POKEDEX; + }else if (menu == "full"){ + result.menu = StartMenu::FULL; + }else{ + throw ParseException("Unsupported authored checkpoint menu."); + } + return result; +} + + +} // namespace + +StoryCheckpoint read_authored_checkpoint(const JsonValue& value, std::string_view source){ + return apply_execution(Pokemon::read_authored_checkpoint(value, "frlg", source), source); +} + +StoryCheckpoint load_authored_checkpoint(std::string_view file){ + return apply_execution(Pokemon::load_authored_checkpoint(autostory_checkpoint_json(), file, "frlg"), file); +} + +StoryCheckpoint read_story_checkpoint_export(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, + {"format", "game", "status", "checkpoint", "native_fields", "state_basis", "unbound", "source_revision"}, source); + check_envelope(root, source); + StoryCheckpoint result; + Pokemon::StoryCheckpoint& common = result; + common = Pokemon::read_story_checkpoint(root.get_value_throw("checkpoint"), source); + const JsonObject& native = fields(root.get_value_throw("native_fields"), {"menu", "supplies"}, source); + result.menu = read_named({StartMenu::BEFORE_POKEMON, StartMenu::BEFORE_POKEDEX, StartMenu::FULL}, + StoryJsonInternal::menu_name, native.get_string_throw("menu"), source); + result.supplies = read_supplies(native.get_value_throw("supplies"), source); + return result; +} + +StoryCheckpoint load_story_checkpoint(std::string_view file){ + for (const auto& [name, text] : autostory_checkpoint_json()){ + if (name == file){ + return read_story_checkpoint_export(parse_json(text), std::format("StoryData/{}.json", file)); + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("StoryData/{}.json is not built into the program.", file)); +} + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h new file mode 100644 index 0000000000..9b20466141 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h @@ -0,0 +1,47 @@ +/* FRLG Checkpoint JSON Parser + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_CheckpointJsonParser_H +#define PokemonAutomation_PokemonFRLG_CheckpointJsonParser_H + +#include +#include +#include +#include +#include "Common/Cpp/Json/JsonValue.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// Read a data-authored checkpoint, retaining native bookkeeping defaults. +// Unknown envelope fields/menu values and malformed common actions throw ParseException. +StoryCheckpoint read_authored_checkpoint(const JsonValue& value, std::string_view source); + +// Load an embedded authored plan by filename without .json; no controller IO. +StoryCheckpoint load_authored_checkpoint(std::string_view file); + + +// Embedded StoryData/*.json records: (filename without .json, text), sorted by name. +const std::vector>& autostory_checkpoint_json(); + + +// Migration only: read a captured native-table export, including all bookkeeping. +// Validate the format/game/status/state basis/unbound list and require a nonempty +// source revision. Unknown fields/values and missing native fields throw ParseException. +// The reader cannot verify the source revision against the captured table. +StoryCheckpoint read_story_checkpoint_export(const JsonValue& value, std::string_view source); + +// Migration only: read an embedded export capture by filename without .json. +// Throws InternalProgramError if the file is not built into the program. +StoryCheckpoint load_story_checkpoint(std::string_view file); + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp similarity index 77% rename from SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp rename to SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp index a0b85c21ea..069658edee 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp @@ -1,25 +1,22 @@ -/* FRLG Authored Checkpoint Export +/* Legacy FRLG Checkpoint JSON Writer * From: https://github.com/PokemonAutomation/ */ + #include #include -#include "Common/Cpp/Exceptions.h" #include "Common/Cpp/Json/JsonArray.h" #include "Common/Cpp/Json/JsonObject.h" #include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" -#include "PokemonFRLG_AutoStoryExport.h" +#include "LegacyJsonFormat.h" +#include "JsonWriter.h" + namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ + + namespace{ -const char* menu_name(StartMenu menu){ - switch (menu){ - case StartMenu::BEFORE_POKEMON: return "before_pokemon"; - case StartMenu::BEFORE_POKEDEX: return "before_pokedex"; - case StartMenu::FULL: return "full"; - } - throw ParseException("Unsupported FRLG START menu."); -} + JsonArray strings(const std::vector& values){ JsonArray result; for (const std::string& value : values){ @@ -27,12 +24,14 @@ JsonArray strings(const std::vector& values){ } return result; } + int64_t count(size_t value){ if (value > (size_t)std::numeric_limits::max()){ throw ParseException("FRLG checkpoint count exceeds JSON integer range."); } return (int64_t)value; } + JsonObject stats_json(const Stats& stats){ JsonObject result; result["hp"] = stats.hp; @@ -43,6 +42,7 @@ JsonObject stats_json(const Stats& stats){ result["speed"] = stats.speed; return result; } + JsonObject member_json(const PartyMember& member){ JsonObject result; result["species"] = member.species; @@ -60,6 +60,7 @@ JsonObject member_json(const PartyMember& member){ result["stats"] = member.stats ? JsonValue(stats_json(*member.stats)) : JsonValue(); return result; } + JsonObject supplies_json(const Supplies& supplies){ JsonObject result; result["potions"] = supplies.potions; @@ -83,21 +84,26 @@ JsonObject supplies_json(const Supplies& supplies){ result["wild_foes"] = strings(supplies.wild_foes); return result; } + + } // namespace + JsonValue story_checkpoint_export(const StoryCheckpoint& checkpoint){ JsonObject result; - result["format"] = "pokesim-native-checkpoint-export/1"; - result["game"] = "frlg"; - result["status"] = "native-table-export"; + result["format"] = std::string(StoryJsonInternal::FORMAT); + result["game"] = std::string(StoryJsonInternal::GAME); + result["status"] = std::string(StoryJsonInternal::STATUS); result["checkpoint"] = Pokemon::story_checkpoint_json(checkpoint); JsonObject native; - native["menu"] = menu_name(checkpoint.menu); + native["menu"] = StoryJsonInternal::menu_name(checkpoint.menu); native["supplies"] = supplies_json(checkpoint.supplies); result["native_fields"] = std::move(native); - result["state_basis"] = "native-table-bookkeeping"; - result["unbound"] = strings({"runtime_configuration", "map_pins", "executor_bindings", "entry_evidence", "effective_save_retry_policy"}); + result["state_basis"] = std::string(StoryJsonInternal::STATE_BASIS); + result["unbound"] = strings(StoryJsonInternal::unbound_fields()); return result; } + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h new file mode 100644 index 0000000000..0fa8d2d229 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h @@ -0,0 +1,26 @@ +/* Legacy FRLG Checkpoint JSON Writer + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_CheckpointJsonWriter_H +#define PokemonAutomation_PokemonFRLG_CheckpointJsonWriter_H + +#include "Common/Cpp/Json/JsonValue.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// Migration diagnostics only: capture shared fields and FRLG bookkeeping. +// This is not the authored production format or observed entry requirements. +// Runtime configuration, map pins, policies and executor bindings remain unbound. +JsonValue story_checkpoint_export(const StoryCheckpoint& checkpoint); + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp new file mode 100644 index 0000000000..2b4715df94 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp @@ -0,0 +1,282 @@ +/* FRLG Checkpoint Export Tests + * From: https://github.com/PokemonAutomation/ + */ + +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" +#include "JsonParser.h" +#include "JsonWriter.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" +#include "Json_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +namespace{ + +struct ExportInput{ + std::string_view id; + bool custom = false; +}; + +struct ExportExpectation{ + std::vector events; + std::string_view menu; + std::string_view native_json = {}; +}; + +struct ExportCase{ + ExportInput input; + ExportExpectation expectation; +}; + +const ExportCase CASES[] = { + {.input = {.id = "00.05"}, .expectation = {.events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "before_pokedex"}}, + {.input = {.id = "00.05", .custom = true}, .expectation = { + .events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "full", + .native_json = R"({"menu":"full","supplies":{"potions":2,"antidotes":3,"parlyz_heals":4,"pocket":["antidote",""],"status":"PSN","hurt":true,"hp_red":true,"hp_fill":0.25,"lead":{"species":"wartortle","moves":[{"move":"bite","pp":0}],"moves_through_level":18,"moves_read":true,"level":19,"stats":{"hp":50,"attack":20,"defense":30,"sp_attack":40,"sp_defense":35,"speed":25}},"protected_slots":[0,2],"foes":["staryu"],"trainer":"misty","risk":"high","foe_out":0,"wild_foes":["pidgey"]}})" + }}, +}; + +void customize(StoryCheckpoint& row){ + row.menu = StartMenu::FULL; + Supplies& state = row.supplies; + state.potions = 2; + state.antidotes = 3; + state.parlyz_heals = 4; + state.pocket = {"antidote", ""}; + state.status = StatusCondition::POISON; + state.hurt = true; + state.hp_red = true; + state.hp_fill = 0.25; + state.lead = PartyMember{.species = "wartortle", .moves = {{"bite", 0}}, + .moves_through_level = 18, .moves_read = true, .level = 19, + .stats = Stats{.hp = 50, .attack = 20, .defense = 30, .sp_attack = 40, .sp_defense = 35, .speed = 25}}; + state.protected_slots = {0, 2}; + state.foes = {"staryu"}; + state.trainer = "misty"; + state.risk = BattleRisk::HIGH; + state.foe_out = 0; + state.wild_foes = {"pidgey"}; +} + +// The export as the tool writes it to a file: with its source revision. +JsonValue exported_file(const StoryCheckpoint& row, const std::string& revision){ + JsonValue result = story_checkpoint_export(row); + result.to_object_throw()["source_revision"] = revision; + return result; +} + +std::string check_export(StoryCheckpoint row, const ExportCase& test){ + if (test.input.custom){ + customize(row); + } + const JsonValue encoded = story_checkpoint_export(row); + const JsonObject& root = encoded.to_object_throw(); + const JsonObject& native = root.get_object_throw("native_fields"); + if (native.get_string_throw("menu") != test.expectation.menu){ + return "Expected menu " + std::string(test.expectation.menu) + ", read " + native.get_string_throw("menu"); + } + if (!test.expectation.native_json.empty() && !(JsonValue(native.clone()) == parse_json(std::string(test.expectation.native_json)))){ + return "Expected native fields " + std::string(test.expectation.native_json) + ", read " + native.dump(); + } + const auto decoded = Pokemon::read_story_checkpoint(root.get_value_throw("checkpoint"), "export test"); + if (!(Pokemon::story_checkpoint_json(decoded) == Pokemon::story_checkpoint_json(row))){ + return "Expected every authored common field to survive export/import."; + } + // Every native field too: the whole file, read back, exports the same. + const StoryCheckpoint reread = read_story_checkpoint_export(exported_file(row, "test"), "export test"); + if (!(exported_file(reread, "test") == exported_file(row, "test"))){ + return "Expected every native field to survive export/import, read " + story_checkpoint_export(reread).dump(-1); + } + std::vector names; + for (const auto& step : decoded.steps){ + names.push_back(step.name); + } + return names == test.expectation.events ? "" : "Expected Parcel's three authored events in order."; +} + +class ExportTest : public UnitTest{ +public: + explicit ExportTest(ExportCase test) + : UnitTest("PokemonFRLG::CheckpointExport - " + std::string(test.input.id) + (test.input.custom ? " custom" : " authored")), m_test(test){} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + for (const auto& row : story_checkpoints()){ + if (row.id == m_test.input.id){ + const std::string error = check_export(row, m_test); + return error.empty() ? UnitTestResult(true) : UnitTestResult(error); + } + } + return UnitTestResult("Expected checkpoint " + std::string(m_test.input.id) + ", read no matching row."); + } +private: + ExportCase m_test; +}; + + +// Reading the Parcel capture built into the program (StoryData/ +// frlg-parcel-00.05.json, PokemonSimulation's data/story/native copy of +// the export tool's output): as written, it is row 00.05 and exports back +// to the same file; changed, it is refused rather than read with a guess. +struct LoadInput{ + std::string_view name; + // Applied to the capture before reading; none reads it as written. + void (*mutate)(JsonObject& root) = nullptr; + bool authored = false; +}; + +struct LoadExpectation{ + // The table row the loaded one must export like; empty when rejected. + std::string_view row = {}; + bool rejected = false; +}; + +struct LoadCase{ + LoadInput input; + LoadExpectation expectation; +}; + +// `object` without `key`: JsonObject has no erase. +JsonObject without(const JsonObject& object, std::string_view key){ + JsonObject result; + for (const auto& [name, value] : object){ + if (name != key){ + result[name] = value.clone(); + } + } + return result; +} + +void add_top_level_field(JsonObject& root){ + root["mystery"] = 1; +} + +void change_format(JsonObject& root){ + root["format"] = "pokesim-story-plan/1"; +} + +void add_native_field(JsonObject& root){ + root.get_object_throw("native_fields")["speed"] = "fast"; +} + +void unknown_menu(JsonObject& root){ + root.get_object_throw("native_fields")["menu"] = "before_bag"; +} + +void drop_potions(JsonObject& root){ + JsonObject& native = root.get_object_throw("native_fields"); + native["supplies"] = without(native.get_object_throw("supplies"), "potions"); +} + +void negative_pp(JsonObject& root){ + JsonObject& supplies = root.get_object_throw("native_fields").get_object_throw("supplies"); + supplies.get_object_throw("lead").get_array_throw("moves")[0].to_object_throw()["pp"] = -1; +} + +void foe_out_as_text(JsonObject& root){ + root.get_object_throw("native_fields").get_object_throw("supplies")["foe_out"] = "0"; +} + +void drop_revision(JsonObject& root){ + root = without(root, "source_revision"); +} + +void unknown_execution_menu(JsonObject& root){ + root.get_object_throw("execution")["menu"] = "guess"; +} + +const LoadCase LOAD_CASES[] = { + {.input = {.name = "authored Parcel plan", .authored = true}, .expectation = {.row = "00.05"}}, + {.input = {.name = "authored plan unknown field", .mutate = add_top_level_field, .authored = true}, .expectation = {.rejected = true}}, + {.input = {.name = "authored plan unknown format", .mutate = change_format, .authored = true}, .expectation = {.rejected = true}}, + {.input = {.name = "authored plan unknown menu", .mutate = unknown_execution_menu, .authored = true}, .expectation = {.rejected = true}}, + {.input = {.name = "the Parcel capture is row 00.05"}, .expectation = {.row = "00.05"}}, + {.input = {.name = "an unknown envelope field", .mutate = add_top_level_field}, .expectation = {.rejected = true}}, + {.input = {.name = "another format", .mutate = change_format}, .expectation = {.rejected = true}}, + {.input = {.name = "an unknown native field", .mutate = add_native_field}, .expectation = {.rejected = true}}, + {.input = {.name = "an unknown START menu", .mutate = unknown_menu}, .expectation = {.rejected = true}}, + {.input = {.name = "a missing supplies field", .mutate = drop_potions}, .expectation = {.rejected = true}}, + {.input = {.name = "PP below zero", .mutate = negative_pp}, .expectation = {.rejected = true}}, + {.input = {.name = "foe_out as text", .mutate = foe_out_as_text}, .expectation = {.rejected = true}}, + {.input = {.name = "no source revision", .mutate = drop_revision}, .expectation = {.rejected = true}}, +}; + +const std::string& parcel_text(const LoadInput& input){ + for (const auto& [name, text] : autostory_checkpoint_json()){ + if (name == (input.authored ? "frlg-parcel-00.05.plan" : "frlg-parcel-00.05")){ + return text; + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "StoryData/frlg-parcel-00.05.json is not built into the program."); +} + +const StoryCheckpoint* table_row(std::string_view id){ + for (const StoryCheckpoint& row : story_checkpoints()){ + if (row.id == id){ + return &row; + } + } + return nullptr; +} + +std::string check_loaded(const StoryCheckpoint& loaded, const JsonValue& capture, const LoadCase& test){ + const std::string revision = test.input.authored ? "" : capture.to_object_throw().get_string_throw("source_revision"); + if (!test.input.authored && !(exported_file(loaded, revision) == capture)){ + return "Expected the loaded row to export as the capture, read " + story_checkpoint_export(loaded).dump(-1); + } + const StoryCheckpoint* row = table_row(test.expectation.row); + if (!row){ + return "Expected table row " + std::string(test.expectation.row) + ", read no matching row."; + } + if (!(story_checkpoint_export(loaded) == story_checkpoint_export(*row))){ + return "Expected the loaded row to export as the table's " + row->id + ", read " + story_checkpoint_export(loaded).dump(-1); + } + return ""; +} + +std::string check_load(const LoadCase& test){ + JsonValue capture = parse_json(parcel_text(test.input)); + if (test.input.mutate){ + test.input.mutate(capture.to_object_throw()); + } + try{ + const StoryCheckpoint loaded = test.input.authored ? read_authored_checkpoint(capture, test.input.name) + : read_story_checkpoint_export(capture, test.input.name); + if (test.expectation.rejected){ + return "Expected rejection, read checkpoint " + loaded.id; + } + return check_loaded(loaded, capture, test); + }catch (const ParseException& error){ + return test.expectation.rejected ? "" : "Unexpected parse failure: " + error.message(); + } +} + +class LoadTest : public UnitTest{ +public: + explicit LoadTest(LoadCase test) + : UnitTest("PokemonFRLG::CheckpointExport - read " + std::string(test.input.name)), m_test(test){} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + const std::string error = check_load(m_test); + return error.empty() ? UnitTestResult(true) : UnitTestResult(error); + } +private: + LoadCase m_test; +}; + +} // namespace + +void add_tests_AutoStoryJson(UnitTestDatabase& database){ + for (const ExportCase& test : CASES){ + database.add(test); + } + for (const LoadCase& test : LOAD_CASES){ + database.add(test); + } +} + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h similarity index 66% rename from SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h rename to SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h index 90eabc74a9..b43a86f3d1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h @@ -1,14 +1,18 @@ /* FRLG Checkpoint Export Tests * From: https://github.com/PokemonAutomation/ */ -#ifndef PokemonAutomation_PokemonFRLG_AutoStoryExport_Tests_H -#define PokemonAutomation_PokemonFRLG_AutoStoryExport_Tests_H + +#ifndef PokemonAutomation_PokemonFRLG_AutoStoryJson_Tests_H +#define PokemonAutomation_PokemonFRLG_AutoStoryJson_Tests_H + #include "Common/Cpp/TestRunners/UnitTestDatabase.h" + namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ + // Register evaluated-table and non-default native-extension preservation tests. -void add_tests_AutoStoryExport(UnitTestDatabase& database); +void add_tests_AutoStoryJson(UnitTestDatabase& database); } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h new file mode 100644 index 0000000000..527273360c --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h @@ -0,0 +1,53 @@ +/* Legacy FRLG Checkpoint Export Format + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_LegacyJsonFormat_H +#define PokemonAutomation_PokemonFRLG_LegacyJsonFormat_H + +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace StoryJsonInternal{ + +// The envelope constants the export writes and the reader requires. +constexpr std::string_view FORMAT = "pokesim-native-checkpoint-export/1"; +// Game identifier in the legacy envelope. +constexpr std::string_view GAME = "frlg"; +// Marks a captured table row rather than an authored plan. +constexpr std::string_view STATUS = "native-table-export"; +// Describes the native bookkeeping snapshot, not observed game state. +constexpr std::string_view STATE_BASIS = "native-table-bookkeeping"; +// Fields deliberately left outside the captured native table state. +inline const std::vector& unbound_fields(){ + static const std::vector UNBOUND{ + "runtime_configuration", "map_pins", "executor_bindings", "entry_evidence", "effective_save_retry_policy", + }; + return UNBOUND; +} + +// Stable legacy spellings of the native START menu variants. +inline const char* menu_name(StartMenu menu){ + switch (menu){ + case StartMenu::BEFORE_POKEMON: return "before_pokemon"; + case StartMenu::BEFORE_POKEDEX: return "before_pokedex"; + case StartMenu::FULL: return "full"; + } + throw ParseException("Unsupported FRLG START menu."); +} + +} // namespace StoryJsonInternal + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp index fe0a5c9578..5442216c83 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp @@ -70,6 +70,11 @@ constexpr StoryTrainer STORY_TRAINERS[] = { {.map = "VermilionCity_Gym", .battle = "engineer_baily", .trainer = "TRAINER_ENGINEER_BAILY"}, {.map = "VermilionCity_Gym", .battle = "gentleman_tucker", .trainer = "TRAINER_GENTLEMAN_TUCKER"}, {.map = "VermilionCity_Gym", .battle = "lt_surge", .trainer = "TRAINER_LEADER_LT_SURGE"}, + // Route 9's three the way can't walk around (Route9 scripts.inc): + // Alicia (14, 10), Jeremy (20, 17) and Conner (49, 7). + {.map = "Route9", .battle = "picnicker_alicia", .trainer = "TRAINER_PICNICKER_ALICIA"}, + {.map = "Route9", .battle = "hiker_jeremy", .trainer = "TRAINER_HIKER_JEREMY"}, + {.map = "Route9", .battle = "bug_catcher_conner", .trainer = "TRAINER_BUG_CATCHER_CONNER"}, }; using JsonTable = std::map>; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp new file mode 100644 index 0000000000..853b4e4af7 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -0,0 +1,443 @@ +/* AutoStory Celadon Tests + * + * From: https://github.com/PokemonAutomation/ + * + * Rows from 00.26 on (PokemonFRLG_AutoStoryTableCeladon.cpp) on the + * overworld sim: 00.26 from where 00.25 ends, to the Fan Club's chairman, + * north past Route 6's trainers, through the tree into Cerulean, to the + * Bike Shop's clerk, the Mart and the nurse, with Oak's aide on + * Vermilion's (25, 7); and what its Mart step buys. 00.27 from where + * 00.26 ends, east over Route 9 to Route 10's Pokemon Center. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/TestRunners/UnitTest.h" +#include "VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMap.h" +#include "VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h" +#include "VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h" +#include "PokemonFRLG_AutoStoryEvents.h" +#include "PokemonFRLG_AutoStoryMart.h" +#include "PokemonFRLG_AutoStoryRowSim.h" +#include "PokemonFRLG_AutoStoryTable.h" +#include "PokemonFRLG_AutoStoryTableSteps.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +struct AutoStoryCeladonInput{ + std::string name; + std::function body; +}; + +struct AutoStoryCeladonExpectation{ +}; + +struct AutoStoryCeladonCase{ + AutoStoryCeladonInput input; + AutoStoryCeladonExpectation expectation; +}; + +class Test_AutoStoryCeladon : public UnitTest{ +public: + explicit Test_AutoStoryCeladon(AutoStoryCeladonCase test_case) + : UnitTest("PokemonFRLG::AutoStory - Simulation: " + test_case.input.name) + , m_case(std::move(test_case)) + {} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + try{ + return m_case.input.body(); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } + +private: + AutoStoryCeladonCase m_case; +}; + +size_t row_index(std::string_view id){ + const std::vector& rows = story_checkpoints(); + const auto row = std::find_if(rows.begin(), rows.end(), [&](const StoryCheckpoint& data){ return data.id == id; }); + if (row == rows.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("No checkpoint {}.", id)); + } + return row - rows.begin(); +} + +const StoryCheckpoint& checkpoint(std::string_view id){ + return story_checkpoints()[row_index(id)]; +} + +MapData& map_named(std::vector& maps, const std::string& name){ + for (MapData& map : maps){ + if (map.name == name){ + return map; + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "No map " + name + "."); +} + +// The maps as 00.25 leaves them: story_maps() with Oak's aide on +// Vermilion's (25, 7), shown by Lt. Surge's win, and Route 6's Jeff +// beaten (00.21). Isabelle and Elijah are not. +std::vector maps_after_surge(){ + const std::vector all = overworld_maps(); + std::set flags = map_flag_set(all, "after_hall_of_fame"); + flags.merge(map_flag_set(all, "new_game")); + flags.erase("FLAG_HIDE_VERMILION_CITY_OAKS_AIDE"); + std::vector maps = overworld_maps(flags); + for (MapObject& object : map_named(maps, StoryTable::ROUTE6).objects){ + if (object.name == "camper" && object.trainer){ + object.trainer.reset(); + } + } + return maps; +} + +// Whoever stands `tiles` ahead of `pose`, or nothing. +const MapObject* object_ahead(const std::vector& maps, const StoryPose& pose, int tiles){ + TilePos tile = pose.tile; + for (int c = 0; c < tiles; c++){ + tile = tile.next(*pose.facing); + } + for (const MapData& map : maps){ + if (map.name == pose.map){ + return object_at(map, tile); + } + } + return nullptr; +} + +// The row's last route's target. +std::optional last_target(const StoryCheckpoint& row){ + for (auto step = row.steps.rbegin(); step != row.steps.rend(); ++step){ + if (step->kind == StoryStepKind::ROUTE && step->target){ + return story_pose(*step->target); + } + } + return std::nullopt; +} + + +// Row `id` comes right after row `previous` and starts where its last +// walk ends. +UnitTestResult starts_where_ends(const std::string& previous, const std::string& id){ + if (row_index(id) != row_index(previous) + 1){ + return "Expected " + id + " right after " + previous + "."; + } + const std::optional end = last_target(checkpoint(previous)); + const StoryCheckpoint& row = checkpoint(id); + const std::optional start = row.start ? story_pose(*row.start) : std::nullopt; + if (!end || !start || start->map != end->map || !(start->tile == end->tile) || start->facing != end->facing){ + return "Expected " + id + " to start where " + previous + "'s last walk ends."; + } + return true; +} + +// 00.26 starts at Vermilion's nurse, where 00.25 ends. +UnitTestResult test_starts_where_lt_surge_ends(){ + return starts_where_ends("00.25", "00.26"); +} + +// 00.27 starts at Cerulean's nurse, where 00.26 ends. +UnitTestResult test_starts_where_bicycle_ends(){ + return starts_where_ends("00.26", "00.27"); +} + +// 00.26 on the sim from Vermilion's nurse to Cerulean's, the maps as +// 00.25 leaves them: no trainer meets the player (Isabelle and Elijah +// stay unfought), no violation, each talk faces the one it's for (the +// chairman beside the player, the Bike Shop's clerk across the counter), +// and the walk ends at Cerulean's nurse. +UnitTestResult test_bike_voucher_row(){ + const StoryCheckpoint& row = checkpoint("00.26"); + const std::vector maps = maps_after_surge(); + const MapData& city = *std::find_if(maps.begin(), maps.end(), [](const MapData& map){ return map.name == StoryTable::VERMILION; }); + if (object_at(city, {25, 7}) == nullptr){ + return "Expected Oak's aide on (25, 7) after Lt. Surge."; + } + StoryPose at = *story_pose(*row.start); + OverworldSim sim(maps, MapPosition{at.map, at.tile}, *at.facing); + std::vector talked_to; + for (const StoryStep& step : row.steps){ + if (step.kind == StoryStepKind::TALK){ + const StoryPose here{sim.state().position.map, sim.state().position.point, sim.state().facing}; + const bool counter = here.map == StoryTable::BIKE_SHOP; + const MapObject* person = object_ahead(maps, here, counter ? 2 : 1); + if (person == nullptr || (counter && object_ahead(maps, here, 1) != nullptr)){ + return std::format("00.26/{}: no one to talk to from {}.", step.name, to_string(sim.state().position)); + } + talked_to.push_back(person->name); + } + if (const std::string failure = row_step_on_sim(sim, at, step, row.id); !failure.empty()){ + return failure; + } + // A walk the sim's player didn't finish, stopped by someone the + // maps the program plans on don't have. + if (!(sim.state().position == MapPosition{at.map, at.tile})){ + return std::format("00.26/{}: expected {}, on {}.", step.name, to_string(MapPosition{at.map, at.tile}), to_string(sim.state().position)); + } + } + if (talked_to != std::vector{"gentleman", "man"}){ + return "00.26: expected to talk to the chairman, then the Bike Shop's clerk."; + } + if (!sim.state().trainers_beaten.empty() || !sim.state().violations.empty()){ + return "00.26: a battle or a violation on the way" + (sim.state().violations.empty() ? std::string(".") : ": " + sim.state().violations.back()); + } + const MapPosition nurse{StoryTable::CERULEAN_CENTER, {7, 4}}; + if (!(sim.state().position == nurse)){ + return "00.26: ended on " + to_string(sim.state().position) + ", not Cerulean's nurse."; + } + return true; +} + +// 00.26's Mart step, at its money floor: 5 Repels and 2 Escape Ropes, +// and 3000 or more left. +UnitTestResult test_repels_and_escape_ropes(){ + const StoryCheckpoint& row = checkpoint("00.26"); + const auto step = std::find_if(row.steps.begin(), row.steps.end(), [](const StoryStep& data){ + return parse_mart_variant(data.variant).has_value(); + }); + if (step == row.steps.end()){ + return "00.26: expected a Mart step."; + } + const MartPlan plan = *parse_mart_variant(step->variant); + if (plan.map != StoryTable::CERULEAN_MART || !plan.money_floor){ + return "00.26: expected the Mart step at Cerulean's Mart, with a money floor."; + } + const std::optional> buys = plan_mart_buys(mart_stock(plan.map), plan, *plan.money_floor); + if (!buys){ + return "00.26: the Mart step names an item Cerulean's Mart doesn't sell."; + } + size_t repels = 0; + size_t escape_ropes = 0; + size_t spent = 0; + for (const MartBuy& buy : *buys){ + repels += buy.item == "repel" ? buy.quantity : 0; + escape_ropes += buy.item == "escape-rope" ? buy.quantity : 0; + spent += buy.cost; + } + if (repels < 5 || escape_ropes != 2 || *plan.money_floor < spent + 3000){ + return std::format("00.26: bought {} Repels and {} Escape Ropes for {} from {}.", repels, escape_ropes, spent, *plan.money_floor); + } + return true; +} + +// 00.27 on the sim from Cerulean's nurse to Route 10's, the maps as 00.26 +// leaves them, both trees grown back: the trees cut, Alicia, Jeremy and +// Conner fought where the table steps into their lines and no one else +// met (Brice, Caitlin and the rest unfought), no violation, each step +// ending where the table says, and the walk ending at Route 10's nurse. +UnitTestResult test_route10_row(){ + const StoryCheckpoint& row = checkpoint("00.27"); + const std::vector maps = maps_after_surge(); + StoryPose at = *story_pose(*row.start); + OverworldSim sim(maps, MapPosition{at.map, at.tile}, *at.facing); + for (const StoryStep& step : row.steps){ + if (const std::string failure = row_step_on_sim(sim, at, step, row.id); !failure.empty()){ + return failure; + } + // A step into a trainer's line ends where the battle after it says. + const bool into_line = step.kind == StoryStepKind::STEP && !step.variant.empty(); + if (!into_line && !(sim.state().position == MapPosition{at.map, at.tile})){ + return std::format("00.27/{}: expected {}, on {}.", step.name, to_string(MapPosition{at.map, at.tile}), to_string(sim.state().position)); + } + } + if (sim.state().trainers_beaten != std::vector{"picnicker", "hiker", "bug_catcher"}){ + return "00.27: expected to fight Alicia, Jeremy and Conner, no one else; fought " + std::to_string(sim.state().trainers_beaten.size()) + "."; + } + if (!sim.state().violations.empty()){ + return "00.27: a violation on the way: " + sim.state().violations.back(); + } + const MapPosition nurse{StoryTable::ROUTE10_CENTER, {7, 4}}; + if (!(sim.state().position == nurse)){ + return "00.27: ended on " + to_string(sim.state().position) + ", not Route 10's nurse."; + } + return true; +} + +// The next row starts from 00.27's save in Route 10's Center, whose save +// card says ROUTE 10: a place of its own, not Route 1's. +UnitTestResult test_route10_save_place(){ + if (save_card_place(StoryTable::ROUTE10_CENTER) != "route-10" || save_card_place("Route1") == "route-10"){ + return "Wrong save card place for Route 10's Pokemon Center."; + } + return true; +} + +// 00.27's walk from Route 9 (70, 8) to Route 10's nurse: its steps from +// route10_ledge_end to route10_nurse. Console B, 2026-11-09, the walk +// then planned in one ended its open legs a tile from a blocker, and both +// runs ended "no fade in and out" at the Center's door (13, 20): +// - run 3: the crossing onto Route 10 wasn't held up, so the player +// stood a tile further east, row 11 ended on (17, 11), the leg down +// column 17 met the fence post (17, 12), and the walk ended in the +// grass on (14, 10). +// - run 2: the leg down column 16 ended a tile short, on (16, 20), west +// 3 met the Center's east wall (15, 20) and north 1 ended on (16, 19). +std::vector route10_walk(){ + const StoryCheckpoint& row = checkpoint("00.27"); + const auto named = [&row](std::string_view name){ + return std::find_if(row.steps.begin(), row.steps.end(), [&](const StoryStep& data){ return data.name == name; }); + }; + const auto first = named("route10_ledge_end"); + const auto last = named("route10_nurse"); + if (first == row.steps.end() || last == row.steps.end() || last < first){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "00.27: no steps route10_ledge_end to route10_nurse."); + } + return std::vector(first, last + 1); +} + +const StoryPose ROUTE10_WALK_START{StoryTable::ROUTE9, {70, 8}, Direction::EAST}; + +// `steps` from `first` on, on `sim` from `at`, where the table says the +// player stands: the failure, or empty once at Route 10's nurse. +std::string route10_walk_rest(OverworldSim& sim, StoryPose at, const std::vector& steps, size_t first){ + std::string failure; + for (size_t c = first; c < steps.size() && failure.empty(); c++){ + failure = row_step_on_sim(sim, at, steps[c], "00.27"); + } + const MapPosition nurse{StoryTable::ROUTE10_CENTER, {7, 4}}; + if (!failure.empty() || !(sim.state().position == nurse)){ + return std::format("ended on {}, not Route 10's nurse; {}", to_string(sim.state().position), failure); + } + return ""; +} + +std::string route10_walk_on_sim(const SimScript& script){ + OverworldSim sim(maps_after_surge(), MapPosition{ROUTE10_WALK_START.map, ROUTE10_WALK_START.tile}, Direction::EAST, script); + return route10_walk_rest(sim, ROUTE10_WALK_START, route10_walk(), 0); +} + +UnitTestResult test_route10_crossing_not_held_up(){ + SimScript script; + script.connections_stall = false; + if (const std::string failure = route10_walk_on_sim(script); !failure.empty()){ + return "The crossing onto Route 10 not held up (run 3): " + failure; + } + return true; +} + +UnitTestResult test_route10_column_16_short(){ + SimScript script; + // Holds: east 7, south 3 and east 12 onto row 11; west 1; south 12 + // down column 16. + script.short_holds[5] = 1; + if (const std::string failure = route10_walk_on_sim(script); !failure.empty()){ + return "The leg down column 16 a tile short (run 2): " + failure; + } + return true; +} + +// Where an open leg of two tiles or more ends a tile short or long, the +// next leg starts a tile off. From each such start (on an open tile), the +// rest of the walk still reaches the nurse. A leg to a wall ends on its +// tile, and a one-tile leg is one step (as grass_held() takes it), so the +// leg after either starts where planned. The one exception is the door's +// column: the three east from the wall (9, 21) set it, and nothing after +// them can. +UnitTestResult test_route10_tile_off_after_open_legs(){ + const std::vector steps = route10_walk(); + const std::vector& maps = story_maps(); + // above_caitlin_line ends east 6 on (70, 8), an open leg. + RouteStep before{.direction = Direction::EAST, .tiles = 6}; + StoryPose at = ROUTE10_WALK_START; + for (size_t s = 0; s < steps.size(); s++){ + const std::vector route = walked_route(at, steps[s]).value_or(std::vector{}); + MapPosition from{at.map, at.tile}; + for (size_t c = 0; c < route.size(); c++){ + const bool door_column = steps[s].name == "route10_nurse" && c == 0; + const bool off = before.action == RouteAction::WALK && !before.to_wall && !before.warps && before.tiles > 1 && !door_column; + const TilePos on = from.point.next(before.direction); + const TilePos short_of{2 * from.point.x - on.x, 2 * from.point.y - on.y}; + const auto map = std::find_if(maps.begin(), maps.end(), [&](const MapData& data){ return data.name == from.map; }); + for (const TilePos& start : off ? std::vector{short_of, on} : std::vector{}){ + const bool open = map != maps.end() && in_bounds(*map, start) && object_at(*map, start) == nullptr + && (tile_at(*map, start) == TileKind::OPEN || tile_at(*map, start) == TileKind::TALL_GRASS); + if (!open){ + continue; + } + OverworldSim sim(maps_after_surge(), MapPosition{from.map, start}, before.direction); + const std::vector rest(route.begin() + c, route.end()); + const RoutePlanning planning{.maps = &maps, .costs = story_route_costs(), .start = from, .label = steps[s].name}; + std::string failure = OverworldSimWalker(sim).run(rest, planning); + const StoryPose next{route.back().end.map, route.back().end.point, route.back().facing_after}; + failure = failure.empty() ? route10_walk_rest(sim, next, steps, s + 1) : failure; + if (!failure.empty()){ + return std::format("{} leg {} ({}) from {}, not {}: {}", steps[s].name, c + 1, to_string(route[c]), to_string(start), to_string(from.point), failure); + } + } + before = route[c]; + from = route[c].end; + } + at = route.empty() ? at : StoryPose{route.back().end.map, route.back().end.point, route.back().facing_after}; + } + return true; +} + +} // namespace + + +const AutoStoryCeladonCase AUTO_STORY_CELADON_CASES[] = { + { + .input = {"00.26 starts where 00.25 ends", test_starts_where_lt_surge_ends}, + .expectation = {}, + }, + { + .input = {"00.26 the Fan Club, Route 6 and the Underground Path north, the Bike Shop, past Oak's aide", test_bike_voucher_row}, + .expectation = {}, + }, + { + .input = {"00.26 Repels and Escape Ropes, 3000 left", test_repels_and_escape_ropes}, + .expectation = {}, + }, + { + .input = {"00.27 starts where 00.26 ends", test_starts_where_bicycle_ends}, + .expectation = {}, + }, + { + .input = {"00.27 the two trees, Route 9's Alicia, Jeremy and Conner, to Route 10's Pokemon Center", test_route10_row}, + .expectation = {}, + }, + { + .input = {"00.27 saves in Route 10's Pokemon Center, ROUTE 10 on the save card", test_route10_save_place}, + .expectation = {}, + }, + { + .input = {"00.27 route10_nurse: the crossing onto Route 10 not held up", test_route10_crossing_not_held_up}, + .expectation = {}, + }, + { + .input = {"00.27 route10_nurse: the leg down column 16 a tile short", test_route10_column_16_short}, + .expectation = {}, + }, + { + .input = {"00.27 route10_nurse: a tile off after each open leg", test_route10_tile_off_after_open_legs}, + .expectation = {}, + }, +}; + +void add_tests_AutoStoryCeladon(UnitTestDatabase& database){ + for (const auto& item : AUTO_STORY_CELADON_CASES){ + database.add(item); + } +} + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp index 0dda0baa36..d06a03851a 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp @@ -22,6 +22,7 @@ #include "PokemonFRLG_AutoStoryCapture.h" #include "PokemonFRLG_AutoStoryEvents.h" #include "PokemonFRLG_AutoStoryGrind.h" +#include "PokemonFRLG_AutoStoryScreenEvents.h" #include "PokemonFRLG_AutoStorySummary.h" #include "PokemonFRLG_AutoStoryTable.h" #include "PokemonFRLG_AutoStoryTeachScreens.h" @@ -103,6 +104,17 @@ std::optional route_start(const Pokemon::Pose& pose){ return MapPosition{pose.map, tiles[0]}; } +// The story's interrupts for `step`'s walk, with FRLG's screen observer, +// which places a wild battle seen only after an open leg's hold +// (OverworldInterruptSet::make_observer). +OverworldInterruptSet walk_interrupts(StoryEnv& env, const StoryStep& step){ + OverworldInterruptSet out = story_interrupts(env.supplies, step.fight_wild, env.checks, env.language); + out.make_observer = []() -> std::unique_ptr{ + return std::make_unique(); + }; + return out; +} + // `maps`: where the route is planned; the story's but where a step knows // better (the trash cans' walks, past trainers who have walked up). Outcome walk_route(StoryEnv& env, RunState& state, const StoryStep& step, const std::vector& maps = story_maps()){ @@ -132,7 +144,7 @@ Outcome walk_route(StoryEnv& env, RunState& state, const StoryStep& step, const if (!start){ env.console.log("Story: " + step.name + ": on one of " + Pokemon::to_string(state.pose) + "; holds to walls first, to one tile."); } - const OverworldInterruptSet interrupts = story_interrupts(env.supplies, step.fight_wild, env.checks, env.language); + const OverworldInterruptSet interrupts = walk_interrupts(env, step); // Planning lets the walker recover a stopped open leg: it plans back to // the leg's end, then walks the rest as given. From a set of tiles // the start isn't known: the walker reads the facing. @@ -157,7 +169,7 @@ Outcome walk_legs(StoryEnv& env, RunState& state, const StoryStep& step){ if (!facing || !route || route->empty()){ return Pokemon::outcome_failed("bad_legs", to_string(start)); } - const OverworldInterruptSet interrupts = story_interrupts(env.supplies, step.fight_wild, env.checks, env.language); + const OverworldInterruptSet interrupts = walk_interrupts(env, step); // Planning lets the walker recover a stopped open leg: it plans back to // the leg's end, then walks the rest as given. const RoutePlanning planning{.maps = &story_maps(), .costs = story_route_costs(), .start = start, .label = step.name, .wall_check = wall_check(env)}; @@ -382,6 +394,7 @@ std::string save_card_place(const std::string& map){ {"Route24", "route-24"}, {"Route25", "route-25"}, {"VermilionCity", "vermilion-city"}, + {"Route10", "route-10"}, }; for (const auto& [prefix, place] : PLACES){ if (map == prefix || map.rfind(prefix + "_", 0) == 0){ diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h deleted file mode 100644 index f2cc73c6b2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h +++ /dev/null @@ -1,18 +0,0 @@ -/* FRLG Authored Checkpoint Export - * From: https://github.com/PokemonAutomation/ - */ -#ifndef PokemonAutomation_PokemonFRLG_AutoStoryExport_H -#define PokemonAutomation_PokemonFRLG_AutoStoryExport_H -#include "Common/Cpp/Json/JsonValue.h" -#include "PokemonFRLG_AutoStoryEvents.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -// Capture evaluated shared fields and every FRLG checkpoint extension. -// Native supplies are bookkeeping, not observed state or entry requirements. -// Runtime options, map pins, effective policies and execution remain unbound. -JsonValue story_checkpoint_export(const StoryCheckpoint& checkpoint); -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation -#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp deleted file mode 100644 index bc4e0f3f12..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* FRLG Checkpoint Export Tests - * From: https://github.com/PokemonAutomation/ - */ -#include "Common/Cpp/Json/JsonArray.h" -#include "Common/Cpp/Json/JsonObject.h" -#include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" -#include "PokemonFRLG_AutoStoryExport.h" -#include "PokemonFRLG_AutoStoryTable.h" -#include "PokemonFRLG_AutoStoryExport_Tests.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -namespace{ -struct ExportInput{ - std::string_view id; - bool custom = false; -}; -struct ExportExpectation{ - std::vector events; - std::string_view menu; - std::string_view native_json = {}; -}; -struct ExportCase{ - ExportInput input; - ExportExpectation expectation; -}; -const ExportCase CASES[] = { - {.input = {.id = "00.05"}, .expectation = {.events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "before_pokedex"}}, - {.input = {.id = "00.05", .custom = true}, .expectation = { - .events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "full", - .native_json = R"({"menu":"full","supplies":{"potions":2,"antidotes":3,"parlyz_heals":4,"pocket":["antidote",""],"status":"PSN","hurt":true,"hp_red":true,"hp_fill":0.25,"lead":{"species":"wartortle","moves":[{"move":"bite","pp":0}],"moves_through_level":18,"moves_read":true,"level":19,"stats":{"hp":50,"attack":20,"defense":30,"sp_attack":40,"sp_defense":35,"speed":25}},"protected_slots":[0,2],"foes":["staryu"],"trainer":"misty","risk":"high","foe_out":0,"wild_foes":["pidgey"]}})" - }}, -}; -void customize(StoryCheckpoint& row){ - row.menu = StartMenu::FULL; - Supplies& state = row.supplies; - state.potions = 2; - state.antidotes = 3; - state.parlyz_heals = 4; - state.pocket = {"antidote", ""}; - state.status = StatusCondition::POISON; - state.hurt = true; - state.hp_red = true; - state.hp_fill = 0.25; - state.lead = PartyMember{.species = "wartortle", .moves = {{"bite", 0}}, - .moves_through_level = 18, .moves_read = true, .level = 19, - .stats = Stats{.hp = 50, .attack = 20, .defense = 30, .sp_attack = 40, .sp_defense = 35, .speed = 25}}; - state.protected_slots = {0, 2}; - state.foes = {"staryu"}; - state.trainer = "misty"; - state.risk = BattleRisk::HIGH; - state.foe_out = 0; - state.wild_foes = {"pidgey"}; -} -std::string check_export(StoryCheckpoint row, const ExportCase& test){ - if (test.input.custom){ - customize(row); - } - const JsonValue encoded = story_checkpoint_export(row); - const JsonObject& root = encoded.to_object_throw(); - const JsonObject& native = root.get_object_throw("native_fields"); - if (native.get_string_throw("menu") != test.expectation.menu){ - return "Expected menu " + std::string(test.expectation.menu) + ", read " + native.get_string_throw("menu"); - } - if (!test.expectation.native_json.empty() && !(JsonValue(native.clone()) == parse_json(std::string(test.expectation.native_json)))){ - return "Expected native fields " + std::string(test.expectation.native_json) + ", read " + native.dump(); - } - const auto decoded = Pokemon::read_story_checkpoint(root.get_value_throw("checkpoint"), "export test"); - if (!(Pokemon::story_checkpoint_json(decoded) == Pokemon::story_checkpoint_json(row))){ - return "Expected every authored common field to survive export/import."; - } - std::vector names; - for (const auto& step : decoded.steps){ - names.push_back(step.name); - } - return names == test.expectation.events ? "" : "Expected Parcel's three authored events in order."; -} -class ExportTest : public UnitTest{ -public: - explicit ExportTest(ExportCase test) - : UnitTest("PokemonFRLG::CheckpointExport - " + std::string(test.input.id) + (test.input.custom ? " custom" : " authored")), m_test(test){} - virtual UnitTestResult run(Logger&, CancellableScope&) const override{ - for (const auto& row : story_checkpoints()){ - if (row.id == m_test.input.id){ - const std::string error = check_export(row, m_test); - return error.empty() ? UnitTestResult(true) : UnitTestResult(error); - } - } - return UnitTestResult("Expected checkpoint " + std::string(m_test.input.id) + ", read no matching row."); - } -private: - ExportCase m_test; -}; -} // namespace -void add_tests_AutoStoryExport(UnitTestDatabase& database){ - for (const ExportCase& test : CASES){ - database.add(test); - } -} -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp index c6d23e3371..791fe18e85 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp @@ -392,9 +392,13 @@ std::string OverworldStepSim::walk(const StoryStep& step){ return failure; } -// A step one tap at a time, or for a step into a trainer's line by +// A hop() or step_onto() (a plain step the table says the end of); else +// a step one tap at a time, or for a step into a trainer's line by // step_until_seen(), each watched for the trainer. std::string OverworldStepSim::step_ahead(const StoryStep& step){ + if (step.variant.empty() && step.pose_after){ + return step_onto(step); + } const auto tap = [this, &step]{ if (m_sim.state().facing != step.direction){ m_sim.tap(step.direction); @@ -408,6 +412,16 @@ std::string OverworldStepSim::step_ahead(const StoryStep& step){ return step_until_seen(tiles, tap, seen) ? "" : "no trainer saw the player."; } +// A hop() or step_onto(): one step, ending where the table says. +std::string OverworldStepSim::step_onto(const StoryStep& step){ + if (m_sim.state().facing != step.direction){ + m_sim.tap(step.direction); + } + m_sim.tap(step.direction); + m_at = *story_pose(*step.pose_after); + return m_sim.state().position == MapPosition{m_at.map, m_at.tile} ? "" : "expected to end on " + to_string(MapPosition{m_at.map, m_at.tile}) + "."; +} + // A CUT_TREE_VARIANT step: turned to the tree, cut, and a step onto its // tile. std::string OverworldStepSim::cut(const StoryStep& step){ diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h index b8e5526496..15655a8162 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h @@ -151,10 +151,12 @@ std::optional> walked_route(const StoryPose& at, const St // A row's steps on the OverworldSim as the program does them: routes and // legs by the sim's walker from where the table says the player stands, -// fighting a trainer who sees the player on the way; a step one tap at a -// time, or for a step into a trainer's line by step_until_seen(), each -// watched for the trainer; a battle from the trainer's message, which -// must be up; a cut tree cut and stepped onto. The rest is stubbed. +// fighting a trainer who sees the player on the way; a hop down a ledge +// (hop()) or a step onto the tile before one (step_onto()), to where the +// table says; a step one tap at a time, or for a step into a trainer's +// line by step_until_seen(), each watched for the trainer; a battle from +// the trainer's message, which must be up; a cut tree cut and stepped +// onto. The rest is stubbed. class OverworldStepSim : public Pokemon::TableExecutor{ public: // `sim` must outlive the executor. @@ -178,6 +180,7 @@ class OverworldStepSim : public Pokemon::TableExecutor{ Pokemon::StoryAction action(StoryActionId id); std::string walk(const StoryStep& step); std::string step_ahead(const StoryStep& step); + std::string step_onto(const StoryStep& step); std::string cut(const StoryStep& step); std::string battle(const StoryStep& step); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp index ffbd519592..39230fefbc 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp @@ -30,35 +30,35 @@ namespace PokemonFRLG{ namespace{ -struct AutoStoryInput{ - std::string name; - std::function body; -}; - -struct AutoStoryExpectation{ -}; - -struct AutoStoryCase{ - AutoStoryInput input; - AutoStoryExpectation expectation; -}; - +struct AutoStoryInput{ + std::string name; + std::function body; +}; + +struct AutoStoryExpectation{ +}; + +struct AutoStoryCase{ + AutoStoryInput input; + AutoStoryExpectation expectation; +}; + class Test_AutoStory : public UnitTest{ public: - explicit Test_AutoStory(AutoStoryCase test_case) - : UnitTest("PokemonFRLG::AutoStory - " + test_case.input.name) - , m_case(std::move(test_case)) + explicit Test_AutoStory(AutoStoryCase test_case) + : UnitTest("PokemonFRLG::AutoStory - " + test_case.input.name) + , m_case(std::move(test_case)) {} virtual UnitTestResult run(Logger&, CancellableScope&) const override{ try{ - return m_case.input.body(); + return m_case.input.body(); }catch (Exception& e){ return std::string(e.name()) + ": " + e.message(); } } private: - AutoStoryCase m_case; + AutoStoryCase m_case; }; @@ -94,38 +94,38 @@ const char* handoff_name(StoryHandoff handoff){ return "?"; } -struct RunPlanInput{ +struct RunPlanInput{ std::string name = ""; std::string first = ""; // The table's last row if empty. std::string last = ""; StoryHandoffs handoffs = {}; -}; - -struct RunPlanExpectation{ +}; + +struct RunPlanExpectation{ std::string expected_ids = ""; StoryHandoff expected_stop = StoryHandoff::NONE; }; -struct RunPlanScenario{ - RunPlanInput input; - RunPlanExpectation expectation; -}; - +struct RunPlanScenario{ + RunPlanInput input; + RunPlanExpectation expectation; +}; + class Test_RunPlan : public UnitTest{ public: - explicit Test_RunPlan(RunPlanScenario scenario) - : UnitTest("PokemonFRLG::AutoStory - run plan: " + scenario.input.name) + explicit Test_RunPlan(RunPlanScenario scenario) + : UnitTest("PokemonFRLG::AutoStory - run plan: " + scenario.input.name) , m_scenario(std::move(scenario)) {} virtual UnitTestResult run(Logger&, CancellableScope&) const override{ try{ - const size_t last = m_scenario.input.last.empty() ? story_checkpoints().size() - 1 : row_index(m_scenario.input.last); - const StoryRunPlan plan = plan_story_run(story_checkpoints(), row_index(m_scenario.input.first), last, m_scenario.input.handoffs); + const size_t last = m_scenario.input.last.empty() ? story_checkpoints().size() - 1 : row_index(m_scenario.input.last); + const StoryRunPlan plan = plan_story_run(story_checkpoints(), row_index(m_scenario.input.first), last, m_scenario.input.handoffs); const std::string ids = joined_ids(plan.rows); - if (ids != m_scenario.expectation.expected_ids || plan.stop != m_scenario.expectation.expected_stop){ - return "Expected rows \"" + m_scenario.expectation.expected_ids + "\", stop " + handoff_name(m_scenario.expectation.expected_stop) + if (ids != m_scenario.expectation.expected_ids || plan.stop != m_scenario.expectation.expected_stop){ + return "Expected rows \"" + m_scenario.expectation.expected_ids + "\", stop " + handoff_name(m_scenario.expectation.expected_stop) + "; got \"" + ids + "\", stop " + handoff_name(plan.stop) + "."; } return true; @@ -138,7 +138,7 @@ class Test_RunPlan : public UnitTest{ const RunPlanScenario m_scenario; }; -const std::string TODAY = "00.01 00.02 00.03 00.04 00.05 00.06 00.07 00.08 00.09 00.10 00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"; +const std::string TODAY = "00.01 00.02 00.03 00.04 00.05 00.06 00.07 00.08 00.09 00.10 00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"; const RunPlanScenario RUN_PLAN_CASES[] = { { @@ -157,124 +157,124 @@ const RunPlanScenario RUN_PLAN_CASES[] = { .input = { .name = "SID Helper: 00.01 stops on the keyboard", .first = "00.01", .handoffs = {.sid_helper = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "", .expected_stop = StoryHandoff::SID_HELPER, }, - }, - { - .input = { + }, + { + .input = { .name = "SID Helper: back in at 00.01b", .first = "00.01b", .last = "00.03", .handoffs = {.sid_helper = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "00.01b 00.02 00.03", }, - }, - { - .input = {.name = "00.01b runs when started at, option off", .first = "00.01b", .last = "00.02"}, - .expectation = {.expected_ids = "00.01b 00.02"}, - }, - { - .input = {.name = "SID Helper: a later start passes over it", .first = "00.02", .last = "00.03", .handoffs = {.sid_helper = true}}, - .expectation = {.expected_ids = "00.02 00.03"}, - }, - { - .input = {.name = "handoffs off: an end at 00.04 still takes the rival's battle", .first = "00.03", .last = "00.04"}, - .expectation = {.expected_ids = "00.03 00.04"}, - }, - { - .input = { + }, + { + .input = {.name = "00.01b runs when started at, option off", .first = "00.01b", .last = "00.02"}, + .expectation = {.expected_ids = "00.01b 00.02"}, + }, + { + .input = {.name = "SID Helper: a later start passes over it", .first = "00.02", .last = "00.03", .handoffs = {.sid_helper = true}}, + .expectation = {.expected_ids = "00.02 00.03"}, + }, + { + .input = {.name = "handoffs off: an end at 00.04 still takes the rival's battle", .first = "00.03", .last = "00.04"}, + .expectation = {.expected_ids = "00.03 00.04"}, + }, + { + .input = { .name = "Starter RNG: stops after 00.04's save", .first = "00.02", .handoffs = {.starter_rng = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "00.02 00.03 00.04", .expected_stop = StoryHandoff::STARTER_RNG, }, - }, - { - .input = {.name = "00.04b runs from its own save", .first = "00.04b", .last = "00.05"}, - .expectation = {.expected_ids = "00.04b 00.05"}, - }, - { - .input = {.name = "Starter RNG: a later start doesn't stop", .first = "00.05", .last = "00.06", .handoffs = {.starter_rng = true}}, - .expectation = {.expected_ids = "00.05 00.06"}, - }, - { - .input = { + }, + { + .input = {.name = "00.04b runs from its own save", .first = "00.04b", .last = "00.05"}, + .expectation = {.expected_ids = "00.04b 00.05"}, + }, + { + .input = {.name = "Starter RNG: a later start doesn't stop", .first = "00.05", .last = "00.06", .handoffs = {.starter_rng = true}}, + .expectation = {.expected_ids = "00.05 00.06"}, + }, + { + .input = { .name = "both handoffs, back in after SID Helper", .first = "00.01b", .handoffs = {.sid_helper = true, .starter_rng = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "00.01b 00.02 00.03 00.04", .expected_stop = StoryHandoff::STARTER_RNG, }, - }, - { - .input = { + }, + { + .input = { .name = "Gift RNG: stops after 00.12's save", .first = "00.11", .handoffs = {.gift_rng = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "00.11 00.12", .expected_stop = StoryHandoff::GIFT_RNG, }, }, { .input = {.name = "Gift RNG off: 00.12b is passed over", .first = "00.11"}, - .expectation = {.expected_ids = "00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"}, + .expectation = {.expected_ids = "00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"}, }, { .input = {.name = "Gift RNG off: 00.12b runs when started at", .first = "00.12b"}, - .expectation = {.expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"}, + .expectation = {.expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"}, }, { .input = { .name = "Gift RNG: started at 00.12b, from the save beside the salesman", .first = "00.12b", .handoffs = {.gift_rng = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "", .expected_stop = StoryHandoff::GIFT_RNG, }, - }, - { - .input = { + }, + { + .input = { .name = "Gift RNG: started at 00.12b with Starter RNG on too", .first = "00.12b", .handoffs = {.sid_helper = true, .starter_rng = true, .gift_rng = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "", .expected_stop = StoryHandoff::GIFT_RNG, }, }, { .input = {.name = "Starter RNG alone: 00.12b runs when started at", .first = "00.12b", .handoffs = {.starter_rng = true}}, - .expectation = {.expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"}, + .expectation = {.expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"}, }, { .input = { .name = "Starter RNG first, with both on", .first = "00.03", .handoffs = {.starter_rng = true, .gift_rng = true}, - }, - .expectation = { + }, + .expectation = { .expected_ids = "00.03 00.04", .expected_stop = StoryHandoff::STARTER_RNG, }, - }, -}; - -void add_tests_RunPlan(UnitTestDatabase& database){ - for (const auto& item : RUN_PLAN_CASES){ - database.add(item); + }, +}; + +void add_tests_RunPlan(UnitTestDatabase& database){ + for (const auto& item : RUN_PLAN_CASES){ + database.add(item); } } @@ -570,43 +570,43 @@ UnitTestResult test_misty_grind_order(){ return true; } -} // namespace - - -const AutoStoryCase AUTO_STORY_CASES[] = { - { - .input = {"00.01b and 00.12b: back in after SID Helper and Gift RNG", test_after_sid_helper_row}, - .expectation = {}, - }, - { - .input = {"00.04 and 00.04b: one checkpoint with Starter RNG off", test_starter_rows_joined}, - .expectation = {}, - }, - { - .input = {"00.15: Potions at Cerulean's Mart and a GRIND before the rival", test_cerulean_mart_and_grind}, - .expectation = {}, - }, - { - .input = {"00.15b: the rival's battle alone, from a save before him", test_cerulean_rival_row}, - .expectation = {}, - }, - { - .input = {"00.14b and 00.14c: Cerulean Gym, then Misty alone from a save before her", test_misty_rows}, - .expectation = {}, - }, - { - .input = {"00.14b: Potions and Super Potions, a GRIND to Lv23, the nurse, then the Gym", test_misty_grind_order}, - .expectation = {}, - }, -}; - +} // namespace + + +const AutoStoryCase AUTO_STORY_CASES[] = { + { + .input = {"00.01b and 00.12b: back in after SID Helper and Gift RNG", test_after_sid_helper_row}, + .expectation = {}, + }, + { + .input = {"00.04 and 00.04b: one checkpoint with Starter RNG off", test_starter_rows_joined}, + .expectation = {}, + }, + { + .input = {"00.15: Potions at Cerulean's Mart and a GRIND before the rival", test_cerulean_mart_and_grind}, + .expectation = {}, + }, + { + .input = {"00.15b: the rival's battle alone, from a save before him", test_cerulean_rival_row}, + .expectation = {}, + }, + { + .input = {"00.14b and 00.14c: Cerulean Gym, then Misty alone from a save before her", test_misty_rows}, + .expectation = {}, + }, + { + .input = {"00.14b: Potions and Super Potions, a GRIND to Lv23, the nurse, then the Gym", test_misty_grind_order}, + .expectation = {}, + }, +}; + void add_tests_AutoStoryRunPlan(UnitTestDatabase& database){ add_tests_RunPlan(database); - for (const auto& item : AUTO_STORY_CASES){ - database.add(item); - } + for (const auto& item : AUTO_STORY_CASES){ + database.add(item); + } } -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp index faf96e34c5..6685da4ef9 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" @@ -35,6 +36,7 @@ #include "PokemonFRLG_AutoStoryEvents.h" #include "PokemonFRLG_AutoStoryPaceSim.h" #include "PokemonFRLG_AutoStoryRowSim.h" +#include "Data/JsonParser.h" #include "PokemonFRLG_AutoStoryStepTable.h" #include "PokemonFRLG_AutoStoryTable.h" @@ -170,7 +172,7 @@ struct AutoStorySimulationInput{ }; struct AutoStorySimulationExpectation{ - const char* description; + std::string description; }; struct AutoStorySimulationTestCase{ @@ -290,18 +292,25 @@ UnitTestResult test_retry_from_save(){ } // console B, FRLG 00.27, 2026-11-09: the save stood below Cerulean Center's -// door, and the run walked as if from the counter where 00.27 starts. 00.09 -// from below Pewter Center's door (17, 25) stops before any step or save. +// door (22, 19), and the run walked as if from the counter where 00.27 +// starts. It, and 00.09 from below Pewter Center's door (17, 25), stop +// before any step or save. UnitTestResult test_save_off_start_pose(){ - Simulation sim; - sim.game.save_stands = StoryPose{"PewterCity", TilePos{17, 26}, Direction::SOUTH}; - const Pokemon::SegmentReport report = sim.run({.from = "00.09", .to = "00.09"}); - const auto stepped = [](const std::string& item){ return item.starts_with("00.09/"); }; - const auto told = [](const std::string& line){ return line.starts_with("start pose mismatch: "); }; - if (report.completed || report.failure.failure != "start_failed" || !sim.saves.empty() - || std::ranges::any_of(sim.game.trace, stepped) || std::ranges::none_of(sim.lines, told) - ){ - return "Expected 00.09 stopped at its start check, the mismatch logged:" + joined(sim.game.trace) + joined(sim.lines); + const std::pair CASES[] = { + {"00.09", StoryPose{"PewterCity", TilePos{17, 26}, Direction::SOUTH}}, + {"00.27", StoryPose{"CeruleanCity", TilePos{22, 20}, Direction::SOUTH}}, + }; + for (const auto& [id, save] : CASES){ + Simulation sim; + sim.game.save_stands = save; + const Pokemon::SegmentReport report = sim.run({.from = id, .to = id}); + const auto stepped = [&id](const std::string& item){ return item.starts_with(id + "/"); }; + const auto told = [](const std::string& line){ return line.starts_with("start pose mismatch: "); }; + if (report.completed || report.failure.failure != "start_failed" || !sim.saves.empty() + || std::ranges::any_of(sim.game.trace, stepped) || std::ranges::none_of(sim.lines, told) + ){ + return "Expected " + id + " stopped at its start check, the mismatch logged:" + joined(sim.game.trace) + joined(sim.lines); + } } return true; } @@ -772,6 +781,40 @@ UnitTestResult test_out_of_bill_cottage(){ } +// 00.05 as the table authors it and as the data file built into the +// program (StoryData/frlg-parcel-00.05.json, the export tool's capture of +// that row): the loaded row builds the same events, and run against the +// fake game walks the same route, leaves the same trace and saves in the +// same place. +UnitTestResult test_data_row_parity(){ + const std::vector& table = story_checkpoints(); + const auto authored = std::find_if(table.begin(), table.end(), [](const StoryCheckpoint& row){ return row.id == "00.05"; }); + if (authored == table.end()){ + return "Expected row 00.05 in the table."; + } + const StoryCheckpoint loaded = load_story_checkpoint("frlg-parcel-00.05"); + FakeGame describer; + const std::string authored_events = Pokemon::describe(Pokemon::build_story_checkpoint(*authored, describer)); + const std::string loaded_events = Pokemon::describe(Pokemon::build_story_checkpoint(loaded, describer)); + if (authored_events != loaded_events){ + return "Expected the same events from the table and the data:\n" + authored_events + "\n" + loaded_events; + } + Simulation from_table; + from_table.rows = {*authored}; + Simulation from_data; + from_data.rows = {loaded}; + const bool completed = from_table.run({}).completed && from_data.run({}).completed; + if (!completed || from_table.game.trace != from_data.game.trace || from_table.lines != from_data.lines){ + return "Expected the same run from the table and the data:" + joined(from_table.game.trace) + joined(from_data.game.trace); + } + if (from_table.saves.size() != 1 || from_data.saves.size() != 1 + || Pokemon::to_string(from_table.saves[0].second) != Pokemon::to_string(from_data.saves[0].second) + ){ + return "Expected both runs to save once, in the same place:" + joined(from_table.lines) + joined(from_data.lines); + } + return true; +} + // Someone talks to the player before every step (StorySimKnobs:: // dialog_before): cleared between steps, the whole story still passes; // left up, as before the executor looked, a walk fails on it. @@ -823,6 +866,7 @@ UnitTestResult test_dialogs_at_random(Logger& logger){ const AutoStorySimulationTestCase AUTO_STORY_SIMULATION_CASES[] = { + {.input = {.name = "00.05 loaded row parity", .body = test_data_row_parity}, .expectation = {.description = "loaded row preserves events, trace and save pose"}}, {.input = {.name = "new game to Brock, in order, each start where the last save stood", .body = test_whole_story}, .expectation = {.description = "runs full story progression from new game through Brock gym"}}, {.input = {.name = "the steps it stubs need the console", .body = nullptr, .logged = test_stubbed_steps_need_the_console}, .expectation = {.description = "stubs each needs-console step of the whole story and plays the rest"}}, {.input = {.name = "start and end checkpoints", .body = test_start_and_end}, .expectation = {.description = "executes segment bounded by start and end checkpoints"}}, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp index d426f8d08e..3f88fe03e1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp @@ -5,6 +5,7 @@ */ #include "PokemonFRLG_AutoStoryCapture.h" +#include "Data/JsonParser.h" #include "PokemonFRLG_AutoStoryChoice.h" #include "PokemonFRLG_AutoStoryGrind.h" #include "PokemonFRLG_AutoStoryMart.h" @@ -81,13 +82,10 @@ StoryCheckpoint starter(){ }); } +// TODO: Load an ordered series of authored checkpoints together and construct +// a partial story table, instead of loading one file in each row factory. StoryCheckpoint parcel(){ - return row("00.05", "Route 1 to Viridian Mart: Oak's Parcel", at(LAB, 7, 8, SOUTH), {}, StartMenu::BEFORE_POKEDEX, { - route("below_mart_door", at(VIRIDIAN, 36, 20, NORTH), "Route 1, running from wild battles"), - step_into("mart_door", NORTH, "the clerk's scene starts on entering"), - // ApproachCounter: up 4 from the mat, then faces the clerk. - scene("parcel_from_clerk", at(VIRIDIAN_MART, 4, 3, WEST), "the clerk gives OAK'S PARCEL"), - }); + return load_authored_checkpoint("frlg-parcel-00.05.plan"); } StoryCheckpoint pokedex(){ @@ -793,13 +791,13 @@ const std::vector& story_checkpoints(){ running_shoes(), route3(), magikarp_salesman(), after_gift_rng(), magikarp_to_box(), mt_moon(), route4_to_cerulean(), cerulean_gym(), misty(), cerulean_grind(), cerulean_rival(), nugget_bridge(), route25(), bill(), back_to_cerulean(), oddish(), to_vermilion(), ss_anne(), - vermilion_gym(), to_lt_surge(), lt_surge(), + vermilion_gym(), to_lt_surge(), lt_surge(), bike_voucher(), route10_center(), }; return checkpoints; } -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h index 1e12574fe5..2778f00eb5 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h @@ -44,6 +44,8 @@ void add_tests_AutoStorySimulation(UnitTestDatabase& database); void add_tests_AutoStoryVermilion(UnitTestDatabase& database); // Rows 00.23 to 00.25 on the overworld sim (PokemonFRLG_AutoStoryVermilionGym_Tests.cpp). void add_tests_AutoStoryVermilionGym(UnitTestDatabase& database); +// Rows from 00.26 on on the overworld sim (PokemonFRLG_AutoStoryCeladon_Tests.cpp). +void add_tests_AutoStoryCeladon(UnitTestDatabase& database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp new file mode 100644 index 0000000000..6406c1e857 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp @@ -0,0 +1,165 @@ +/* AutoStory Table: to Celadon + * + * From: https://github.com/PokemonAutomation/ + * + * Rows from 00.26 on, after the THUNDERBADGE: the BICYCLE, and the way + * east and south to Celadon City. + * + */ + +#include "PokemonFRLG_AutoStoryMart.h" +#include "PokemonFRLG_AutoStoryTableSteps.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ +namespace StoryTable{ + + +// The BIKE VOUCHER and the BICYCLE, from Vermilion's Center to +// Cerulean's (pokeguy 43:50 to 45:00). +// - The Pokemon Fan Club (VermilionCity_PokemonFanClub map.json, +// scripts.inc), in by Vermilion's (12, 17): the chairman (5, 4) sits +// above a table (5..6, 5..6) with the worker (6, 4) east of him, so he +// is talked to from (4, 4), west of him. "Did you come to hear about +// my POKEMON?" YES: his story, then the BIKE VOUCHER +// (FLAG_GOT_BIKE_VOUCHER). Oak's aide stands on Vermilion's (25, 7) +// since Lt. Surge, where the story's maps don't have him: the way +// planned from the Center's door goes east along his row 7, so the +// walk goes down to the water (14, 10) first, as 00.25 came up from +// it. From the Fan Club, up to (22, 10) first, so that the way on to +// Route 6 goes up column 22, west of him, not round by column 27. +// - Route 6 north, the trainers as 00.21 left them: Jeff (13, 33) +// beaten, Isabelle (13, 32) and Elijah (20, 25) not. As in 00.24, onto +// Jeff's row, west to the wall (7, 33) and up to row 31, above +// Isabelle's line; then east along row 31 to the wall (17, 31) and up +// column 16, west of Elijah's line (17..19, 25) and east of Keigo's +// (4..8, 16), to the ledge (16, 11). The first three legs are 00.24's, +// walked on console B; the two after end on walls, which the walker +// holds again after a wild battle in the grass. +// - The Underground Path north, Route 5's sides, which have no trainer, +// and Cerulean's south edge, whose way into the city without the +// burgled house is the tree (26, 32) (pokeguy 44:42). +// - The Bike Shop (CeruleanCity_BikeShop), in by (13, 28): the clerk +// (9, 3) is across the counter (8, 3), talked to from (7, 3). With the +// voucher he gives the BICYCLE (FLAG_GOT_BICYCLE) with no YES/NO; it +// isn't registered or ridden yet. +// - The Mart: 5 Repels (row 9, 350 each) and 2 Escape Ropes (row 8, +// 550 each; pokefirered items.json), 2850 in all. Console B had about +// 9000 after Lt. Surge (2026-11-03); the floor of 6000 leaves 3000 or +// more after them. +// - The nurse, and the save in Cerulean's Center. +StoryCheckpoint bike_voucher(){ + const MartPlan repels{ + .map = CERULEAN_MART, + .lines = {{.item = "repel", .quantity = 5}, {.item = "escape-rope", .quantity = 2}}, + .budget = 2850, + .money_floor = 6000, + }; + return row("00.26", "The BIKE VOUCHER from the Fan Club, back north to Cerulean, the BICYCLE, Repels and Escape Ropes", at(VERMILION_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { + running(route("below_center", at(VERMILION, 14, 10), "out of the Center, down to the water (14, 10): off Oak's aide's row 7")), + running(route("beside_chairman", at(FAN_CLUB, 4, 4, EAST), "east along row 10, down to the Fan Club's door (12, 17), in, and up its west side beside the chairman (5, 4)")), + talk("chairman", "\"I chair the POKEMON Fan Club!\" ... \"Did you come to hear about my POKEMON?\""), + answer("hear_story", true, "YES: his story"), + scene("bike_voucher", at(FAN_CLUB, 4, 4, EAST), "the story of his Rapidash; the BIKE VOUCHER"), + running(route("west_of_oaks_aide", at(VERMILION, 22, 10), "out of the Fan Club, east along row 18 and up column 22 to (22, 10)")), + running(route("route6_south_path", at(ROUTE6, 11, 34, NORTH), "up column 22 west of Oak's aide (25, 7), out of the city onto Route 6's path, below Jeff's row")), + running(legs("round_isabelle_and_elijah", {{NORTH, 1}, {WEST, 4}, {NORTH, 2}, {EAST, 9}, {NORTH, 19}}, "onto Jeff's row (11, 33), west to the wall (7, 33), up to (7, 31), east along row 31 to the wall (16, 31), up column 16 to the ledge (16, 12): out of Isabelle's and Elijah's lines")), + running(route("below_cerulean_tree", at(CERULEAN, 26, 33, NORTH), "the Underground Path north, up Route 5's side into Cerulean, below the tree (26, 32)")), + cut_tree("cut_cerulean_tree", at(CERULEAN, 26, 32, NORTH), "the tree; a step onto its tile"), + running(route("bike_clerk", at(BIKE_SHOP, 7, 3, EAST), "west into the Bike Shop (13, 28), across the counter from the clerk (9, 3)")), + talk("talk_bike_clerk", "\"Oh, that's... A BIKE VOUCHER!\""), + scene("bicycle", at(BIKE_SHOP, 7, 3, EAST), "the voucher exchanged for a BICYCLE"), + running(route("mart_clerk", at(CERULEAN_MART, 4, 3, WEST), "out of the Bike Shop to the Mart's clerk")), + mart_step("buy_repels", "5 Repels and 2 Escape Ropes, 2850 at most", repels), + running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "out of the Mart to the Center")), + heal("heal_cerulean"), + }); +} + +// East out of Cerulean over Route 9 to Route 10's Pokemon Center +// (pokeguy 45:12 to 47:27), on foot: the Bicycle isn't registered yet. +// - Cerulean (CeruleanCity map.json): the way east, column 39..40 up +// from row 33 to rows 18..19 and out of the city's east edge, is +// reached from the city only through the tree (26, 32), grown back +// since 00.26 (its flag is a FLAG_TEMP), or the burgled house. Cut +// from above. Route 9 joins the city's rows 18..19 by its rows 8..9 +// (offset -10); its tree (2, 8) (FLAG_TEMP_12) closes them, (2, 9) +// being rock: cut from (1, 8), west of it. +// - Route 9 (Route9 map.json, scripts.inc), its ledges all facing south. +// Of its nine trainers, three can't be walked around, as in pokeguy's +// run, where Jeremy happened to look away: +// - Picnicker Alicia (14, 10) faces west, sight 3, over (11..13, 10), +// the only tiles above the ledge row 11 the way west goes down by: +// fought on the step onto (11, 10); she walks up to (12, 10). Then +// down the ledge (11, 11). +// - Hiker Jeremy (20, 17) looks north and west (FACE_UP_AND_LEFT), +// sight 4: north over (20, 13..16), every way east of column 19 +// below the rocks of row 12. Fought on (20, 13), waiting for him to +// look north (TRAINER_WAIT_VARIANT); he walks up to (20, 14). +// - Bug Catcher Conner (49, 7) faces east, sight 3, over (50..52, 7): +// the way north from row 13 is column 52 alone (the ledges of row +// 11 face south), and row 7 is the way onto the ridge. Fought on the +// step onto (52, 7); he walks up to (51, 7). +// - Hiker Brice wanders row 2 (50..56), looking west and east, sight +// 1, over (49..57, 2), and the way up from the ledges of row 4 is +// (50..51, 4) below him. The leg up column 51 ends on row 3 if he +// stands at (51, 2), else it goes on to (51, 1) past him, and he may +// see the player there; either way the legs to walls after it meet +// on (48, 1): west to the rock (48, 3) or (48, 1), up to (48, 1). +// Row 1 is clear of his sight; east along it to the rock (64, 1) +// and down column 64 past the hidden Rare Candy (63, 2), not picked +// up. A held leg down to (64, 3) would hop the ledge (64, 4) and go +// on, so the last tile is one step, then the hop. +// - Picnicker Caitlin (60, 9) faces east over (61..64, 9): the way to +// Route 10 goes along row 8, above her line, by legs to (70, 8), a +// tile short of the edge. Camper Chris (26, 7), +// Bug Catcher Brent (26, 1), Camper Drew (36, 6) and Hiker Alan (61, +// 16) are off the way; TM40 (12, 17) is left. +// - Route 10 (Route10 map.json) joins Route 9's rows 8..10 by its own; +// its north part, down to the Center's door (13, 20), has no trainer +// on the way and its grass is walked round. The trees west of the +// door (10, 20..26) stay. The walk there ends its open legs where a +// tile of error can't reach a blocker, and finds its place again on +// walls (console B, 2026-11-09, two runs "no fade" at the door): the +// step onto Route 10 is not always held up, so the player may stand a +// tile further east, and the leg along row 11 holds to the wall +// (17, 11) instead of ending on (16, 11) by count. The 12 tiles down +// column 16 may end a tile short or long: from rows 22..29 a tile west +// and up to the Center's wall (15, 20) all end on (15, 21). West along +// row 21 to the wall (9, 21) ends on (10, 21), three west of the +// door's column: the only count no wall after it checks. +// - The nurse, and the save in Route 10's Center: the save card names +// ROUTE 10. +StoryCheckpoint route10_center(){ + return row("00.27", "East over Route 9 to Route 10's Pokemon Center: the two trees, Alicia, Jeremy and Conner", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { + running(route("above_cerulean_tree", at(CERULEAN, 26, 31, SOUTH), "out of the Center, south through the city to above the tree (26, 32), grown back")), + cut_tree("cut_cerulean_tree", at(CERULEAN, 26, 32, SOUTH), "the tree; a step onto its tile"), + running(route("west_of_route9_tree", at(ROUTE9, 1, 8, EAST), "down to row 33, east and up column 39 or 40 to row 18, east out of the city onto Route 9, west of its tree (2, 8)")), + cut_tree("cut_route9_tree", at(ROUTE9, 2, 8, EAST), "Route 9's tree; a step onto its tile"), + running(route("above_alicia_line", at(ROUTE9, 11, 9, SOUTH), "east to (11, 9), above the west end of Alicia's line")), + into_line("alicia_line", SOUTH, "onto (11, 10), in Alicia's line: she walks up to (12, 10)"), + battle("picnicker_alicia", at(ROUTE9, 11, 10, EAST), false, "Oddish, Bellsprout, Oddish and Bellsprout, Lv18"), + hop("alicia_ledge_hop", SOUTH, at(ROUTE9, 11, 12, SOUTH), "down the ledge (11, 11)"), + running(route("west_of_jeremy_sight", at(ROUTE9, 19, 13, EAST), "down to row 13 and east along it to (19, 13), west of Jeremy's north line")), + into_sight("jeremy_sight", EAST, "onto (20, 13), four above Jeremy (20, 17): when he looks north he walks up to (20, 14)"), + battle("hiker_jeremy", at(ROUTE9, 20, 13, SOUTH), false, "Machop and Onix, Lv20"), + running(route("below_conner_line", at(ROUTE9, 52, 8, NORTH), "east along row 13 and up column 52 to (52, 8), below the east end of Conner's line")), + into_line("conner_line", NORTH, "onto (52, 7), the end of Conner's line: he walks up to (51, 7)"), + battle("bug_catcher_conner", at(ROUTE9, 52, 7, WEST), false, "Caterpie, Weedle and Venonat, Lv20"), + running(legs("round_brice", {{NORTH, 2}, {WEST, 1}, {NORTH, 2}, {WEST, 3}, {NORTH, 2}, {EAST, 16}, {SOUTH, 1}}, "up to the ledge (52, 4), up column 51 to Brice's row, west to the rock (48, 3) (or (48, 1)), up to (48, 1), east along row 1 to the rock (64, 1), down to (64, 2)")), + step_onto("above_east_ledge", SOUTH, at(ROUTE9, 64, 3, SOUTH), "onto (64, 3), above the ledge (64, 4)"), + hop("east_ledge_hop", SOUTH, at(ROUTE9, 64, 5, SOUTH), "down the ledge (64, 4)"), + running(legs("above_caitlin_line", {{SOUTH, 3}, {EAST, 6}}, "down column 64 to row 8, east along it to (70, 8): above Caitlin's line (61..64, 9)")), + running(route("route10_ledge_end", at(ROUTE10, 17, 11, EAST), "east onto Route 10, down to row 11 west of the grass and east along it to the wall (17, 11), above the fence post (17, 12)")), + running(legs("below_center_door", {{WEST, 1}, {SOUTH, 12}, {WEST, 1}, {NORTH, 2}, {WEST, 5}, {EAST, 3}}, "into the ledge's gap (16, 11), down column 16 to row 23, a tile west, up to the Center's wall (15, 21), west along row 21 to the wall (10, 21), east to (13, 21), below the door")), + running(route("route10_nurse", at(ROUTE10_CENTER, 7, 4, NORTH), "up onto the Center's door (13, 20), to the nurse")), + heal("heal_route10"), + }); +} + + +} // namespace StoryTable +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp index 5ea62165bc..acd9a79de8 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp @@ -69,6 +69,10 @@ StoryStep hop(std::string name, Direction direction, Pokemon::Pose landing, std: return step; } +StoryStep step_onto(std::string name, Direction direction, Pokemon::Pose tile, std::string why){ + return hop(std::move(name), direction, std::move(tile), std::move(why)); +} + StoryStep answer(std::string name, bool yes, std::string why){ StoryStep step = make(StoryStepKind::ANSWER, std::move(name), std::move(why)); step.yes = yes; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h index dd038a4704..8775379b84 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h @@ -91,6 +91,16 @@ inline constexpr const char* SS_ANNE_1F = "SSAnne_1F_Corridor"; inline constexpr const char* SS_ANNE_2F = "SSAnne_2F_Corridor"; // Captain's Office map. inline constexpr const char* CAPTAINS_OFFICE = "SSAnne_CaptainsOffice"; +// Vermilion City Pokemon Fan Club map. +inline constexpr const char* FAN_CLUB = "VermilionCity_PokemonFanClub"; +// Cerulean City Bike Shop map. +inline constexpr const char* BIKE_SHOP = "CeruleanCity_BikeShop"; +// Route 9 overworld map. +inline constexpr const char* ROUTE9 = "Route9"; +// Route 10 overworld map. +inline constexpr const char* ROUTE10 = "Route10"; +// Route 10 Pokemon Center map. +inline constexpr const char* ROUTE10_CENTER = "Route10_PokemonCenter_1F"; // Constructs pose at map coordinates. Pokemon::Pose at(const char* map, int x, int y, std::optional facing = std::nullopt); @@ -133,6 +143,9 @@ StoryStep into_sight(std::string name, Direction direction, std::string why); // ledge (they hold into walls, and a held ledge hops), so where the only // way on is down a ledge the table takes it as this one step. StoryStep hop(std::string name, Direction direction, Pokemon::Pose landing, std::string why); +// One step `direction` onto `tile`, the next one, where a walk's hold +// would go on: onto the tile before a ledge, which a held leg hops. +StoryStep step_onto(std::string name, Direction direction, Pokemon::Pose tile, std::string why); // Constructs a dialog answer step. StoryStep answer(std::string name, bool yes, std::string why); // Constructs an in-game cutscene or dialog interaction step. @@ -163,12 +176,17 @@ StoryStep options_step(); StoryStep cut_tree(std::string name, Pokemon::Pose tree, std::string why); -// Rows in files of their own (PokemonFRLG_AutoStoryTableVermilionGym.cpp). +// Rows in files of their own (PokemonFRLG_AutoStoryTableVermilionGym.cpp, +// PokemonFRLG_AutoStoryTableCeladon.cpp). StoryCheckpoint vermilion_gym(); // Route navigation step towards Lt. Surge battle. StoryCheckpoint to_lt_surge(); // Lt. Surge gym leader battle checkpoint. StoryCheckpoint lt_surge(); +// Bike Voucher retrieval checkpoint in Vermilion City Fan Club. +StoryCheckpoint bike_voucher(); +// Route 10 Pokemon Center navigation checkpoint. +StoryCheckpoint route10_center(); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp index 315c85a120..4512a610ac 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp @@ -230,8 +230,8 @@ UnitTestResult check_checkpoint(const StoryCheckpoint& checkpoint, StoryPose& po UnitTestResult test_routes_and_chain(){ const std::vector& checkpoints = story_checkpoints(); - if (checkpoints.size() != 32 || checkpoints[0].start){ - return "Expected 32 checkpoints, the first from a new game."; + if (checkpoints.size() != 34 || checkpoints[0].start){ + return "Expected 34 checkpoints, the first from a new game."; } StoryPose pose; for (size_t c = 0; c < checkpoints.size(); c++){ @@ -600,7 +600,7 @@ UnitTestResult check_lines(const StoryCheckpoint& checkpoint, std::set/src` when running it. All five existing battle-data +outputs reproduced byte-for-byte after the move. Map-builder tests already live +beside their source in PokemonSimulation. Native game tests remain here. +The shared Sinnoh decomp CLI remains a separate consumer migration. + +Native map, story, transition and recovery tests must run from this tree without +the old map directory before adopting this handoff in the owning chain. This +fork setup is not yet an upstream resource submission: handoff must make the +required data available through the agreed Packages repository/process. + +## Full native test fixtures + +Fork CI pins `dayfine/CommandLineTests` at +`d150a6c547434110a920f95ba7857b93d5bb9222`. This includes the pending FRLG +fixture submissions needed by the existing native image tests and preserves +all current upstream fixture files. It is a test-data pin, independent of the +map resource lock. The upstream-only fixture checkout lacked 69 required frames; +those failures must not be skipped or treated as passing detector tests. +Upstream handoff must also arrange publication of the required image fixtures. diff --git a/devtools/maps/frlg_battle_data.py b/devtools/maps/frlg_battle_data.py index d89c74e5ec..b46a637431 100644 --- a/devtools/maps/frlg_battle_data.py +++ b/devtools/maps/frlg_battle_data.py @@ -1,277 +1,14 @@ -"""Writes the FRLG battle data AutoStory chooses its attacks from, as JSON, from the pret/pokefirered decompilation. +"""Forward native battle-data generation to the pinned PokemonSimulation tools. -Usage: - python3 devtools/maps/frlg_battle_data.py --out SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData - python3 devtools/maps/frlg_battle_data.py --out --check +Example: + PYTHONPATH=/path/to/pokesim/src python3 devtools/maps/frlg_battle_data.py \ + --out SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData --check -Reads, at frlg_maps.COMMIT (cached in --cache like frlg_maps.py): -- src/battle_main.c gTypeEffectiveness: type_chart.json, each pairing that - isn't 1x, in tenths; -- src/data/pokemon/species_info.h: species.json, every species' two types - and base stats: HP, Attack, Defense, Sp. Atk, Sp. Def, Speed; -- src/data/battle_moves.h: moves.json, every move's power, type, PP, - accuracy, priority, effect (EFFECT_FLINCH_HIT is "flinch-hit") and its - secondary effect's chance; -- src/data/trainers.h and src/data/trainer_parties.h: trainers.json, every - trainer's AI flags (AI_SCRIPT_TRY_TO_FAINT is "try-to-faint"), items and - party (not the Ruby/Sapphire placeholders, DUMMY_TRAINER_MON): each - member's species, level, iv (0 to 255, as pret has it), nature and moves. - A party that gives no moves gets the game's (GiveBoxMonInitialMoveset: - the last four of its level-up moves up to its level, from - src/data/pokemon/level_up_learnsets.h). The nature is the one - CreateNPCTrainerParty's personality gives: a hash of the trainer's and - the species' names (charmap.txt) on top of 0x78 for a female trainer, - 0x80 for a double battle, else 0x88, mod 25; -- data/maps/*/scripts.inc, map.json and data/scripts/trainers.inc: - map_trainers.json, the trainers each map battles (trainerbattle_* - TRAINER_X in its scripts or its objects'). -Names are the program's slugs: SPECIES_MR_MIME is "mr-mime", MOVE_WATER_GUN -"water-gun", TYPE_WATER "water". One entry per line, so a change in pret -shows as a line in the diff. --check writes nothing: it regenerates the files -and fails if any differs. +The implementation and FireRed source pin belong to PokemonSimulation. Use the +same tools checkout documented in RESOURCE_MAPS.md; no map-builder copy is needed. """ -import argparse -import json -import os -import re -import sys -from concurrent.futures import ThreadPoolExecutor -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import frlg_maps # noqa: E402 - -# Slugs the plain rule (lower case, '_' to '-') gets wrong: the program's -# names, from Resources/Pokemon/Pokedex/Pokedex-National.json and PokeAPI. -SPECIES_SLUGS = {'FARFETCHD': 'farfetch-d'} -MOVE_SLUGS = { - 'FAINT_ATTACK': 'feint-attack', - 'HI_JUMP_KICK': 'high-jump-kick', - 'SMELLING_SALT': 'smelling-salts', - 'VICE_GRIP': 'vise-grip', -} -# Not species a battle can show. -SKIPPED_SPECIES = re.compile(r'NONE$|EGG$|OLD_UNOWN_') -# The AI flags, as include/constants/battle_ai.h names them. -AI_FLAG = re.compile(r'AI_SCRIPT_(\w+)') -# gTypeEffectiveness's multipliers, in tenths. -MULTIPLIERS = {'TYPE_MUL_NO_EFFECT': 0, 'TYPE_MUL_NOT_EFFECTIVE': 5, 'TYPE_MUL_SUPER_EFFECTIVE': 20} - - -def slug(name, fixes=None): - return (fixes or {}).get(name) or name.lower().replace('_', '-') - - -def type_slug(name): - return name[len('TYPE_'):].lower() - - -def entries(text, prefix): - """(NAME, body) for each designated initializer `[PREFIX_NAME] = { body }`.""" - pattern = re.compile(r'\[' + prefix + r'_(\w+)\]\s*=\s*\{((?:(?!\n \[).)*?)\n \},', re.S) - return pattern.findall(text) - - -def field(body, name): - match = re.search(r'\.' + name + r'\s*=\s*([^,\n]+(?:\{[^}]*\})?)', body) - return match.group(1).strip() if match else None - - -def type_chart(text): - table = re.search(r'gTypeEffectiveness\[\d+\]\s*=\s*\{(.*?)\};', text, re.S).group(1) - chart = [] - for attack, defend, mul in re.findall(r'(TYPE_\w+),\s*(TYPE_\w+),\s*(TYPE_MUL_\w+)', table): - if attack in ('TYPE_FORESIGHT', 'TYPE_ENDTABLE'): - continue - chart.append([type_slug(attack), type_slug(defend), MULTIPLIERS[mul]]) - return chart - - -def species(text): - data = {} - for name, body in entries(text, 'SPECIES'): - if SKIPPED_SPECIES.search(name): - continue - types = re.search(r'\.types\s*=\s*\{(TYPE_\w+),\s*(TYPE_\w+)\}', body) - stats = [int(field(body, f)) for f in ('baseHP', 'baseAttack', 'baseDefense', 'baseSpAttack', 'baseSpDefense', 'baseSpeed')] - data[slug(name, SPECIES_SLUGS)] = [type_slug(types.group(1)), type_slug(types.group(2))] + stats - return data - - -def moves(text): - data = {} - for name, body in entries(text, 'MOVE'): - if name == 'NONE': - continue - data[slug(name, MOVE_SLUGS)] = [ - int(field(body, 'power')), type_slug(field(body, 'type')), int(field(body, 'pp')), - int(field(body, 'accuracy')), int(field(body, 'priority')), - slug(field(body, 'effect')[len('EFFECT_'):]), int(field(body, 'secondaryEffectChance')), - ] - return data - - -def learnsets(text, pointers): - """{species slug: [(level, move slug), ...]} from level_up_learnsets.h.""" - by_name = {} - for name, body in re.findall(r'static const u16 (s\w+LevelUpLearnset)\[\]\s*=\s*\{(.*?)\};', text, re.S): - by_name[name] = [(int(lvl), slug(move, MOVE_SLUGS)) for lvl, move in re.findall(r'LEVEL_UP_MOVE\(\s*(\d+),\s*MOVE_(\w+)\)', body)] - return {slug(s, SPECIES_SLUGS): by_name[n] for s, n in re.findall(r'\[SPECIES_(\w+)\]\s*=\s*(s\w+LevelUpLearnset)', pointers)} - - -def initial_moves(learnset, level): - """GiveBoxMonInitialMoveset: each level-up move up to `level` in turn; - one already known is skipped, a fifth pushes out the first.""" - known = [] - for move_level, move in learnset: - if move_level > level: - break - if move in known: - continue - if len(known) == 4: - known.pop(0) - known.append(move) - return known - - -def charmap(text): - """{character: byte} for the single characters charmap.txt maps.""" - chars = {} - for char, code in re.findall(r"^'(\\?.)'\s*=\s*([0-9A-F]{2})\s*$", text, re.M): - chars[char[-1]] = int(code, 16) - return chars - - -def name_sum(name, chars): - return sum(chars[c] for c in name) - - -def species_names(text): - return {slug(name, SPECIES_SLUGS): value for name, value in re.findall(r'\[SPECIES_(\w+)\]\s*=\s*_\("(.*?)"\)', text)} - - -def parties(text, moves_at): - """sParty_X: [(species, level, iv, [moves]), ...]; `moves_at(species, - level)` gives a member's moves where the party doesn't.""" - data = {} - # A placeholder's {DUMMY_TRAINER_MON} on one line doesn't match. - for name, body in re.findall(r'struct TrainerMon\w+ (sParty_\w+)\[\]\s*=\s*\{\n((?:(?!\nstatic ).)*?)\n\};', text, re.S): - party = [] - for mon in re.findall(r'\{(.*?)\n \}', body, re.S): - species, level = slug(field(mon, 'species')[len('SPECIES_'):], SPECIES_SLUGS), int(field(mon, 'lvl')) - given = re.search(r'\.moves\s*=\s*\{(.*?)\}', mon) - if given: - moves = [slug(m.strip()[len('MOVE_'):], MOVE_SLUGS) for m in given.group(1).split(',') if m.strip() != 'MOVE_NONE'] - else: - moves = moves_at(species, level) - party.append((species, level, int(field(mon, 'iv')), moves)) - data[name] = party - return data - - -def personality_base(body): - """CreateNPCTrainerParty's personality before the name hash.""" - if field(body, 'doubleBattle') == 'TRUE': - return 0x80 - if 'F_TRAINER_FEMALE' in field(body, 'encounterMusic_gender'): - return 0x78 - return 0x88 - - -def trainers(text, party_text, learned, names, chars): - by_party = parties(party_text, lambda species, level: initial_moves(learned.get(species, []), level)) - data = {} - for name, body in entries(text, 'TRAINER'): - if name == 'NONE': - continue - party = re.search(r'\.party\s*=\s*\w+\((sParty_\w+)\)', body) - # Left out: the Ruby/Sapphire placeholders (DUMMY_TRAINER_MON). - if not party or party.group(1) not in by_party: - continue - trainer_sum = name_sum(re.search(r'\.trainerName\s*=\s*_\("(.*?)"\)', body).group(1), chars) - name_hash = 0 - members = [] - for species, level, iv, moves in by_party[party.group(1)]: - name_hash += trainer_sum + name_sum(names[species], chars) - nature = ((personality_base(body) + (name_hash << 8)) & 0xFFFFFFFF) % 25 - members.append([species, level, iv, nature, moves]) - items = re.search(r'\.items\s*=\s*\{(.*?)\}', body).group(1) - data['TRAINER_' + name] = { - 'ai': [slug(flag) for flag in AI_FLAG.findall(field(body, 'aiFlags') or '')], - 'items': [slug(item.strip()[len('ITEM_'):]) for item in items.split(',') if item.strip() not in ('', 'ITEM_NONE')], - 'party': members, - } - return data - - -def script_trainers(text): - """{label: TRAINER_X} for each script whose first line battles a trainer.""" - return dict(re.findall(r'^(\w+)::\n\s*trainerbattle\w* (TRAINER_\w+)', text, re.M)) - - -def map_trainers(decomp): - """The trainers each map battles: in its own scripts, and its objects' - scripts in data/scripts/trainers.inc (most trainers on routes).""" - groups = json.loads(decomp.read('data/maps/map_groups.json')) - names = sorted(m for group in groups['group_order'] for m in groups[group]) - shared = script_trainers(decomp.read('data/scripts/trainers.inc').decode()) - read = lambda m: (decomp.read(f'data/maps/{m}/scripts.inc').decode(), json.loads(decomp.read(f'data/maps/{m}/map.json'))) - with ThreadPoolExecutor(16) as pool: - sources = list(pool.map(read, names)) - data = {} - for name, (scripts, source) in zip(names, sources): - battled = set(re.findall(r'trainerbattle\w* (TRAINER_\w+)', scripts)) - battled |= {shared[o['script']] for o in source.get('object_events', []) if o.get('script') in shared} - if battled: - data[name] = sorted(battled) - return data - - -def json_lines(data): - """A JSON object (or a list), one entry per line.""" - if isinstance(data, list): - lines = [json.dumps(item) for item in data] - return '[\n' + ',\n'.join(lines) + '\n]\n' - lines = [f'{json.dumps(key)}: {json.dumps(value)}' for key, value in data.items()] - return '{\n' + ',\n'.join(lines) + '\n}\n' - - -def files(decomp): - read = lambda rel: decomp.read(rel).decode() - return { - 'type_chart': json_lines(type_chart(read('src/battle_main.c'))), - 'species': json_lines(species(read('src/data/pokemon/species_info.h'))), - 'moves': json_lines(moves(read('src/data/battle_moves.h'))), - 'trainers': json_lines(trainers( - read('src/data/trainers.h'), read('src/data/trainer_parties.h'), - learnsets(read('src/data/pokemon/level_up_learnsets.h'), read('src/data/pokemon/level_up_learnset_pointers.h')), - species_names(read('src/data/text/species_names.h')), charmap(read('charmap.txt')), - )), - 'map_trainers': json_lines(map_trainers(decomp)), - } - - -def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--commit', default=frlg_maps.COMMIT) - parser.add_argument('--cache', default=os.path.expanduser('~/.cache/pokefirered')) - parser.add_argument('--out', required=True, help='folder of the .json files') - parser.add_argument('--check', action='store_true', help='check the files in --out instead of writing them') - args = parser.parse_args() - generated = files(frlg_maps.Decomp(args.commit, args.cache)) - if args.check: - differ = [] - for name, text in generated.items(): - path = os.path.join(args.out, name + '.json') - if not os.path.exists(path) or open(path, newline='').read() != text: - differ.append(name) - if differ: - sys.exit(f'Differ from the generator: {" ".join(differ)}. Regenerate them without --check.') - print(f'{len(generated)} files in {args.out} match the generator.') - return - os.makedirs(args.out, exist_ok=True) - for name, text in generated.items(): - with open(os.path.join(args.out, name + '.json'), 'w', newline='') as out: - out.write(text) +from pokesim.games.frlg.battle_data_export import main if __name__ == '__main__': diff --git a/devtools/maps/frlg_maps.py b/devtools/maps/frlg_maps.py deleted file mode 100644 index 6af5fd6f06..0000000000 --- a/devtools/maps/frlg_maps.py +++ /dev/null @@ -1,444 +0,0 @@ -"""Writes FRLG overworld maps as JSON, from the pret/pokefirered decompilation. - -TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonFRLG/Maps) when ready. - -Usage: - python3 devtools/maps/frlg_maps.py --out SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps \ - Route3 Route4 Route4_PokemonCenter_1F - python3 devtools/maps/frlg_maps.py --out - python3 devtools/maps/frlg_maps.py --out --check - -Downloads each map's data/maps//map.json, its layout's map.bin and its -tilesets' metatile_attributes.bin at --commit into --cache, then writes -/.json in the schema of Pokemon::MapData (Pokemon_MapData.h). -A map whose header doesn't allow running (indoors) says so; a trainer has -their sight and the ways they look. -Warps and connections lead only to maps in the same run: a warp to another map -has no destination, and a connection to one is left out. A warp arrives at -point "warp_" of its map, the tile of that map's warp n. Other named points -and flag sets come from POINTS and FLAG_SETS below. - -Without map names, the maps are the ones already in --out; with them, those -maps are added to the ones in --out, so warps and connections still join -them all. --check writes nothing: it regenerates them and fails if any file -differs. The program -builds in the files of SerialPrograms/Source/VideoGames/PokemonFRLG/ -Overworld/Maps, the folder to write and check. -""" -import argparse -import json -import os -import re -import struct -import subprocess -import sys - -COMMIT = 'ce6406d8c2eaf46d543c8a2606de0c3cb1951982' -# When COMMIT was pinned, for each map's "source". -COMMIT_DATE = '2026-10-31' - -# Behaviors with a glyph of their own (Pokemon_MapData.h has the legend). -TERRAIN = { - 'MB_TALL_GRASS': '~', 'MB_WARP_DOOR': 'D', - 'MB_IMPASSABLE_NORTH': '8', 'MB_IMPASSABLE_SOUTH': '2', - 'MB_IMPASSABLE_EAST': '6', 'MB_IMPASSABLE_WEST': '4', -} -# Open tiles whose warp is taken other than by walking into it: how. -WARP_ENTRIES = { - 'MB_CAVE_DOOR': 'cave_entrance', - 'MB_LADDER': 'step_on', 'MB_UP_ESCALATOR': 'step_on', 'MB_DOWN_ESCALATOR': 'step_on', - 'MB_SOUTH_ARROW_WARP': 'walk_south', 'MB_NORTH_ARROW_WARP': 'walk_north', - 'MB_EAST_ARROW_WARP': 'walk_east', 'MB_WEST_ARROW_WARP': 'walk_west', - # Stairs at the side of a room warp like an exit mat, the way the stairs - # go up or down (IsDirectionalStairWarpMetatileBehavior()). - 'MB_UP_RIGHT_STAIR_WARP': 'walk_east', 'MB_DOWN_RIGHT_STAIR_WARP': 'walk_east', - 'MB_UP_LEFT_STAIR_WARP': 'walk_west', 'MB_DOWN_LEFT_STAIR_WARP': 'walk_west', -} -# Ledges, by the way the player hops over them. -LEDGES = {'MB_JUMP_NORTH': '^', 'MB_JUMP_SOUTH': 'v', 'MB_JUMP_WEST': '<', 'MB_JUMP_EAST': '>'} -FLOOR = ('MB_NORMAL', 'MB_CAVE', 'MB_ROCK_STAIRS', 'MB_TALL_GRASS') -# A metatile's encounter type (METATILE_ATTRIBUTE_ENCOUNTER_TYPE in -# include/fieldmap.h): on a map with land encounters, a step onto a LAND -# tile may start a wild battle, as on every floor tile of a cave. -ENCOUNTER_TYPE_SHIFT, ENCOUNTER_TYPE_MASK = 24, 0x7 -TILE_ENCOUNTER_LAND = 1 -# Open tiles closed on one side, and the region that lists those of them -# with wild encounters, which their glyph can't say. -CLOSED_GLYPHS = '8264' -WILD_REGION = 'wild' -DIRECTIONS = {'up': 'north', 'down': 'south', 'left': 'west', 'right': 'east'} - -# Places and flag sets the decompilation doesn't name, written into the map -# they belong to. -# A `*_front` point is where the player stands to talk to someone across a -# counter, or to walk north through a door. -POINTS = { - # Where a new game starts (WarpToPlayersRoom() in src/new_game.c). - 'PalletTown_PlayersHouse_2F': {'start': (6, 6)}, - 'PalletTown': {'oaks_lab_front': (16, 14)}, - # The clerk is across the counter to the west. - 'ViridianCity_Mart': {'clerk_front': (4, 3)}, - 'ViridianCity_PokemonCenter_1F': {'nurse_front': (7, 4)}, - 'PewterCity': {'gym_front': (15, 17)}, - 'PewterCity_PokemonCenter_1F': {'nurse_front': (7, 4)}, - 'PewterCity_Gym': {'brock_front': (6, 6)}, - # The Magikarp salesman is at (1, 3), west of `salesman_front`. - 'Route4_PokemonCenter_1F': {'nurse_front': (7, 4), 'salesman_front': (2, 3)}, - # The tile in front of the PC. - 'CeruleanCity_PokemonCenter_1F': {'pc': (11, 2), 'nurse_front': (7, 4)}, - # The clerk (2, 3) is across the counter to the west. - 'CeruleanCity_Mart': {'clerk_front': (4, 3)}, - # Below Misty (8, 6). - 'CeruleanCity_Gym': {'misty_front': (8, 7)}, - # Where the player stands after entering from Cerulean City. - 'CeruleanCave_1F': {'entrance': (33, 21)}, - # Mewtwo's tile. Its battle starts by talking to it from the south. - 'CeruleanCave_B1F': {'mewtwo': (7, 12)}, - 'VermilionCity_PokemonCenter_1F': {'nurse_front': (7, 4)}, -} -FLAG_SETS = { - # Set once the player has entered the Hall of Fame. They clear the way - # to Cerulean Cave: its guard, the rival and the Rocket. - 'CeruleanCity': {'after_hall_of_fame': [ - 'FLAG_HIDE_CERULEAN_CAVE_GUARD', - 'FLAG_HIDE_CERULEAN_RIVAL', - 'FLAG_HIDE_CERULEAN_ROCKET', - ]}, - # Set at every new game (EventScript_ResetAllMapFlags in - # data/event_scripts.s): people who are on the map only in a scene. - # The S.S. Anne's rival stands on the Captain's door until his scene - # brings him out. - 'SSAnne_2F_Corridor': {'new_game': ['FLAG_HIDE_SS_ANNE_RIVAL']}, - 'VermilionCity': {'new_game': ['FLAG_HIDE_VERMILION_CITY_OAKS_AIDE']}, -} - -# Tiles a script changes for good once the story is past it, written as -# the story finds them after: Vermilion Gym's beams across rows 6 and 7 -# go off once both switches are found -# (VermilionCity_Gym_EventScript_SetBeamsOff: (4..6, 6..7) floor, -# FLAG_FOUND_BOTH_VERMILION_GYM_SWITCHES). No route of the story's goes -# north of them before that, and one after can't end a leg on them as on -# a wall. -OPENED_TILES = { - 'VermilionCity_Gym': [(x, y) for y in (6, 7) for x in (4, 5, 6)], -} - - -class Decomp: - def __init__(self, commit, cache): - self.raw = 'https://raw.githubusercontent.com/pret/pokefirered/' + commit - self.cache = os.path.join(cache, commit) - - def path(self, rel): - local = os.path.join(self.cache, rel) - if not os.path.exists(local): - os.makedirs(os.path.dirname(local), exist_ok=True) - subprocess.run(['curl', '-sfL', f'{self.raw}/{rel}', '-o', local], check=True) - return local - - def read(self, rel): - with open(self.path(rel), 'rb') as file: - return file.read() - - -def behaviors(decomp): - names = {} - for line in decomp.read('include/constants/metatile_behaviors.h').decode().splitlines(): - match = re.match(r'#define (MB_\w+)\s+(0x[0-9A-Fa-f]+|\d+)', line) - if match: - names[int(match.group(2), 0)] = match.group(1) - return names - - -def tileset_attributes(decomp, symbol): - """The metatile attributes of tileset `symbol` (gTileset_), from the - file src/data/tilesets/metatiles.h names for gMetatileAttributes_: - folder names don't always follow the symbol (gTileset_SSAnne is ss_anne).""" - attributes = symbol.replace('gTileset_', 'gMetatileAttributes_') - match = re.search( - r'\b' + attributes + r'\[\]\s*=\s*INCBIN_U32\("([^"]+)"\)', - decomp.read('src/data/tilesets/metatiles.h').decode(), - ) - if not match: - sys.exit(f'No {attributes} in src/data/tilesets/metatiles.h.') - data = decomp.read(match.group(1)) - return [struct.unpack_from('> ENCOUNTER_TYPE_SHIFT) & ENCOUNTER_TYPE_MASK == TILE_ENCOUNTER_LAND - - -def encounter_glyph(glyph, attributes): - """On a map with land encounters: `glyph` for a tile with these metatile - attributes, open floor where wild Pokemon appear written as tall grass.""" - return '~' if glyph == '.' and land_encounter(attributes) else glyph - - -def wild_edge(glyph, attributes): - """On a map with land encounters: whether a tile closed on one side, with - these metatile attributes, has wild encounters. Its glyph can't say so; - the map lists it in region WILD_REGION.""" - return glyph in CLOSED_GLYPHS and land_encounter(attributes) - - -def grid(decomp, layout, names, unhandled, signs=frozenset(), land_encounters=False): - """The map's tile rows, elevation rows, how the warp on each tile is - entered, by (x, y), where it isn't by walking into it, and the tiles closed - on one side that have wild encounters. With `land_encounters`, open floor - where wild Pokemon appear (a cave's) is written as tall grass, the glyph - for "open, with wild encounters".""" - width, height = layout['width'], layout['height'] - blocks = decomp.read(layout['blockdata_filepath']) - primary = tileset_attributes(decomp, layout['primary_tileset']) - secondary = tileset_attributes(decomp, layout['secondary_tileset']) - tiles, elevation, entries, wild_edges = [], [], {}, [] - for y in range(height): - row, levels = '', '' - for x in range(width): - value = struct.unpack_from('> 10) & 3, value >> 12 - attributes = primary[metatile] if metatile < 640 else secondary[metatile - 640] - behavior = names.get(attributes & 0x1ff, hex(attributes & 0x1ff)) - glyph, entry = tile_glyph(behavior, collision, unhandled, (x, y) in signs) - if land_encounters and entry is None: - glyph = encounter_glyph(glyph, attributes) - if wild_edge(glyph, attributes): - wild_edges.append([x, y]) - row += glyph - if entry: - entries[(x, y)] = entry - levels += '%x' % level - tiles.append(row) - elevation.append(levels) - return tiles, elevation, entries, wild_edges - - -def wander_box(obj, tiles): - """The tiles a wandering person may stand on, kept inside the map, or None - if they stand still.""" - movement = obj.get('movement_type', '') - if 'WANDER' not in movement: - return None - x = 0 if 'UP_AND_DOWN' in movement else int(obj.get('movement_range_x') or 0) - y = 0 if 'LEFT_AND_RIGHT' in movement else int(obj.get('movement_range_y') or 0) - if x == 0 and y == 0: - return None - return { - 'x0': max(obj['x'] - x, 0), 'y0': max(obj['y'] - y, 0), - 'x1': min(obj['x'] + x, len(tiles[0]) - 1), 'y1': min(obj['y'] + y, len(tiles) - 1), - } - - -# Which way a person stands, by how they move. -FACINGS = { - 'MOVEMENT_TYPE_FACE_UP': 'north', 'MOVEMENT_TYPE_FACE_DOWN': 'south', - 'MOVEMENT_TYPE_FACE_LEFT': 'west', 'MOVEMENT_TYPE_FACE_RIGHT': 'east', -} -# The ways a trainer who turns looks. A trainer who moves some other way -# (wanders, looks around) may look any way. -TURNING_LOOKS = { - 'MOVEMENT_TYPE_FACE_DOWN_AND_UP': ['south', 'north'], - 'MOVEMENT_TYPE_FACE_LEFT_AND_RIGHT': ['west', 'east'], - 'MOVEMENT_TYPE_FACE_UP_AND_LEFT': ['north', 'west'], - 'MOVEMENT_TYPE_FACE_UP_AND_RIGHT': ['north', 'east'], - 'MOVEMENT_TYPE_FACE_DOWN_AND_LEFT': ['south', 'west'], - 'MOVEMENT_TYPE_FACE_DOWN_AND_RIGHT': ['south', 'east'], - 'MOVEMENT_TYPE_WANDER_UP_AND_DOWN': ['north', 'south'], - 'MOVEMENT_TYPE_WANDER_LEFT_AND_RIGHT': ['west', 'east'], -} -ALL_LOOKS = ['north', 'east', 'south', 'west'] - - -def trainer(obj): - """A trainer's sight and, unless they only face one way, the ways they - look; None for anyone who doesn't battle on sight.""" - sight = int(obj.get('trainer_sight_or_berry_tree_id') or 0) - if obj.get('trainer_type') != 'TRAINER_TYPE_NORMAL' or sight < 1: - return None - movement = obj.get('movement_type', '') - if movement in FACINGS: - return {'sight': sight} - return {'sight': sight, 'looks': TURNING_LOOKS.get(movement, ALL_LOOKS)} - - -def map_object(obj, tiles): - kind = obj['graphics_id'].replace('OBJ_EVENT_GFX_', '').lower() - data = {'name': kind, 'kind': 'item' if kind == 'item_ball' else 'npc', 'x': obj['x'], 'y': obj['y']} - box = wander_box(obj, tiles) - if box: - data['wander'] = box - sight = trainer(obj) - if sight: - # A trainer who faces one way looks that way. - if 'looks' not in sight: - data['facing'] = FACINGS[obj['movement_type']] - data['trainer'] = sight - flag = obj.get('flag') or '0' - if flag != '0': - data['hide_flag'] = flag - return data - - -# Tiles the player never walks onto: their elevation only matters for doors, -# which the player is put on by a warp and leaves at once. -UNWALKABLE = '#^v<>DS' -GROUND = '3' - - -def ground_only(tiles, elevation): - """Whether every walkable tile is at ground level, so that the map can - leave out its elevation grid (the loader reads ground everywhere).""" - return all(level == GROUND for row, levels in zip(tiles, elevation) - for tile, level in zip(row, levels) if tile not in UNWALKABLE) - - -def warp_point(index): - return f'warp_{index}' - - -def warp(event, entries, loaded, sources): - """A warp event as MapData's warp: how it is entered, and the point it - leads to, if its map is loaded and has that warp.""" - data = {'x': event['x'], 'y': event['y'], 'enter': entries.get((event['x'], event['y']), 'walk_into')} - to_map, index = loaded.get(event['dest_map']), int(event['dest_warp_id']) - if to_map is not None and index < len(sources[to_map].get('warp_events', [])): - data['to_map'] = to_map - data['to_point'] = warp_point(index) - return data - - -def arrivals(sources, loaded): - """For each map, the indices of its warps that a warp leads to.""" - arriving = {name: set() for name in sources} - for source in sources.values(): - for event in source.get('warp_events', []): - to_map = loaded.get(event['dest_map']) - if to_map is not None and int(event['dest_warp_id']) < len(sources[to_map].get('warp_events', [])): - arriving[to_map].add(int(event['dest_warp_id'])) - return arriving - - -def land_encounter_maps(decomp): - """The ids of the maps that have land encounters (src/data/wild_encounters.json).""" - groups = json.loads(decomp.read('src/data/wild_encounters.json'))['wild_encounter_groups'] - return {e['map'] for group in groups for e in group.get('encounters', []) if 'land_mons' in e} - - -def map_json(decomp, name, sources, layouts, names, loaded, unhandled, land_maps=frozenset()): - source = sources[name] - signs = frozenset((e['x'], e['y']) for e in source.get('bg_events', []) if e.get('type') == 'sign') - tiles, elevation, entries, wild_edges = grid( - decomp, layouts[source['layout']], names, unhandled, signs, source['id'] in land_maps - ) - for x, y in OPENED_TILES.get(name, []): - tiles[y] = tiles[y][:x] + '.' + tiles[y][x + 1:] - warp_events = source.get('warp_events', []) - points = {n: [x, y] for n, (x, y) in POINTS.get(name, {}).items()} - for index in sorted(arrivals(sources, loaded)[name]): - points[warp_point(index)] = [warp_events[index]['x'], warp_events[index]['y']] - data = { - 'name': name, - 'source': {'decomp_file': f'data/maps/{name}/map.json', 'revision': COMMIT, 'date': COMMIT_DATE}, - 'allow_running': source.get('allow_running', True), - 'tiles': tiles, 'elevation': elevation, - 'points': points, - 'regions': {WILD_REGION: wild_edges} if wild_edges else {}, - 'warps': [warp(event, entries, loaded, sources) for event in warp_events], - # A few objects stand off the map, where no route meets them. - 'objects': [ - map_object(obj, tiles) for obj in source.get('object_events', []) - if 0 <= obj['x'] < len(tiles[0]) and 0 <= obj['y'] < len(tiles) - ], - 'flag_sets': FLAG_SETS.get(name, {}), - 'connections': [ - {'side': DIRECTIONS[c['direction']], 'to_map': loaded[c['map']], 'offset': c['offset']} - for c in (source.get('connections') or []) if c['map'] in loaded - ], - } - if not data['regions']: - del data['regions'] - if ground_only(tiles, elevation): - del data['elevation'] - if data['allow_running']: - del data['allow_running'] - return data - - -def json_text(data): - text = json.dumps(data, indent=4) - # A point's [x, y] on one line. - return re.sub(r'\[\s*(-?\d+),\s*(-?\d+)\s*\]', r'[\1, \2]', text) + '\n' - - -def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--commit', default=COMMIT) - parser.add_argument('--cache', default=os.path.expanduser('~/.cache/pokefirered')) - parser.add_argument('--out', required=True, help='folder of the .json files') - parser.add_argument('--check', action='store_true', help='check the files in --out instead of writing them') - parser.add_argument('maps', nargs='*', help='default: the maps already in --out') - args = parser.parse_args() - if os.path.isdir(args.out): - args.maps = sorted(set(args.maps) | {f[:-5] for f in os.listdir(args.out) if f.endswith('.json')}) - if not args.maps: - parser.error('no maps given, and none in --out') - - decomp = Decomp(args.commit, args.cache) - names = behaviors(decomp) - layouts = {l['id']: l for l in json.loads(decomp.read('data/layouts/layouts.json'))['layouts'] if 'id' in l} - sources = {m: json.loads(decomp.read(f'data/maps/{m}/map.json')) for m in args.maps} - loaded = {source['id']: m for m, source in sources.items()} - unhandled = {} - land_maps = land_encounter_maps(decomp) - maps = [(name, json_text(map_json(decomp, name, sources, layouts, names, loaded, unhandled, land_maps))) for name in args.maps] - if unhandled: - print(f'Non-collision behaviors written as floor: {unhandled}') - if args.check: - differ = [] - for name, text in maps: - path = os.path.join(args.out, name + '.json') - if not os.path.exists(path) or open(path, newline='').read() != text: - differ.append(name) - if differ: - sys.exit(f'Differ from the generator: {" ".join(differ)}. Regenerate them without --check.') - print(f'{len(maps)} maps in {args.out} match the generator.') - return - os.makedirs(args.out, exist_ok=True) - for name, text in maps: - with open(os.path.join(args.out, name + '.json'), 'w', newline='') as out: - out.write(text) - - -if __name__ == '__main__': - main() diff --git a/devtools/maps/map-resources.lock.json b/devtools/maps/map-resources.lock.json new file mode 100644 index 0000000000..3bded16bc7 --- /dev/null +++ b/devtools/maps/map-resources.lock.json @@ -0,0 +1,11 @@ +{ + "format": "pokesim-map-resource-lock/1", + "repository": "dayfine/PokemonSimulation", + "games": { + "frlg": { + "revision": "906956e94fc912ae9adb0e44845da780f3bc7e4c", + "manifest_path": "data/maps/runtime-manifest.json", + "manifest_sha256": "856e07a265da0b273172a7aa97a2aa335c90ea0a39162d50912b55fb60a86bc7" + } + } +} diff --git a/devtools/maps/tests/test_frlg_maps.py b/devtools/maps/tests/test_frlg_maps.py deleted file mode 100644 index a25d3051d1..0000000000 --- a/devtools/maps/tests/test_frlg_maps.py +++ /dev/null @@ -1,90 +0,0 @@ -import unittest - -from devtools.maps import frlg_maps - -TILES = ['.....'] * 5 - - -def event(**fields): - obj = {'graphics_id': 'OBJ_EVENT_GFX_BUG_CATCHER', 'x': 2, 'y': 2, 'movement_type': 'MOVEMENT_TYPE_FACE_RIGHT', - 'movement_range_x': 1, 'movement_range_y': 1, 'trainer_type': 'TRAINER_TYPE_NORMAL', - 'trainer_sight_or_berry_tree_id': '3', 'flag': '0'} - obj.update(fields) - return obj - - -class TrainerTest(unittest.TestCase): - def test_facing_trainer_looks_ahead(self): - data = frlg_maps.map_object(event(), TILES) - self.assertEqual(data['facing'], 'east') - self.assertEqual(data['trainer'], {'sight': 3}) - - def test_wandering_trainer_looks_both_ways(self): - data = frlg_maps.map_object(event(movement_type='MOVEMENT_TYPE_WANDER_UP_AND_DOWN'), TILES) - self.assertNotIn('facing', data) - self.assertEqual(data['trainer'], {'sight': 3, 'looks': ['north', 'south']}) - self.assertEqual(data['wander'], {'x0': 2, 'y0': 1, 'x1': 2, 'y1': 3}) - - def test_trainer_looking_around_looks_every_way(self): - data = frlg_maps.map_object(event(movement_type='MOVEMENT_TYPE_LOOK_AROUND'), TILES) - self.assertEqual(data['trainer']['looks'], frlg_maps.ALL_LOOKS) - - def test_others_are_no_trainers(self): - for fields in ({'trainer_type': 'TRAINER_TYPE_NONE'}, {'trainer_sight_or_berry_tree_id': '0'}): - data = frlg_maps.map_object(event(**fields), TILES) - self.assertNotIn('trainer', data) - self.assertNotIn('facing', data) - - -class EncounterTest(unittest.TestCase): - # MtMoon_1F's cave floor: behavior MB_CAVE (0x08), encounter type LAND. - CAVE_FLOOR = 0x08 | (frlg_maps.TILE_ENCOUNTER_LAND << frlg_maps.ENCOUNTER_TYPE_SHIFT) - - def test_cave_floor_has_wild_encounters(self): - self.assertEqual(frlg_maps.encounter_glyph('.', self.CAVE_FLOOR), '~') - - def test_floor_without_encounters_stays_open(self): - self.assertEqual(frlg_maps.encounter_glyph('.', 0x08), '.') - - def test_walls_stay_walls(self): - self.assertEqual(frlg_maps.encounter_glyph('#', self.CAVE_FLOOR), '#') - - # MtMoon_B2F's floor closed on the north: MB_IMPASSABLE_NORTH (0x32), - # encounter type LAND, as wild as the cave floor around it. - CLOSED_NORTH = 0x32 | (frlg_maps.TILE_ENCOUNTER_LAND << frlg_maps.ENCOUNTER_TYPE_SHIFT) - - def test_closed_side_floor_has_wild_encounters(self): - for glyph in '8264': - self.assertTrue(frlg_maps.wild_edge(glyph, self.CLOSED_NORTH)) - - def test_closed_side_floor_without_encounters_is_not_wild(self): - self.assertFalse(frlg_maps.wild_edge('8', 0x32)) - - def test_other_tiles_are_no_wild_edges(self): - for glyph in '.~#': - self.assertFalse(frlg_maps.wild_edge(glyph, self.CAVE_FLOOR)) - - -class FakeDecomp: - def __init__(self, files): - self.files = files - - def read(self, rel): - return self.files[rel] - - -class TilesetTest(unittest.TestCase): - # gTileset_SSAnne's folder is ss_anne, which the symbol doesn't say. - METATILES = (b'const u32 gMetatileAttributes_SS[] = INCBIN_U32("data/tilesets/secondary/ss/metatile_attributes.bin");\n' - b'const u32 gMetatileAttributes_SSAnne[] = INCBIN_U32("data/tilesets/secondary/ss_anne/metatile_attributes.bin");\n') - - def test_attributes_from_the_file_metatiles_h_names(self): - decomp = FakeDecomp({ - 'src/data/tilesets/metatiles.h': self.METATILES, - 'data/tilesets/secondary/ss_anne/metatile_attributes.bin': bytes([1, 0, 0, 0, 0x22, 0, 0, 1]), - }) - self.assertEqual(frlg_maps.tileset_attributes(decomp, 'gTileset_SSAnne'), [1, 0x01000022]) - - -if __name__ == '__main__': - unittest.main()