fixing bugs

This commit is contained in:
bryanthaboi
2026-07-25 12:35:00 -04:00
parent 9307488dc1
commit 347838619b
83 changed files with 5625 additions and 1160 deletions
+22 -7
View File
@@ -6,7 +6,8 @@ Sources:
data/tilesets/water_tilesets.asm -> tilesets where Surf works
data/events/hidden_events.asm -> hidden items / coins / slot machines,
PC tiles, bench guys, gym statues,
Vermilion Gym trash cans
PrintTrashText bins, Vermilion Gym
trash cans
data/events/bench_guys.asm -> bench guy text per map
data/events/slot_machine_wheels.asm -> the three slot wheel symbol lists
data/events/card_key_{coords,maps}.asm + engine/events/card_key.asm
@@ -117,15 +118,20 @@ def parse_hidden_events(pokered):
StartSlotMachine (slot machine seats; arg SLOTS_* marks broken ones).
Also collects the engine text hooks that the port implements natively:
OpenPokemonCenterPC / OpenRedsPC (the player's storage PC in the
bedroom), PrintBenchGuyText, GymStatues and the Vermilion
Gym GymTrashScript cans (arg = [wGymTrashCanIndex]). For those the
fourth macro argument is the facing direction required to trigger the
event, except GymTrashScript where it is the can index.
bedroom), PrintBenchGuyText, GymStatues, PrintTrashText (plain
"only trash here" bins) and the Vermilion Gym GymTrashScript cans
(arg = [wGymTrashCanIndex]). For those the fourth macro argument is
the facing direction required to trigger the event, except
GymTrashScript where it is the can index and PrintTrashText which
stores a facing but ignores it.
"""
items = {}
coins = {}
slots = {}
extras = {"pcTiles": {}, "benchGuys": {}, "gymStatues": {}, "trashCans": []}
extras = {
"pcTiles": {}, "benchGuys": {}, "gymStatues": {},
"printTrash": {}, "trashCans": [],
}
current = None
path = os.path.join(pokered, "data/events/hidden_events.asm")
for lineno, line in read_asm(path):
@@ -163,6 +169,9 @@ def parse_hidden_events(pokered):
elif func == "GymStatues":
extras["gymStatues"].setdefault(current, []).append(
{"x": x, "y": y, "facing": DIRS.get(arg, arg)})
elif func == "PrintTrashText":
extras["printTrash"].setdefault(current, []).append(
{"x": x, "y": y, "facing": DIRS.get(arg, arg)})
elif func == "GymTrashScript":
if current != "VERMILION_GYM":
util.die(f"hidden_events.asm:{lineno}: GymTrashScript outside VERMILION_GYM")
@@ -1339,6 +1348,7 @@ def extract(pokered, out_dir):
"pcTiles": extras["pcTiles"],
"benchGuys": extras["benchGuys"],
"gymStatues": extras["gymStatues"],
"printTrash": extras["printTrash"],
"trashCans": parse_trash_can_puzzle(pokered, extras["trashCans"]),
}
@@ -1373,6 +1383,7 @@ def extract(pokered, out_dir):
credits["theEnd"] = gfx.extract_the_end(pokered, assets_dir)
battle_hud = gfx.extract_battle_hud(pokered, assets_dir)
town_map["background"] = gfx.extract_town_map_bg(pokered, assets_dir)
trade_art = gfx.extract_trade(pokered, assets_dir)
if not ledges or not cut_trees or "OVERWORLD" not in water or len(trades) < 8 \
or "PALLET_TOWN" not in fly_warps:
@@ -1387,7 +1398,9 @@ def extract(pokered, out_dir):
or len(hidden_extras["pcTiles"]) < 10 \
or "VIRIDIAN_GYM" not in hidden_extras["gymStatues"] \
or hidden_extras["benchGuys"].get("VIRIDIAN_POKECENTER",
[{}])[0].get("text") is None:
[{}])[0].get("text") is None \
or len(hidden_extras["printTrash"].get("SS_ANNE_KITCHEN", [])) != 2 \
or len(hidden_extras["printTrash"].get("VERMILION_GYM", [])) != 1:
util.die("hidden extras extraction sanity check failed")
if sorted(forced_movement["tiles"]) != ["ROUTE_16", "ROUTE_18",
"SEAFOAM_ISLANDS_B3F",
@@ -1499,6 +1512,7 @@ def extract(pokered, out_dir):
"townMap": town_map,
"credits": credits,
"slotSymbols": slot_symbols,
"tradeArt": trade_art,
"emotionBubbles": emotion_bubbles,
"oakSpeech": oak_speech,
"overworldFx": overworld_fx,
@@ -1525,6 +1539,7 @@ def extract(pokered, out_dir):
"+ gfx/credits/the_end.png,\n"
"constants/script_constants.asm (SLOTS*, *_BUBBLE)\n"
"+ gfx/slots/red_slots_{1,2}.png + gfx/emotes/*.png\n"
"+ gfx/trade/* (InternalClockTradeAnim),\n"
"+ engine/{slots/slot_machine,overworld/emotion_bubbles}.asm,\n"
"scripts/ViridianCity.asm + engine/battle/core.asm,\n"
"scripts/GameCorner.asm + text/GameCorner.asm,\n"
+107
View File
@@ -809,3 +809,110 @@ def extract_town_map_bg(pokered, assets_dir):
"source": "gfx/town_map/town_map.rle + town_map.png "
"(engine/items/town_map.asm LoadTownMap)",
}
# ---------------------------------------------------------------------------
# Trade animation graphics (engine/movie/trade.asm)
# ---------------------------------------------------------------------------
def _unique_tiles(im, cols):
"""Row-major 8x8 tiles kept in first-seen order (rgbgfx --remove-duplicates)."""
kept, index_of = [], {}
for r in range(im.size[1] // 8):
for c in range(cols):
tile = im.crop((c * 8, r * 8, c * 8 + 8, r * 8 + 8))
key = tile.tobytes()
if key not in index_of:
index_of[key] = len(kept)
kept.append(tile)
return kept
def _all_tiles(im, cols):
return [im.crop((c * 8, r * 8, c * 8 + 8, r * 8 + 8))
for r in range(im.size[1] // 8) for c in range(cols)]
def extract_trade(pokered, assets_dir):
"""TradingAnimationGraphics (+ cable ball / bubble) for InternalClockTradeAnim.
game_boy.2bpp is built with --remove-duplicates; link_cable.2bpp is not.
Tilemaps reference absolute vChars2 ids starting at $31. We rebuild the
open-cable plate and cable segment tiles from that atlas, and convert the
Game Boy / ball / bubble sheets for direct blitting.
"""
trade_dir = os.path.join(pokered, "gfx/trade")
out_dir = os.path.join(assets_dir, "trade")
gb_src = Image.open(os.path.join(trade_dir, "game_boy.png")).convert("L")
lc_src = Image.open(os.path.join(trade_dir, "link_cable.png")).convert("L")
if gb_src.size != (48, 64) or lc_src.size != (24, 40):
util.die(f"trade sheets: unexpected sizes {gb_src.size} / {lc_src.size}")
atlas = _unique_tiles(gb_src, 6) + _all_tiles(lc_src, 3)
if len(atlas) != 49:
util.die(f"trade atlas: expected 49 tiles after dedupe, got {len(atlas)}")
convert_png(os.path.join(trade_dir, "game_boy.png"),
os.path.join(out_dir, "game_boy.png"), transparent_matte=True)
convert_png(os.path.join(trade_dir, "bubble.png"),
os.path.join(out_dir, "bubble.png"), transparent_color0=True)
# cable_ball.2bpp is four 8x8 tiles at vSprites $7c; the ball OAM uses
# tile $7e four times with X/Y flips (and $7f for the bulge frame).
ball_src = _convert_image(
Image.open(os.path.join(trade_dir, "cable_ball.png")),
transparent_color0=True)
if ball_src.size != (16, 16):
util.die(f"cable_ball.png: expected 16x16, got {ball_src.size}")
def compose_ball(tile):
out = Image.new("RGBA", (16, 16), (0, 0, 0, 0))
out.paste(tile, (0, 0))
out.paste(tile.transpose(Image.FLIP_LEFT_RIGHT), (8, 0))
out.paste(tile.transpose(Image.FLIP_TOP_BOTTOM), (0, 8))
out.paste(tile.transpose(Image.FLIP_LEFT_RIGHT).transpose(
Image.FLIP_TOP_BOTTOM), (8, 8))
return out
_save_png(compose_ball(ball_src.crop((0, 8, 8, 16))),
os.path.join(out_dir, "cable_ball.png"))
_save_png(compose_ball(ball_src.crop((8, 8, 16, 16))),
os.path.join(out_dir, "cable_ball_alt.png"))
with open(os.path.join(trade_dir, "link_cable.tilemap"), "rb") as fh:
lc_map = fh.read()
if len(lc_map) != 36:
util.die(f"link_cable.tilemap: expected 36 bytes, got {len(lc_map)}")
open_cable = Image.new("L", (96, 24), 255)
for i, tid in enumerate(lc_map):
idx = tid - 0x31
if idx < 0 or idx >= len(atlas):
util.die(f"link_cable.tilemap: tile ${tid:02x} outside atlas")
open_cable.paste(atlas[idx], ((i % 12) * 8, (i // 12) * 8))
_save_png(_convert_image(open_cable), os.path.join(out_dir, "open_cable.png"))
for name, vid in (("cable_seg", 0x5E), ("cable_conn", 0x5D),
("cable_vert", 0x61), ("cable_corner", 0x5F),
("cable_end", 0x60)):
_save_png(_convert_image(atlas[vid - 0x31]),
os.path.join(out_dir, f"{name}.png"))
horiz = Image.new("L", (160, 8), 255)
seg = atlas[0x5E - 0x31]
for x in range(20):
horiz.paste(seg, (x * 8, 0))
_save_png(_convert_image(horiz), os.path.join(out_dir, "cable_horiz.png"))
return {
"gameBoy": "assets/generated/trade/game_boy.png",
"openCable": "assets/generated/trade/open_cable.png",
"cableHoriz": "assets/generated/trade/cable_horiz.png",
"cableConn": "assets/generated/trade/cable_conn.png",
"cableVert": "assets/generated/trade/cable_vert.png",
"cableCorner": "assets/generated/trade/cable_corner.png",
"cableEnd": "assets/generated/trade/cable_end.png",
"cableBall": "assets/generated/trade/cable_ball.png",
"cableBallAlt": "assets/generated/trade/cable_ball_alt.png",
"bubble": "assets/generated/trade/bubble.png",
"source": "gfx/trade/* (TradingAnimationGraphics, "
"engine/movie/trade.asm InternalClockTradeAnim)",
}