Makefile: improve build rules slightly

I'm still not satisfied with the Makefile, but this at least makes it
converge in a single make invocation again.

This also removes the enum value arrays.
This commit is contained in:
Zack Buhman 2023-07-26 18:17:45 +00:00
parent d8fb314a79
commit da49dbeb2b
12 changed files with 148 additions and 408 deletions

View File

@ -3,11 +3,19 @@ CFLAGS = -Isaturn
OPT ?= -Og
LIB = ./saturn
SRC = main.o input.o
SRC += gen/maps.o
SRC += gen/map_objects.o
SRC += gen/sprites.o
DEP = $(patsubst %.o,%.d,$(SRC))
GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py)
GEN_SRC =
GEN_SRC += gen/maps.cpp
GEN_SRC += gen/map_objects.cpp
GEN_SRC += gen/sprites.cpp
SRC =
SRC += $(GEN_SRC)
SRC += main.cpp
SRC += input.cpp
DEP = $(patsubst %.cpp,%.cpp.d,$(SRC))
res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1))))
res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png)))
@ -17,8 +25,9 @@ GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
GFX_SPRITES = $(call res_png,2bpp,pokered/gfx/sprites/)
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(MAPS_BLOCKS)
OBJ = $(SRC) $(GENERATED)
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(MAPS_BLOCKS) $(GEN_SRC)
OBJ = $(patsubst %.cpp,%.o,$(SRC) $(GENERATED))
all: main.cue
@ -30,20 +39,16 @@ define LINK_BINARY
ln -sf $(shell realpath --relative-to="$(dir $@)" $<) $@
endef
GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py)
define RUN_GENERATE
@mkdir -p ./gen
PYTHONPATH=./tools python -m generate ./pokered ./gen ./res
endef
gen/%.cpp: $(GEN_PYTHON_SOURCE)
$(RUN_GENERATE)
gen/%.hpp: $(GEN_PYTHON_SOURCE)
$(RUN_GENERATE)
$(RUN_GENERATE) $@
generated: $(GENERATED)
gen/%.cpp: gen/%.hpp $(GEN_PYTHON_SOURCE)
$(RUN_GENERATE) $@
res/%.2bpp: pokered/%.png
@mkdir -p $(dir $@)
@ -85,4 +90,6 @@ clean: clean-sh
clean-sh:
rm -rf res gen
PHONY: generated-headers
generated: $(GENERATED)
PHONY: generated

View File

