mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
discord rich presence, and MORE COLORS, video options, shiddddd so much stuff
This commit is contained in:
+308
-4
@@ -18,11 +18,44 @@ Output: data/generated/palettes.lua
|
||||
pokemon[SPECIES] = NAME (PAL_ prefix stripped)
|
||||
|
||||
Output: data/palettes_gbc.lua (committed; not wiped by ROM import)
|
||||
same shape, plus per-species palette entries (BULBASAUR, …)
|
||||
same shape, plus per-species palette entries (BULBASAUR, …), plus a
|
||||
`world` table (below) for true overworld tile/roof/sprite coloring.
|
||||
|
||||
The HP bar fill is GB color 2 of PAL_GREENBAR / PAL_YELLOWBAR /
|
||||
PAL_REDBAR; the thresholds live in home/palettes.asm GetHealthBarColor
|
||||
(>= 27 pixels green, >= 10 yellow, else red).
|
||||
|
||||
Sources (pokered-gbc color/**, the real GBC overworld coloring engine):
|
||||
color/data/map_palette_assignments.asm per tileset: 96 tile-graphic-id
|
||||
-> palette-group (0-7) bytes
|
||||
color/data/map_palette_sets.asm per tileset: 8 group -> palette-
|
||||
constant bytes (positional)
|
||||
color/data/map_palettes.asm palette-constant -> 4 RGB colors
|
||||
color/data/roofpalettes.asm per pokered map ID (positional,
|
||||
matches map.def.index): roof
|
||||
2-color override + the labeled
|
||||
RGB blocks it points at
|
||||
color/data/spritepalettes.asm 8 four-color OBJ palettes
|
||||
color/sprites.asm SpritePaletteAssignments: 1-based
|
||||
picture ID -> OBJ palette 0-3, or
|
||||
the "db 4" random-per-instance
|
||||
sentinel (ColorOverworldSprite)
|
||||
|
||||
Output `world` table shape:
|
||||
world.tileGroups[TILESET][tileId 0-95] = group index 0-7
|
||||
world.groupColors[TILESET][group 0-7] = { {r,g,b} x4 }
|
||||
world.roofGroup[TILESET] = the town-overridable group index (OVERWORLD/
|
||||
PLATEAU only)
|
||||
world.roofByMapIndex[mapIndex] = { {r,g,b} x2 } (matches map.def.index)
|
||||
world.spritePalettes[0-7] = { {r,g,b} x4 }
|
||||
world.spriteAssignment[pictureIndex 0-based] = group 0-3 or "random"
|
||||
(pictureIndex matches the N in a sprite def's
|
||||
`source = "ROM:SpriteSheetPointerTable[N]"`)
|
||||
|
||||
The 3 hardcoded Celadon Mart tile-group exceptions and the Route 6/Saffron
|
||||
roof y-split (LoadTilesetPalette / LoadTownPalette control flow, not data)
|
||||
are NOT extracted here -- they live as a small named table in
|
||||
src/render/PaletteFX.lua next to the code that consumes `world`.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -185,6 +218,271 @@ def _parse_gbc_monster_palettes(path):
|
||||
return mon_pals
|
||||
|
||||
|
||||
# ---- color/** overworld attribute data ------------------------------------
|
||||
#
|
||||
# Two tiny fixed enums, hand-transcribed rather than parsed: both are
|
||||
# `EQU`/`const` lists that never change (they are the hardware-shaped slot
|
||||
# numbering, not game content), and each uses a different const-declaration
|
||||
# style that isn't worth a bespoke parser for 4-8 entries.
|
||||
# color/data/map_palette_constants.asm:69-78 (`const_value = 0` block)
|
||||
_GROUP_NAMES = ["GRAY", "RED", "GREEN", "BLUE", "YELLOW", "BROWN", "ROOF", "TEXT"]
|
||||
_GROUP_INDEX = {name: i for i, name in enumerate(_GROUP_NAMES)}
|
||||
# color/sprites.asm:15-18 (`SPR_PAL_* EQU n`)
|
||||
_SPR_PAL = {"SPR_PAL_ORANGE": 0, "SPR_PAL_BLUE": 1, "SPR_PAL_GREEN": 2, "SPR_PAL_BROWN": 3}
|
||||
|
||||
|
||||
def _rgbN(nums):
|
||||
return [[_scale5(nums[i]), _scale5(nums[i + 1]), _scale5(nums[i + 2])]
|
||||
for i in range(0, len(nums), 3)]
|
||||
|
||||
|
||||
def _parse_rgb_table(path, header_re, n_nums):
|
||||
"""name -> n_nums/3 RGB colors, from blocks of `header_re` + RGB lines
|
||||
(one or more `RGB r,g,b[,r,g,b...]` lines per block, however they're
|
||||
wrapped -- matches both the 1-color-per-line and 4-colors-per-line
|
||||
styles pokered-gbc uses in different files)."""
|
||||
out = {}
|
||||
order = []
|
||||
pending = None
|
||||
buf = []
|
||||
for lineno, line in _read_raw(path):
|
||||
s = line.strip()
|
||||
m = header_re.match(s)
|
||||
if m:
|
||||
if pending:
|
||||
util.die(f"{path}:{lineno}: incomplete RGB block for "
|
||||
f"{pending} ({len(buf)}/{n_nums})")
|
||||
pending = m.group(1)
|
||||
buf = []
|
||||
continue
|
||||
m = re.match(r"RGB\s+([\d,\s]+)", s)
|
||||
if not m or not pending:
|
||||
continue
|
||||
nums = [n for n in re.split(r"[,\s]+", m.group(1).strip()) if n]
|
||||
buf.extend(nums)
|
||||
if len(buf) < n_nums:
|
||||
continue
|
||||
if len(buf) != n_nums:
|
||||
util.die(f"{path}:{lineno}: expected {n_nums} components for "
|
||||
f"{pending}, got {len(buf)}")
|
||||
out[pending] = _rgbN(buf)
|
||||
order.append(pending)
|
||||
pending = None
|
||||
buf = []
|
||||
if pending:
|
||||
util.die(f"{path}: incomplete RGB block for {pending}")
|
||||
return out, order
|
||||
|
||||
|
||||
_TILESET_HEADER_RE = re.compile(r"^;\s*([A-Z][A-Z0-9_]*)\s*$")
|
||||
|
||||
|
||||
def _parse_map_palette_assignments(path):
|
||||
"""color/data/map_palette_assignments.asm: per tileset, 96 tile-graphic
|
||||
palette-group bytes (0-7, symbolic), 16 per line / 6 lines."""
|
||||
tile_groups = {}
|
||||
order = []
|
||||
current = None
|
||||
vals = []
|
||||
|
||||
def flush(lineno):
|
||||
if len(vals) != 96:
|
||||
util.die(f"{path}:{lineno}: {current} has {len(vals)} tile "
|
||||
f"groups (want 96)")
|
||||
tile_groups[current] = vals[:]
|
||||
order.append(current)
|
||||
|
||||
for lineno, line in _read_raw(path):
|
||||
s = line.strip()
|
||||
m = _TILESET_HEADER_RE.match(s)
|
||||
if m:
|
||||
if current:
|
||||
flush(lineno)
|
||||
current, vals = m.group(1), []
|
||||
continue
|
||||
m = re.match(r"db\s+(.*)$", s)
|
||||
if not m or not current:
|
||||
continue
|
||||
for tok in m.group(1).split(","):
|
||||
tok = tok.strip()
|
||||
if tok not in _GROUP_INDEX:
|
||||
util.die(f"{path}:{lineno}: unknown palette group {tok!r}")
|
||||
vals.append(_GROUP_INDEX[tok])
|
||||
if current:
|
||||
flush("EOF")
|
||||
return tile_groups, order
|
||||
|
||||
|
||||
def _parse_map_palette_sets(path):
|
||||
"""color/data/map_palette_sets.asm: per tileset, 8 palette-constant
|
||||
names, positional (index = the palette-group 0-7 above)."""
|
||||
sets = {}
|
||||
order = []
|
||||
current = None
|
||||
vals = []
|
||||
|
||||
def flush(lineno):
|
||||
if len(vals) != 8:
|
||||
util.die(f"{path}:{lineno}: {current} has {len(vals)} palette "
|
||||
f"sets (want 8)")
|
||||
sets[current] = vals[:]
|
||||
order.append(current)
|
||||
|
||||
for lineno, line in _read_raw(path):
|
||||
s = line.strip()
|
||||
m = _TILESET_HEADER_RE.match(s)
|
||||
if m:
|
||||
if current:
|
||||
flush(lineno)
|
||||
current, vals = m.group(1), []
|
||||
continue
|
||||
m = re.match(r"db\s+(\w+)", s)
|
||||
if m and current:
|
||||
vals.append(m.group(1))
|
||||
if current:
|
||||
flush("EOF")
|
||||
return sets, order
|
||||
|
||||
|
||||
def _parse_roof_order(path):
|
||||
"""color/data/roofpalettes.asm's RoofPalettes: dw list, positional by
|
||||
pokered map ID (matches map.def.index in the recomp project)."""
|
||||
names = []
|
||||
in_table = False
|
||||
for lineno, line in _read_raw(path):
|
||||
s = line.strip()
|
||||
if s.startswith("RoofPalettes:"):
|
||||
in_table = True
|
||||
continue
|
||||
if not in_table:
|
||||
continue
|
||||
m = re.match(r"dw\s+(\w+)", s)
|
||||
if m:
|
||||
names.append(m.group(1))
|
||||
continue
|
||||
if s == "":
|
||||
continue
|
||||
break # first non-dw, non-blank line (a Label:) ends the table
|
||||
if not names:
|
||||
util.die(f"{path}: RoofPalettes table not found or empty")
|
||||
return names
|
||||
|
||||
|
||||
def _parse_sprite_assignments(path):
|
||||
"""color/sprites.asm SpritePaletteAssignments: 1-based picture ID (from
|
||||
the '; 0xNN: SPRITE_NAME' comment) -> OBJ palette 0-3, or "random" for
|
||||
the "db 4" per-instance sentinel (ColorOverworldSprite). Returned keyed
|
||||
0-based (pictureId - 1), matching a recomp sprite def's
|
||||
`source = "ROM:SpriteSheetPointerTable[N]"` index."""
|
||||
assignments = {}
|
||||
in_table = False
|
||||
pending_id = None
|
||||
header_re = re.compile(r"^;\s*0x([0-9a-fA-F]+):")
|
||||
for lineno, line in _read_raw(path):
|
||||
s = line.strip()
|
||||
if s.startswith("SpritePaletteAssignments:"):
|
||||
in_table = True
|
||||
continue
|
||||
if not in_table:
|
||||
continue
|
||||
m = header_re.match(s)
|
||||
if m:
|
||||
pending_id = int(m.group(1), 16)
|
||||
continue
|
||||
m = re.match(r"db\s+(\w+)", s)
|
||||
if m and pending_id is not None:
|
||||
tok = m.group(1)
|
||||
if tok == "4":
|
||||
assignments[pending_id - 1] = "random"
|
||||
elif tok in _SPR_PAL:
|
||||
assignments[pending_id - 1] = _SPR_PAL[tok]
|
||||
else:
|
||||
util.die(f"{path}:{lineno}: unknown sprite palette {tok!r}")
|
||||
pending_id = None
|
||||
continue
|
||||
if s and not s.startswith(";"):
|
||||
break # next label (AnimationTileset1Palettes:) ends the table
|
||||
if not assignments:
|
||||
util.die(f"{path}: SpritePaletteAssignments not found or empty")
|
||||
return assignments
|
||||
|
||||
|
||||
def extract_gbc_world(pokered_gbc):
|
||||
"""pokered-gbc color/** -> the `world` table (see module docstring):
|
||||
real per-tile GBC BG-palette groups, per-town roof overrides, and
|
||||
overworld sprite OBJ palettes, for true overworld parity under
|
||||
COLORS=RED++ (LoadTilesetPalette / LoadTownPalette / ColorOverworldSprite)."""
|
||||
base = os.path.join(pokered_gbc, "color")
|
||||
|
||||
tile_groups, ts_order = _parse_map_palette_assignments(
|
||||
os.path.join(base, "data/map_palette_assignments.asm"))
|
||||
pal_sets, ts_order2 = _parse_map_palette_sets(
|
||||
os.path.join(base, "data/map_palette_sets.asm"))
|
||||
if set(ts_order) != set(ts_order2):
|
||||
util.die("map_palette_assignments.asm / map_palette_sets.asm "
|
||||
"tileset lists disagree")
|
||||
if len(ts_order) < 20:
|
||||
util.die(f"map_palette_assignments.asm: parsed only {len(ts_order)} "
|
||||
f"tilesets (want ~24)")
|
||||
|
||||
map_palettes, _ = _parse_rgb_table(
|
||||
os.path.join(base, "data/map_palettes.asm"),
|
||||
re.compile(r"^;\s*0x[0-9a-fA-F]+:\s*(\w+)\s*$"), 12)
|
||||
|
||||
group_colors = {}
|
||||
for ts in ts_order:
|
||||
colors = []
|
||||
for slot, const in enumerate(pal_sets[ts]):
|
||||
if const not in map_palettes:
|
||||
util.die(f"map_palette_sets.asm: {ts} slot {slot}: unknown "
|
||||
f"palette constant {const}")
|
||||
colors.append(map_palettes[const])
|
||||
group_colors[ts] = colors
|
||||
|
||||
# Only these two tilesets are ever town-roof-swapped
|
||||
# (LoadTilesetPalette: `cp 0` / `cp PLATEAU` before calling
|
||||
# LoadTownPalette) -- the ROOF slot for every other tileset is static.
|
||||
roof_group = {ts: _GROUP_INDEX["ROOF"]
|
||||
for ts in ("OVERWORLD", "PLATEAU") if ts in group_colors}
|
||||
|
||||
roof_path = os.path.join(base, "data/roofpalettes.asm")
|
||||
roof_colors, _ = _parse_rgb_table(
|
||||
roof_path, re.compile(r"^(?!RoofPalettes)(\w+):\s*$"), 6)
|
||||
roof_order = _parse_roof_order(roof_path)
|
||||
roof_by_index = {}
|
||||
for i, name in enumerate(roof_order):
|
||||
if name not in roof_colors:
|
||||
util.die(f"{roof_path}: RoofPalettes references unknown label "
|
||||
f"{name}")
|
||||
roof_by_index[i] = roof_colors[name]
|
||||
|
||||
sprite_path = os.path.join(base, "data/spritepalettes.asm")
|
||||
raw_sprite_pals, _ = _parse_rgb_table(
|
||||
sprite_path, re.compile(r"^;\s*(\d+)\s*$"), 12)
|
||||
sprite_palettes = {int(k): v for k, v in raw_sprite_pals.items()}
|
||||
if len(sprite_palettes) != 8:
|
||||
util.die(f"{sprite_path}: parsed {len(sprite_palettes)} sprite "
|
||||
f"palettes (want 8)")
|
||||
|
||||
sprite_assignment = _parse_sprite_assignments(
|
||||
os.path.join(base, "sprites.asm"))
|
||||
if len(sprite_assignment) != 72:
|
||||
util.die(f"sprites.asm: parsed {len(sprite_assignment)} sprite "
|
||||
f"palette assignments (want 72)")
|
||||
|
||||
tile_groups_out = {ts: {i: v for i, v in enumerate(vals)}
|
||||
for ts, vals in tile_groups.items()}
|
||||
|
||||
return {
|
||||
"tileGroups": tile_groups_out,
|
||||
"groupColors": group_colors,
|
||||
"roofGroup": roof_group,
|
||||
"roofByMapIndex": roof_by_index,
|
||||
"spritePalettes": sprite_palettes,
|
||||
"spriteAssignment": sprite_assignment,
|
||||
}
|
||||
|
||||
|
||||
def extract_gbc(pokered_gbc, out_path):
|
||||
"""Build the Red++ / pokered-gbc SuperPalette pack used by COLORS=RED++."""
|
||||
super_path = os.path.join(pokered_gbc, "data/super_palettes.asm")
|
||||
@@ -218,17 +516,23 @@ def extract_gbc(pokered_gbc, out_path):
|
||||
if name not in palettes:
|
||||
util.die(f"super_palettes.asm: PAL_{name} missing")
|
||||
|
||||
world = extract_gbc_world(pokered_gbc)
|
||||
|
||||
# write_lua stamps "Generated by tools/build_data.py"; keep that header
|
||||
# but point the source field at the gbc tree.
|
||||
util.write_lua(
|
||||
out_path,
|
||||
{"source": "pokered-gbc data/super_palettes.asm + data/mon_palettes.asm",
|
||||
{"source": "pokered-gbc data/super_palettes.asm + data/mon_palettes.asm"
|
||||
" + color/**",
|
||||
"palettes": palettes,
|
||||
"order": order,
|
||||
"pokemon": mon_pals},
|
||||
"pokemon": mon_pals,
|
||||
"world": world},
|
||||
header="Red++ / pokered-gbc SuperPalettes (COLORS=RED++): 4 8-bit RGB\n"
|
||||
"colors per palette (color 0 first), including per-species\n"
|
||||
"pals and the GEN_2 MonsterPalettes assignment.")
|
||||
"pals and the GEN_2 MonsterPalettes assignment, plus `world`\n"
|
||||
"(true overworld tile/roof/sprite GBC coloring -- see this\n"
|
||||
"file's module docstring for its shape).")
|
||||
return palettes, mon_pals
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user