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-sh:
rm -rf res
rm -rf res gen
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 pprint import pprint
import io
import sys
from parse import parse
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
from generate import maps
from generate import map_objects
def generate(base_path):
files = [
(generate_maps_header, "maps.hpp"),
(generate_maps_source, "maps.cpp"),
(maps.generate_maps_header, "maps.hpp"),
(maps.generate_maps_source, "maps.cpp"),
(map_objects.generate_map_objects_source, "map_objects.cpp")
]
for func, filename in files:
path = base_path / filename
with open(path, 'w') as f:
f.write(func().getvalue())
print(path)
# sys.argv[1] is secretly used in parse
base_path = Path(sys.argv[2])
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"
def script(self):
return f"{self.name1}_Script",
return f"{self.name1}_Script"
def object(self):
return f"{self.name1}_Object",
return f"{self.name1}_Object"
def width(self):
return f"{self.name2}_WIDTH",
return f"{self.name2}_WIDTH"
def height(self):
return f"{self.name2}_HEIGHT",
return f"{self.name2}_HEIGHT"
def flatten(tokens):
# expects tokens from a single file
@ -81,6 +81,4 @@ def parse(path):
def parse_all(prefix):
base_path = prefix / 'data/maps/headers'
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]

View File

@ -11,7 +11,7 @@ class ObjectEvent:
movement: str
range_or_direction: 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
@dataclass
@ -53,12 +53,16 @@ def tokenize_lines(lines):
def object_id(object_args):
types = {
6: "trainer",
6: "pokemon",
5: "item",
4: "generic",
}
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):
label = None