Compare commits

...

2 Commits

Author SHA1 Message Date
5c588dbe29 map_objects: initial
The map_objects generator is incomplete as there are no models for,
e.g: sprites yet.
2023-07-25 07:46:57 +00:00
e37d56b729 tools/generate: move maps to a new module 2023-07-25 05:43:09 +00:00
9 changed files with 399 additions and 262 deletions

View File

@ -75,6 +75,6 @@ main.elf: $(OBJ)
clean: clean-sh clean: clean-sh
clean-sh: clean-sh:
rm -rf res rm -rf res gen
PHONY: generated-headers PHONY: generated-headers

View File

@ -1,60 +0,0 @@
struct position_t {
uint8_t x;
uint8_t y;
};
struct warp_event_t {
position_t position;
map_t::map destination_map;
uint8_t destination_warp_index;
};
struct object_event_t {
enum type {
generic,
item,
trainer,
pokemon,
};
enum movement {
stay,
walk,
};
enum range {
any_dir,
up_down,
left_right,
};
enum direction {
down,
up,
left,
right,
none,
};
position_t position;
uint8_t sprite_id; // fixme
enum movement movement;
union {
enum range range;
enum direction direction;
};
uint8_t text_id; // fixme
union {
struct {
uint8_t id;
} item;
struct {
uint8_t type; // trainer class
uint8_t id; // trainer number
} trainer;
struct {
uint8_t id; // pokemon id
uint8_t level;
} pokemon;
};
};

82
map_objects.hpp Normal file
View File

@ -0,0 +1,82 @@
#pragma once
#include <cstdint>
#include "gen/maps.hpp"
struct position_t {
uint8_t x;
uint8_t y;
};
struct warp_event_t {
position_t position;
struct {
map_t::map map;
uint8_t warp_index;
} destination;
};
struct bg_event_t {
position_t position;
uint8_t sign_id;
};
struct object_event_t {
enum struct type {
generic,
item,
trainer,
pokemon,
};
enum struct movement {
stay,
walk,
};
enum struct range_or_direction {
any_dir,
up_down,
left_right,
down,
up,
left,
right,
none,
boulder_movement_byte_2,
};
enum type type;
position_t position;
uint8_t sprite_id; // fixme
enum movement movement;
enum range_or_direction range_or_direction;
uint8_t text_id; // fixme
union {
struct {
uint8_t id;
} item;
struct {
uint8_t type; // trainer class
uint8_t number; // trainer number
} trainer;
struct {
uint8_t id; // pokemon id
uint8_t level;
} pokemon;
};
};
// this is written "oddly" to make the struct more compact; making a
// {length, events} struct for each event type would would require 28
// bytes contrast to 16 bytes as modeled here.
struct object_t {
uint8_t border_block;
uint8_t warp_length;
uint8_t bg_length;
uint8_t object_length;
const warp_event_t * warp_events;
const bg_event_t * bg_events;
const object_event_t * object_events;
};

View File

