diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index d0966df32f..ad0ff7ae70 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -197,6 +197,8 @@ 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/PokemonBDSP/Programs/AutoStory/Maps + PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h PokemonAutomation::NintendoSwitch::PokemonBDSP bdsp_map_json) # Create binary target diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index 9a7a818793..e262f24f95 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -33,6 +33,7 @@ #include "Pokemon/AutoStory/Pokemon_AutoStory_Tests.h" #include "Pokemon/Navigation/Pokemon_Navigation_Tests.h" +#include "PokemonBDSP/PokemonBDSP_Tests.h" #include "VideoGames/PokemonHome/PokemonHome_Tests.h" #include "VideoGames/PokemonSwSh/PokemonSwSh_Tests.h" #include "VideoGames/PokemonLA/PokemonLA_Tests.h" @@ -80,6 +81,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ NintendoSwitch::add_tests_FailedToConnectDetector(ret); NintendoSwitch::add_tests_UpdatePopupDetector(ret); NintendoSwitch::Typewriter::add_tests_TypewriterTextMapping(ret); + NintendoSwitch::PokemonBDSP::add_tests(ret); NintendoSwitch::PokemonFRLG::add_tests(ret); NintendoSwitch::PokemonRSE::add_tests(ret); NintendoSwitch::PokemonHome::add_tests(ret); diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp index b8ff5e26a5..36f4de8f6d 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp @@ -5,6 +5,7 @@ */ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h" #include "PokemonBDSP_Tests.h" namespace PokemonAutomation{ @@ -14,6 +15,7 @@ namespace PokemonBDSP{ void add_tests(UnitTestDatabase& database){ + add_tests_AutoStory_Maps(database); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp new file mode 100644 index 0000000000..1992a6cb4d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp @@ -0,0 +1,77 @@ +/* BDSP AutoStory - Maps + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonValue.h" +#include "PokemonBDSP_AutoStory_Maps.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using Pokemon::MapData; +using Pokemon::TilePos; + + +namespace{ + +const std::map>& story_maps_by_name(){ + static const std::map> maps = []{ + std::map> parsed; + for (const auto& [name, json] : bdsp_map_json()){ + MapData map = Pokemon::parse_map(parse_json(json), name + ".json"); + if (map.name != name){ + throw ParseException(std::format("{}.json: name is \"{}\".", name, map.name)); + } + parsed.emplace(name, std::move(map)); + } + return parsed; + }(); + return maps; +} + +} // namespace + + +const MapData& story_map(std::string_view name){ + const auto& maps = story_maps_by_name(); + if (auto iter = maps.find(name); iter != maps.end()){ + return iter->second; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("No BDSP story map named \"{}\".", name)); +} + +TilePos map_point(std::string_view map, std::string_view point){ + const MapData& data = story_map(map); + if (auto iter = data.points.find(std::string(point)); iter != data.points.end()){ + return iter->second; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("{} has no point \"{}\".", map, point)); +} + +std::vector map_region(std::string_view map, std::string_view region){ + const MapData& data = story_map(map); + std::vector tiles; + while (true){ + const auto iter = data.points.find(std::format("{}.{}", region, tiles.size() + 1)); + if (iter == data.points.end()){ + break; + } + tiles.push_back(iter->second); + } + if (tiles.empty()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("{} has no region \"{}\".", map, region)); + } + return tiles; +} + + + +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h new file mode 100644 index 0000000000..988fb7b52f --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Maps + * + * From: https://github.com/PokemonAutomation/ + * + * The maps the story walks, as JSON files in the shared map schema + * (Pokemon_MapData.h): PokemonBDSP/Programs/AutoStory/Maps, written by + * devtools/maps/bdsp_maps.py from the Diamond decompilation and built into + * the program by CMake. Each is named as its file ("Route202"). + * + * A map's named tiles are its points. A region, a list of tiles, is the + * points ".1", ".2", ... in order: the schema has + * single-tile points only. + * + * TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonBDSP/Maps) when ready + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Maps_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Maps_H + +#include +#include +#include +#include +#include "Pokemon/Navigation/Pokemon_Grid.h" +#include "Pokemon/Navigation/Pokemon_MapData.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// (name, JSON text) of each map in PokemonBDSP/Programs/AutoStory/Maps/, +// built into the program by pa_embed_text_files() (cmake/EmbedTextFiles.cmake). +const std::vector>& bdsp_map_json(); + +// The map `name`, read from bdsp_map_json() on first use, with its patches +// applied. Throws InternalProgramError for a name it does not have, and +// ParseException if a map does not parse or is not named as its file. +// The name is a string_view so that a temporary string argument is not +// mistaken (by GCC's -Wdangling-reference) for what the result refers to. +const Pokemon::MapData& story_map(std::string_view name); + +// The point `point` of the map `map`. Throws InternalProgramError for an +// unknown map or point. +Pokemon::TilePos map_point(std::string_view map, std::string_view point); + +// The tiles of the region `region` of the map `map`, in order. Throws +// InternalProgramError for an unknown map, or a region with no tiles. +std::vector map_region(std::string_view map, std::string_view region); + + + +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json new file mode 100644 index 0000000000..f6f6bfd349 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json @@ -0,0 +1,59 @@ +{ + "name": "FloaromaTown", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0032.bin; zone_event narc_0404", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "######........................##", + "##..##........................##", + "..............................##", + "..............................##", + "..............................##", + "..............................##", + "..............................##", + "..................#####.......##", + "..................#####.......##", + "..................#####.......##", + "..................#####.......##", + "..........####...S##.##.......##", + "..........####................##", + "..........####................##", + "..........#D##................##", + ".......................####..S##", + ".......................####...##", + ".......................#D##...##", + "..............................##", + ".............S..................", + "................................", + "................................", + "................................", + "..............#####....####...##", + "..............#####....####...##", + "..............#####....####...##", + "..............##D##....#D##...##", + "..............................##", + "..............................##", + "..............................##", + "########.#...###################", + "########.#...###################" + ], + "points": { + "route_204_arrival": [11, 29], + "old_woman": [3, 25], + "old_man": [2, 25], + "pokemon_center_door": [16, 26] + }, + "objects": [ + {"name": "pokemon_breeder_f_0", "kind": "npc", "x": 19, "y": 21}, + {"name": "lass_1", "kind": "npc", "x": 10, "y": 5, "wander": {"x0": 9, "y0": 5, "x1": 11, "y1": 5}}, + {"name": "school_kid_m_2", "kind": "npc", "x": 16, "y": 16, "wander": {"x0": 15, "y0": 15, "x1": 17, "y1": 17}}, + {"name": "cyrus_3", "kind": "npc", "x": 11, "y": 26}, + {"name": "lass_4", "kind": "npc", "x": 23, "y": 11, "facing": "west"}, + {"name": "grunt_m_7", "kind": "npc", "x": 2, "y": 1, "facing": "east"}, + {"name": "grunt_m_8", "kind": "npc", "x": 3, "y": 1, "facing": "west"}, + {"name": "camper_12", "kind": "npc", "x": 6, "y": 10}, + {"name": "old_woman", "kind": "npc", "x": 3, "y": 25}, + {"name": "old_man", "kind": "npc", "x": 2, "y": 25} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 10, "y": 29, "width": 3, "length": 1, "var": "VAR_UNK_0x4072", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0404.bin coord_events[0] (170, 669), script 2"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json new file mode 100644 index 0000000000..4f28c563a9 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json @@ -0,0 +1,146 @@ +{ + "name": "JubilifeCity", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0015.bin, files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0017.bin, files/fielddata/land_data/land_data_release/narc_0018.bin; zone_event narc_0002", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "##########################################..............########", + "##########################################..............########", + "############################################.....###############", + "############################################......##############", + "############################################......##############", + "############################################......##############", + "############################################......##############", + "#############################################....###############", + "############################....#########.......S.##############", + "############################....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..##D###D##..######....####D####.........##############", + "#########.............######...............#.....#######....####", + "#########.............######......................######....####", + "..#######.............######......................######....####", + "..###########S......########...........S.........S##D###....####", + "..#.S.#.......................#..........................#.S####", + "................................................................", + "D...............................................................", + "D...............................................................", + "................................................................", + "..#...#.......................#..........................#..####", + "..############.............####......####...................####", + "..############.............####......####................#######", + "##############.################...##.####..#.....######..#######", + "##############.################...##.####.........#####..#######", + "##############.################...................#D############", + "################################.........................#######", + "################################.........................##D####", + "################################.........................#######", + "################################.........................#######", + "###############............#####.........................#######", + "###############............#####......#####..............#######", + "###############..#########.#####.##########.......######.#######", + "###############..#########.#####.##########.......######.#######", + "###############..#########.#####.###########.....#######.#######", + "##################################..S.##D##.......##D###########", + "###############..#########.#####.........................#######", + "##############...####D####..###..........................#######", + "##############...........................................#######", + "##############.....S.....................................#######", + "##############...........................................#######", + "##############.............#########.....................#######", + "###############............#########.#####...............#######", + "###############............#########.#####...............#######", + "###############.####..####.###############...............#######", + "###############.####..####.###############...............#######", + "###############.####..####.###############...........###########", + "############D##.####..####.############D##...........###########", + "##########.................#########.......#.....#...########.##", + "##########.................#########.................########.##", + "##########............#..#.#########......S..........########.##", + "##########............#..#.#########.................########.##", + "##########.................#########.................########.##", + "###########################################.......##############", + "###########################################.......##############", + "########################################.##.......##############", + "########################################.####...################", + "########################################..........##############", + "########################################..........##############" + ], + "points": { + "trainers_school_door": [40, 40], + "pokemon_center_door": [52, 40], + "tv_station_door": [36, 15], + "poketch_president": [46, 35], + "clown_mart": [55, 33], + "clown_tv_station": [36, 16], + "clown_poketch_company": [15, 18], + "up_the_steps.1": [46, 58], + "up_the_steps.2": [47, 58], + "up_the_steps.3": [46, 59], + "up_the_steps.4": [47, 59], + "up_the_steps.5": [46, 60], + "up_the_steps.6": [47, 60], + "east_entrance.1": [61, 21], + "east_entrance.2": [62, 21], + "east_entrance.3": [63, 21], + "east_entrance.4": [61, 22], + "east_entrance.5": [62, 22], + "east_entrance.6": [63, 22], + "east_entrance.7": [61, 23], + "east_entrance.8": [62, 23], + "east_entrance.9": [63, 23], + "east_entrance.10": [61, 24], + "east_entrance.11": [62, 24], + "east_entrance.12": [63, 24], + "north_road.1": [44, 16], + "north_road.2": [45, 16], + "north_road.3": [46, 16], + "north_road.4": [47, 16], + "north_road.5": [48, 16], + "north_road.6": [44, 17], + "north_road.7": [45, 17], + "north_road.8": [46, 17], + "north_road.9": [47, 17], + "north_road.10": [48, 17], + "north_road.11": [44, 18], + "north_road.12": [45, 18], + "north_road.13": [46, 18], + "north_road.14": [47, 18], + "north_road.15": [48, 18], + "north_road.16": [44, 19], + "north_road.17": [45, 19], + "north_road.18": [46, 19], + "north_road.19": [47, 19], + "north_road.20": [48, 19], + "north_road.21": [44, 20], + "north_road.22": [45, 20], + "north_road.23": [46, 20], + "north_road.24": [47, 20], + "north_road.25": [48, 20] + }, + "objects": [ + {"name": "ace_trainer_f_0", "kind": "npc", "x": 22, "y": 20, "wander": {"x0": 21, "y0": 20, "x1": 23, "y1": 20}}, + {"name": "ace_trainer_m_1", "kind": "npc", "x": 46, "y": 21, "wander": {"x0": 45, "y0": 21, "x1": 47, "y1": 21}}, + {"name": "school_kid_m_2", "kind": "npc", "x": 17, "y": 55}, + {"name": "twin_3", "kind": "npc", "x": 33, "y": 18, "wander": {"x0": 32, "y0": 17, "x1": 34, "y1": 19}}, + {"name": "expert_m_4", "kind": "npc", "x": 44, "y": 44}, + {"name": "school_kid_m_5", "kind": "npc", "x": 51, "y": 34, "wander": {"x0": 51, "y0": 33, "x1": 51, "y1": 35}}, + {"name": "pokefan_m_6", "kind": "npc", "x": 41, "y": 30, "wander": {"x0": 40, "y0": 29, "x1": 42, "y1": 31}}, + {"name": "ace_trainer_m_22", "kind": "npc", "x": 36, "y": 25}, + {"name": "poketch_president", "kind": "npc", "x": 46, "y": 35}, + {"name": "clown_mart", "kind": "npc", "x": 55, "y": 33}, + {"name": "clown_poketch_company", "kind": "npc", "x": 15, "y": 18}, + {"name": "cashier_m_27", "kind": "npc", "x": 26, "y": 42}, + {"name": "clown_tv_station", "kind": "npc", "x": 36, "y": 16}, + {"name": "kid_with_nds_29", "kind": "npc", "x": 52, "y": 46}, + {"name": "kid_with_nds_30", "kind": "npc", "x": 54, "y": 46} + ], + "coord_events": [ + {"name": "dawn_at_the_steps", "x": 45, "y": 60, "width": 4, "length": 1, "var": "VAR_JUBILIFE_CITY_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[0] (173, 796), script 2"}, + {"name": "coord_event_1", "x": 45, "y": 7, "width": 3, "length": 1, "var": "VAR_JUBILIFE_CITY_STATE", "value": 3, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[1] (173, 743), script 4"}, + {"name": "coord_event_2", "x": 60, "y": 21, "width": 1, "length": 4, "var": "VAR_JUBILIFE_CITY_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[2] (188, 757), script 3"}, + {"name": "coord_event_3", "x": 27, "y": 43, "width": 1, "length": 3, "var": "VAR_GTS_ACCESS_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[3] (155, 779), script 19"}, + {"name": "poketch_president", "x": 44, "y": 40, "width": 5, "length": 1, "var": "VAR_POKETCH_CAMPAIGN_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[4] (172, 776), script 17"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json new file mode 100644 index 0000000000..f7b521b3cb --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json @@ -0,0 +1,43 @@ +{ + "name": "LakeVerity", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0512.bin; zone_event narc_0298", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "wwwwwwwwwwwwwwww################", + "wwwwwwwwwwwwwwww################", + "wwwwwwwwwwwwwwww################", + "wwwwwwwwwwwwwwww################", + "......wwwwwwwwww##....##########", + "......wwwwwwwwww##....##########", + ".......wwwwwwwww##......########", + ".......wwwwwwwww##......########", + ".......wwwwwww####~.....########", + ".......wwwwwww####~~....########", + "wwwwwwwwwwww####~~~~....########", + "wwwwwwwwwwww####.~~~....########", + "wwwwwwwwww####.~~~~~..##########", + "wwwwwwwwww####.~~~~...##########", + "wwwwwwww####...~~~..############", + "wwwwwwww####........############", + "##########..........############", + "##########..........############", + "############......##############", + "############......##############", + "############......##############", + "############......##############", + "##############..################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "objects": [ + {"name": "rowan", "kind": "npc", "x": 16, "y": 11, "facing": "north"}, + {"name": "counterpart", "kind": "npc", "x": 17, "y": 11, "facing": "north"}, + {"name": "starly_3", "kind": "npc", "x": 16, "y": 19, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json new file mode 100644 index 0000000000..0043b0bea2 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json @@ -0,0 +1,95 @@ +{ + "name": "OreburghCity", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0022.bin, files/fielddata/land_data/land_data_release/narc_0023.bin, files/fielddata/land_data/land_data_release/narc_0024.bin, files/fielddata/land_data/land_data_release/narc_0175.bin; zone_event narc_0044", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "#########################################......#################", + "#########################################...S..#################", + "#########################################......#################", + "#########################################......#################", + "#########################################...####################", + "#########################################...####################", + "#########################################....###################", + "############.#####.######.#.############.....###################", + "###########..#####..#####...############.....###################", + "#########....#####..#####...############.....###################", + "##.....########D######D######D##########.....###################", + "##..S..###..........................####........#.#...##########", + "##..................................####.....S........##########", + "#D..................................####..............##########", + "##..................................####..............####.#####", + "##..................................####..............####.#####", + "##.....###.............#######......#D##..............####.#####", + "##.....##.......####...#######...............#####....####.#####", + "#########.......####.#.#######..............######......#.######", + "#########.......####...#######..............######.....#########", + "#########.......#D####S###D###...............##D##.....#########", + "#########........................................#.....#########", + "#########........................................#.....#########", + "#########........................................#....##########", + "#########........................................#....##########", + "#########........................................#....##########", + "##########.#.#..########.........................#....##########", + "########################.######.############.....#....##########", + "########################.######.###........#.....#....##D#######", + "########################.######.###.#..#...#.....#.........#####", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....##D############", + "###################################.########.....#...........###", + "###################################.....####.....#...........###", + "###################################.....####.....#...........###", + "###################################....#####.....#...........###", + "#####################################..#.........#...........###", + "#####################################..#..#.#....#....#.#....###", + "#####################################..#..#.#....#....#.#....###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################..###..#.#................###", + "###################################..###..#.#...#.#########.####", + "###################################..###........#.#########.####", + "###################################....#..........#####.....####", + "###################################...............#####.....####", + "#####################################.............##.........###", + "#####################################..####.#.....###.....######", + "#####################################..####.#S....###.....######", + "###########################################.......####.......###", + "############################################.....#####.......###", + "############################################.....#####.....#####", + "######################################################..#..#####", + "################################################################", + "################################################################", + "################################################################" + ], + "points": { + "pokemon_center_door": [47, 20], + "pokemon_center_doorstep": [46, 21], + "gym_door": [26, 20], + "gym_doorstep": [26, 21], + "gate_mouth": [1, 13] + }, + "objects": [ + {"name": "hiker_0", "kind": "npc", "x": 47, "y": 26, "wander": {"x0": 46, "y0": 25, "x1": 48, "y1": 27}}, + {"name": "school_kid_f_1", "kind": "npc", "x": 32, "y": 18, "wander": {"x0": 32, "y0": 16, "x1": 32, "y1": 20}}, + {"name": "pokefan_f_2", "kind": "npc", "x": 45, "y": 38, "wander": {"x0": 45, "y0": 37, "x1": 45, "y1": 39}}, + {"name": "youngster_4", "kind": "npc", "x": 10, "y": 16, "facing": "north"}, + {"name": "worker_15", "kind": "npc", "x": 11, "y": 25}, + {"name": "worker_16", "kind": "npc", "x": 56, "y": 57, "wander": {"x0": 55, "y0": 54, "x1": 57, "y1": 60}}, + {"name": "camper_17", "kind": "npc", "x": 36, "y": 19, "wander": {"x0": 33, "y0": 19, "x1": 39, "y1": 19}}, + {"name": "worker_18", "kind": "npc", "x": 37, "y": 39, "wander": {"x0": 36, "y0": 38, "x1": 38, "y1": 40}}, + {"name": "worker_19", "kind": "npc", "x": 50, "y": 41, "wander": {"x0": 50, "y0": 38, "x1": 50, "y1": 44}}, + {"name": "guitarist_20", "kind": "npc", "x": 23, "y": 13, "wander": {"x0": 22, "y0": 10, "x1": 24, "y1": 16}}, + {"name": "battle_girl_21", "kind": "npc", "x": 36, "y": 23, "wander": {"x0": 36, "y0": 21, "x1": 36, "y1": 25}}, + {"name": "machop_22", "kind": "npc", "x": 55, "y": 56, "wander": {"x0": 53, "y0": 54, "x1": 57, "y1": 58}} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 10, "y": 12, "width": 1, "length": 4, "var": "VAR_OREBURGH_CITY_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0044.bin coord_events[0] (266, 748), script 3"}, + {"name": "coord_event_1", "x": 6, "y": 12, "width": 1, "length": 4, "var": "VAR_OREBURGH_CITY_STATE", "value": 2, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0044.bin coord_events[1] (262, 748), script 4"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json new file mode 100644 index 0000000000..f3f6b5a73b --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json @@ -0,0 +1,46 @@ +{ + "name": "OreburghGate", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0418.bin; zone_event narc_0250", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################", + "################################", + "################################", + "################################", + "############..#......###########", + "###########..........###########", + "###########........#.###########", + "###########..###################", + "###########..###################", + "###########..###################", + "###########.####################", + "############.###################", + "###########.####################", + "###########..###################", + "###########..###################", + "###########..###################", + "###########..###################", + "###########..###################", + "############.###################", + "###########..###################", + "#####...##...##########...#.####", + "####....##...##########.....####", + "###D........................D###", + "####...#....................####", + "###########..............#.#####", + "############.......#############", + "###########..#.#...#############", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "objects": [ + {"name": "picnicker_0", "kind": "npc", "x": 24, "y": 21, "facing": "south"}, + {"name": "camper_1", "kind": "npc", "x": 17, "y": 25, "facing": "north"}, + {"name": "hiker_10", "kind": "npc", "x": 7, "y": 20, "facing": "south"} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 7, "y": 22, "width": 1, "length": 1, "var": "VAR_OREBURGH_GATE_1F_HIKER_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0250.bin coord_events[0] (7, 22), script 3"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json new file mode 100644 index 0000000000..89341d5b3a --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json @@ -0,0 +1,50 @@ +{ + "name": "OreburghGym", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0228.bin; zone_event narc_0046", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################", + "################################", + "################################", + "#..........#####################", + "#..........#####################", + "#..........#####################", + "#..##.##...#####################", + "#..........#####################", + "#..##.##...#####################", + "#..........#####################", + "#..........#####################", + "###.....#.######################", + "#..........#####################", + "#####.##...#####################", + "#####.##...#####################", + "#..........#####################", + "#..........#####################", + "#..........#####################", + "#..........#####################", + "##.#....########################", + "##.#....########################", + "#..........#####################", + "#..#...#...#####################", + "#..S...S...#####################", + "#....D.....#####################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "points": { + "entrance": [5, 23], + "roark": [5, 3], + "trainer_first_tier": [4, 18], + "trainer_second_tier": [7, 11] + }, + "objects": [ + {"name": "roark_0", "kind": "npc", "x": 5, "y": 3, "facing": "south"}, + {"name": "gym_guide_1", "kind": "npc", "x": 6, "y": 23, "facing": "south"}, + {"name": "youngster_2", "kind": "npc", "x": 4, "y": 18, "facing": "east"}, + {"name": "youngster_3", "kind": "npc", "x": 7, "y": 11, "facing": "west"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json new file mode 100644 index 0000000000..d8ea9a4ff6 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json @@ -0,0 +1,56 @@ +{ + "name": "OreburghMine", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0314.bin; zone_event narc_0197", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################", + "###############D################", + "#############.....##############", + "#############.....##############", + "#############.....##############", + "#############.....##############", + "#############.....##############", + "#############...################", + "#############...################", + "#############.....##############", + "#############..#..##############", + "###############...##############", + "#############...################", + "#############.....##############", + "###############...##############", + "########...####..............###", + "######.....##.................##", + "##.#..........................##", + "##....####################....##", + "##vvv##..................#....##", + "##....#..................#....##", + "##.##.#..................#....##", + "##..###..................###..##", + "##..###..................###..##", + "##....#..................#....##", + "##.##.#..................#....##", + "####..####################....##", + "####..####################....##", + "####.............#.#........####", + "######......................####", + "################################", + "################################" + ], + "points": { + "roark": [18, 28], + "corridor_exit_start": [14, 10], + "corridor_top.1": [14, 2], + "corridor_top.2": [15, 2], + "corridor_top.3": [16, 2] + }, + "objects": [ + {"name": "roark", "kind": "npc", "x": 18, "y": 28, "facing": "east"}, + {"name": "item_3", "kind": "item", "x": 4, "y": 28}, + {"name": "item_4", "kind": "item", "x": 26, "y": 24}, + {"name": "worker_5", "kind": "npc", "x": 8, "y": 28, "facing": "north"}, + {"name": "worker_6", "kind": "npc", "x": 26, "y": 20, "facing": "west"}, + {"name": "worker_7", "kind": "npc", "x": 21, "y": 15, "facing": "north"}, + {"name": "machop_8", "kind": "npc", "x": 12, "y": 28, "wander": {"x0": 11, "y0": 28, "x1": 13, "y1": 28}}, + {"name": "machop_9", "kind": "npc", "x": 20, "y": 15}, + {"name": "machop_10", "kind": "npc", "x": 27, "y": 15, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json new file mode 100644 index 0000000000..c959a1a426 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json @@ -0,0 +1,36 @@ +{ + "name": "PlayerHouse1F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "#############", + "#############", + "#############", + "#SS..S####..#", + "#.....####..#", + "#SSS..SS....#", + "#...........#", + "#...........#", + "#..##.......#", + "##.##......##", + "##....D....##", + "#############" + ], + "points": { + "stairs": [10, 3], + "mom": [7, 8], + "door": [6, 10] + }, + "warps": [ + {"x": 6, "y": 10, "to_map": "TwinleafTown", "to_point": "player_house_door"} + ], + "objects": [ + {"name": "mom_0", "kind": "npc", "x": 7, "y": 8, "facing": "north"} + ], + "coord_events": [ + {"name": "moms_warning", "x": 6, "y": 10, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_STATE", "value": 1, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_1f.json coord_events[0] (6, 10), script 5"}, + {"name": "moms_call", "x": 6, "y": 9, "width": 1, "length": 1, "var": "CONSOLE_MOMS_CALL", "value": 0, "source": "console r45, r46 (2026-11-01): after her heal Mom calls us back as we step above the door; no coord event in the decomp"} + ], + "patches": [ + {"x": 4, "y": 8, "glyph": ".", "measured_by": "r32, 2026-11-01"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json new file mode 100644 index 0000000000..c78c817c70 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json @@ -0,0 +1,28 @@ +{ + "name": "PlayerHouse2F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_187.bin; events_twinleaf_town_player_house_2f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "############", + "############", + "############", + "#######S####", + "#S#SSS...###", + "#........###", + "#..........#", + "#..........#", + "#........#.#", + "##.........#", + "##.......#.#", + "############" + ], + "points": { + "stairs": [8, 4], + "start": [4, 6] + }, + "coord_events": [ + {"name": "coord_event_0", "x": 4, "y": 5, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[0] (4, 5), script 7"}, + {"name": "coord_event_1", "x": 3, "y": 6, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[1] (3, 6), script 8"}, + {"name": "coord_event_2", "x": 5, "y": 6, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[2] (5, 6), script 9"}, + {"name": "coord_event_3", "x": 4, "y": 7, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[3] (4, 7), script 10"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json new file mode 100644 index 0000000000..7c48de6775 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json @@ -0,0 +1,87 @@ +{ + "name": "RavagedPath", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_435.bin, res/field/maps/data/map_data_436.bin; events_ravaged_path.json", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "................................", + "................................", + "........................########", + "........................########", + "........................##....##", + "############............##....##", + "############............##....##", + "####...#####............##www.##", + "##...#...############...##www###", + "##..####.############...##www###", + "##..####.#######...#######www###", + "##..####......##...#######www###", + "##.#.###########...##www##www###", + "###.##########.....##wwwwwwwww##", + "###..######.#########wwwwwwwww##", + "###..#####..#########wwwwwwwww##", + "####.#####vv#########www########", + "##...#####..#########www########", + "##..######..#########www###.....", + "##..######..#########www###.....", + "###.######..######wwwwwww##.....", + "##..######..######wwwwwww##.....", + "##.#.#####..######wwwwwww##.....", + "####.#####vv######www######.....", + "####.#......######www######.....", + "####........######www##.........", + "####....wwwwwwwwwwwww##.........", + "..##....wwwwwwwwwwwww##.........", + "..##....wwwwwwwwwwwww##.........", + "..#######www###########.........", + "..#######www###########.........", + "....#####www####................", + "....#####www####................", + "....##wwwwww####................", + "....##wwwwww####................", + "....##wwwwww####................", + "....##.wwww#.###................", + "....##.#....#.##................", + "....##..#.#...#########.........", + "....##....#..##########.........", + "....##.#.#.......#...###########", + "....###.....######...###########", + "....##############....#.......##", + "....##############....#.......##", + "..............####.....#......##", + "..............####...#######D###", + "..............####...###########", + "..............####...####.......", + "..............####...####.......", + "..............####...####.......", + "..............####...####.......", + "..............#####D#####.......", + "..............###########.......", + "..............###########......." + ], + "points": { + "entrance": [19, 50], + "east_end": [29, 44], + "above_exit": [28, 44], + "rocks.1": [11, 36], + "rocks.2": [7, 37], + "rocks.3": [12, 37], + "rocks.4": [8, 38], + "rocks.5": [10, 38], + "rocks.6": [10, 39], + "rocks.7": [13, 39], + "rocks.8": [7, 40], + "rocks.9": [9, 40], + "rocks.10": [17, 40], + "rocks.11": [12, 41], + "rocks.12": [22, 42], + "rocks.13": [22, 43], + "rocks.14": [23, 44], + "before_rocks.1": [21, 42], + "before_rocks.2": [21, 43] + }, + "objects": [ + {"name": "item_27", "kind": "item", "x": 7, "y": 38}, + {"name": "item_28", "kind": "item", "x": 17, "y": 11}, + {"name": "item_29", "kind": "item", "x": 28, "y": 5}, + {"name": "item_30", "kind": "item", "x": 22, "y": 44} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json new file mode 100644 index 0000000000..7d1b616c29 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json @@ -0,0 +1,28 @@ +{ + "name": "RivalHouse1F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_184.bin; events_twinleaf_town_rival_house_1f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "#############", + "#############", + "#############", + "#..#####..###", + "#..####.....#", + "#....##..####", + "#...........#", + "#...........#", + "#.......##..#", + "##......##.##", + "##....D....##", + "#############" + ], + "points": { + "door": [6, 10], + "stairs": [2, 3] + }, + "warps": [ + {"x": 6, "y": 10, "to_map": "TwinleafTown", "to_point": "rival_house_door"} + ], + "objects": [ + {"name": "pokefan_f_0", "kind": "npc", "x": 4, "y": 6} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json new file mode 100644 index 0000000000..d0b03b36e6 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json @@ -0,0 +1,25 @@ +{ + "name": "RivalHouse2F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_185.bin; events_twinleaf_town_rival_house_2f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "############", + "############", + "############", + "############", + "###...S##S##", + "###........#", + "#..........#", + "#..........#", + "#.#........#", + "#.........##", + "#.#.......##", + "############" + ], + "points": { + "stairs": [3, 4], + "barry_talk": [8, 5] + }, + "objects": [ + {"name": "barry", "kind": "npc", "x": 9, "y": 5, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json new file mode 100644 index 0000000000..fe88167472 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json @@ -0,0 +1,60 @@ +{ + "name": "Route201", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0005.bin, files/fielddata/land_data/land_data_release/narc_0006.bin; zone_event narc_0327", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################################################", + "################################################################", + "################################################################", + "##########################################~~~~~~~~~~~...........", + "##########################################~~~~~~~~~~~..S........", + "##########################################~~~~~~~~~~~...........", + "##########################################~~~~~~~~~~~...........", + "....####..........######..##....##..####..~~~~~~~~~##...........", + "....####..........######..##....##..####..~~~~~~~~~##...........", + "##........####............................~~~~~~~~~##vvvvv######", + "##........####............................~~~~~~~~~##.....######", + "..........................................~~~~~~~~~##.....######", + "vvvv####vvvvvvvvvvvvvvvvvvvvvvvv........S.~~~~~~~~~##.....######", + "....####............~~~~~~~~~~............................######", + "....................~~~~~~~~~~............................######", + "....................~~~~~~~~~~............................######", + "....................~~~~~~~~~~............................######", + "########.....S......##########............................######", + "########............##########............~~~~~~~~~~~~~~~~######", + "##############....############################~~~~~~~~~~~~######", + "##############....############################~~~~~~~~~~~~######", + "##############....##############################################", + "##############....##############################################", + "##############....##############################################", + "##############....##############################################" + ], + "points": { + "border_sign": [55, 4], + "potion_giver": [43, 3], + "potion_giver_talk_from": [44, 3], + "route_201_arrival": [15, 24], + "barry_warning": [15, 18], + "route_201_return": [0, 14], + "rowan_stop": [5, 14], + "route_201_east_end": [63, 5] + }, + "objects": [ + {"name": "pokemon_breeder_m_0", "kind": "npc", "x": 27, "y": 15, "wander": {"x0": 26, "y0": 14, "x1": 28, "y1": 16}}, + {"name": "school_kid_m_9", "kind": "npc", "x": 57, "y": 8, "facing": "west"}, + {"name": "lass_11", "kind": "npc", "x": 46, "y": 16, "wander": {"x0": 44, "y0": 16, "x1": 48, "y1": 16}} + ], + "coord_events": [ + {"name": "barry_warning", "x": 14, "y": 18, "width": 6, "length": 1, "var": "VAR_FOLLOWER_RIVAL_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[0] (110, 857), script 2"}, + {"name": "rowan_stop", "x": 5, "y": 14, "width": 1, "length": 3, "var": "VAR_FOLLOWER_RIVAL_STATE", "value": 2, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[1] (101, 853), script 5"}, + {"name": "coord_event_2", "x": 14, "y": 19, "width": 6, "length": 1, "var": "VAR_FOLLOWER_RIVAL_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[2] (110, 858), script 3"} + ], + "patches": [ + {"x": 8, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 9, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 10, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 11, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 12, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 13, "y": 18, "glyph": "S", "measured_by": "r16, 2026-10-31"}, + {"x": 13, "y": 17, "glyph": ".", "measured_by": "r16, 2026-10-31"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json new file mode 100644 index 0000000000..50486733f1 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json @@ -0,0 +1,144 @@ +{ + "name": "Route202", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0014.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0328", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "###########........#################", + "###########........#################", + "###########........#################", + "###########........#################", + "###~~~~~~........S..~~~~~......#####", + "###~~~~~~...........~~~~~......#####", + "###.~~~~~...........~~~~~......#####", + "###~~~~~~...........~~~~~......#####", + "###~~~~~~...........~~~~~......#####", + "###~~~~~~.....########.........#####", + "#########vvvvv########vvvvvv...~~###", + "#########.....########~~~~~~~~~~~###", + "#######.......########~~~~~~~~~~~###", + "#######...........####~~~~~~~~~~~###", + "###...............####~~~~~~~~~~~###", + "###...............####~~~~~~~~~~~###", + "###............S..####~~~~~~~~~~~###", + "###...............####~~~~~~~~~~~###", + "###...............####..~~~~~~~~~###", + "###~~~~##...............~~~~~~~~~###", + "###~~~~##.................~~~~~~~###", + "###~~~~##....................~~~~###", + "###~~~~##......................#####", + "###~~~~##vvvvv########vvvvvvvvv#####", + "###~~~~##.....########.........#####", + "###~~~~~~......~~~~~~...S......#####", + "###~~~~~~......~~~~~~..........#####", + "###~~~~~~......~~~~~~..........#####", + "###~~~~~~......~~~~~~..........#####", + "###~~~~~~......~~~~~~..........#####", + "#####################..........#####", + "#####################..........#####" + ], + "points": { + "catch_spot": [19, 27], + "save_spot": [30, 24], + "trainer_tristan": [7, 13], + "trainer_natalie": [22, 18], + "trainer_logan": [26, 4], + "exit_jubilife": [15, 0], + "jubilife_exit.1": [11, 0], + "jubilife_exit.2": [12, 0], + "jubilife_exit.3": [13, 0], + "jubilife_exit.4": [14, 0], + "jubilife_exit.5": [15, 0], + "jubilife_exit.6": [16, 0], + "jubilife_exit.7": [17, 0], + "jubilife_exit.8": [18, 0], + "jubilife_exit.9": [11, 1], + "jubilife_exit.10": [12, 1], + "jubilife_exit.11": [13, 1], + "jubilife_exit.12": [14, 1], + "jubilife_exit.13": [15, 1], + "jubilife_exit.14": [16, 1], + "jubilife_exit.15": [17, 1], + "jubilife_exit.16": [18, 1], + "jubilife_exit.17": [11, 2], + "jubilife_exit.18": [12, 2], + "jubilife_exit.19": [13, 2], + "jubilife_exit.20": [14, 2], + "jubilife_exit.21": [15, 2], + "jubilife_exit.22": [16, 2], + "jubilife_exit.23": [17, 2], + "jubilife_exit.24": [18, 2], + "jubilife_exit.25": [11, 3], + "jubilife_exit.26": [12, 3], + "jubilife_exit.27": [13, 3], + "jubilife_exit.28": [14, 3], + "jubilife_exit.29": [15, 3], + "jubilife_exit.30": [16, 3], + "jubilife_exit.31": [17, 3], + "jubilife_exit.32": [18, 3], + "catch_save_area.1": [15, 25], + "catch_save_area.2": [16, 25], + "catch_save_area.3": [17, 25], + "catch_save_area.4": [18, 25], + "catch_save_area.5": [19, 25], + "catch_save_area.6": [20, 25], + "catch_save_area.7": [21, 25], + "catch_save_area.8": [15, 26], + "catch_save_area.9": [16, 26], + "catch_save_area.10": [17, 26], + "catch_save_area.11": [18, 26], + "catch_save_area.12": [19, 26], + "catch_save_area.13": [20, 26], + "catch_save_area.14": [21, 26], + "catch_save_area.15": [15, 27], + "catch_save_area.16": [16, 27], + "catch_save_area.17": [17, 27], + "catch_save_area.18": [18, 27], + "catch_save_area.19": [19, 27], + "catch_save_area.20": [20, 27], + "catch_save_area.21": [21, 27], + "catch_save_area.22": [21, 28], + "catch_save_area.23": [21, 29], + "after_training_area.1": [18, 25], + "after_training_area.2": [19, 25], + "after_training_area.3": [20, 25], + "after_training_area.4": [18, 26], + "after_training_area.5": [19, 26], + "after_training_area.6": [20, 26], + "after_training_area.7": [18, 27], + "after_training_area.8": [19, 27], + "after_training_area.9": [20, 27], + "catch_grass.1": [18, 25], + "catch_grass.2": [19, 25], + "catch_grass.3": [20, 25], + "catch_grass.4": [18, 26], + "catch_grass.5": [19, 26], + "catch_grass.6": [20, 26], + "catch_grass.7": [18, 27], + "catch_grass.8": [19, 27], + "catch_grass.9": [20, 27] + }, + "objects": [ + {"name": "youngster_0", "kind": "npc", "x": 7, "y": 13, "facing": "south"}, + {"name": "lass_1", "kind": "npc", "x": 22, "y": 18, "facing": "south"}, + {"name": "youngster_2", "kind": "npc", "x": 26, "y": 4, "facing": "south"}, + {"name": "youngster_7", "kind": "npc", "x": 10, "y": 8, "wander": {"x0": 9, "y0": 8, "x1": 11, "y1": 8}}, + {"name": "item_8", "kind": "item", "x": 3, "y": 6} + ], + "coord_events": [ + {"name": "catch_tutorial", "x": 21, "y": 25, "width": 1, "length": 5, "var": "VAR_ROUTE_202_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0328.bin coord_events[0] (180, 825), script 6"} + ], + "patches": [ + {"x": 15, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 16, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 17, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 18, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 19, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 20, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 15, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 16, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 17, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 18, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 19, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 20, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 21, "y": 30, "glyph": "#", "measured_by": "r254, 2026-11-01"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json new file mode 100644 index 0000000000..faccffbe74 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json @@ -0,0 +1,67 @@ +{ + "name": "Route203", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0019.bin, files/fielddata/land_data/land_data_release/narc_0020.bin; zone_event narc_0329", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "#############################################################", + "#############################################################", + "#############################################################", + "#############################################################", + "#####..............###########..~~~~#########################", + "#####..............###########...~~~..~~~~~~~~~~~~~~#########", + "#####..................#######........~~~~~~~~~~~~~.....#####", + "#####..................#######wwwwww##~~~~~~~~~~~....S..#####", + "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......#####", + "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......D####", + "#####~~~~##...####.....#..S...wwwwww##~~~~~~~~~~~.......#####", + "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......#####", + "#####~~~~##...####.....#....~~~~~~~~##~~~~~~~~~S...~~~..#####", + "#####~~~~##...####vvvvv#...~~...~.~~##~~~~~~~~~....~~~..#####", + "#####~~................#....######~~##.....~~....##~~~..#####", + "#####..................#....######~~##vvvvvvvvvvv##~~~..#####", + "#####....S.............#....~~~..~~~.............~~~~~..#####", + "...........................~~~~...~~.............~~~~~..#####", + "...........................~~.....~~.............~~~~~..#####", + "..........................~~~~.....~##..........~~~~~~..#####", + "..........................~~~~~.~~~~#########################", + "#############################################################", + "#############################################################", + "#############################################################" + ], + "points": { + "gate_exit.1": [55, 9], + "gate_exit.2": [54, 9], + "gate_exit.3": [55, 8], + "gate_exit.4": [55, 10], + "west_line.1": [24, 18], + "west_line.2": [25, 18], + "west_line.3": [26, 18], + "west_line.4": [27, 18], + "west_line.5": [28, 18], + "west_line.6": [29, 18], + "west_line.7": [30, 18], + "west_line.8": [31, 18], + "west_line.9": [32, 18], + "west_line.10": [33, 18], + "west_line.11": [34, 18], + "west_line.12": [35, 18], + "west_line.13": [36, 18], + "west_line.14": [37, 18], + "west_line.15": [38, 18], + "west_line.16": [39, 18], + "west_line.17": [40, 18], + "west_line.18": [41, 18], + "west_line.19": [42, 18] + }, + "objects": [ + {"name": "youngster_1", "kind": "npc", "x": 13, "y": 10, "facing": "west"}, + {"name": "lass_2", "kind": "npc", "x": 43, "y": 17, "facing": "west"}, + {"name": "youngster_3", "kind": "npc", "x": 31, "y": 16, "facing": "south"}, + {"name": "lass_4", "kind": "npc", "x": 49, "y": 8, "facing": "south"}, + {"name": "item_10", "kind": "item", "x": 31, "y": 5}, + {"name": "item_11", "kind": "item", "x": 20, "y": 10}, + {"name": "lass_12", "kind": "npc", "x": 15, "y": 17, "wander": {"x0": 14, "y0": 17, "x1": 16, "y1": 17}} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 5, "y": 17, "width": 1, "length": 4, "var": "VAR_ROUTE_203_RIVAL_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0329.bin coord_events[0] (196, 757), script 6"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json new file mode 100644 index 0000000000..65fe1fb8cf --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json @@ -0,0 +1,83 @@ +{ + "name": "Route204North", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0031.bin; zone_event narc_0331", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "########.#...###################", + "########.#...###################", + "####.....#...########.##########", + "##########...#......#.##########", + "#####....#...#......#...........", + "#####....#~~~#......#...######..", + "#####....#~~~###################", + "##########~~~.........##########", + "##........~~~~~.......##########", + "##..######.~~~~..S....##########", + "##..#....#.~~~........##########", + "##..#....#.~~.........##########", + "##########..............########", + "##########..............########", + "##########...####~~~~~~~########", + "##########vvv####~~~~~~~########", + "##########...######~~~~~########", + "##########...######~~~~~########", + "##########...######~~~~~########", + "##########...######~.~~~########", + "##########............~~########", + "##########............##########", + "##########............##########", + "##########...##.......##########", + "##########...##.......##########", + "##########...###################", + "##########...#######.###########", + "##########............##########", + "##########............##########", + "##########............##########", + "##########............##########", + "##########...###################" + ], + "points": { + "cave_exit": [20, 26], + "floaroma_waypoints.1": [20, 27], + "floaroma_waypoints.2": [11, 27], + "floaroma_waypoints.3": [11, 22], + "floaroma_waypoints.4": [19, 22], + "floaroma_waypoints.5": [19, 12], + "floaroma_waypoints.6": [11, 12], + "floaroma_waypoints.7": [11, 5], + "floaroma_path.1": [10, 0], + "floaroma_path.2": [11, 0], + "floaroma_path.3": [12, 0], + "floaroma_path.4": [10, 1], + "floaroma_path.5": [11, 1], + "floaroma_path.6": [12, 1], + "floaroma_path.7": [10, 2], + "floaroma_path.8": [11, 2], + "floaroma_path.9": [12, 2], + "floaroma_path.10": [10, 3], + "floaroma_path.11": [11, 3], + "floaroma_path.12": [12, 3], + "floaroma_path.13": [10, 4], + "floaroma_path.14": [11, 4], + "floaroma_path.15": [12, 4], + "floaroma_path.16": [10, 5], + "floaroma_path.17": [11, 5], + "floaroma_path.18": [12, 5], + "trainer_twin_left": [15, 7], + "trainer_twin_right": [16, 7], + "trainer_taylor": [17, 20], + "trainer_brandon": [20, 12] + }, + "objects": [ + {"name": "bug_catcher_brandon", "kind": "npc", "x": 20, "y": 12}, + {"name": "pokemon_breeder_f_1", "kind": "npc", "x": 17, "y": 20}, + {"name": "twin_2", "kind": "npc", "x": 15, "y": 7, "facing": "south"}, + {"name": "twin_3", "kind": "npc", "x": 16, "y": 7, "facing": "south"}, + {"name": "youngster_4", "kind": "npc", "x": 15, "y": 28, "wander": {"x0": 14, "y0": 28, "x1": 16, "y1": 28}}, + {"name": "item_9", "kind": "item", "x": 2, "y": 10}, + {"name": "ace_trainer_f_10", "kind": "npc", "x": 31, "y": 4, "facing": "west"}, + {"name": "item_11", "kind": "item", "x": 20, "y": 23} + ], + "patches": [ + {"x": 12, "y": 6, "glyph": "#", "measured_by": "r248a, 2026-10-31"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json new file mode 100644 index 0000000000..d70775dce3 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json @@ -0,0 +1,56 @@ +{ + "name": "Route204South", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0021.bin; zone_event narc_0330", "revision": "default branch", "date": "2026-11-01"}, + "tiles": [ + "################################", + "###########D###.################", + "########..S.....################", + "########........################", + "########........################", + "##########...~##################", + "##########...~##################", + "##########..~~##################", + "##########..~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww....##", + "####~~~~~~~~~~###wwwwwwwww....##", + "####~~~~~~~~~~#..wwwww##########", + "##~~~~~~~~~~~~#..wwwww##########", + "##~~~~~~~~~~~~#..wwwww##########", + "##~~~~~~~~~~~~S.################", + "##~~~~~.........#.##############", + "##~~~~~.........#.##############", + "##~~~~~.........#.##############", + "##.#########....#.##############", + "##wwwwwwwww#....#......~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##..######.#....#..~~~~~########", + "##..######.#....#...~~~~########", + "##########.#....#.......########", + "############....######..########" + ], + "points": { + "below_door": [11, 2], + "jubilife_entry.1": [14, 31], + "jubilife_entry.2": [14, 30], + "jubilife_entry.3": [14, 29], + "trainer_samantha": [11, 8], + "trainer_sarah": [12, 21], + "trainer_tyler": [7, 18] + }, + "objects": [ + {"name": "lass_0", "kind": "npc", "x": 12, "y": 21}, + {"name": "lass_1", "kind": "npc", "x": 11, "y": 8, "facing": "south"}, + {"name": "youngster_2", "kind": "npc", "x": 7, "y": 18}, + {"name": "youngster_3", "kind": "npc", "x": 13, "y": 2, "wander": {"x0": 12, "y0": 2, "x1": 14, "y1": 2}}, + {"name": "item_7", "kind": "item", "x": 2, "y": 28}, + {"name": "item_8", "kind": "item", "x": 29, "y": 12}, + {"name": "item_9", "kind": "item", "x": 17, "y": 18} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json new file mode 100644 index 0000000000..8faef4e607 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json @@ -0,0 +1,36 @@ +{ + "name": "RowansLab", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_222.bin; events_sandgem_town_pokemon_research_lab.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "######################", + "######################", + "###SSSS####S####.###S#", + "#........#...##......#", + "#.###................#", + "#...................##", + "#.............#....###", + "###........###########", + "###........###########", + "###........###########", + "#............#########", + "##..........##########", + "##..........##########", + "###........###########", + "###........###########", + "###....D...###########", + "######################", + "######################" + ], + "points": { + "door": [7, 15], + "rowan_talk": [7, 6] + }, + "warps": [ + {"x": 7, "y": 15, "to_map": "SandgemTown", "to_point": "lab_door"} + ], + "objects": [ + {"name": "rowan", "kind": "npc", "x": 7, "y": 5, "facing": "south"}, + {"name": "scientist_m_1", "kind": "npc", "x": 3, "y": 12, "wander": {"x0": 1, "y0": 12, "x1": 5, "y1": 12}}, + {"name": "scientist_f_2", "kind": "npc", "x": 10, "y": 12} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json new file mode 100644 index 0000000000..3691f9266e --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json @@ -0,0 +1,58 @@ +{ + "name": "SandgemTown", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "########################......####", + "########################......####", + "####################..........####", + "####################..........####", + "####################..........####", + "####################..........####", + "####################......########", + "####################......########", + "####################......########", + "####################......########", + "########D##....##D##S....S#D######", + ".....S........................####", + "..............................####", + "..............................####", + "..............................####", + "..............................####", + "######........................####", + "######........................####", + "######.........#####..........####", + "##########.....#####..........####", + "##########.....#####..........####", + "##########.....#####..........####", + "#######D##....S#D###..S.......####", + "######........................####", + "######........................####", + "######........................####", + "######....................########", + "####################......########", + "####################......########", + "####################......########", + "####################......########", + "####################......########" + ], + "points": { + "route_202_entry_stop": [29, 5], + "lab_door": [8, 10], + "pokemon_center_door": [17, 10], + "pokemon_center_sign": [20, 10], + "poke_mart_door": [27, 10], + "front_row_east_end": [29, 11], + "below_sign_gap.1": [21, 11], + "below_sign_gap.2": [22, 11], + "below_sign_gap.3": [23, 11], + "below_sign_gap.4": [24, 11], + "sandgem_arrival": [0, 12], + "dawn_stop": [4, 12] + }, + "objects": [ + {"name": "youngster_0", "kind": "npc", "x": 27, "y": 23, "wander": {"x0": 26, "y0": 22, "x1": 28, "y1": 24}} + ], + "coord_events": [ + {"name": "dawn_walks_us_to_the_lab", "x": 4, "y": 10, "width": 1, "length": 6, "var": "VAR_SANDGEM_TOWN_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0396.bin coord_events[0] (164, 842), script 2"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json new file mode 100644 index 0000000000..9441a324e5 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json @@ -0,0 +1,54 @@ +{ + "name": "TwinleafTown", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0000.bin; zone_event narc_0389", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "############........############", + "#######....#........#....#######", + "#######....#........#....#######", + "############........############", + "######....................######", + "######....................######", + "######....................######", + "######..#####.............######", + "######..#####......####...######", + "######..#####......####...######", + "######..######.....####...######", + "######..#D###S.....#D##...######", + "######....................######", + "######....................######", + "######..........S.........######", + "######....................######", + "######....................######", + "######.............#####..######", + "######...####......#####..######", + "######...####......#####..######", + "######...####.....######..######", + "######...#D##.....S#D###..######", + "######....................######", + "######....................######", + "######....................######", + "######....................######", + "######....................######", + "######......wwwwwwww......######", + "############wwwwwwww############", + "############wwwwwwww############", + "############wwwwwwww############", + "############wwwwwwww############" + ], + "points": { + "house_doorstep": [20, 22], + "barry_doorstep": [9, 12], + "twinleaf_north_exit": [15, 0], + "player_house_door": [20, 21], + "rival_house_door": [9, 11] + }, + "objects": [ + {"name": "pokemon_breeder_f_0", "kind": "npc", "x": 23, "y": 14, "wander": {"x0": 22, "y0": 14, "x1": 24, "y1": 14}}, + {"name": "collector_1", "kind": "npc", "x": 12, "y": 25}, + {"name": "guitarist_3", "kind": "npc", "x": 12, "y": 5} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 12, "y": 3, "width": 8, "length": 1, "var": "VAR_TWINLEAF_TOWN_GUITARIST_TRIGGER_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0389.bin coord_events[0] (108, 867), script 4"}, + {"name": "barry_doorstep", "x": 9, "y": 12, "width": 1, "length": 1, "var": "VAR_TWINLEAF_TOWN_RIVAL_TRIGGER_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0389.bin coord_events[1] (105, 876), script 2"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json new file mode 100644 index 0000000000..f7e12b3b98 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json @@ -0,0 +1,47 @@ +{ + "name": "VerityLakefront", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0004.bin; zone_event narc_0319", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "....############################", + "....############################", + "....############################", + "....############################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################..##############", + "################..##############", + "################..##############", + "##############.S....<...........", + "##############......<.....####..", + "##############......<.....####..", + "##############......<...........", + "##############......<...........", + "##############......#vvvvv##vvvv", + "################..........##....", + "################................", + "##################..............", + "##################..............", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "points": { + "lakefront_arrival": [31, 23], + "below_lake_entrance": [17, 14], + "lake_entrance": [17, 12] + }, + "coord_events": [ + {"name": "lake_entrance", "x": 16, "y": 12, "width": 2, "length": 1, "var": "VAR_VERITY_LAKEFRONT_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0319.bin coord_events[0] (80, 844), script 3"}, + {"name": "coord_event_1", "x": 16, "y": 12, "width": 2, "length": 1, "var": "VAR_VERITY_LAKEFRONT_STATE", "value": 3, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0319.bin coord_events[1] (80, 844), script 4"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp new file mode 100644 index 0000000000..01174e5b99 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp @@ -0,0 +1,420 @@ +/* BDSP AutoStory Maps Tests + * + * From: https://github.com/PokemonAutomation/ + * + * The built-in story maps (Data/PokemonBDSP_AutoStory_Maps.h): every map + * loads, its points and objects stand where they can, and the lookups + * find what the story code asks for. + * + */ + +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Data/PokemonBDSP_AutoStory_Maps.h" +#include "PokemonBDSP_AutoStory_Maps_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +namespace{ + +using Pokemon::MapData; +using Pokemon::MapObject; +using Pokemon::TileKind; +using Pokemon::TilePos; + +bool is_ground(TileKind tile){ + return tile == TileKind::OPEN || tile == TileKind::TALL_GRASS; +} + +// The person or item that stands still on `pos`, if any. +const MapObject* standing_object(const MapData& map, const TilePos& pos){ + for (const MapObject& object : map.objects){ + if (!object.wander && object.x == pos.x && object.y == pos.y){ + return &object; + } + } + return nullptr; +} + +// "rocks" for "rocks.3", the point's own name otherwise. +std::string region_of(const std::string& point){ + const size_t dot = point.rfind('.'); + if (dot == std::string::npos || dot + 1 == point.size()){ + return point; + } + const bool numbered = std::all_of(point.begin() + dot + 1, point.end(), [](char c){ return '0' <= c && c <= '9'; }); + return numbered ? point.substr(0, dot) : point; +} + + +// Every built-in map parses through the shared loader, named as its file. +UnitTestResult test_story_maps_load(Logger& logger){ + if (bdsp_map_json().empty()){ + return std::string("No built-in maps."); + } + for (const auto& [name, json] : bdsp_map_json()){ + try{ + const MapData& map = story_map(name); + if (map.name != name){ + return std::format("{}.json: story_map() returned \"{}\".", name, map.name); + } + logger.log(std::format( + "{}: {} x {}, {} points, {} objects, {} patches", + name, map.tiles.width, map.tiles.height, map.points.size(), map.objects.size(), map.patches.size() + )); + }catch (Exception& e){ + return std::format("{}: {}: {}", name, e.name(), e.message()); + } + } + return true; +} + + +// What a named point must be on. Points not listed are ground the player +// stands on, with nobody standing there. +enum class PointOn{ + DOOR, + SIGN, + PERSON, // someone stands still there + WALL, // a rock until smashed +}; +struct PointCase{ + std::string map; + std::string point; // a point, or a region for all its tiles + PointOn on; +}; + +const std::vector& point_cases(){ + static const std::vector cases{ + {.map = "Route201", .point = "border_sign", .on = PointOn::SIGN}, + {.map = "Route202", .point = "trainer_tristan", .on = PointOn::PERSON}, + {.map = "Route202", .point = "trainer_natalie", .on = PointOn::PERSON}, + {.map = "Route202", .point = "trainer_logan", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_twin_left", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_twin_right", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_taylor", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_brandon", .on = PointOn::PERSON}, + {.map = "Route204South", .point = "trainer_samantha", .on = PointOn::PERSON}, + {.map = "Route204South", .point = "trainer_sarah", .on = PointOn::PERSON}, + {.map = "Route204South", .point = "trainer_tyler", .on = PointOn::PERSON}, + {.map = "SandgemTown", .point = "lab_door", .on = PointOn::DOOR}, + {.map = "SandgemTown", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "SandgemTown", .point = "pokemon_center_sign", .on = PointOn::SIGN}, + {.map = "SandgemTown", .point = "poke_mart_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "trainers_school_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "tv_station_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "poketch_president", .on = PointOn::PERSON}, + {.map = "JubilifeCity", .point = "clown_mart", .on = PointOn::PERSON}, + {.map = "JubilifeCity", .point = "clown_tv_station", .on = PointOn::PERSON}, + {.map = "JubilifeCity", .point = "clown_poketch_company", .on = PointOn::PERSON}, + {.map = "OreburghCity", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "OreburghCity", .point = "gym_door", .on = PointOn::DOOR}, + {.map = "OreburghCity", .point = "gate_mouth", .on = PointOn::DOOR}, + {.map = "OreburghGym", .point = "roark", .on = PointOn::PERSON}, + {.map = "OreburghGym", .point = "trainer_first_tier", .on = PointOn::PERSON}, + {.map = "OreburghGym", .point = "trainer_second_tier", .on = PointOn::PERSON}, + {.map = "OreburghMine", .point = "roark", .on = PointOn::PERSON}, + {.map = "RavagedPath", .point = "rocks", .on = PointOn::WALL}, + {.map = "FloaromaTown", .point = "old_woman", .on = PointOn::PERSON}, + {.map = "FloaromaTown", .point = "old_man", .on = PointOn::PERSON}, + {.map = "FloaromaTown", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "TwinleafTown", .point = "player_house_door", .on = PointOn::DOOR}, + {.map = "TwinleafTown", .point = "rival_house_door", .on = PointOn::DOOR}, + {.map = "PlayerHouse1F", .point = "mom", .on = PointOn::PERSON}, + {.map = "PlayerHouse1F", .point = "door", .on = PointOn::DOOR}, + {.map = "RivalHouse1F", .point = "door", .on = PointOn::DOOR}, + {.map = "RowansLab", .point = "door", .on = PointOn::DOOR}, + }; + return cases; +} + +// An error naming what `pos` should be, or empty when it is. +std::string check_point_on(const MapData& map, const TilePos& pos, PointOn on){ + const TileKind tile = map.tiles.at(pos); + const MapObject* object = standing_object(map, pos); + switch (on){ + case PointOn::DOOR: + return tile == TileKind::DOOR ? "" : "not a door"; + case PointOn::SIGN: + return tile == TileKind::SIGN ? "" : "not a sign"; + case PointOn::PERSON: + return is_ground(tile) && object != nullptr && object->kind == Pokemon::ObjectKind::NPC ? "" : "nobody stands still there"; + case PointOn::WALL: + return tile == TileKind::WALL ? "" : "not a wall"; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown PointOn."); +} + +std::string check_ground(const MapData& map, const TilePos& pos){ + if (!is_ground(map.tiles.at(pos))){ + return std::format("'{}', not ground", Pokemon::glyph_of(map.tiles.at(pos))); + } + return standing_object(map, pos) == nullptr ? "" : "someone stands there"; +} + + +// Every point is ground with nobody on it, unless point_cases() says +// otherwise; every numbered region runs from 1 with no gap; every case +// names a point the map has; every warp leads to a point that exists. +UnitTestResult test_story_map_points(Logger& logger){ + for (const PointCase& item : point_cases()){ + const MapData& map = story_map(item.map); + if (!map.points.contains(item.point) && !map.points.contains(item.point + ".1")){ + return std::format("{}: no point or region \"{}\".", item.map, item.point); + } + } + size_t checked = 0; + for (const auto& [name, json] : bdsp_map_json()){ + const MapData& map = story_map(name); + for (const auto& [point, pos] : map.points){ + const std::string region = region_of(point); + // map_region() counts from 1 and stops at a gap. + if (region != point && std::stoul(point.substr(region.size() + 1)) > map_region(name, region).size()){ + return std::format("{}: {} is not reached counting {}.1, {}.2, ...", name, point, region, region); + } + const auto it = std::ranges::find_if(point_cases(), [&](const PointCase& item){ + return item.map == name && item.point == region; + }); + const std::string error = it == point_cases().end() ? check_ground(map, pos) : check_point_on(map, pos, it->on); + if (!error.empty()){ + return std::format("{}: {} {}: {}.", name, point, Pokemon::to_string(pos), error); + } + checked++; + } + for (const Pokemon::MapWarp& warp : map.warps){ + if (!warp.to){ + return std::format("{}: the warp at {} leads nowhere.", name, Pokemon::to_string(TilePos{warp.x, warp.y})); + } + try{ + map_point(warp.to->map, warp.to->point); + }catch (InternalProgramError& e){ + return std::format("{}: the warp at {}: {}", name, Pokemon::to_string(TilePos{warp.x, warp.y}), e.message()); + } + } + } + logger.log(std::format("{} points checked.", checked)); + return true; +} + + +// Everyone stands on ground, and a wanderer's box holds their tile. +UnitTestResult test_story_map_objects(Logger& logger){ + size_t checked = 0; + for (const auto& [name, json] : bdsp_map_json()){ + const MapData& map = story_map(name); + for (const MapObject& object : map.objects){ + const TilePos pos{object.x, object.y}; + if (!is_ground(map.tiles.at(pos))){ + return std::format("{}: {} stands on '{}' at {}.", name, object.name, Pokemon::glyph_of(map.tiles.at(pos)), Pokemon::to_string(pos)); + } + const auto& box = object.wander; + if (box && (pos.x < box->x0 || pos.x > box->x1 || pos.y < box->y0 || pos.y > box->y1)){ + return std::format("{}: {} at {} is outside their wander box.", name, object.name, Pokemon::to_string(pos)); + } + checked++; + } + } + logger.log(std::format("{} objects checked.", checked)); + return true; +} + + +// Route 202's regions are the tiles the story code computed before they +// were data: each case is the old helper's loop, over the map's tiles. +struct RegionCase{ + std::string region; + std::function(const MapData& map)> old_helper; +}; + +// The tiles of a box, both corners inclusive, row by row, that someone +// can stand on: ground with nobody standing still there. +std::vector standable_in_box(const MapData& map, const TilePos& low, const TilePos& high){ + std::vector tiles; + for (int y = low.y; y <= high.y; y++){ + for (int x = low.x; x <= high.x; x++){ + if (map.tiles.contains({x, y}) && is_ground(map.tiles.at({x, y})) && standing_object(map, {x, y}) == nullptr){ + tiles.push_back({x, y}); + } + } + } + return tiles; +} + +std::vector after_training_area(const MapData& map){ + const TilePos catch_spot = map_point(map.name, "catch_spot"); + return standable_in_box(map, {catch_spot.x - 1, catch_spot.y - 2}, {catch_spot.x + 1, catch_spot.y + 2}); +} + +UnitTestResult test_route202_regions(Logger& logger){ + const std::vector cases{ + { + .region = "catch_save_area", // story_catch_save_area() + .old_helper = [](const MapData& map){ return standable_in_box(map, {15, 25}, {21, 29}); }, + }, + { + .region = "after_training_area", // story_after_training_area() + .old_helper = after_training_area, + }, + { + .region = "catch_grass", // the story table's catch_grass() + .old_helper = [](const MapData& map){ + std::vector grass = after_training_area(map); + std::erase_if(grass, [&](const TilePos& pos){ return map.tiles.at(pos) != TileKind::TALL_GRASS; }); + return grass; + }, + }, + { + .region = "jubilife_exit", // the story table's jubilife_exit() + .old_helper = [](const MapData& map){ return standable_in_box(map, {0, 0}, {map.tiles.width - 1, 3}); }, + }, + }; + const MapData& map = story_map("Route202"); + for (const RegionCase& item : cases){ + const std::vector region = map_region("Route202", item.region); + const std::vector expected = item.old_helper(map); + if (region != expected){ + return std::format("Route202 {}: {} tiles, the old helper computes {}.", item.region, region.size(), expected.size()); + } + logger.log(std::format("Route202 {}: {} tiles.", item.region, region.size())); + } + return true; +} + + +// map_point() and map_region() find what the story asks for, regions in +// order, and throw for what a map does not have. +struct LookupCase{ + std::string name; + std::function()> lookup; + // The tiles, or empty when the lookup must throw InternalProgramError. + std::vector expected; +}; + +UnitTestResult test_story_map_lookups(Logger&){ + const std::vector cases{ + { + .name = "a point", + .lookup = []{ return std::vector{map_point("SandgemTown", "pokemon_center_door")}; }, + .expected = {{17, 10}}, + }, + { + .name = "a region, in order", + .lookup = []{ return map_region("Route204North", "floaroma_waypoints"); }, + .expected = {{20, 27}, {11, 27}, {11, 22}, {19, 22}, {19, 12}, {11, 12}, {11, 5}}, + }, + { + .name = "an unknown map", + .lookup = []{ return std::vector{map_point("Route299", "door")}; }, + .expected = {}, + }, + { + .name = "an unknown point", + .lookup = []{ return std::vector{map_point("SandgemTown", "gym_door")}; }, + .expected = {}, + }, + { + .name = "a point is not a region", + .lookup = []{ return map_region("SandgemTown", "lab_door"); }, + .expected = {}, + }, + }; + for (const LookupCase& item : cases){ + std::vector tiles; + try{ + tiles = item.lookup(); + }catch (InternalProgramError&){ + if (item.expected.empty()){ + continue; + } + return item.name + ": threw InternalProgramError."; + } + if (tiles != item.expected){ + std::string read; + for (const TilePos& pos : tiles){ + read += " " + Pokemon::to_string(pos); + } + return std::format("{}: read{}, expected {} tiles{}.", item.name, read, item.expected.size(), + item.expected.empty() ? " (an InternalProgramError)" : ""); + } + } + return true; +} + + +struct StoryMapsInput{ + std::string name; + std::function body; +}; + +struct StoryMapsExpectation{ +}; + +struct StoryMapsCase{ + StoryMapsInput input; + StoryMapsExpectation expectation; +}; + +class Test_StoryMaps : public UnitTest{ +public: + explicit Test_StoryMaps(StoryMapsCase test_case) + : UnitTest("PokemonBDSP::AutoStory - Maps: " + test_case.input.name) + , m_case(std::move(test_case)) + {} + virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ + try{ + return m_case.input.body(logger); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } + +private: + StoryMapsCase m_case; +}; + +} // namespace + + +const StoryMapsCase STORY_MAPS_CASES[] = { + { + .input = {"the built-in JSON maps load", test_story_maps_load}, + .expectation = {}, + }, + { + .input = {"points stand where they can", test_story_map_points}, + .expectation = {}, + }, + { + .input = {"objects stand on open ground", test_story_map_objects}, + .expectation = {}, + }, + { + .input = {"map_point() and map_region()", test_story_map_lookups}, + .expectation = {}, + }, + { + .input = {"Route 202's regions are what the story computed", test_route202_regions}, + .expectation = {}, + }, +}; + +void add_tests_AutoStory_Maps(UnitTestDatabase& database){ + for (const auto& item : STORY_MAPS_CASES){ + database.add(item); + } +} + + + +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h new file mode 100644 index 0000000000..dc3d4cc52e --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h @@ -0,0 +1,24 @@ +/* BDSP AutoStory Maps Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Maps_Tests_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Maps_Tests_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// Registers AutoStory map unit tests with the database. +void add_tests_AutoStory_Maps(UnitTestDatabase& database); + + +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index fefe3be155..54765460e5 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1632,6 +1632,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/PokemonBDSP_Settings.h Source/PokemonBDSP/PokemonBDSP_Tests.cpp Source/PokemonBDSP/PokemonBDSP_Tests.h + Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp + Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomous.cpp Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomous.h Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomousState.cpp diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py new file mode 100644 index 0000000000..6454bb3b47 --- /dev/null +++ b/devtools/maps/bdsp_maps.py @@ -0,0 +1,671 @@ +"""Writes BDSP AutoStory's maps as JSON, from the Pokemon Diamond decompilation. + +TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonBDSP/Maps) when ready. + +Usage: + python3 devtools/maps/bdsp_maps.py # rewrite every map in OUT + python3 devtools/maps/bdsp_maps.py Route202 # rewrite one + python3 devtools/maps/bdsp_maps.py --check # regenerate, fail on any difference + +Each map is a rectangle of the decompilation's world grid (MAPS below), +decoded tile for tile by devtools/maps/decomp.py from the files cached in +~/.cache/maps/decomp (`python3 -m devtools.maps decomp` fetches missing ones +with `gh api`; this script never fetches, it names the file it lacks). BDSP +keeps Diamond/Pearl's layout, so Diamond is read unless a map says otherwise. +The tables below add what the decompilation doesn't say or BDSP changed: + +- POINTS: named tiles the story walks from and to. A region (a set of + tiles, in order where the order matters) is written as numbered points, + `.1`, `.2`, ...: the shared schema (Pokemon_MapData.h) + has single-tile points only. +- OBJECTS: which of the events file's objects BDSP shows. People hidden by + a story flag are left out, and people who stand still or wander are kept, + unless an entry here says otherwise (`show`, `hide`, `still`, `at`), or + adds someone the console found that Diamond doesn't have. +- WARP_DOORS: warp tiles written 'D' so that no plan steps onto them; the + other warp tiles are written '.', since the story arrives on them. +- WARPS: doors that lead to a point of another map in OUT. +- PATCHES: tiles measured on the console that differ from Diamond's, with + the run that measured them. The map's tiles stay the decompilation's; the + loader applies the patches. +- COORD_EVENTS: names for the events file's coord events (the scene + triggers), and triggers found on the console that the decompilation + doesn't have, with the run that found them. + +The legend is the schema's: '#' wall, '.' open, '~' tall grass, 'w' water, +'^' 'v' '<' '>' ledges hopped the arrow's way, 'D' a door or cave mouth +(walked into) or a warp tile in WARP_DOORS, 'S' a sign or mailbox (read by +facing it; a sign script on a wall too). Items on the ground are objects of +kind "item"; rocks, cut trees, berry soil and steam vents are '#'. + +Rules: terrain from the decomp, people and BDSP's edits from the console; +a route is coded only over tiles a human or a probe has walked; a map change +re-runs every checkpoint that walks it. Bulbapedia's DP images (12 px rows +for 16 px columns, cliffs painted tall) are for drawing routes over, not for +terrain. + +Each map lists its events file's coord events under "coord_events" (the +shared schema, Pokemon_MapData.h, leaves the key to the game; BDSP AutoStory's +Data/PokemonBDSP_AutoStory_CoordEvents.h reads it): `width` columns and +`length` rows from local (x, y), armed while the story variable `var` holds +`value`, and a `source` that names the file, the event's index, its world +tile and its script. Boxes are clipped to the map's rectangle. +""" +import argparse +import json +import os +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from devtools.maps import decomp # noqa: E402 + +OUT = 'SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps' +CACHE = Path.home() / '.cache' / 'maps' / 'decomp' +# When the cache was filled from the decompilations' default branches. +READ = '2026-10-31' + +LEDGES = {'north': '^', 'south': 'v', 'west': '<', 'east': '>'} +SIGN_GFX = ('MAILBOX',) +OBSTACLE_GFX = ('CUT_TREE', 'ROCK_SMASH', 'STRENGTH_BOULDER', 'BERRY_SOIL', 'VENT') +FACING = {'LOOK_NORTH': 'north', 'LOOK_SOUTH': 'south', 'LOOK_WEST': 'west', 'LOOK_EAST': 'east'} + + +# Decoded from pokeplatinum at this revision, not yet checked on the console. +INTERIOR = {'repo': decomp.Platinum.REPO, 'revision': 'c6689696d5fb', 'read': '2026-11-01', 'unverified': True} + +# Each map: its header constant, the world rectangle (x0, z0, width, +# height), and the decompilation when not Diamond. Local (x, y) = world +# (x - x0, z - z0). A map that joins another on the world grid keeps its +# rectangle's world corner, so story code can place them side by side. +MAPS = { + # Cells (3..4, 26) from z 839: the image-traced grid's columns and the + # game's rows. Console: the border sign read from (54, 4) facing east, + # row 3 open east of the grass, row 5 to Sandgem without a stop, the + # Potion lady on row 3 (2026-10-28 to 30, CFR "walk"). + 'Route201': {'header': 'ROUTE_201', 'rect': (96, 839, 64, 25)}, + # Cell (5, 25) from x 159. Console: under the south ledge at (22, 24), + # the save spot (30, 24) against the east trees, Natalie's challenge at + # (22, 22), column 30 north to row 4 (r75), the catch spot's walls. + 'Route202': {'header': 'ROUTE_202', 'rect': (159, 800, 36, 32)}, + # Cells (5..6, 23) from x 191: column 0 is Jubilife's east edge. + # Console (r227 hand pass, 2026-10-31): the Gate's warp at column 55, + # the ledge on row 15, the girl at (43, 17), row 18 straight west to + # Jubilife (35 taps). Diamond has four sand rows under the ledge where + # the console counted three (the first tap may only have turned). + 'Route203': {'header': 'ROUTE_203', 'rect': (191, 740, 61, 24)}, + # Cell (5, 21). Console: row 27 open under the cliff, the stairs, + # Taylor's sight at column 17 (human walk, 2026-10-31). Suspects: + # column 23 rows 12..19 blocked (r242a); three open rows under the cave, + # not four (r241a, r243a). + 'Route204North': {'header': 'ROUTE_204_NORTH', 'rect': (160, 672, 32, 32)}, + # Cell (5, 22). Console: north up column 14 stops at the signpost + # (14, 17); column 13 is grass to the cliff (13, 2); Sarah walked up and + # fought on row 21 (r233a, r234a). 01.08 passed once (r235a). + 'Route204South': {'header': 'ROUTE_204_SOUTH', 'rect': (160, 704, 32, 32), 'read': '2026-11-01'}, + # Cell (5, 26). Console (2026-10-29/30): the three doors, five signs, + # the doors' front row, the Lab's sign, the stop above the Mart (29, 5). + 'SandgemTown': {'header': 'SANDGEM', 'rect': (160, 832, 34, 32)}, + # Cells (4..5, 23..24). Console (2026-10-30/31): the lamps (43, 53) and + # (49, 53), north from (52, 53) to the Center's door, the School's door + # from the Center's doorstep, the east street to Route 203 (r230a), the + # north street, and the 00.05 people on their events-file tiles. + 'JubilifeCity': {'header': 'JUBLIFE', 'rect': (128, 736, 64, 64)}, + # Cells (8..9, 23..24); Platinum's map_data_022/023/175/024 are the + # same terrain. Console (r204 to r215): 20 D-pad taps from the + # Center's doorstep (46, 21) to below the Gym's door. + 'OreburghCity': {'header': 'OREBURGH', 'rect': (256, 736, 64, 64)}, + # The only cell of matrix 003; Platinum identical. + 'OreburghGate': {'header': 'OREBURGH_GATE_1F', 'rect': (0, 0, 32, 32)}, + # The only cell of matrix 113. 01.04 walks north up column 5. + 'OreburghGym': {'header': 'OREBURGH_GYM', 'rect': (0, 0, 32, 32)}, + # B2F, the coal room: matrix 006. Console (2026-10-28, r99): the + # corridor's rocks zigzag a row at a time, the room's second row passes + # under the worker, Roark a row above the bottom between two rocks; the + # walk north into the hall went up column 14 (the warp is column 15: a + # stick hold drifts, or BDSP's stairs are wider). + 'OreburghMine': {'header': 'OREBURGH_MINE_B2F', 'rect': (0, 0, 32, 32)}, + # Platinum's matrix 051 (Diamond's matrix is not in the cache), rows + # 0..53. Console (2026-10-31, r235b and D-pad probes): in from the south + # door onto (19, 50), up the stairs (18..20, 45..47) to the wall at row + # 39, the corridor three wide to row 41 and open east on rows 42..44, + # the rocks (22, 42), (22, 43), (23, 44) and the Potion (22, 44). Rows + # 0..29 were a hand trace from the DP image before; nothing walks there. + 'RavagedPath': {'header': 'RAVAGED_PATH', 'rect': (0, 0, 32, 54), 'repo': decomp.Platinum.REPO}, + # Cell (5, 20); the cell above carries the same header but is cut off + # from the town. Console: the old couple (2026-10-31 walk, r251a). + 'FloaromaTown': {'header': 'FLOAROMA', 'rect': (160, 640, 32, 32)}, + # Cell (3, 27). The player's house is the south-east one, door + # (20, 21); Barry's the north-west one, door (9, 11) (the interiors say + # so; Diamond's map constants name them the other way round). + 'TwinleafTown': {'header': 'TWINLEAF', 'rect': (96, 864, 32, 32)}, + # Cell (2, 26), of the four the header covers (the others are trees + # and water). The ledges on column 20 (rows 14..18) are hopped west. + 'VerityLakefront': {'header': 'VERITY_LAKEFRONT', 'rect': (64, 832, 32, 32)}, + # Cell (1, 1) of the lake's matrix 101: the shore where the starter + # scene plays. Only this cell is reached on foot. + 'LakeVerity': {'header': 'LAKE_VERITY', 'rect': (32, 32, 32, 32)}, + # 00.01 to 00.03's interiors, from pokeplatinum (c6689696d5fb), each + # the only cell of its own matrix: decomp only, not yet checked on the + # console. Mom's warning on the way out is a coord event on the + # player's door tile (6, 10). + 'PlayerHouse1F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, + 'PlayerHouse2F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, + 'RivalHouse1F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, + 'RivalHouse2F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, + 'RowansLab': {'header': 'SANDGEM_TOWN_POKEMON_RESEARCH_LAB', 'rect': (0, 0, 22, 18), **INTERIOR}, +} + + +def box(x0, y0, x1, y1): + """Every tile of a box, both corners inclusive, row by row.""" + return [(x, y) for y in range(y0, y1 + 1) for x in range(x0, x1 + 1)] + + +# Named tiles; a list is a region, written as numbered points. +POINTS = { + 'Route201': { + # The arrow sign to Sandgem. Console (CFR "walk"): east along row 4 + # stops at its west side and A reads it. + 'border_sign': (55, 4), + # The Poke Mart lady (10 Potions): Diamond spawns her at (44, 3), the + # console had her at (43, 3) and walking west along row 3 stopped at + # (44, 3), facing her (99715d5d6, f823453e9; r135a missed her on row 4). + 'potion_giver': (43, 3), + 'potion_giver_talk_from': (44, 3), + # Segment 00's lines (Segment00Lines). + 'route_201_arrival': (15, 24), + 'barry_warning': (15, 18), + 'route_201_return': (0, 14), + 'rowan_stop': (5, 14), + 'route_201_east_end': (63, 5), + }, + 'Route202': { + # 00.03's save after the catch: the catch patch's bottom row. The + # sign (24, 25) is 5 right and 2 up; south are trees (r253). + 'catch_spot': (19, 27), + # 00.04's save before the trainers, under the south ledge against + # the east trees (r113a..r117a). + 'save_spot': (30, 24), + 'trainer_tristan': (7, 13), + 'trainer_natalie': (22, 18), + 'trainer_logan': (26, 4), + # The middle of the gap to Jubilife, below its 3-wide neck. + 'exit_jubilife': (15, 0), + # The open rows at the top: north from any of them is Jubilife. + 'jubilife_exit': box(11, 0, 18, 3), + # Where 00.04b's save after the catch may stand: the patch and the + # lawn column east of it, down to the pocket where r252 saved. + 'catch_save_area': box(15, 25, 21, 27) + [(21, 28), (21, 29)], + # Where training may leave us (the catch spot's column and one either + # side, between the tree rows), and its grass: all of it. + 'after_training_area': box(18, 25, 20, 27), + 'catch_grass': box(18, 25, 20, 27), + }, + 'Route203': { + # The warp tile the Gate puts us on, and its open neighbours. + 'gate_exit': [(55, 9), (54, 9), (55, 8), (55, 10)], + # Row 18 between the cliffs: it runs straight west into Jubilife. + 'west_line': box(24, 18, 42, 18), + }, + 'Route204North': { + 'cave_exit': (20, 26), + # The line a human walked on 2026-10-31: out of the cave, west under + # the cliff, up the stairs, east (Taylor), north up column 19, west + # below the flower beds, north between them on column 11 ((12, 6) + # is blocked on the console). + 'floaroma_waypoints': [(20, 27), (11, 27), (11, 22), (19, 22), (19, 12), (11, 12), (11, 5)], + # The path north into Floaroma: walking north from here leaves. + 'floaroma_path': box(10, 0, 12, 5), + 'trainer_twin_left': (15, 7), + 'trainer_twin_right': (16, 7), + 'trainer_taylor': (17, 20), + 'trainer_brandon': (20, 12), + }, + 'Route204South': { + 'below_door': (11, 2), + # Where the walk north from Jubilife may stop, up to two tiles past + # the border (400 ms holds between looks). + 'jubilife_entry': [(14, 31), (14, 30), (14, 29)], + 'trainer_samantha': (11, 8), + 'trainer_sarah': (12, 21), + 'trainer_tyler': (7, 18), + }, + 'SandgemTown': { + # The walk south from Route 202 down the road's east side stops here, + # above the Mart where the road narrows (console, 2026-10-29/30). + 'route_202_entry_stop': (29, 5), + 'lab_door': (8, 10), + 'pokemon_center_door': (17, 10), + # Walking north from row 14 in its column stops below it. + 'pokemon_center_sign': (20, 10), + 'poke_mart_door': (27, 10), + # East along the doors' front row stops here, at the trees (CFR + # "walk", 2026-10-30): a known tile to plan north from. + 'front_row_east_end': (29, 11), + # The front row below the gap between the Center's and the Mart's + # signs: the only way north from that row that passes no door. + 'below_sign_gap': box(21, 11, 24, 11), + 'sandgem_arrival': (0, 12), + 'dawn_stop': (4, 12), + }, + 'JubilifeCity': { + # A doorway (warp tile) stepped onto from (40, 41). + 'trainers_school_door': (40, 40), + # Walked into from (52, 41); eleven taps west from its doorstep put + # us under the School's door (2026-10-30). + 'pokemon_center_door': (52, 40), + 'tv_station_door': (36, 15), + # Talked to from the east (also works from the south). + 'poketch_president': (46, 35), + # His spawn tile; Diamond has him wander a tile either side, the + # console had him a tile east of it. + 'clown_mart': (55, 33), + 'clown_tv_station': (36, 16), + # Talked to from the south; west along row 18 stops beside him. + 'clown_poketch_company': (15, 18), + # A tile west of 00.05's save below the steps, then north: on + # column 46 or 47 a few rows up (CFR "walk", 2026-10-30). + 'up_the_steps': box(46, 58, 47, 60), + # Where Route 203's road enters, rows 21..24 at the east edge. + 'east_entrance': box(61, 21, 63, 24), + # The north road where it leaves the plaza: north from here goes + # up the road to Route 204 and Rowan's scene. + 'north_road': box(44, 16, 48, 20), + }, + 'OreburghCity': { + 'pokemon_center_door': (47, 20), + # The taps say x 46, a column west of the door. + 'pokemon_center_doorstep': (46, 21), + 'gym_door': (26, 20), + 'gym_doorstep': (26, 21), + 'gate_mouth': (1, 13), + }, + 'OreburghGym': { + # Coming in puts us here; the door is below. + 'entrance': (5, 23), + 'roark': (5, 3), + 'trainer_first_tier': (4, 18), + 'trainer_second_tier': (7, 11), + }, + 'OreburghMine': { + 'roark': (18, 28), + 'corridor_exit_start': (14, 10), + # The first open row below the warp tile, where the walk in from + # the hall leaves us. + 'corridor_top': box(14, 2, 16, 2), + }, + 'RavagedPath': { + 'entrance': (19, 50), + 'east_end': (29, 44), + 'above_exit': (28, 44), + # Rock Smash rocks, open once smashed. + 'rocks': [(11, 36), (7, 37), (12, 37), (8, 38), (10, 38), (10, 39), (13, 39), + (7, 40), (9, 40), (17, 40), (12, 41), (22, 42), (22, 43), (23, 44)], + # West of the two rocks on rows 42 and 43. + 'before_rocks': [(21, 42), (21, 43)], + }, + 'FloaromaTown': { + 'route_204_arrival': (11, 29), + 'old_woman': (3, 25), + 'old_man': (2, 25), + 'pokemon_center_door': (16, 26), + }, + 'TwinleafTown': { + 'house_doorstep': (20, 22), + # Barry's scene fires here, below his door. + 'barry_doorstep': (9, 12), + 'twinleaf_north_exit': (15, 0), + 'player_house_door': (20, 21), + 'rival_house_door': (9, 11), + }, + 'VerityLakefront': { + 'lakefront_arrival': (31, 23), + 'below_lake_entrance': (17, 14), + # The lake's scene fires here; it is assumed to leave us here too. + 'lake_entrance': (17, 12), + }, + 'PlayerHouse1F': {'stairs': (10, 3), 'mom': (7, 8), 'door': (6, 10)}, + # A new game starts on (4, 6); the TV is the signs at (3, 4) and (4, 4). + 'PlayerHouse2F': {'stairs': (8, 4), 'start': (4, 6)}, + 'RivalHouse1F': {'door': (6, 10), 'stairs': (2, 3)}, + # Barry stands at (9, 5) facing his PC; he is talked to from the west. + 'RivalHouse2F': {'stairs': (3, 4), 'barry_talk': (8, 5)}, + # Rowan is talked to from below, facing north. + 'RowansLab': {'door': (7, 15), 'rowan_talk': (7, 6)}, +} + + +# Overrides by events-file object index. `show`: a person hidden by a +# story flag who is there when the story walks the map. `hide`: someone +# BDSP doesn't have, or the story never meets. `still`: a wanderer who +# stood still on the console. `at`: where the console found them. `add`: +# people Diamond doesn't have, from the console. +OBJECTS = { + 'Route201': { + # The Potion lady stands a tile off her spawn on the console, and + # her talk is walked to, not planned (POINTS). + 1: {'hide': 'her talk is walked to, not planned'}, + }, + 'Route203': { + # Stands still in Diamond, wanders near the Gate in BDSP (console). + 0: {'hide': 'wanders in BDSP'}, + }, + 'Route204North': { + # Bug Catcher Brandon paces in Diamond from (22, 13); he stopped us + # on row 12 (r246a), and the DP map draws him at (20, 12). + 0: {'at': (20, 12), 'still': 'r246a', 'name': 'bug_catcher_brandon'}, + }, + 'SandgemTown': { + # Stand still in Diamond, wander on the console. + 1: {'hide': 'wanders in BDSP'}, + 2: {'hide': 'wanders in BDSP'}, + }, + 'JubilifeCity': { + # 00.05's story people, hidden before and after it. + 24: {'show': '00.05', 'name': 'poketch_president'}, + 28: {'show': '00.05', 'name': 'clown_tv_station'}, + # Wander in Diamond; stood still on the console (00.05, three runs). + 25: {'show': '00.05', 'still': '00.05, three runs', 'name': 'clown_mart'}, + 26: {'show': '00.05', 'still': '00.05, three runs', 'name': 'clown_poketch_company'}, + }, + 'FloaromaTown': { + # Grunts at the Meadow until the Valley Windworks is cleared, and + # the man with Cyrus's sprite (whether he is there is for the console). + 7: {'show': 'until the Valley Windworks'}, + 8: {'show': 'until the Valley Windworks'}, + 3: {'show': 'not yet checked on the console'}, + 'add': [ + # Not in Diamond's data. Walking north from (3, 29) stopped 3 + # tiles up at the old woman (r251a). + {'name': 'old_woman', 'x': 3, 'y': 25, 'measured_by': 'r251a, 2026-10-31'}, + {'name': 'old_man', 'x': 2, 'y': 25, 'measured_by': 'r251a, 2026-10-31'}, + ], + }, + 'OreburghMine': { + # Hidden once his scene here is over; the story walks to him. + 0: {'show': '01.03', 'name': 'roark'}, + }, + 'RivalHouse2F': { + # Barry in his room, until he leaves for the lake. + 0: {'show': '00.01', 'name': 'barry'}, + }, + 'RowansLab': { + # Rowan at his desk when the story walks in. + 0: {'show': '00.03', 'name': 'rowan'}, + }, + 'LakeVerity': { + # The starter scene's actors, where the walk in ends. + 0: {'show': 'the starter scene', 'name': 'rowan'}, + 1: {'show': 'the starter scene', 'name': 'counterpart'}, + 3: {'show': 'the starter scene'}, + }, +} + + +WARP_DOORS = { + # The Ravaged Path's door: its warp tile is the mouth. + 'Route204South': [(11, 1)], + # The Route 218 gate, and the doorways of the School and the Global + # Terminal: entered, never stood on by a route. + 'JubilifeCity': [(0, 22), (0, 23), (40, 40), (21, 42)], + # The way up to the entrance hall: stepping on it leaves the room. + 'OreburghMine': [(15, 1)], +} + + +# Stepping onto (x, y) puts the player at a point of another map. +WARPS = { + 'PlayerHouse1F': [(6, 10, 'TwinleafTown', 'player_house_door')], + 'RivalHouse1F': [(6, 10, 'TwinleafTown', 'rival_house_door')], + 'RowansLab': [(7, 15, 'SandgemTown', 'lab_door')], +} + + +PATCHES = { + 'Route202': ( + # BDSP has trees where Diamond's catch patch has its two bottom rows: + # west along row 29 stopped at column 21, beside them. + [(x, y, '#', 'r253, 2026-11-01') for y in (28, 29) for x in range(15, 21)] + # South from the pocket (21, 29), where the catch's save loads, + # was blocked at once. + + [(21, 30, '#', 'r254, 2026-11-01')] + ), + 'PlayerHouse1F': [ + # Open on the console, a wall in the data: west along row 8 stops + # at x 4 (r32). + (4, 8, '.', 'r32, 2026-11-01'), + ], + 'Route201': ( + # Row 18 west of the exit path is trees on the console, open in the + # decomp, and the 201 sign stands at (13, 18), not (13, 17) (r16, + # walked by hand). + [(x, 18, '#', 'r16, 2026-10-31') for x in range(8, 13)] + + [(13, 18, 'S', 'r16, 2026-10-31'), (13, 17, '.', 'r16, 2026-10-31')] + ), + 'Route204North': [ + # Grass in the data, blocked on the console twice. + (12, 6, '#', 'r248a, 2026-10-31'), + ], +} + + +# Coord events by events-file index: their names (the default is +# coord_event_), and under 'add' the triggers the console found that +# the decompilation doesn't have. +COORD_EVENTS = { + # Barry stops us on row 18 (POINTS barry_warning); Rowan on column 5 + # (POINTS rowan_stop). + 'Route201': {0: 'barry_warning', 1: 'rowan_stop'}, + 'Route202': {0: 'catch_tutorial'}, + 'SandgemTown': {0: 'dawn_walks_us_to_the_lab'}, + 'JubilifeCity': {0: 'dawn_at_the_steps', 4: 'poketch_president'}, + 'TwinleafTown': {1: 'barry_doorstep'}, + 'VerityLakefront': {0: 'lake_entrance'}, + 'PlayerHouse1F': { + 0: 'moms_warning', + 'add': [ + {'name': 'moms_call', 'x': 6, 'y': 9, 'width': 1, 'length': 1, 'var': 'CONSOLE_MOMS_CALL', 'value': 0, + 'measured_by': 'console r45, r46 (2026-11-01): after her heal Mom calls us back as we step above' + ' the door; no coord event in the decomp'}, + ], + }, +} + + +def no_fetch(repo, path): + raise SystemExit(f'{repo}/{path} is not in {CACHE}. Fetch it with `python3 -m devtools.maps decomp ...` first.') + + +def glyph(attr): + behaviour = attr & 0xff + if behaviour in decomp.JUMP: + return LEDGES[decomp.JUMP[behaviour]] + if behaviour in decomp.JUMP_TWICE: + return LEDGES[decomp.JUMP_TWICE[behaviour]] + if behaviour == decomp.DOOR or behaviour in decomp.CAVE_MOUTHS: + return 'D' + if behaviour in decomp.WARP_TILES or behaviour in decomp.WARP_STAIRS: + return 'W' + if behaviour in decomp.WATER: + return 'w' + if attr & decomp.BLOCKED: + return '#' + if behaviour in decomp.TALL_GRASS: + return '~' + return '.' + + +def wander_box(obj, x, y, width, height): + return {'x0': max(0, x - obj.range_x), 'y0': max(0, y - obj.range_z), + 'x1': min(width - 1, x + obj.range_x), 'y1': min(height - 1, y + obj.range_z)} + + +def place_objects(name, events, rect, rows): + """Writes signs and obstacles into `rows`; returns the objects.""" + x0, z0, width, height = rect + overrides = OBJECTS.get(name, {}) + objects = [] + for index, obj in enumerate(events.objects): + x, y = obj.x - x0, obj.z - z0 + if not (0 <= x < width and 0 <= y < height): + continue + override = overrides.get(index, {}) + if obj.gfx in OBSTACLE_GFX: + rows[y][x] = '#' + continue + if obj.gfx == decomp.ITEM: + objects.append({'name': f'item_{index}', 'kind': 'item', 'x': x, 'y': y}) + continue + if obj.hidden and 'show' not in override or 'hide' in override: + continue + if 'SIGN' in obj.gfx or obj.gfx in SIGN_GFX: + rows[y][x] = 'S' + continue + x, y = override.get('at', (x, y)) + entry = {'name': override.get('name', f'{obj.gfx.lower()}_{index}'), 'kind': 'npc', 'x': x, 'y': y} + if obj.movement in FACING: + entry['facing'] = FACING[obj.movement] + if obj.movement.startswith(('WANDER', 'WALK')) and 'still' not in override: + entry['wander'] = wander_box(obj, x, y, width, height) + objects.append(entry) + for added in overrides.get('add', []): + objects.append({'name': added['name'], 'kind': 'npc', 'x': added['x'], 'y': added['y']}) + return objects + + +def coord_events(name, repo, game, events_id, rect): + """The map's coord events: the events file's, clipped to `rect`, then COORD_EVENTS' additions.""" + x0, z0, width, height = rect + names = COORD_EVENTS.get(name, {}) + out = [] + for index, event in enumerate(game.events(events_id).coord_events): + left, top = max(event.x, x0), max(event.z, z0) + right, bottom = min(event.x + event.width, x0 + width), min(event.z + event.length, z0 + height) + if left >= right or top >= bottom: + continue + out.append({ + 'name': names.get(index, f'coord_event_{index}'), + 'x': left - x0, 'y': top - z0, 'width': right - left, 'length': bottom - top, + 'var': event.var, 'value': event.value, + 'source': f'{repo} {game.events_path(events_id)} coord_events[{index}] ({event.x}, {event.z}),' + f' script {event.script}', + }) + for added in names.get('add', []): + fields = {k: v for k, v in added.items() if k != 'measured_by'} + out.append({**fields, 'source': added['measured_by']}) + return out + + +def decode_map(name): + spec = MAPS[name] + repo = spec.get('repo', decomp.Diamond.REPO) + game = decomp.game_for(repo, CACHE, None, no_fetch) + header = game.header(spec['header']) + matrix = game.matrix(header.matrix_id) + world = decomp.World(game, matrix) + x0, z0, width, height = spec['rect'] + rows = [] + for y in range(height): + row = [] + for x in range(width): + attr = world.attr(x0 + x, z0 + y) + if attr is None: + raise SystemExit(f'{name}: ({x}, {y}) is outside matrix {header.matrix_id}.') + row.append(glyph(attr)) + rows.append(row) + events = game.events(header.events_id) + for sign in events.signs: + x, y = sign.x - x0, sign.z - z0 + if 0 <= x < width and 0 <= y < height and rows[y][x] == '#': + rows[y][x] = 'S' + objects = place_objects(name, events, spec['rect'], rows) + doors = set(WARP_DOORS.get(name, [])) + for y, row in enumerate(rows): + for x, tile in enumerate(row): + if tile == 'W': + row[x] = 'D' if (x, y) in doors else '.' + # Every cell the rectangle touches, also when it does not start on a cell's edge. + C = decomp.CELL + lands = sorted({world.land_id(C * col, C * row) for row in range(z0 // C, (z0 + height - 1) // C + 1) + for col in range(x0 // C, (x0 + width - 1) // C + 1)} - {None}) + unverified = '; decomp only, not checked on the console' if spec.get('unverified') else '' + source = { + 'decomp_file': f'{repo}: ' + ', '.join(game.land_path(land) for land in lands) + + f'; {game.events_name(header.events_id)}{unverified}', + 'revision': spec.get('revision', 'default branch'), + 'date': spec.get('read', READ), + } + triggers = coord_events(name, repo, game, header.events_id, spec['rect']) + return [''.join(row) for row in rows], objects, source, triggers + + +def points_of(name): + points = {} + for point, value in POINTS.get(name, {}).items(): + if isinstance(value, list): + for i, pos in enumerate(value, 1): + points[f'{point}.{i}'] = pos + else: + points[point] = value + return points + + +def map_text(name): + """The map's JSON: one line per row, point, object and patch.""" + tiles, objects, source, triggers = decode_map(name) + lines = ['{', f' "name": {json.dumps(name)},', f' "source": {json.dumps(source)},', ' "tiles": ['] + lines += [f' {json.dumps(row)},' for row in tiles] + lines[-1] = lines[-1].rstrip(',') + lines.append(' ]') + + def section(key, items): + if not items: + return + lines[-1] += ',' + lines.append(f' "{key}": ' + ('{' if key == 'points' else '[')) + lines.extend(f' {item},' for item in items) + lines[-1] = lines[-1].rstrip(',') + lines.append(' ' + ('}' if key == 'points' else ']')) + + section('points', [f'{json.dumps(p)}: [{x}, {y}]' for p, (x, y) in points_of(name).items()]) + section('warps', [json.dumps({'x': x, 'y': y, 'to_map': m, 'to_point': p}) for x, y, m, p in WARPS.get(name, [])]) + section('objects', [json.dumps(o) for o in objects]) + section('coord_events', [json.dumps(e) for e in triggers]) + section('patches', [json.dumps({'x': x, 'y': y, 'glyph': g, 'measured_by': m}) + for x, y, g, m in PATCHES.get(name, [])]) + lines.append('}') + return '\n'.join(lines) + '\n' + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + parser.add_argument('--out', default=OUT, 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: every map in MAPS') + args = parser.parse_args() + names = args.maps or sorted(MAPS) + for name in names: + if name not in MAPS: + parser.error(f'{name}: not in MAPS') + texts = {name: map_text(name) for name in names} + if args.check: + differ = [n for n, text in texts.items() + if not os.path.exists(os.path.join(args.out, n + '.json')) + or open(os.path.join(args.out, n + '.json'), newline='').read() != text] + if not args.maps: + extra = sorted(f[:-5] for f in os.listdir(args.out) if f.endswith('.json') and f[:-5] not in MAPS) + differ += [f'{n} (not in MAPS)' for n in extra] + if differ: + sys.exit(f'Differ from the generator: {", ".join(differ)}. Regenerate them without --check.') + print(f'{len(texts)} maps in {args.out} match the generator.') + return + os.makedirs(args.out, exist_ok=True) + for name, text in texts.items(): + with open(os.path.join(args.out, name + '.json'), 'w', newline='') as file: + file.write(text) + print(f'Wrote {len(texts)} maps to {args.out}.') + + +if __name__ == '__main__': + main() diff --git a/devtools/maps/decomp.py b/devtools/maps/decomp.py index f5e85b6c55..881fa19347 100644 --- a/devtools/maps/decomp.py +++ b/devtools/maps/decomp.py @@ -12,8 +12,11 @@ pokeplatinum keeps matrices and events as JSON and its map headers in include/data/map_headers.h. pokediamond keeps them as binary NARC entries (the layout pokeplatinum's tools/jsoncnv packs) with the header table in -arm9/src/map_header.c, and gives sprites, movement types and trainer types -by number; pokeplatinum's generated/*.txt tables name them. BDSP keeps +arm9/src/map_header.c, and gives sprites, movement types, trainer types and +story variables by number; pokeplatinum's generated/*.txt tables name them. +An events file lists bg events, object events, warps and coord events (the +scene triggers: stepping onto one of a box of tiles while a story variable +holds a value starts a script), in that order. BDSP keeps Diamond/Pearl's layout; Platinum's few changes show up on the console. """ import datetime @@ -153,11 +156,24 @@ class Sign: z: int +@dataclass +class CoordEvent: + """A scene trigger: `width` x `length` tiles from (x, z), armed while `var` holds `value`.""" + script: int + x: int + z: int + width: int + length: int + var: str + value: int + + @dataclass class Events: objects: list = field(default_factory=list) warps: list = field(default_factory=list) signs: list = field(default_factory=list) + coord_events: list = field(default_factory=list) def strip_prefix(name, prefix): @@ -203,8 +219,11 @@ def land_data(self, land_id): def events_name(self, events_id): return f'{events_id}.json' + def events_path(self, events_id): + return f'res/field/events/{events_id}.json' + def events(self, events_id): - data = json.loads(self.source.text(f'res/field/events/{events_id}.json')) + data = json.loads(self.source.text(self.events_path(events_id))) events = Events() for o in data.get('object_events', []): trainer = o['trainer_type'] != 'TRAINER_TYPE_NONE' @@ -222,6 +241,9 @@ def events(self, events_id): for b in data.get('bg_events', []): if b['type'] in BG_SIGNS: events.signs.append(Sign(b['x'], b['z'])) + for c in data.get('coord_events', []): + events.coord_events.append(CoordEvent( + c['script'], c['x'], c['z'], c['width'], c['length'], c['var'], c['value'])) return events @@ -231,6 +253,11 @@ class Diamond: Sprites, movement types and trainer types are numbers there; `names` is a Source for pret/pokeplatinum, whose generated/*.txt tables list them in order (checked on Route 204 North: the same twelve objects come out). + Story variables are numbered from 0x4000 in both games; Platinum's + generated/vars_flags.txt (a C enum) names them (checked: the coord events + that pokeplatinum also has on Route 202, Sandgem, Jubilife and the + player's house come out with Platinum's variable names). A number that + table does not name stays a number. """ REPO = 'pret/pokediamond' GAME = 'Pokemon Diamond' @@ -271,6 +298,16 @@ def named(self, table, prefix, value): entries = self.table(table) return strip_prefix(entries[value], prefix) if value < len(entries) else str(value) + def var_name(self, number): + if 'vars' not in self._tables: + values = enum_values(self.names.text('generated/vars_flags.txt').splitlines()) + names = {} + for name, value in values.items(): + if name.startswith('VAR_'): + names.setdefault(value, name) + self._tables['vars'] = names + return self._tables['vars'].get(number, str(number)) + def matrix(self, matrix_id): data = self.source.read(f'files/fielddata/mapmatrix/map_matrix/narc_{matrix_id:04d}.bin') width, height, has_headers, has_altitudes, name_len = struct.unpack_from('<5B', data, 0) @@ -297,8 +334,11 @@ def land_data(self, land_id): def events_name(self, events_id): return f'zone_event narc_{events_id:04d}' + def events_path(self, events_id): + return f'files/fielddata/eventdata/zone_event_release/narc_{events_id:04d}.bin' + def events(self, events_id): - data = self.source.read(f'files/fielddata/eventdata/zone_event_release/narc_{events_id:04d}.bin') + data = self.source.read(self.events_path(events_id)) events = Events() offset = 0 (count,) = struct.unpack_from(' value of a C enum's lines: `NAME` (the previous value + 1) or `NAME = number or NAME`.""" + values = {} + following = 0 + for line in lines: + name, _, value = (part.strip() for part in line.partition('=')) + if not name: + continue + if value: + following = int(value, 0) if value[0].isdigit() else values[value] + values[name] = following + following += 1 + return values + + def title(constant): """AROMA_LADY_TAYLOR -> Aroma Lady Taylor.""" return ' '.join(w.capitalize() for w in constant.split('_')) diff --git a/devtools/maps/tests/test_decomp.py b/devtools/maps/tests/test_decomp.py index daf514c156..57f25908a5 100644 --- a/devtools/maps/tests/test_decomp.py +++ b/devtools/maps/tests/test_decomp.py @@ -4,7 +4,9 @@ import unittest from pathlib import Path -from devtools.maps import decomp +from unittest import mock + +from devtools.maps import bdsp_maps, decomp # One synthetic 32 x 32 cell, open ground but for the first row: # wall, tall grass, ledge south, ledge east, door, warp tile, water, cave wall, cave floor. @@ -67,7 +69,9 @@ def platinum_tree(root): ], 'warp_events': [{'x': 5, 'z': 0, 'dest_header_id': 'MAP_HEADER_OTHER', 'dest_warp_id': 0}, {'x': 4, 'z': 1, 'dest_header_id': 'MAP_HEADER_OTHER', 'dest_warp_id': 1}], - 'coord_events': []})) + 'coord_events': [ + {'script': 6, 'x': 2, 'z': 3, 'y': 0, 'width': 1, 'length': 5, 'var': 'VAR_TEST_STATE', 'value': 0}, + {'script': 7, 'x': 30, 'z': 0, 'y': 0, 'width': 4, 'length': 1, 'var': '16390', 'value': 2}]})) def diamond_tree(root): @@ -88,6 +92,7 @@ def diamond_tree(root): gfx[4], gfx[20], gfx[87], gfx[94] = 'OBJ_EVENT_GFX_YOUNGSTER', 'OBJ_EVENT_GFX_LASS', 'OBJ_EVENT_GFX_POKEBALL', 'OBJ_EVENT_GFX_ARROW_SIGNPOST' write(root, 'generated/movement_types.txt', '\n'.join(movement)) write(root, 'generated/object_events_gfx.txt', '\n'.join(gfx)) + write(root, 'generated/vars_flags.txt', 'FLAG_UNUSED\nVARS_START = 16384\nVAR_UNUSED_0x4000 = VARS_START\nVAR_TEST_STATE\n') def obj(gfx_id, movement_id, trainer, flag, data0, range_x, x, z): return struct.pack('<14HI', 0, gfx_id, movement_id, trainer, flag, 1, 0, data0, 0, 0, range_x, 0, x, z, 0) @@ -97,7 +102,8 @@ def obj(gfx_id, movement_id, trainer, flag, data0, range_x, x, z): events += obj(87, 0, 0, 1000, 0, 0, 9, 0) + obj(94, 0, 0, 0, 13, 0, 10, 0) + obj(20, 15, 1, 0, 4, 0, 11, 0) events += obj(4, 5, 0, 0, 0, 2, 12, 0) + obj(20, 0, 0, 0, 0, 0, 40, 0) events += struct.pack('