PoP roomtest: страж — спрайты, таблица кадров и появление в комнате
Шаги 1-2 из плана стражей: 1. Спрайты (chtab_5_guard, база 750). Новый упаковщик pop_pack_guard.py: data/GUARD/res751..784 -> GUARD\g0..g4.atl (5 EMM-страниц, адресация id>>3 / id&7, как у Kid). Палитра берётся НЕ из PNG, а из res10.bin (guard_palettes: 7 палитр по 16 цветов, 6-бит) по level.guards_color — на уровне 1 у обоих стражей color = 2; группа слотов 0x90..0x9F добавлена в общий kid.pal. 2. Таблица кадров стража у оригинала СВОЯ (frame_tbl_guard, seg006:372, 41 запись) и индексируется как frame + add_frame - 149, где add_frame = 70 для кадров 102..106. Она дописана в kid_data.bin (3515 -> 3720 Б, смещение в KID_BIN_GFRAMES_OFF); pop_kid получил pop_kid_data_frame() — чтение кадра из ЛЮБОЙ таблицы страницы данных. 3. Появление: pop_level_guard() читает guards_tile/dir/color/skill из уровня, pop_guard_enter() ставит стража по enter_guard (seg002:0112) + pos_guards (seg003): row из тайла, y = y_land[row+1], x из колонки, charid = guard, sword сложен, alive = -1, HP = 3. Отрисовка pop_guard_draw() — та же математика, что kid_draw (load_frame_to_obj + calc_screen_x_coord), но атлас стража и своя таблица кадров; heal по странице дабл-буфера, как у Kid. Интерпретатора последовательностей у стража ПОКА НЕТ: кадр ставится напрямую (166 = frame_166_stand_inactive, что и даёт seq_77 при входе в комнату). play_seq для произвольного персонажа + ИИ — следующая фаза. Проверено в MAME: в комнате 3 страж появляется на своём месте (ряд 1, кол 7) и рисуется; цвета совпадают с эталонным рендером спрайта в палитре color=2. ВНИМАНИЕ по памяти: куча W2 просела до 805 Б (было 2349). Перед ИИ стражей нужен шаг 5 плана (данные: room_modif 720 Б, dl1/dl2 512 Б, _kbdraw_down 512 Б) либо вынос кода отрисовки стража в резидент W3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,10 +26,11 @@ def read(name):
|
||||
return open(os.path.join(SRC, name)).read()
|
||||
|
||||
|
||||
def parse_frame_table(txt):
|
||||
"""frame_table_kid[] -> список (image, dx, dy, flags). Записи вида
|
||||
{ IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как OR-выражения."""
|
||||
m = re.search(r'const frame_type frame_table_kid\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||||
def parse_frame_table(txt, name="frame_table_kid"):
|
||||
"""frame_table_kid[]/frame_tbl_guard[] -> список (image, dx, dy, flags).
|
||||
Записи вида { IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как
|
||||
OR-выражения."""
|
||||
m = re.search(r'const frame_type ' + name + r'\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||||
body = m.group(1)
|
||||
frames = []
|
||||
for row in re.findall(r'\{([^}]*)\}', body):
|
||||
@@ -65,6 +66,7 @@ def main():
|
||||
seqc = read("seqtbl.c")
|
||||
|
||||
frames = parse_frame_table(seg006)
|
||||
gframes = parse_frame_table(seg006, "frame_tbl_guard")
|
||||
seqtbl = parse_byte_array(seqc, "original_seqtbl")
|
||||
offs = parse_word_array(seqc, "original_seqtbl_offsets")
|
||||
|
||||
@@ -90,7 +92,11 @@ def main():
|
||||
# раскладка kid_data.bin (смещения ОТ НАЧАЛА ДАННЫХ страницы)
|
||||
f.write("/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */\n")
|
||||
f.write("#define KID_BIN_FRAMES_OFF 0\n")
|
||||
f.write("#define KID_BIN_SEQTBL_OFF %d\n\n" % (len(frames) * 5))
|
||||
f.write("#define KID_BIN_SEQTBL_OFF %d\n" % (len(frames) * 5))
|
||||
f.write("/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс\n"
|
||||
" * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */\n")
|
||||
f.write("#define KID_NGFRAMES %d\n" % len(gframes))
|
||||
f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))
|
||||
|
||||
# seq offsets
|
||||
f.write("#define KID_NSEQ %d\n" % len(offs))
|
||||
@@ -105,7 +111,9 @@ def main():
|
||||
for (img, dx, dy, fl, sw) in frames:
|
||||
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
||||
b.write(bytes(seqtbl))
|
||||
print(f"kid_data.bin: {len(frames)*5 + len(seqtbl)} Б -> {binpath}")
|
||||
for (img, dx, dy, fl, sw) in gframes:
|
||||
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
||||
print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5} Б (кадры Kid + seqtbl + кадры стража) -> {binpath}")
|
||||
|
||||
print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, "
|
||||
f"{len(offs)} seq-оффсетов -> {OUT}")
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
pop_pack_guard.py — упаковщик спрайтов СТРАЖА (chtab_5_guard, база 750).
|
||||
|
||||
Оригинал грузит стражей из отдельного .DAT по типу уровня
|
||||
(GUARD/FAT/SKEL/VIZIER/SHADOW, seg000:1092); уровень 1 — обычный «guard»,
|
||||
то есть data/GUARD/res(750+id).png. Палитра — НЕ из PNG: оригинал
|
||||
подменяет её из res10.bin (guard_palettes, 7 палитр по 16 цветов, 6-бит) по
|
||||
level.guards_color; на уровне 1 у обоих стражей color = 2.
|
||||
|
||||
Выход: poc/res/guard/g0..gN.atl (по 8 спрайтов на страницу, адресация
|
||||
id>>3 / id&7 — как у Kid) + группа палитры 0x90..0x9F в общий kid.pal
|
||||
(его собирает pop_pack_kid.py, см. GUARD_PAL_BASE там).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import pop_pack_bg as B
|
||||
from pop_pack_kid import transpose_cols
|
||||
|
||||
GUARD_DIR = os.path.join(HERE, "..", "SDLPoP", "data", "GUARD")
|
||||
PAL_BIN = os.path.join(HERE, "..", "SDLPoP", "data", "PRINCE", "res10.bin")
|
||||
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "guard")
|
||||
PAL_BASE = 0x90 # слоты палитры Sprinter под стража
|
||||
COLOR = 2 # level.guards_color обоих стражей уровня 1
|
||||
SHIFT = 3 # g[id>>3], idx id&7 (спрайты крупные)
|
||||
MASK = (1 << SHIFT) - 1
|
||||
MAX_ID = 34 # res751..res784
|
||||
|
||||
|
||||
def load_guard(id):
|
||||
"""id -> res(750+id).png -> (w, h, 8bpp COLUMN-MAJOR); 0 -> 0xFF (прозр.)."""
|
||||
path = os.path.join(GUARD_DIR, f"res{750 + id}.png")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
im = Image.open(path)
|
||||
if im.mode != "P":
|
||||
B.die(f"res{750+id}: mode {im.mode}, ожидался P")
|
||||
src = im.tobytes()
|
||||
rowmajor = bytes((0xFF if v == 0 else PAL_BASE + v) for v in src)
|
||||
return im.width, im.height, transpose_cols(im.width, im.height, rowmajor)
|
||||
|
||||
|
||||
def guard_palette():
|
||||
"""16 цветов (r,g,b 0..63) из res10.bin для COLOR."""
|
||||
b = open(PAL_BIN, "rb").read()
|
||||
off = 0x30 * COLOR - 0x30
|
||||
return [(b[off + i * 3], b[off + i * 3 + 1], b[off + i * 3 + 2]) for i in range(16)]
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
pages = {}
|
||||
n = 0
|
||||
for id in range(1, MAX_ID + 1):
|
||||
g = load_guard(id)
|
||||
if g is None:
|
||||
continue
|
||||
pages.setdefault(id >> SHIFT, {})[id & MASK] = g
|
||||
n += 1
|
||||
if not pages:
|
||||
B.die("нет спрайтов стража")
|
||||
total = 0
|
||||
for pg in range(max(pages) + 1):
|
||||
nm = f"g{pg}.atl"
|
||||
cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, nm), pages.get(pg, {}))
|
||||
total += sz
|
||||
print(f" {nm}: {len(pages.get(pg, {}))} спрайтов, каталог {cnt}, {sz} Б")
|
||||
print(f"ИТОГО: {n} спрайтов стража, {max(pages)+1} EMM-страниц, {total} Б -> {OUT_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -25,6 +25,7 @@ KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDL
|
||||
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
||||
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
||||
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
||||
GUARD_PAL_BASE = 0x90 # chtab_5 (страж) -> слоты 0x90..0x9F
|
||||
SWORD_IDS = tuple(range(20, 28)) # sword_tbl для кадров 229..236 (найти-убрать)
|
||||
SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ)
|
||||
MASK = (1 << SHIFT) - 1
|
||||
@@ -108,6 +109,14 @@ def build_palette(out_path):
|
||||
for i, (r, g, bl) in enumerate(_read16(path)):
|
||||
o = (base + i) * 4
|
||||
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
|
||||
# Палитра стража берётся НЕ из .pal, а из res10.bin (guard_palettes:
|
||||
# 7 палитр по 16 цветов, 6-бит) по level.guards_color; на уровне 1 у
|
||||
# обоих стражей color = 2. См. toolchain/pop_pack_guard.py.
|
||||
import pop_pack_guard as G
|
||||
for i, (r, g, bl) in enumerate(G.guard_palette()):
|
||||
o = (GUARD_PAL_BASE + i) * 4
|
||||
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
|
||||
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(pal)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user