generate/text: added

Of the "trivial" texts that the current parser can handle, these are
now being inserted in obj_events.
This commit is contained in:
Zack Buhman 2023-07-30 08:52:46 +00:00
parent a56def6074
commit e4ff1b4c5a
11 changed files with 300 additions and 26 deletions

View File

@ -11,6 +11,8 @@ GEN_SRC += gen/map_objects.cpp
GEN_SRC += gen/sprites.cpp
GEN_SRC += gen/tilesets.cpp
GEN_SRC += gen/collision_tile_ids.cpp
GEN_SRC += gen/text.cpp
GEN_SRC += gen/text_pointers.cpp
SRC =
SRC += $(GEN_SRC)

View File

@ -3,13 +3,14 @@
#include <stdint.h>
struct control_t {
static constexpr uint8_t next = 0x80;
static constexpr uint8_t line = 0x81;
static constexpr uint8_t para = 0x82;
static constexpr uint8_t cont = 0x83;
static constexpr uint8_t done = 0x84;
static constexpr uint8_t prompt = 0x85;
static constexpr uint8_t page = 0x86;
static constexpr uint8_t text = 0x80;
static constexpr uint8_t next = 0x81;
static constexpr uint8_t line = 0x82;
static constexpr uint8_t para = 0x83;
static constexpr uint8_t cont = 0x84;
static constexpr uint8_t done = 0x85;
static constexpr uint8_t prompt = 0x86;
static constexpr uint8_t page = 0x87;
};
struct ligatures_t {
@ -24,6 +25,7 @@ struct ligatures_t {
struct extended_t {
static constexpr uint8_t jpy = 0xa5; // ¥
static constexpr uint8_t colon_sm = 0xa6; // (small ':' symbol)
static constexpr uint8_t e = 0xe9; // é
static constexpr uint8_t ellipsis = 0xa8; // …
static constexpr uint8_t pk = 0xb2; // ᴾₖ

View File

@ -3,6 +3,8 @@ from generate import map_objects
from generate import sprites
from generate import tilesets
from generate import collision_tile_ids
from generate import text
from generate import text_pointers
files = [
(maps.generate_header, "maps.hpp"),
@ -15,4 +17,8 @@ files = [
(sprites.generate_source, "sprites.cpp"),
(collision_tile_ids.generate_header, "collision_tile_ids.hpp"),
(collision_tile_ids.generate_source, "collision_tile_ids.cpp"),
(text.generate_header, "text.hpp"),
(text.generate_source, "text.cpp"),
(text_pointers.generate_header, "text_pointers.hpp"),
(text_pointers.generate_source, "text_pointers.cpp"),
]

View File

@ -4,13 +4,13 @@ def _render(out, lines):
indent = " "
level = 0
for l in lines:
if l and l[0] == "}":
if l and (l[0] == "}" or l[0] == ")"):
level -= 2
assert level >= 0, out.getvalue()
out.write(indent * level + l + "\n")
if l and l[-1] == "{":
if l and (l[-1] == "{" or l[-1] == "("):
level += 2
if level == 0 and l and l[-1] == ";":

View File

@ -3,6 +3,7 @@ from parse import parse
from generate.generate import renderer
from generate.maps import sorted_map_headers
from generate.sprites import sprite_name
from generate.text_pointers import sorted_text_pointers
def warp_event(ev):
x, y = ev.position
@ -31,8 +32,9 @@ def range_or_direction(ev):
direction = 'any_dir' # hack?
yield f".range_or_direction = object_event_t::range_or_direction::{direction},"
def object_event(ev):
def object_event(ev, sorted_text_constants):
x, y = ev.position
text_id = '0xff' if ev.text_id not in sorted_text_constants else ev.text_id
return [
"{",
f".type = object_event_t::type::{ev.type},",
@ -40,7 +42,7 @@ def object_event(ev):
f".sprite_id = spritesheet_t::{sprite_name(ev.sprite_id)},",
f".movement = object_event_t::movement::{ev.movement.lower()},",
*(range_or_direction(ev)),
".text_id = 0,", # fixme
f".text_id = {text_id},", # fixme
".trainer = { 0 },", # fixme
"},",
]
@ -57,10 +59,10 @@ def bg_events(map_name, obj):
yield from bg_event(ev)
yield "};"
def object_events(map_name, obj):
def object_events(map_name, obj, sorted_text_constants):
yield f"const object_event_t {map_name}_object_events[] = {{"
for ev in obj.object_events:
yield from object_event(ev)
yield from object_event(ev, sorted_text_constants)
yield "};"
def object(map_name, obj):
@ -80,17 +82,22 @@ def includes_source():
yield '#include <cstdint>'
yield ""
yield '#include "../map_objects.hpp"'
yield '#include "text_pointers.hpp"'
yield '#include "maps.hpp"'
yield ""
def map_objects():
map_headers = list(sorted_map_headers())
scripts_list = parse.scripts_list()
for map_header in map_headers:
map_name = map_header.name2.lower()
map_objects = parse.map_objects_list()[map_header.object()]
# fixme: hack due to viridanmart
text_pointers = scripts_list.get(map_header.text_pointers(), ({}, {}))
sorted_text_constants = {k for k, _ in sorted_text_pointers(*text_pointers)}
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 from object_events(map_name, map_objects, sorted_text_constants)
yield "const object_t map_objects[] = {"
for map_header in map_headers:

View File

@ -64,6 +64,7 @@ def struct_map_t():
*struct_connection_t(),
"",
"start_size_t blocks;",
"start_size_t text_pointers;",
"uint32_t width;",
"uint32_t height;",
"connection_t connections[4];",
@ -104,14 +105,19 @@ def connections(map_header):
yield f".map = map_t::{connection.map_name2.lower()},"
yield f".offset = {connection.offset},"
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),
"},",
".text_pointers = {",
"},",
f".width = {map_constant.width},",
f".height = {map_constant.height},",

145
tools/generate/text.py Normal file
View File

@ -0,0 +1,145 @@
"""
const uint8_t string[] = "foo" "\x0a" "abc";
"""
from itertools import chain
from parse import parse
from generate.generate import renderer
_control_encode = {
'text_end': None,
'text_ram': None,
'text_start': None,
'text_decimal': None,
'text_bcd': None,
'text' : 0x80,
'next' : 0x81,
'line' : 0x82,
'para' : 0x83,
'cont' : 0x84,
'done' : 0x85,
'prompt' : 0x86,
'page' : 0x87,
}
_bracketed_encode = {
'PKMN' : '\\x96',
'COLON' : '\\xa6',
# fixme hacks
'PLAYER': 'PLAYER',
'RIVAL' : 'RIVAL',
'TARGET': 'TARGET',
'USER' : 'USER',
'SCROLL': 'SCROLL', # _ContTextNoPause
'_CONT' : '_CONT', # _ContText
}
def encode_bracketed(bracketed):
# see control.hpp
assert bracketed in _bracketed_encode, bracketed
return _bracketed_encode[bracketed]
def parse_bracketed(string):
chars = []
in_bracket = False
for c in string:
if c == '<':
assert in_bracket is False
in_bracket = True
if chars:
yield ''.join(chars)
chars = []
elif c == '>':
assert in_bracket is True
in_bracket = False
yield encode_bracketed(''.join(chars))
chars = []
elif ord(c) == 165:
if chars:
yield ''.join(chars)
chars = []
yield '\\xa5'
else:
chars.append(c)
if chars:
yield ''.join(chars)
def encode_control(control):
assert control in _control_encode, control
encode = _control_encode[control]
if encode is not None:
return f'\\x{encode:x}'
else:
return None
def quote(s):
return f'"{s}"'
def generate_literals(text):
for control__value in text:
if len(control__value) == 1:
control, = control__value
ec = encode_control(control)
if ec is not None:
yield ec
elif len(control__value) == 2:
control, value = control__value
ec = encode_control(control)
if ec is None:
continue
yield ec
yield from parse_bracketed(value)
else:
assert False, control__value
def generate_text(name, text):
yield f"const uint8_t {name}[] = ("
for literal in generate_literals(text):
yield quote(literal)
yield ");"
def calculate_length(text):
return sum(
1 if literal[0:2] == '\\x' else len(literal)
for literal in generate_literals(text)
)
def texts_header():
text_list = chain.from_iterable(
d.items() for d in parse.text_list())
for name, text in text_list:
length = calculate_length(text) + 1
yield f"extern const uint8_t {name}[{length}];"
def includes_header():
yield "#pragma once"
yield ""
yield "#include <cstdint>"
yield ""
def texts_source():
text_list = chain.from_iterable(
d.items() for d in parse.text_list())
for name, text in text_list:
yield from generate_text(name, text)
yield ""
def generate_header():
render, out = renderer()
render(includes_header());
render(texts_header())
return out
def includes_source():
yield "#include <cstdint>"
yield ""
yield '#include "text.hpp"'
yield ""
def generate_source():
render, out = renderer()
render(includes_source());
render(texts_source())
return out

View File

@ -0,0 +1,90 @@
from operator import itemgetter
from parse import parse
from generate.generate import renderer
"""
parse.scripts_list() == {
'PalletTown_TextPointers':
({'TEXT_PALLETTOWN_FISHER': 'PalletTownFisherText',
'TEXT_PALLETTOWN_GIRL': 'PalletTownGirlText',
'TEXT_PALLETTOWN_OAK': 'PalletTownOakText',
'TEXT_PALLETTOWN_OAKSLAB_SIGN': 'PalletTownOaksLabSignText',
'TEXT_PALLETTOWN_PLAYERSHOUSE_SIGN': 'PalletTownPlayersHouseSignText',
'TEXT_PALLETTOWN_RIVALSHOUSE_SIGN': 'PalletTownRivalsHouseSignText',
'TEXT_PALLETTOWN_SIGN': 'PalletTownSignText'},
{'PalletTownFisherText': '_PalletTownFisherText',
'PalletTownGirlText': '_PalletTownGirlText',
'PalletTownOaksLabSignText': '_PalletTownOaksLabSignText',
'PalletTownPlayersHouseSignText': '_PalletTownPlayersHouseSignText',
'PalletTownRivalsHouseSignText': '_PalletTownRivalsHouseSignText',
'PalletTownSignText': '_PalletTownSignText'}),
}
"""
def labels_by_constant(constants, labels):
# fixme: removes constants with no (parsed) label
for text_name, label_name in constants.items():
if label_name in labels:
yield text_name, labels[label_name]
def sorted_text_pointers(constants, labels):
return sorted(labels_by_constant(constants, labels), key=itemgetter(0))
def text_constant_enum(name, constants, labels):
yield f"enum {name} {{"
for i, (k, _) in enumerate(sorted_text_pointers(constants, labels)):
yield f"{k} = {i},"
yield "};"
def header_includes():
yield "#pragma once"
yield ""
def text_constant_enums():
map_text_pointers = parse.scripts_list()
for name, (constants, labels) in map_text_pointers.items():
yield from text_constant_enum(name, constants, labels)
def extern_text_pointer(name, constants, labels):
pointers = list(sorted_text_pointers(constants, labels))
yield f"extern const start_size_t {name}[{len(pointers)}];"
def extern_text_pointers():
map_text_pointers = parse.scripts_list()
for name, (constants, labels) in map_text_pointers.items():
yield from extern_text_pointer(name, constants, labels)
def generate_header():
render, out = renderer()
render(header_includes())
render(text_constant_enums())
render(extern_text_pointers())
return out
def text_pointer(name, constants, labels):
yield f"const start_size_t {name}[] = {{"
pointers = list(sorted_text_pointers(constants, labels))
for constant, label in pointers:
yield f"[{constant}] = {{"
yield f".start = &{label}[0],"
yield f".size = (sizeof ({label})),"
yield "},"
yield "};"
def text_pointers():
map_text_pointers = parse.scripts_list()
for name, (constants, labels) in map_text_pointers.items():
yield from text_pointer(name, constants, labels)
def source_includes():
yield '#include "../start_size.hpp"'
yield ""
yield '#include "text_pointers.hpp"'
yield '#include "text.hpp"'
yield ""
def generate_source():
render, out = renderer()
render(source_includes())
render(text_pointers())
return out

View File

@ -1,6 +1,6 @@
def skip_whitespace(lines):
i = 0
while lines[i:] and not lines[i].strip():
while lines[i:] and (not lines[i].strip() or lines[i][0] == ';'):
i += 1
return lines[i:]

View File

@ -16,6 +16,9 @@ from parse import gfx_sprites
from parse import spritesheets
from parse import sprite_constants
from parse import text
from parse import scripts
prefix = Path(sys.argv[1])
def memoize(f):
@ -51,3 +54,5 @@ spritesheets_list = memoize(lambda: spritesheets.parse(prefix))
sprite_constants_list = memoize(lambda: sprite_constants.parse(prefix))
# text
scripts_list = memoize(lambda: scripts.parse_all(prefix))
text_list = memoize(lambda: text.parse_all(prefix))

View File

@ -1,3 +1,5 @@
from itertools import chain
from parse.line import next_line, skip_whitespace
def parse_label(lines):
@ -6,7 +8,7 @@ def parse_label(lines):
name = line.removesuffix('::')
return lines, name
string_tokens = {"text", "cont", "para", "line"}
string_tokens = {"text", "cont", "para", "line", "next"}
def parse_string(line):
line = line.strip()
@ -42,18 +44,29 @@ def parse_body(lines):
value, = rest
body.append((type, parse_args(value)))
else:
# hack hack; some texts don't have a control word at the end
# _MoveNameText
if line.endswith('::'):
return [line] + lines, body
assert False, line
return lines, body
def tokenize_text(lines):
lines, name = parse_label(lines)
# fixme: hack
if name == '_CableClubNPCLinkClosedBecauseOfInactivityText':
return None
lines, body = parse_body(lines)
return lines, (name, body)
def tokenize(lines):
while lines:
lines, tokens = tokenize_text(lines)
lines__tokens = tokenize_text(lines)
if lines__tokens is None:
# fixme: hack9000
return
lines, tokens = lines__tokens
lines = skip_whitespace(lines)
yield tokens
@ -65,11 +78,9 @@ def parse(path):
return d
def parse_all(prefix):
base_path = prefix / 'text'
paths = [p for p in base_path.iterdir() if p.is_file()]
return [parse(path) for path in paths]
import sys
from pprint import pprint
from pathlib import Path
pprint(parse_all(Path(sys.argv[1])))
base_path0 = prefix / 'text'
paths0 = [p for p in base_path0.iterdir() if p.is_file()]
base_path1 = prefix / 'data/text'
paths1 = [p for p in base_path1.iterdir()
if p.is_file() and p.stem.startswith('text_')]
return [parse(path) for path in chain(paths0, paths1)]