@ -15,8 +15,6 @@
#include "gen/maps.hpp"
#include "gen/sprites.hpp"
#include "map_objects.hpp"
#include "maps.hpp" // hack?
#include "sprites.hpp"
constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b)
{
@ -88,8 +86,8 @@ constexpr inline void render_block(const uint32_t base_pattern,
const int32_t block_ix = 4 * block_y + block_x;
const uint8_t tile_ix = tileset.blockset.start[block * 4 * 4 + block_ix];
const int32_t cell_y = map_y * 4 + block_y;
const int32_t cell_x = map_x * 4 + block_x;
const uint32_t cell_y = map_y * 4 + block_y;
const uint32_t cell_x = map_x * 4 + block_x;
// assumes NBG0 map plane_a is at offset 0
vdp2.vram.u16[64 * (cell_y % 64) + (cell_x % 64)] = (base_pattern & 0xfff) + tile_ix;
//vdp2.vram.u32[64 * cell_y + cell_x] = base_pattern + tile_ix;
@ -101,25 +99,25 @@ constexpr int32_t last_map = map_t::wardens_house;
struct draw_t {
struct {
uint16_t tilesets[tilesets_ix_last]; // div 32
uint16_t spritesheets[spritesheets_ix_last]; // div 128
uint16_t tilesets[tileset_t::count]; // div 32
uint16_t spritesheets[spritesheet_t::count]; // div 128
} base_pattern;
};
struct player_t {
struct {
int16_t x;
int16_t y;
int32_t x;
int32_t y;
};
};
struct state_t {
int32_t map_ix;
enum map_t::map map;
draw_t draw;
player_t player;
};
static state_t state = { 0 };
static state_t state = { map_t::pallet_town, 0 };
uint32_t load_tileset(uint32_t top, enum tileset_t::tileset tileset)
{
@ -146,8 +144,8 @@ void load_vram()
vdp2.reg.CYCB1 = 0xeeee'eeee;
uint32_t vdp2_top = (sizeof (union vdp2_vram));
for (uint32_t i = 0; i < tilesets_ix_last; i++)
vdp2_top = load_tileset(vdp2_top, tilesets_ix[i]);
for (uint32_t i = 0; i < tileset_t::count; i++)
vdp2_top = load_tileset(vdp2_top, static_cast<enum tileset_t::tileset>(i));
vdp2.reg.CYCA0 = 0x0121'ffff;
vdp2.reg.CYCA1 = 0x0121'ffff;
@ -155,8 +153,8 @@ void load_vram()
vdp2.reg.CYCB1 = 0x4565'ffff;
uint32_t vdp1_top = (sizeof (union vdp1_vram));
for (uint32_t i = 0; i < spritesheets_ix_last; i++)
vdp1_top = load_sprite(vdp1_top, spritesheets_ix[i]);
for (uint32_t i = 0; i < spritesheet_t::count; i++)
vdp1_top = load_sprite(vdp1_top, static_cast<enum spritesheet_t::spritesheet>(i));
}
// screen space
@ -244,16 +242,18 @@ static uint16_t scroll = 0;
*/
// there are 16 pixels per block
void render_map()
{
if (++scroll > 64) {
if (++scroll > 4) {
scroll = 0;
state.player.x += 1;
state.player.y += 1;
}
vdp2.reg.SCXIN0 = (state.player.x - 1) * 16 + (scroll / 4);
vdp2.reg.SCYIN0 = (state.player.y - 1) * 16 + (scroll / 4);
vdp2.reg.SCXIN0 = state.player.x - 16;
vdp2.reg.SCYIN0 = state.player.y - 16;
/*
vdp2.reg.WPSX0 = 80 << 1;
vdp2.reg.WPSY0 = 48;
@ -262,20 +262,18 @@ void render_map()
vdp2.reg.WCTLA = WCTLA__N0W0E | WCTLA__N0W0A__OUTSIDE;
*/
const map_t& map = maps[maps_ix[state.map_ix]];
const uint8_t border_block = map_objects[maps_ix[state.map_ix]].border_block;
const map_t& map = maps[state.map];
const uint8_t border_block = map_objects[state.map].border_block;
const uint32_t base_pattern = state.draw.base_pattern.tilesets[map.tileset];
vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD | PNCN0__N0CNSM | PNCN0__N0SCN((base_pattern >> 10) & 0x1f);
int32_t origin_x = state.player.x / 2;
int32_t origin_y = state.player.y / 2;
int32_t offset_x = state.player.x & 1;
int32_t offset_y = state.player.y & 1;
int32_t origin_x = state.player.x / 32;
int32_t origin_y = state.player.y / 32;
fill<uint32_t>(vdp2.vram.u32, 0, 64 * 64 * 2);
for (int32_t y = origin_y - 3 + offset_x; y <= (origin_y + 2 + offset_x); y++) {
for (int32_t x = origin_x - 3 + offset_y; x <= (origin_x + 3 + offset_y); x++) {
for (int32_t y = origin_y - 3; y <= (origin_y + 3); y++) {
for (int32_t x = origin_x - 3; x <= (origin_x + 3); x++) {
const uint8_t block = ( (x < static_cast<int32_t>(map.width))
&& (y < static_cast<int32_t>(map.height))
&& (x >= 0)
@ -301,19 +299,6 @@ void render()
render_sprites();
}
void update()
{
if (event::cursor_right()) {
state.map_ix++;
if (state.map_ix >= map_ix_last)
state.map_ix = 0;
}
if (event::cursor_left()) {
state.map_ix--;
if (state.map_ix < 0) state.map_ix = last_map;
}
}
extern "C"
void v_blank_in_int(void) __attribute__ ((interrupt_handler));
void v_blank_in_int()
@ -326,7 +311,6 @@ void v_blank_in_int()
sh2.reg.FTCSR = 0; // clear flags
render();
update();
// wait at least 300us, as specified in the SMPC manual.
// It appears reading FRC.H is mandatory and *must* occur before FRC.L on real
@ -462,7 +446,7 @@ void init_vdp2()
void main()
{
state.map_ix = map_t::pallet_town;
state.map = map_t::pallet_town;
load_vram();

255
maps.hpp
View File

@ -1,255 +0,0 @@
enum map_t::map maps_ix[] = {
map_t::agathas_room,
map_t::bike_shop,
map_t::bills_house,
map_t::blues_house,
map_t::brunos_room,
map_t::celadon_chief_house,
map_t::celadon_city,
map_t::celadon_diner,
map_t::celadon_gym,
map_t::celadon_hotel,
map_t::celadon_mansion_1f,
map_t::celadon_mansion_2f,
map_t::celadon_mansion_3f,
map_t::celadon_mansion_roof,
map_t::celadon_mansion_roof_house,
map_t::celadon_mart_1f,
map_t::celadon_mart_2f,
map_t::celadon_mart_3f,
map_t::celadon_mart_4f,
map_t::celadon_mart_5f,
map_t::celadon_mart_elevator,
map_t::celadon_mart_roof,
map_t::celadon_pokecenter,
map_t::cerulean_badge_house,
map_t::cerulean_cave_1f,
map_t::cerulean_cave_2f,
map_t::cerulean_cave_b1f,
map_t::cerulean_city,
map_t::cerulean_gym,
map_t::cerulean_mart,
map_t::cerulean_pokecenter,
map_t::cerulean_trade_house,
map_t::cerulean_trashed_house,
map_t::champions_room,
map_t::cinnabar_gym,
map_t::cinnabar_island,
map_t::cinnabar_lab,
map_t::cinnabar_lab_fossil_room,
map_t::cinnabar_lab_metronome_room,
map_t::cinnabar_lab_trade_room,
map_t::cinnabar_mart,
map_t::cinnabar_pokecenter,
map_t::colosseum,
map_t::copycats_house_1f,
map_t::copycats_house_2f,
map_t::daycare,
map_t::digletts_cave,
map_t::digletts_cave_route_11,
map_t::digletts_cave_route_2,
map_t::fighting_dojo,
map_t::fuchsia_bills_grandpas_house,
map_t::fuchsia_city,
map_t::fuchsia_good_rod_house,
map_t::fuchsia_gym,
map_t::fuchsia_mart,
map_t::fuchsia_meeting_room,
map_t::fuchsia_pokecenter,
map_t::game_corner,
map_t::game_corner_prize_room,
map_t::hall_of_fame,
map_t::indigo_plateau,
map_t::indigo_plateau_lobby,
map_t::lances_room,
map_t::lavender_cubone_house,
map_t::lavender_mart,
map_t::lavender_pokecenter,
map_t::lavender_town,
map_t::loreleis_room,
map_t::mr_fujis_house,
map_t::mr_psychics_house,
map_t::mt_moon_1f,
map_t::mt_moon_b1f,
map_t::mt_moon_b2f,
map_t::mt_moon_pokecenter,
map_t::museum_1f,
map_t::museum_2f,
map_t::name_raters_house,
map_t::oaks_lab,
map_t::pallet_town,
map_t::pewter_city,
map_t::pewter_gym,
map_t::pewter_mart,
map_t::pewter_nidoran_house,
map_t::pewter_pokecenter,
map_t::pewter_speech_house,
map_t::pokemon_fan_club,
map_t::pokemon_mansion_1f,
map_t::pokemon_mansion_2f,
map_t::pokemon_mansion_3f,
map_t::pokemon_mansion_b1f,
map_t::pokemon_tower_1f,
map_t::pokemon_tower_2f,
map_t::pokemon_tower_3f,
map_t::pokemon_tower_4f,
map_t::pokemon_tower_5f,
map_t::pokemon_tower_6f,
map_t::pokemon_tower_7f,
map_t::power_plant,
map_t::reds_house_1f,
map_t::reds_house_2f,
map_t::rocket_hideout_b1f,
map_t::rocket_hideout_b2f,
map_t::rocket_hideout_b3f,
map_t::rocket_hideout_b4f,
map_t::rocket_hideout_elevator,
map_t::rock_tunnel_1f,
map_t::rock_tunnel_b1f,
map_t::rock_tunnel_pokecenter,
map_t::route_1,
map_t::route_10,
map_t::route_11,
map_t::route_11_gate_1f,
map_t::route_11_gate_2f,
map_t::route_12,
map_t::route_12_gate_1f,
map_t::route_12_gate_2f,
map_t::route_12_super_rod_house,
map_t::route_13,
map_t::route_14,
map_t::route_15,
map_t::route_15_gate_1f,
map_t::route_15_gate_2f,
map_t::route_16,
map_t::route_16_fly_house,
map_t::route_16_gate_1f,
map_t::route_16_gate_2f,
map_t::route_17,
map_t::route_18,
map_t::route_18_gate_1f,
map_t::route_18_gate_2f,
map_t::route_19,
map_t::route_2,
map_t::route_20,
map_t::route_21,
map_t::route_22,
map_t::route_22_gate,
map_t::route_23,
map_t::route_24,
map_t::route_25,
map_t::route_2_gate,
map_t::route_2_trade_house,
map_t::route_3,
map_t::route_4,
map_t::route_5,
map_t::route_5_gate,
map_t::route_6,
map_t::route_6_gate,
map_t::route_7,
map_t::route_7_gate,
map_t::route_8,
map_t::route_8_gate,
map_t::route_9,
map_t::safari_zone_center,
map_t::safari_zone_center_rest_house,
map_t::safari_zone_east,
map_t::safari_zone_east_rest_house,
map_t::safari_zone_gate,
map_t::safari_zone_north,
map_t::safari_zone_north_rest_house,
map_t::safari_zone_secret_house,
map_t::safari_zone_west,
map_t::safari_zone_west_rest_house,
map_t::saffron_city,
map_t::saffron_gym,
map_t::saffron_mart,
map_t::saffron_pidgey_house,
map_t::saffron_pokecenter,
map_t::seafoam_islands_1f,
map_t::seafoam_islands_b1f,
map_t::seafoam_islands_b2f,
map_t::seafoam_islands_b3f,
map_t::seafoam_islands_b4f,
map_t::silph_co_10f,
map_t::silph_co_11f,
map_t::silph_co_1f,
map_t::silph_co_2f,
map_t::silph_co_3f,
map_t::silph_co_4f,
map_t::silph_co_5f,
map_t::silph_co_6f,
map_t::silph_co_7f,
map_t::silph_co_8f,
map_t::silph_co_9f,
map_t::silph_co_elevator,
map_t::ss_anne_1f,
map_t::ss_anne_1f_rooms,
map_t::ss_anne_2f,
map_t::ss_anne_2f_rooms,
map_t::ss_anne_3f,
map_t::ss_anne_b1f,
map_t::ss_anne_b1f_rooms,
map_t::ss_anne_bow,
map_t::ss_anne_captains_room,
map_t::ss_anne_kitchen,
map_t::trade_center,
map_t::underground_path_north_south,
map_t::underground_path_route_5,
map_t::underground_path_route_6,
map_t::underground_path_route_7,
map_t::underground_path_route_8,
map_t::underground_path_west_east,
map_t::vermilion_city,
map_t::vermilion_dock,
map_t::vermilion_gym,
map_t::vermilion_mart,
map_t::vermilion_old_rod_house,
map_t::vermilion_pidgey_house,
map_t::vermilion_pokecenter,
map_t::vermilion_trade_house,
map_t::victory_road_1f,
map_t::victory_road_2f,
map_t::victory_road_3f,
map_t::viridian_city,
map_t::viridian_forest,
map_t::viridian_forest_north_gate,
map_t::viridian_forest_south_gate,
map_t::viridian_gym,
map_t::viridian_mart,
map_t::viridian_nickname_house,
map_t::viridian_pokecenter,
map_t::viridian_school_house,
map_t::wardens_house,
};
constexpr int map_ix_last = (sizeof (maps_ix)) / (sizeof (enum map_t::map));
enum tileset_t::tileset tilesets_ix[] = {
tileset_t::cavern,
tileset_t::cemetery,
tileset_t::club,
tileset_t::dojo,
tileset_t::facility,
tileset_t::forest,
tileset_t::forest_gate,
tileset_t::gate,
tileset_t::gym,
tileset_t::house,
tileset_t::interior,
tileset_t::lab,
tileset_t::lobby,
tileset_t::mansion,
tileset_t::mart,
tileset_t::museum,
tileset_t::overworld,
tileset_t::plateau,
tileset_t::pokecenter,
tileset_t::reds_house_1,
tileset_t::reds_house_2,
tileset_t::ship,
tileset_t::ship_port,
tileset_t::underground,
};
constexpr int tilesets_ix_last = (sizeof (tilesets_ix)) / (sizeof (enum tileset_t::tileset));

View File

@ -1,77 +0,0 @@
enum spritesheet_t::spritesheet spritesheets_ix[] = {
spritesheet_t::agatha,
spritesheet_t::balding_guy,
spritesheet_t::beauty,
spritesheet_t::biker,
spritesheet_t::bike_shop_clerk,
spritesheet_t::bird,
spritesheet_t::blue,
spritesheet_t::boulder,
spritesheet_t::brunette_girl,
spritesheet_t::bruno,
spritesheet_t::captain,
spritesheet_t::channeler,
spritesheet_t::clerk,
spritesheet_t::clipboard,
spritesheet_t::cook,
spritesheet_t::cooltrainer_f,
spritesheet_t::cooltrainer_m,
spritesheet_t::daisy,
spritesheet_t::fairy,
spritesheet_t::fisher,
spritesheet_t::fishing_guru,
spritesheet_t::fossil,
spritesheet_t::gambler,
spritesheet_t::gambler_asleep,
spritesheet_t::gameboy_kid,
spritesheet_t::gentleman,
spritesheet_t::giovanni,
spritesheet_t::girl,
spritesheet_t::gramps,
spritesheet_t::granny,
spritesheet_t::guard,
spritesheet_t::gym_guide,
spritesheet_t::hiker,
spritesheet_t::koga,
spritesheet_t::lance,
spritesheet_t::link_receptionist,
spritesheet_t::little_boy,
spritesheet_t::little_girl,
spritesheet_t::lorelei,
spritesheet_t::middle_aged_man,
spritesheet_t::middle_aged_woman,
spritesheet_t::mom,
spritesheet_t::monster,
spritesheet_t::mr_fuji,
spritesheet_t::none,
spritesheet_t::nurse,
spritesheet_t::oak,
spritesheet_t::old_amber,
spritesheet_t::paper,
spritesheet_t::pokedex,
spritesheet_t::poke_ball,
spritesheet_t::red,
spritesheet_t::rocker,
spritesheet_t::rocket,
spritesheet_t::safari_zone_worker,
spritesheet_t::sailor,
spritesheet_t::scientist,
spritesheet_t::seel,
spritesheet_t::silph_president,
spritesheet_t::silph_worker_f,
spritesheet_t::silph_worker_m,
spritesheet_t::snorlax,
spritesheet_t::super_nerd,
spritesheet_t::swimmer,
spritesheet_t::unused_gambler_asleep_1,
spritesheet_t::unused_gambler_asleep_2,
spritesheet_t::unused_gameboy_kid,
spritesheet_t::unused_guard,
spritesheet_t::unused_old_amber,
spritesheet_t::unused_scientist,
spritesheet_t::waiter,
spritesheet_t::warden,
spritesheet_t::youngster,
};
constexpr int spritesheets_ix_last = (sizeof (spritesheets_ix)) / (sizeof (enum spritesheet_t::spritesheet));

View File

@ -2,24 +2,17 @@ from pathlib import Path
from pprint import pprint
import sys
from generate import maps
from generate import map_objects
from generate import sprites
from generate.files import files
files = [
(maps.generate_maps_header, "maps.hpp"),
(maps.generate_maps_source, "maps.cpp"),
(map_objects.generate_map_objects_source, "map_objects.cpp"),
(sprites.generate_sprites_header, "sprites.hpp"),
(sprites.generate_sprites_source, "sprites.cpp"),
]
def generate(base_path):
def generate(base_path, target_path):
for func, filename in files:
path = base_path / filename
if path != target_path:
continue
with open(path, 'w') as f:
f.write(func().getvalue())
# sys.argv[1] is secretly used in parse
base_path = Path(sys.argv[2])
generate(base_path)
target_path = Path(sys.argv[4])
generate(base_path, target_path)

View File

@ -0,0 +1,32 @@
from itertools import chain
from generate.maps import sorted_map_headers
def includes_header():
yield "#pragma once"
yield ""
yield '#include "maps.hpp"'
yield ""
def directions():
return set(
# fixme: improve the connection parser
# add dataclass fields
connection[0]
for map_header in sorted_map_headers()
for connection in map_header.connections
)
def struct_connection_t():
struct connection_t {
enum direction {
# directions for lulz
};
enum direction direction;
map_t::map map;
uint8_t offset;
};
def ():

11
tools/generate/files.py Normal file
View File

@ -0,0 +1,11 @@
from generate import maps
from generate import map_objects
from generate import sprites
files = [
(maps.generate_maps_header, "maps.hpp"),
(maps.generate_maps_source, "maps.cpp"),
(map_objects.generate_map_objects_source, "map_objects.cpp"),
(sprites.generate_sprites_header, "sprites.hpp"),
(sprites.generate_sprites_source, "sprites.cpp"),
]

View File

@ -57,9 +57,10 @@ def includes_header():
yield ""
def struct_tileset_t():
_sorted_tilesets_constants_list = list(sorted_tilesets_constants_list())
tileset_names = (
f"{name.lower()},"
for name, _ in sorted_tilesets_constants_list()
for name, _ in _sorted_tilesets_constants_list
)
return [
"struct tileset_t {",
@ -69,13 +70,16 @@ def struct_tileset_t():
"enum tileset {",
*tileset_names,
"};",
"",
f"static constexpr int32_t count = {len(_sorted_tilesets_constants_list)};"
"};",
]
def struct_map_t():
_sorted_map_headers = list(sorted_map_headers())
map_names = (
f"{map_header.name2.lower()},"
for map_header in sorted_map_headers()
for map_header in _sorted_map_headers
)
return [
"struct map_t {",
@ -89,6 +93,8 @@ def struct_map_t():
"last_map,", # special map name
"unused_map_ed,", # special silph co elevator map name
"};",
"",
f"static constexpr int32_t count = {len(_sorted_map_headers)};",
"};",
]

View File

@ -40,9 +40,10 @@ def sprite_name(name):
return name.removeprefix('SPRITE_').lower()
def struct_spritesheet_t():
_sorted_sprite_constants_list = list(sorted_sprite_constants_list())
sprite_names = (
f"{sprite_name(name)},"
for name, _ in sorted_sprite_constants_list()
for name, _ in _sorted_sprite_constants_list
)
return [
"struct spritesheet_t {",
@ -52,6 +53,8 @@ def struct_spritesheet_t():
"enum spritesheet {",
*sprite_names,
"};",
"",
f"static constexpr int32_t count = {len(_sorted_sprite_constants_list)};",
"};",
]

View File

@ -0,0 +1,8 @@
import sys
from pathlib import Path
from generate.files import files
base_path = Path(sys.argv[1])
for _, filename in files:
print(base_path / filename)

View File

@ -1,5 +1,5 @@
from pprint import pprint
from parse import parse
for i in parse.sprite_constants_list:
for i in parse.map_headers:
pprint(i)

View File

@ -49,6 +49,16 @@ class MapHeader:
def height(self):
return f"{self.name2}_HEIGHT"
@dataclass
class Connection:
name: str
map_name1: str
map_name2: str
offset: int
def parse_connection():
return
def flatten(tokens):
# expects tokens from a single file
@ -63,14 +73,32 @@ def flatten(tokens):
map_headers = [s for s in tokens if s[0] == 'map_header']
assert len(map_headers) == 1
map_header, = map_headers
_, (name1, name2, tileset, connection_mask) = map_header
connections = [s for s in tokens if s[0] == 'connection']
_, (name1, name2, tileset, connection_mask0) = map_header
connections = [s[1] for s in tokens if s[0] == 'connection']
connection_mask = connection_mask0 if type(connection_mask0) is list else [connection_mask0]
null_mask = lambda m: (
len(m) == 1 and any(m[0] == n for n in {'0', '$0', '$00'})
)
connection_names = (
[] if null_mask(connection_mask) else connection_mask
)
return MapHeader(
name1 = name1,
name2 = name2,
tileset = tileset,
connection_names = [] if connection_mask == '0' else connection_mask,
connections = [tuple(c[1]) for c in connections]
connection_names = sorted(connection_names),
connections = list(sorted(
(
Connection(
name,
map_name1,
map_name2,
int(offset)
)
for name, map_name1, map_name2, offset in connections
),
key=lambda c: c.name))
)
def parse(path):