Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
86 lines
7.9 KiB
Python
86 lines
7.9 KiB
Python
"""Build the factual region atlas and authored overview maps.
|
|
Run from the repository root. Coordinate sources are cached in /tmp/pokemon-atlas.
|
|
Later regions are explicitly schematic and do not imply exact route geometry.
|
|
"""
|
|
import argparse, hashlib, json, re, urllib.request
|
|
from pathlib import Path
|
|
parser=argparse.ArgumentParser()
|
|
parser.add_argument('--cache',type=Path,default=Path('/tmp/pokemon-atlas'))
|
|
args=parser.parse_args();args.cache.mkdir(parents=True,exist_ok=True)
|
|
sources={
|
|
'pokeemerald-region.json':'https://raw.githubusercontent.com/pret/pokeemerald/master/src/data/region_map/region_map_sections.json',
|
|
'pokefirered-region.json':'https://raw.githubusercontent.com/pret/pokefirered/master/src/data/region_map/region_map_sections.json',
|
|
'platinum-map.json':'https://raw.githubusercontent.com/pret/pokeplatinum/main/res/town_map/town_map_data.json',
|
|
'johto-landmarks.asm':'https://raw.githubusercontent.com/pret/pokecrystal/master/data/maps/landmarks.asm'
|
|
}
|
|
checksums={'pokeemerald-region.json': '3a5f8c07c85c17f05cec06d049305962e9a9a1d2bd6eae935f3887866a4ad491', 'pokefirered-region.json': '82e456cbe86a57ebc4bd0d88d6e000e83e28340895ac038079c38d415bc6de5f', 'platinum-map.json': 'b5f6792e7993afa57df3d5b86c0af57488d23642576c4a224ba401bd762ba6b8', 'johto-landmarks.asm': 'bf61258379901daf794555b1a8f73bcf634d50ef3ca6e067f88f599b9df9816f'}
|
|
for filename,url in sources.items():
|
|
path=args.cache/filename
|
|
if not path.exists():
|
|
with urllib.request.urlopen(url,timeout=60) as response: path.write_bytes(response.read())
|
|
if hashlib.sha256(path.read_bytes()).hexdigest()!=checksums[filename]:
|
|
raise ValueError(f'{filename}: upstream coordinates changed; review the map and update its checksum before rebuilding')
|
|
from pathlib import Path
|
|
locations=json.load(open('data/pokemon-locations.json'))['locations']
|
|
def slug(s):return re.sub(r'[^a-z0-9]+','-',s.lower()).strip('-')
|
|
def location(key,region):
|
|
key=key.replace('eternia','eterna').replace('mount-coronet','mt-coronet').replace('tin-tower','bell-tower')
|
|
keys=[key,f'{region}-{key}',key.replace('route-','sea-route-'),f'{region}-{key.replace("route-","sea-route-")}']
|
|
return next((p for p in locations if p['slug'] in keys and (p['region'] or '').lower()==region),None)
|
|
def node(key,x,y,region,cells=None):
|
|
p=location(key,region)
|
|
return {'slug':p['slug'] if p else key,'name':p['name'] if p else key.replace('-',' ').title(),'x':x,'y':y,'kind':'town' if re.search('town|city|village|area$|league',key) else 'route' if 'route' in key else 'landmark','cells':cells or []}
|
|
regions={}
|
|
for region,file in [('hoenn',args.cache/'pokeemerald-region.json'),('kanto',args.cache/'pokefirered-region.json')]:
|
|
d=json.load(open(file))['map_sections'];nodes=[]
|
|
for p in d:
|
|
if 'x' not in p:continue
|
|
key=slug(p['name']);x=p['x'];y=p['y'];w=p['width'];h=p['height']
|
|
if region=='hoenn' and key=='none':break
|
|
if region=='kanto' and key=='one-island':break
|
|
nodes.append(node(key,x+w/2,y+h/2,region,[[x,y,w,h]]))
|
|
regions[region]={'name':region.title(),'width':32,'height':20,'nodes':nodes,'source':f'https://github.com/pret/poke{ "emerald" if region=="hoenn" else "firered" }/blob/master/src/data/region_map/region_map_sections.json','precision':'Game map coordinates'}
|
|
# Platinum town map blocks preserve branching paths and geographic placement.
|
|
d=json.load(open(args.cache/'platinum-map.json'));groups={}
|
|
for b in d['blocks']:
|
|
key=re.sub(r'_(north|south)$','',b['area'] or '').replace('_','-')
|
|
if not key:continue
|
|
groups.setdefault(key,[]).append([b['x'],b['z'],1,1])
|
|
if b.get('landmark') and b['landmark'].replace('_','-')!=key:
|
|
land=b['landmark'].replace('_','-')
|
|
if location(land,'sinnoh'):groups.setdefault(land,[]).append([b['x'],b['z'],1,1])
|
|
nodes=[node(k,sum(c[0]+.5 for c in cells)/len(cells),sum(c[1]+.5 for c in cells)/len(cells),'sinnoh',cells) for k,cells in groups.items()]
|
|
regions['sinnoh']={'name':'Sinnoh','width':32,'height':30,'nodes':nodes,'source':'https://github.com/pret/pokeplatinum/blob/main/res/town_map/town_map_data.json','precision':'Platinum town map coordinates'}
|
|
s=(args.cache/'johto-landmarks.asm').read_text();nodes=[]
|
|
for x,y,key in re.findall(r'landmark\s+(\d+),\s*(\d+),\s*(\w+)Name',s):
|
|
if key=='PalletTown':break
|
|
name=re.sub(r'([a-z])([A-Z])',r'\1-\2',key);name=re.sub(r'([A-Za-z])(\d)',r'\1-\2',name)
|
|
nodes.append(node(name.lower(),int(x)/5,int(y)/5,'johto'))
|
|
regions['johto']={'name':'Johto','width':32,'height':30,'nodes':nodes,'source':'https://github.com/pret/pokecrystal/blob/master/data/maps/landmarks.asm','precision':'Crystal landmark coordinates'}
|
|
# Later-region overview points: deliberately identified as schematic rather than exact route maps.
|
|
landmarks={
|
|
'unova':'Nuvema Town,23,26;Accumula Town,23,23;Striaton City,26,20;Nacrene City,21,20;Castelia City,16,23;Nimbasa City,16,16;Driftveil City,9,17;Mistralton City,7,10;Icirrus City,11,5;Opelucid City,20,5;Lacunosa Town,26,6;Undella Town,28,12;Black City,26,17;White Forest,28,18;Aspertia City,4,26;Floccesy Town,7,25;Virbank City,10,25;Humilau City,29,4;Pokemon League,28,1',
|
|
'kalos':'Vaniville Town,23,25;Aquacorde Town,23,22;Santalune City,24,19;Lumiose City,16,12;Camphrier Town,12,19;Ambrette Town,5,24;Cyllage City,4,18;Geosenge Town,3,12;Shalour City,6,8;Coumarine City,11,6;Laverre City,17,7;Dendemille Town,24,8;Anistar City,28,12;Couriway Town,28,20;Snowbelle City,27,25;Pokemon Village,24,28;Pokemon League,30,17',
|
|
'alola':'Iki Town,6,6;Hauoli City,3,9;Melemele Sea,6,12;Heahea City,22,10;Paniola Town,24,7;Royal Avenue,26,11;Konikoni City,21,13;Malie City,26,20;Tapu Village,22,23;Po Town,21,17;Mount Lanakila,21,21;Seafolk Village,5,23;Poni Wilds,4,20;Vast Poni Canyon,7,17;Aether Paradise,14,15',
|
|
'galar':'Postwick,15,27;Wedgehurst,15,24;Wild Area,15,20;Motostoke,15,17;Turffield,8,14;Hulbury,23,14;Hammerlocke,15,10;Stow on Side,6,10;Ballonlea,7,5;Circhester,25,8;Spikemuth,26,17;Wyndon,15,3;Isle of Armor,29,23;Crown Tundra,7,28',
|
|
'hisui':'Jubilife Village,6,15;Obsidian Fieldlands,9,22;Crimson Mirelands,19,22;Cobalt Coastlands,26,15;Coronet Highlands,16,13;Alabaster Icelands,16,5;Temple of Sinnoh,16,10;Prelude Beach,4,19',
|
|
'paldea':'Cabo Poco,17,28;Los Platos,17,24;Mesagoza,17,21;Cortondo,11,22;Artazon,24,22;Levincia,28,15;Cascarrafa,9,16;Porto Marinada,4,11;Medali,12,10;Montenevera,20,6;Alfornada,5,27;Glaseado Mountain,21,9;Casseroya Lake,9,6;Area Zero,17,15;Pokemon League,14,19',
|
|
'orre':'Phenac City,21,21;Pyrite Town,12,20;Agate Village,11,6;Mt Battle,22,4;Realgam Tower,19,15;The Under,10,23;Outskirt Stand,26,25;Snagem Hideout,27,15;Cipher Lab,27,8;Gateon Port,5,15;Pokemon HQ Lab,5,22;Rock Poke Spot,16,18;Oasis Poke Spot,23,11;Cave Poke Spot,16,10;Citadark Isle,4,28',
|
|
'kitakami':'Mossui Town,16,23;Loyalty Plaza,11,24;Apple Hills,9,21;Kitakami Hall,22,17;Oni Mountain,16,10;Crystal Pool,16,9;Timeless Woods,26,6;Fellhorn Gorge,26,14;Paradise Barrens,6,8;Revelers Road,20,21',
|
|
'blueberry':'Central Plaza,16,15;Savanna Biome,8,22;Coastal Biome,24,22;Canyon Biome,8,8;Polar Biome,24,8'
|
|
}
|
|
for region,raw in landmarks.items():
|
|
ns=[]
|
|
for point in raw.split(';'):
|
|
name,x,y=point.split(',');n=node(slug(name),int(x),int(y),region);n['name']=name.replace('Pokemon','Pokémon');ns.append(n)
|
|
regions[region]={'name':{'blueberry':'Blueberry Academy'}.get(region,region.title()),'width':32,'height':30,'nodes':ns,'source':f'https://bulbapedia.bulbagarden.net/wiki/{region.title() if region!="blueberry" else "Terarium"}','precision':'Schematic landmark overview; use the location list for routes'}
|
|
# Multiple blocks can describe one route or cave; retain one selectable marker.
|
|
for region in regions.values():
|
|
merged={}
|
|
for point in region['nodes']:
|
|
if point['slug'] in merged: merged[point['slug']]['cells'].extend(point['cells'])
|
|
else: merged[point['slug']]=point
|
|
region['nodes']=list(merged.values())
|
|
Path('data/pokemon-atlas.json').write_text(json.dumps({'source':'Coordinates adapted from pret decompilations; later-region landmark overviews are authored schematics. No game or wiki prose is reproduced.','regions':regions},ensure_ascii=False,indent=2)+'\n')
|
|
print({k:len(v['nodes']) for k,v in regions.items()})
|