Archipelago/worlds/pokemon_emerald/sanity_check.py

308 lines
15 KiB
Python

"""
Looks through data object to double-check it makes sense. Will fail for missing or duplicate definitions or
duplicate claims and give warnings for unused and unignored locations or warps.
"""
import logging
from typing import List
from .data import load_json_data, data
_IGNORABLE_LOCATIONS = frozenset({
"HIDDEN_ITEM_TRICK_HOUSE_NUGGET", # Is permanently mssiable and has special behavior that sets the flag early
# Duplicate rival fights. All variations are represented by the Brandon + Mudkip version
"TRAINER_BRENDAN_ROUTE_103_TREECKO_REWARD",
"TRAINER_BRENDAN_ROUTE_103_TORCHIC_REWARD",
"TRAINER_MAY_ROUTE_103_MUDKIP_REWARD",
"TRAINER_MAY_ROUTE_103_TREECKO_REWARD",
"TRAINER_MAY_ROUTE_103_TORCHIC_REWARD",
"TRAINER_BRENDAN_ROUTE_110_TREECKO_REWARD",
"TRAINER_BRENDAN_ROUTE_110_TORCHIC_REWARD",
"TRAINER_MAY_ROUTE_110_MUDKIP_REWARD",
"TRAINER_MAY_ROUTE_110_TREECKO_REWARD",
"TRAINER_MAY_ROUTE_110_TORCHIC_REWARD",
"TRAINER_BRENDAN_ROUTE_119_TREECKO_REWARD",
"TRAINER_BRENDAN_ROUTE_119_TORCHIC_REWARD",
"TRAINER_MAY_ROUTE_119_MUDKIP_REWARD",
"TRAINER_MAY_ROUTE_119_TREECKO_REWARD",
"TRAINER_MAY_ROUTE_119_TORCHIC_REWARD",
"TRAINER_BRENDAN_RUSTBORO_TREECKO_REWARD",
"TRAINER_BRENDAN_RUSTBORO_TORCHIC_REWARD",
"TRAINER_MAY_RUSTBORO_MUDKIP_REWARD",
"TRAINER_MAY_RUSTBORO_TREECKO_REWARD",
"TRAINER_MAY_RUSTBORO_TORCHIC_REWARD",
"TRAINER_BRENDAN_LILYCOVE_TREECKO_REWARD",
"TRAINER_BRENDAN_LILYCOVE_TORCHIC_REWARD",
"TRAINER_MAY_LILYCOVE_MUDKIP_REWARD",
"TRAINER_MAY_LILYCOVE_TREECKO_REWARD",
"TRAINER_MAY_LILYCOVE_TORCHIC_REWARD",
})
_IGNORABLE_WARPS = frozenset({
# Trick House
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE2:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE2:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE3:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE3:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE4:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE4:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE5:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE5:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE6:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE6:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:10/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:9",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:11/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:12",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:12/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:11",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:3/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:4",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:4/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:3",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:5/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:6",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:6/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:5",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:7/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:8",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:8/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:7",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:9/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:10",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE8:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE8:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
# Department store elevator
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0,1/MAP_DYNAMIC:-1!",
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_1F:3/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_2F:2/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_3F:2/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_4F:2/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_5F:1/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
# Intro truck
"MAP_INSIDE_OF_TRUCK:0,1,2/MAP_DYNAMIC:-1!",
# Battle Frontier
"MAP_BATTLE_FRONTIER_BATTLE_DOME_CORRIDOR:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1!",
"MAP_BATTLE_FRONTIER_BATTLE_DOME_PRE_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1!",
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:2",
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:0,1/MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:2",
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0",
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:3/MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0!",
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:0",
# Terra Cave and Marine Cave
"MAP_TERRA_CAVE_ENTRANCE:0/MAP_DYNAMIC:-1!",
"MAP_ROUTE113:1/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE113:2/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE114:3/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE114:4/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE115:1/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE115:2/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE116:3/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE116:4/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE118:0/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_ROUTE118:1/MAP_TERRA_CAVE_ENTRANCE:0!",
"MAP_UNDERWATER_MARINE_CAVE:0/MAP_DYNAMIC:-1!",
"MAP_UNDERWATER_ROUTE105:0/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE105:1/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE125:0/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE125:1/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE127:0/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE127:1/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE129:0/MAP_UNDERWATER_MARINE_CAVE:0!",
"MAP_UNDERWATER_ROUTE129:1/MAP_UNDERWATER_MARINE_CAVE:0!",
# Altering Cave
"MAP_ALTERING_CAVE:0/MAP_ROUTE103:0",
"MAP_ROUTE103:0/MAP_ALTERING_CAVE:0",
# Event islands
"MAP_BIRTH_ISLAND_EXTERIOR:0/MAP_BIRTH_ISLAND_HARBOR:0",
"MAP_BIRTH_ISLAND_HARBOR:0/MAP_BIRTH_ISLAND_EXTERIOR:0",
"MAP_FARAWAY_ISLAND_ENTRANCE:0,1/MAP_FARAWAY_ISLAND_INTERIOR:0,1",
"MAP_FARAWAY_ISLAND_INTERIOR:0,1/MAP_FARAWAY_ISLAND_ENTRANCE:0,1",
"MAP_SOUTHERN_ISLAND_EXTERIOR:0,1/MAP_SOUTHERN_ISLAND_INTERIOR:0,1",
"MAP_SOUTHERN_ISLAND_INTERIOR:0,1/MAP_SOUTHERN_ISLAND_EXTERIOR:0,1",
"MAP_NAVEL_ROCK_B1F:0/MAP_NAVEL_ROCK_ENTRANCE:0",
"MAP_NAVEL_ROCK_B1F:1/MAP_NAVEL_ROCK_FORK:1",
"MAP_NAVEL_ROCK_BOTTOM:0/MAP_NAVEL_ROCK_DOWN11:0",
"MAP_NAVEL_ROCK_DOWN01:0/MAP_NAVEL_ROCK_FORK:2",
"MAP_NAVEL_ROCK_DOWN01:1/MAP_NAVEL_ROCK_DOWN02:0",
"MAP_NAVEL_ROCK_DOWN02:0/MAP_NAVEL_ROCK_DOWN01:1",
"MAP_NAVEL_ROCK_DOWN02:1/MAP_NAVEL_ROCK_DOWN03:0",
"MAP_NAVEL_ROCK_DOWN03:0/MAP_NAVEL_ROCK_DOWN02:1",
"MAP_NAVEL_ROCK_DOWN03:1/MAP_NAVEL_ROCK_DOWN04:0",
"MAP_NAVEL_ROCK_DOWN04:0/MAP_NAVEL_ROCK_DOWN03:1",
"MAP_NAVEL_ROCK_DOWN04:1/MAP_NAVEL_ROCK_DOWN05:0",
"MAP_NAVEL_ROCK_DOWN05:0/MAP_NAVEL_ROCK_DOWN04:1",
"MAP_NAVEL_ROCK_DOWN05:1/MAP_NAVEL_ROCK_DOWN06:0",
"MAP_NAVEL_ROCK_DOWN06:0/MAP_NAVEL_ROCK_DOWN05:1",
"MAP_NAVEL_ROCK_DOWN06:1/MAP_NAVEL_ROCK_DOWN07:0",
"MAP_NAVEL_ROCK_DOWN07:0/MAP_NAVEL_ROCK_DOWN06:1",
"MAP_NAVEL_ROCK_DOWN07:1/MAP_NAVEL_ROCK_DOWN08:0",
"MAP_NAVEL_ROCK_DOWN08:0/MAP_NAVEL_ROCK_DOWN07:1",
"MAP_NAVEL_ROCK_DOWN08:1/MAP_NAVEL_ROCK_DOWN09:0",
"MAP_NAVEL_ROCK_DOWN09:0/MAP_NAVEL_ROCK_DOWN08:1",
"MAP_NAVEL_ROCK_DOWN09:1/MAP_NAVEL_ROCK_DOWN10:0",
"MAP_NAVEL_ROCK_DOWN10:0/MAP_NAVEL_ROCK_DOWN09:1",
"MAP_NAVEL_ROCK_DOWN10:1/MAP_NAVEL_ROCK_DOWN11:1",
"MAP_NAVEL_ROCK_DOWN11:0/MAP_NAVEL_ROCK_BOTTOM:0",
"MAP_NAVEL_ROCK_DOWN11:1/MAP_NAVEL_ROCK_DOWN10:1",
"MAP_NAVEL_ROCK_ENTRANCE:0/MAP_NAVEL_ROCK_B1F:0",
"MAP_NAVEL_ROCK_ENTRANCE:1/MAP_NAVEL_ROCK_EXTERIOR:1",
"MAP_NAVEL_ROCK_EXTERIOR:0/MAP_NAVEL_ROCK_HARBOR:0",
"MAP_NAVEL_ROCK_EXTERIOR:1/MAP_NAVEL_ROCK_ENTRANCE:1",
"MAP_NAVEL_ROCK_FORK:0/MAP_NAVEL_ROCK_UP1:0",
"MAP_NAVEL_ROCK_FORK:1/MAP_NAVEL_ROCK_B1F:1",
"MAP_NAVEL_ROCK_FORK:2/MAP_NAVEL_ROCK_DOWN01:0",
"MAP_NAVEL_ROCK_HARBOR:0/MAP_NAVEL_ROCK_EXTERIOR:0",
"MAP_NAVEL_ROCK_TOP:0/MAP_NAVEL_ROCK_UP4:1",
"MAP_NAVEL_ROCK_UP1:0/MAP_NAVEL_ROCK_FORK:0",
"MAP_NAVEL_ROCK_UP1:1/MAP_NAVEL_ROCK_UP2:0",
"MAP_NAVEL_ROCK_UP2:0/MAP_NAVEL_ROCK_UP1:1",
"MAP_NAVEL_ROCK_UP2:1/MAP_NAVEL_ROCK_UP3:0",
"MAP_NAVEL_ROCK_UP3:0/MAP_NAVEL_ROCK_UP2:1",
"MAP_NAVEL_ROCK_UP3:1/MAP_NAVEL_ROCK_UP4:0",
"MAP_NAVEL_ROCK_UP4:0/MAP_NAVEL_ROCK_UP3:1",
"MAP_NAVEL_ROCK_UP4:1/MAP_NAVEL_ROCK_TOP:0",
# Secret bases
"MAP_SECRET_BASE_BROWN_CAVE1:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BROWN_CAVE2:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BROWN_CAVE3:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BROWN_CAVE4:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BLUE_CAVE1:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BLUE_CAVE2:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BLUE_CAVE3:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_BLUE_CAVE4:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_YELLOW_CAVE1:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_YELLOW_CAVE2:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_YELLOW_CAVE3:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_YELLOW_CAVE4:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_RED_CAVE1:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_RED_CAVE2:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_RED_CAVE3:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_RED_CAVE4:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_SHRUB1:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_SHRUB2:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_SHRUB3:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_SHRUB4:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_TREE1:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_TREE2:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_TREE3:0/MAP_DYNAMIC:-2!",
"MAP_SECRET_BASE_TREE4:0/MAP_DYNAMIC:-2!",
# Multiplayer rooms
"MAP_RECORD_CORNER:0,1,2,3/MAP_DYNAMIC:-1!",
"MAP_UNION_ROOM:0,1/MAP_DYNAMIC:-1!",
"MAP_PACIFIDLOG_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_MAUVILLE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_PETALBURG_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_EVER_GRANDE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_EVER_GRANDE_CITY_POKEMON_LEAGUE_2F:1/MAP_UNION_ROOM:0!",
"MAP_DEWFORD_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_MOSSDEEP_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_OLDALE_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_SLATEPORT_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_RUSTBORO_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_LILYCOVE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_FORTREE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_FALLARBOR_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_LAVARIDGE_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_SOOTOPOLIS_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_VERDANTURF_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
"MAP_TRADE_CENTER:0,1/MAP_DYNAMIC:-1!",
"MAP_PACIFIDLOG_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_MAUVILLE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_PETALBURG_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_EVER_GRANDE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_EVER_GRANDE_CITY_POKEMON_LEAGUE_2F:2/MAP_TRADE_CENTER:0!",
"MAP_DEWFORD_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_MOSSDEEP_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_OLDALE_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_SLATEPORT_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_RUSTBORO_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_LILYCOVE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_FORTREE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_FALLARBOR_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_LAVARIDGE_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_SOOTOPOLIS_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_VERDANTURF_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
"MAP_BATTLE_COLOSSEUM_2P:0,1/MAP_DYNAMIC:-1!",
"MAP_BATTLE_COLOSSEUM_4P:0,1,2,3/MAP_DYNAMIC:-1!",
# Unused content
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1:0/MAP_CAVE_OF_ORIGIN_1F:1!",
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1:1/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:0",
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:0/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1:1",
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:1/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3:0",
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3:0/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:1",
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3:1/MAP_CAVE_OF_ORIGIN_B1F:0!",
"MAP_LILYCOVE_CITY_UNUSED_MART:0,1/MAP_LILYCOVE_CITY:0!",
})
def validate_regions() -> bool:
"""
Verifies that Emerald's data doesn't have duplicate or missing
regions/warps/locations. Meant to catch problems during development like
forgetting to add a new location or incorrectly splitting a region.
"""
extracted_data_json = load_json_data("extracted_data.json")
error_messages: List[str] = []
warn_messages: List[str] = []
failed = False
def error(message: str) -> None:
nonlocal failed
failed = True
error_messages.append(message)
def warn(message: str) -> None:
warn_messages.append(message)
# Check regions
for name, region in data.regions.items():
for region_exit in region.exits:
if region_exit not in data.regions:
error(f"Pokemon Emerald: Region [{region_exit}] referenced by [{name}] was not defined")
# Check warps
for warp_source, warp_dest in data.warp_map.items():
if warp_source in _IGNORABLE_WARPS:
continue
if warp_dest is None:
error(f"Pokemon Emerald: Warp [{warp_source}] has no destination")
elif not data.warps[warp_dest].connects_to(data.warps[warp_source]) and not data.warps[warp_source].is_one_way:
error(f"Pokemon Emerald: Warp [{warp_source}] appears to be a one-way warp but was not marked as one")
# Check locations
claimed_locations = [location for region in data.regions.values() for location in region.locations]
claimed_locations_set = set()
for location_name in claimed_locations:
if location_name in claimed_locations_set:
error(f"Pokemon Emerald: Location [{location_name}] was claimed by multiple regions")
claimed_locations_set.add(location_name)
for location_name in extracted_data_json["locations"]:
if location_name not in claimed_locations and location_name not in _IGNORABLE_LOCATIONS:
warn(f"Pokemon Emerald: Location [{location_name}] was not claimed by any region")
warn_messages.sort()
error_messages.sort()
for message in warn_messages:
logging.warning(message)
for message in error_messages:
logging.error(message)
logging.debug("Pokemon Emerald sanity check done. Found %s errors and %s warnings.", len(error_messages), len(warn_messages))
return not failed