@ -1,207 +1,22 @@
from pathlib import Path from pathlib import Path
from pprint import pprint from pprint import pprint
import io
import sys import sys
from parse import parse from generate import maps
from generate import map_objects
def sorted_map_headers():
# hack to remove unused/duplicate underground_path_route_7
map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
def includes():
for map_header in sorted_map_headers():
block_path = parse.maps_blocks_list[map_header.blocks()]
yield f'#include "../res/{block_path}.h"'
for tileset_name in sorted(parse.tileset_constants_list):
tileset_index = parse.tileset_constants_list[tileset_name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
yield f'#include "../res/{blockset_path}.h"'
yield f'#include "../res/{gfx_path}.h"'
yield ""
def struct_start_size_t():
return [
"struct start_size_t {",
"uint8_t const * const start;",
"uint32_t size;",
"};",
]
def struct_tileset_t():
tileset_names = (
f"{name.lower()},"
for name in sorted(parse.tileset_constants_list)
)
return [
"struct tileset_t {",
"start_size_t blockset;",
"start_size_t tileset;",
"",
"enum tileset {",
*tileset_names,
"};",
"};",
]
def struct_map_t():
map_names = (
f"{map_header.name2.lower()},"
for map_header in sorted_map_headers()
)
return [
"struct map_t {",
"start_size_t blocks;",
"enum tileset_t::tileset tileset;",
"uint32_t width;",
"uint32_t height;",
"",
"enum map {",
*map_names,
"};",
"};",
]
def binary_res(path, suffix):
# _binary_res_gfx_blocksets_overworld_bst_start
name = path.replace('/', '_').replace('.', '_')
return f"_binary_res_{name}_{suffix}"
def start_size_value(path):
start = binary_res(path, "start")
size = binary_res(path, "size")
return [
f"reinterpret_cast<uint8_t*>(&{start}),",
f"reinterpret_cast<uint32_t>(&{size}),",
]
def blockset_tileset(name: str):
tileset_index = parse.tileset_constants_list[name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
return [
f"[tileset_t::{name.lower()}] = {{",
".blockset = {",
*start_size_value(blockset_path),
"},",
".tileset = {",
*start_size_value(gfx_path),
"}",
"},"
]
def tilesets_header():
yield "extern const tileset_t tilesets[];"
def tilesets():
yield "const tileset_t tilesets[] = {"
for tileset in sorted(parse.tileset_constants_list):
yield from blockset_tileset(tileset)
yield "};"
def map(map_header):
block_path = parse.maps_blocks_list[map_header.blocks()]
map_constant = parse.map_constants_list[map_header.name2]
return [
f"[map_t::{map_header.name2.lower()}] = {{",
".blocks = {",
*start_size_value(block_path),
"},",
f".tileset = tileset_t::{map_header.tileset.lower()},",
f".width = {map_constant.width},",
f".height = {map_constant.height},",
"},",
]
def maps_header():
yield "extern const map_t maps[];"
def maps():
yield "const map_t maps[] = {"
for map_header in sorted_map_headers():
yield from map(map_header)
yield "};"
def _render(out, lines):
indent = " "
level = 0
for l in lines:
if l and l[0] == "}":
level -= 2
assert level >= 0, out.getvalue()
out.write(indent * level + l + "\n")
if l and l[-1] == "{":
level += 2
if level == 0 and l and l[-1] == ";":
out.write("\n")
return out
def renderer():
out = io.StringIO()
def render(lines):
return _render(out, lines)
return render, out
def generate_maps_header():
render, out = renderer()
render(includes())
render(struct_start_size_t())
render(struct_tileset_t())
render(struct_map_t())
render(tilesets_header());
render(maps_header());
return out
def include_maps_header():
yield '#include "maps.hpp"'
yield ""
def generate_maps_source():
render, out = renderer()
render(include_maps_header())
render(tilesets())
render(maps())
return out
def generate(base_path): def generate(base_path):
files = [ files = [
(generate_maps_header, "maps.hpp"), (maps.generate_maps_header, "maps.hpp"),
(generate_maps_source, "maps.cpp"), (maps.generate_maps_source, "maps.cpp"),
(map_objects.generate_map_objects_source, "map_objects.cpp")
] ]
for func, filename in files: for func, filename in files:
path = base_path / filename path = base_path / filename
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(func().getvalue()) f.write(func().getvalue())
print(path)
# sys.argv[1] is secretly used in parse # sys.argv[1] is secretly used in parse
base_path = Path(sys.argv[2]) base_path = Path(sys.argv[2])
generate(base_path) generate(base_path)
"""
map_headers[0].tileset == 'OVERWORLD'
map_headers[0].name1 = 'PalletTown'
map_headers[0].name2 = 'PALLET_TOWN'
map_headers[0].blocks() == 'PalletTown_Blocks'
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
tileset_constants_list['OVERWORLD'] == 0
tileset_headers_list[0].name == 'Overworld'
tileset_headers_list[0].blockset() == 'Overworld_Block'
tileset_headers_list[0].gfx() == 'Overworld_GFX'
gfx_tilesets_list['Overworld_Block'] == 'gfx/blocksets/overworld.bst'
gfx_tilesets_list['Overworld_GFX'] == 'gfx/tilesets/overworld.2bpp'
"""

View File

@ -1,3 +1,24 @@
from pathlib import Path import io
prefix = Path(sys.argv[2]) def _render(out, lines):
indent = " "
level = 0
for l in lines:
if l and l[0] == "}":
level -= 2
assert level >= 0, out.getvalue()
out.write(indent * level + l + "\n")
if l and l[-1] == "{":
level += 2
if level == 0 and l and l[-1] == ";":
out.write("\n")
return out
def renderer():
out = io.StringIO()
def render(lines):
return _render(out, lines)
return render, out

View File

@ -0,0 +1,105 @@
from parse import parse
from generate.generate import renderer
from generate.maps import sorted_map_headers
def warp_event(ev):
x, y = ev.position
return [
"{",
f".position = {{ {x}, {y} }},",
".destination = {",
f".map = map_t::{ev.destination_map.lower()},",
f".warp_index = {ev.destination_warp_index},",
"}",
"},",
]
def bg_event(ev):
x, y = ev.position
return [
"{",
f".position = {{ {x}, {y} }},",
".sign_id = 0,", # fixme
"},",
]
def range_or_direction(ev):
direction = ev.range_or_direction.lower()
if direction == '0':
direction = 'any_dir' # hack?
yield f".range_or_direction = object_event_t::range_or_direction::{direction},"
def object_event(ev):
x, y = ev.position
return [
"{",
f".type = object_event_t::type::{ev.type},",
f".position = {{ {x}, {y} }},",
".sprite_id = 0,", # fixme
f".movement = object_event_t::movement::{ev.movement.lower()},",
*(range_or_direction(ev)),
".text_id = 0,", # fixme
".trainer = { 0 },", # fixme
"},",
]
def warp_events(map_name, obj):
yield f"const warp_event_t {map_name}_warp_events[] = {{"
for ev in obj.warp_events:
yield from warp_event(ev)
yield "};"
def bg_events(map_name, obj):
yield f"const bg_event_t {map_name}_bg_events[] = {{"
for ev in obj.bg_events:
yield from bg_event(ev)
yield "};"
def object_events(map_name, obj):
yield f"const object_event_t {map_name}_object_events[] = {{"
for ev in obj.object_events:
yield from object_event(ev)
yield "};"
def object(map_name, obj):
return [
f"[map_t::{map_name}] = {{",
f".border_block = {obj.border_block},",
f".warp_length = {len(obj.warp_events)},",
f".bg_length = {len(obj.bg_events)},",
f".object_length = {len(obj.object_events)},",
f".warp_events = &{map_name}_warp_events[0],",
f".bg_events = &{map_name}_bg_events[0],",
f".object_events = &{map_name}_object_events[0],",
"},",
]
def includes_source():
yield '#include <cstdint>'
yield ""
yield '#include "../map_objects.hpp"'
yield '#include "maps.hpp"'
yield ""
def map_objects():
map_headers = sorted_map_headers()
for map_header in map_headers:
map_name = map_header.name2.lower()
map_objects = parse.map_objects_list[map_header.object()]
yield from warp_events(map_name, map_objects)
yield from bg_events(map_name, map_objects)
yield from object_events(map_name, map_objects)
yield "const object_t map_objects[] = {"
for map_header in map_headers:
map_name = map_header.name2.lower()
map_objects = parse.map_objects_list[map_header.object()]
yield from object(map_name, map_objects)
yield "};"
def generate_map_objects_source():
render, out = renderer()
render(includes_source())
render(map_objects())
return out

172
tools/generate/maps.py Normal file
View File

@ -0,0 +1,172 @@
from parse import parse
from generate.generate import renderer
def sorted_map_headers():
# hack to remove unused/duplicate underground_path_route_7
map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
def includes_header():
yield "#pragma once"
yield ""
for map_header in sorted_map_headers():
block_path = parse.maps_blocks_list[map_header.blocks()]
yield f'#include "../res/{block_path}.h"'
for tileset_name in sorted(parse.tileset_constants_list):
tileset_index = parse.tileset_constants_list[tileset_name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
yield f'#include "../res/{blockset_path}.h"'
yield f'#include "../res/{gfx_path}.h"'
yield ""
def struct_start_size_t():
return [
"struct start_size_t {",
"uint8_t const * const start;",
"uint32_t size;",
"};",
]
def struct_tileset_t():
tileset_names = (
f"{name.lower()},"
for name in sorted(parse.tileset_constants_list)
)
return [
"struct tileset_t {",
"start_size_t blockset;",
"start_size_t tileset;",
"",
"enum tileset {",
*tileset_names,
"};",
"};",
]
def struct_map_t():
map_names = (
f"{map_header.name2.lower()},"
for map_header in sorted_map_headers()
)
return [
"struct map_t {",
"start_size_t blocks;",
"enum tileset_t::tileset tileset;",
"uint32_t width;",
"uint32_t height;",
"",
"enum map {",
*map_names,
"last_map,", # special map name
"unused_map_ed,", # special silph co elevator map name
"};",
"};",
]
def binary_res(path, suffix):
# _binary_res_gfx_blocksets_overworld_bst_start
name = path.replace('/', '_').replace('.', '_')
return f"_binary_res_{name}_{suffix}"
def start_size_value(path):
start = binary_res(path, "start")
size = binary_res(path, "size")
return [
f"reinterpret_cast<uint8_t*>(&{start}),",
f"reinterpret_cast<uint32_t>(&{size}),",
]
def blockset_tileset(name: str):
tileset_index = parse.tileset_constants_list[name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
return [
f"[tileset_t::{name.lower()}] = {{",
".blockset = {",
*start_size_value(blockset_path),
"},",
".tileset = {",
*start_size_value(gfx_path),
"}",
"},"
]
def tilesets_header():
yield "extern const tileset_t tilesets[];"
def tilesets():
yield "const tileset_t tilesets[] = {"
for tileset in sorted(parse.tileset_constants_list):
yield from blockset_tileset(tileset)
yield "};"
def map(map_header):
block_path = parse.maps_blocks_list[map_header.blocks()]
map_constant = parse.map_constants_list[map_header.name2]
return [
f"[map_t::{map_header.name2.lower()}] = {{",
".blocks = {",
*start_size_value(block_path),
"},",
f".tileset = tileset_t::{map_header.tileset.lower()},",
f".width = {map_constant.width},",
f".height = {map_constant.height},",
"},",
]
def maps_header():
yield "extern const map_t maps[];"
def maps():
yield "const map_t maps[] = {"
for map_header in sorted_map_headers():
yield from map(map_header)
yield "};"
def generate_maps_header():
render, out = renderer()
render(includes_header())
render(struct_start_size_t())
render(struct_tileset_t())
render(struct_map_t())
render(tilesets_header());
render(maps_header());
return out
def includes_source():
yield '#include <cstdint>'
yield ''
yield '#include "maps.hpp"'
yield ''
def generate_maps_source():
render, out = renderer()
render(includes_source())
render(tilesets())
render(maps())
return out
"""
map_headers[0].tileset == 'OVERWORLD'
map_headers[0].name1 = 'PalletTown'
map_headers[0].name2 = 'PALLET_TOWN'
map_headers[0].blocks() == 'PalletTown_Blocks'
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
tileset_constants_list['OVERWORLD'] == 0
tileset_headers_list[0].name == 'Overworld'
tileset_headers_list[0].blockset() == 'Overworld_Block'
tileset_headers_list[0].gfx() == 'Overworld_GFX'
gfx_tilesets_list['Overworld_Block'] == 'gfx/blocksets/overworld.bst'
gfx_tilesets_list['Overworld_GFX'] == 'gfx/tilesets/overworld.2bpp'
"""

View File

@ -38,16 +38,16 @@ class MapHeader:
return f"{self.name1}_TextPointers" return f"{self.name1}_TextPointers"
def script(self): def script(self):
return f"{self.name1}_Script", return f"{self.name1}_Script"
def object(self): def object(self):
return f"{self.name1}_Object", return f"{self.name1}_Object"
def width(self): def width(self):
return f"{self.name2}_WIDTH", return f"{self.name2}_WIDTH"
def height(self): def height(self):
return f"{self.name2}_HEIGHT", return f"{self.name2}_HEIGHT"
def flatten(tokens): def flatten(tokens):
# expects tokens from a single file # expects tokens from a single file
@ -81,6 +81,4 @@ def parse(path):
def parse_all(prefix): def parse_all(prefix):
base_path = prefix / 'data/maps/headers' base_path = prefix / 'data/maps/headers'
paths = [p for p in base_path.iterdir() if p.is_file()] paths = [p for p in base_path.iterdir() if p.is_file()]
from pprint import pprint
pprint(paths)
return [parse(path) for path in paths] return [parse(path) for path in paths]

View File

@ -11,7 +11,7 @@ class ObjectEvent:
movement: str movement: str
range_or_direction: str range_or_direction: str
text_id: str text_id: str
items_id_or_trainer_id_or_pokemon_id: str = None item_id_or_trainer_class_or_pokemon_id: str = None
trainer_number_or_pokemon_level: str = None trainer_number_or_pokemon_level: str = None
@dataclass @dataclass
@ -53,12 +53,16 @@ def tokenize_lines(lines):
def object_id(object_args): def object_id(object_args):
types = { types = {
6: "trainer", 6: "pokemon",
5: "item", 5: "item",
4: "generic", 4: "generic",
} }
assert len(object_args) in types, object_args assert len(object_args) in types, object_args
return types[len(object_args)] type = types[len(object_args)]
# this feels almost too hacky...
if type == "pokemon" and "OPP_" in object_args[-2]:
type = "trainer"
return type
def flatten(tokens): def flatten(tokens):
label = None label = None