e143ee010a
Кнопка-closer (tiles_6) в нажатом состоянии рисуется как tiles_5_stuck
(get_tile_to_draw seg008:2FE), но её грани — env id 35 (правая) и 36
(нижняя) — не попадали в атлас: pop_pack_bg собирает набор спрайтов
прогоном render_room по статике уровней, а тайла 5 статически в уровнях
нет. Итог — чёрный провал на месте нижней грани. Добавлены явным
набором STUCK_ENV_IDS (как loose/пики/ворота). Заодно OPENER_NOFLOOR_ENV_IDS
= {148}: левая половина кнопки-opener, когда слева пусто (draw_tile_base
seg008:0A8E) — ветка была не реализована ни в pop_bg, ни в render_room.
bake_rest: при ЗАПЕЧКЕ статического фона (запись идёт в ОЗУ-копию
акселератора, из неё восстанавливает heal) get_loose_frame возвращает
кадр ПОКОЯ. Иначе транзиентный кадр тряски соседней плиты консервируется
в копии, а так как запечка идёт двумя кадрами (по одному на страницу
дабл-буфера) — на страницах застывают РАЗНЫЕ кадры, вечное мерцание.
mobs[MOB_MAX] вместо одиночных статиков: две плиты, упавшие подряд,
теряли первый кусок (застывал в воздухе) и оставляли щебень только от
одной. Плюс отложенная допечка прошлого приземления ПЕРЕД разбором
нового и ограничение чёрного бара в pop_loose_bake_empty низом тайла.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
422 lines
21 KiB
Python
422 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
pop_pack_bg.py — упаковщик спрайтов СТАТИЧЕСКОГО ФОНА PoP (Шаг 2 порта).
|
||
|
||
Что делает:
|
||
1. Определяет РЕАЛЬНО используемый набор спрайтов фона (прогоняет
|
||
render_room.py по всем комнатам всех уровней, собирает (chtab,id)).
|
||
2. Грузит исходные PNG (VDUNGEON→VPALACE каскад, как в игре), переводит
|
||
16-цветный индексированный пиксель в 8bpp getimage-блоб:
|
||
индекс 0 -> 0xFF (прозрачность getimage)
|
||
индекс i -> 0x50+i (env, chtab_6) / 0x60+i (wall, chtab_7)
|
||
3. Пакует в атласы .atl с ПРЯМОЙ адресацией (idx = id), каталог любого
|
||
размера (блобы кладутся ПОСЛЕ каталога — загрузчик atlas_image читает
|
||
offset явно, лимит 19 из libbgi/mkatlas.py не действует). libbgi НЕ
|
||
трогаем. Дырки в каталоге (неиспользуемые id) = фейковые записи,
|
||
ссылающиеся на общий пустой 0×0-блоб (осознанная трата места в EMM
|
||
ради нулевых remap-таблиц в W2 — требование пользователя).
|
||
4. Собирает палитру Sprinter (.pal, записи B,G,R,0) из res200.pal (env
|
||
-> слоты 0x50..0x5F) и res360.pal (wall -> 0x60..0x6F). VGA 6-бит
|
||
каналы масштабируются в 8-бит.
|
||
5. Пишет C-заголовок с раскладкой (имена файлов, SHIFT, число страниц,
|
||
набор fore-id).
|
||
|
||
Раскладка (прямая адресация, SHIFT=5):
|
||
ENV фон : 5 страниц, страница = id>>5, idx = id&31
|
||
WALL : 1 страница, idx = id
|
||
FORE : 1 страница (спрайты «перед персонажем»: фронты столбов/
|
||
ворот/дебриса), idx = id — отдельный атлас (жизненный цикл
|
||
другой: рисуются каждый кадр как sprite_t, не запекаются).
|
||
W2-цена: 7 хэндлов atlas_t (~21 Б), ноль таблиц id->позиция.
|
||
|
||
Выход: в applications/PoP/poc/res/bg/ — *.atl + pop_bg.pal + pop_bg_atlas.h
|
||
"""
|
||
import os
|
||
import sys
|
||
from PIL import Image
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, HERE)
|
||
import render_room as R # noqa: E402 (экстрактор + каскад загрузки)
|
||
|
||
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "bg")
|
||
|
||
# Спрайты FORE-слоя (add_foretable в seg008 — «перед персонажем»): фронты
|
||
# столбов, ворот, дебриса. См. tile_table.fore_id + анализ pop_background_strategy.
|
||
FORE_ENV_IDS = {9, 12, 49, 88, 91, 95, 100}
|
||
|
||
# Пол-оверлей подъёма (floor_left_overlay, draw_floor_overlay seg008): рисуется
|
||
# рантаймом поверх нижней части Kid при подтягивании на пол у кромки — НЕ
|
||
# попадает в used render_room, добавляем явно в env-фон. (32 обычно уже есть.)
|
||
CLIMB_OVERLAY_ENV_IDS = {32, 150, 151}
|
||
|
||
# Дрожащие/падающие кадры loose-плиты (loose_fram_left/right/bottom, seg008):
|
||
# 41/42/43 = покой (уже в used из render_room), 69-74 = вибрация/раздвоение —
|
||
# используются ТОЛЬКО динамикой (shake/fall), в статический render_room НЕ
|
||
# попадают, поэтому добавляем явно. Без них дрожащая плита невидима.
|
||
LOOSE_ANIM_ENV_IDS = {69, 70, 71, 72, 73, 74}
|
||
|
||
# Кадры ВЫДВИЖЕНИЯ пик (seg008): три части — left (128-132, своя ячейка,
|
||
# draw_tile_anim), right (134-138, правый сосед, draw_tile_anim_right), fore
|
||
# (139-143, поверх Kid, draw_tile_fore). Рисуются ТОЛЬКО динамикой
|
||
# (animate_spike); в уровнях пики статически убраны (modif=0 → кадр 0),
|
||
# поэтому в used render_room НЕ попадают. Без них выдвинутые пики невидимы.
|
||
SPIKE_ANIM_ENV_IDS = {128, 129, 130, 131, 132, # left
|
||
134, 135, 136, 137, 138, # right
|
||
139, 140, 141, 142, 143} # fore
|
||
|
||
# Кадры ОТКРЫТИЯ ворот (portcullis, DOOR_FRAM_SLICE + gate pieces 50/51,
|
||
# seg008 draw_gate): 50-59 + 67. В уровнях ворота статически ЗАКРЫТЫ
|
||
# (низкий modif → используются только нижние срезы 50-53); срезы более
|
||
# открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно.
|
||
GATE_ANIM_ENV_IDS = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 67}
|
||
|
||
# НАЖАТАЯ кнопка-closer (get_tile_to_draw seg008:2FE: при таймере связи >1
|
||
# tiles_6_closer рисуется как tiles_5_stuck). tile_table[5] = база 41 (уже
|
||
# есть), правая грань 35, нижняя 36 — оба id встречаются ТОЛЬКО в этом
|
||
# динамическом состоянии, статически тайла 5 в уровнях нет, значит в used
|
||
# render_room они не попадают. Без них у нажатой кнопки чёрный провал на
|
||
# месте нижней грани и потерянная правая грань.
|
||
STUCK_ENV_IDS = {35, 36}
|
||
|
||
# Левая половина кнопки-opener, когда СЛЕВА пусто (draw_tile_base seg008:0A8E:
|
||
# curr_tile==15 && tile_left==0 && level_type==0 → id 148 вместо базы 147).
|
||
# render_room этой ветки не знает, поэтому добавляем явно.
|
||
OPENER_NOFLOOR_ENV_IDS = {148}
|
||
|
||
# ---- chtab_1 (flame/sword/potion), база ресурса 150, папка PRINCE ----
|
||
# Зелье (seg008 draw_tile_fore/draw_tile_anim): склянка id 12 (малая) / 13
|
||
# (большая, типы 2..4), кадры пузырька 16..22 (potion_fram_bubb) и его маска
|
||
# 23. Пузырёк и маска рисуются MONO-блитом (одним цветом), поэтому пакуем их
|
||
# силуэтами уже в нужном цвете: маска — чёрный env[0], пузырёк — env[12]
|
||
# (красный, тип «лечение»). Цвета других типов (9 синий / 10 зелёный) —
|
||
# TODO, когда появятся такие зелья.
|
||
POT_DIR_NAME = "PRINCE"
|
||
POT_FLAME_IDS = tuple(range(1, 10)) # пламя факела: modifier+1 (seg008:560)
|
||
POT_FLASK_IDS = (12, 13)
|
||
POT_BUBBLE_IDS = (16, 17, 18, 19, 20, 21, 22)
|
||
POT_MASK_ID = 23
|
||
POT_PAL_BASE = 0x40 # 16 цветов chtab_1 -> слоты 0x40..0x4F
|
||
# MONO-блит (method_3_blit_mono, seg009:3040) красит спрайт цветом из ОБЩЕЙ
|
||
# 16-цветной палитры экрана, а не из палитры chtab. Кладём её в 0x30..0x3F.
|
||
VGA16_PAL_BASE = 0x30
|
||
VGA16 = [(0,0,0),(0,0,42),(0,42,0),(0,42,42),(42,0,0),(42,0,42),(42,21,0),
|
||
(42,42,42),(21,21,21),(21,21,63),(21,63,21),(21,63,63),(63,21,21),
|
||
(63,21,63),(63,63,21),(63,63,63)] # 6-битные каналы, как в .pal
|
||
POT_MASK_COLOR = VGA16_PAL_BASE + 0 # чёрный (blitters_40h_mono без цвета)
|
||
POT_BUBBLE_COLOR = VGA16_PAL_BASE + 12 # ярко-красный (color 12: «лечение»)
|
||
|
||
# Основание факела (seg008:489 draw_tile_right: при левом соседе-факеле
|
||
# рисуется chtab_6 id 146). В статический render_room не попадает.
|
||
TORCH_ENV_IDS = {146}
|
||
|
||
ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31
|
||
ENV_MASK = (1 << ENV_SHIFT) - 1
|
||
|
||
# ---- .atl формат (== libbgi, но каталог не ограничен 19) --------------
|
||
ATL_MAGIC = b"SPA1"
|
||
ATL_DIR_OFF = 0x68 # _ATL_DIR_OFF
|
||
ATL_MIN_SIZE = 0x100 # atlas_load требует n >= _ATL_DATA_OFF
|
||
PAGE_SIZE = 16384
|
||
|
||
|
||
def die(msg):
|
||
sys.exit(f"pop_pack_bg: {msg}")
|
||
|
||
|
||
# ---- 1. Набор используемых спрайтов ----------------------------------
|
||
def collect_used():
|
||
"""Прогон render_room по всем комнатам -> множество (chtab, id)."""
|
||
used = set()
|
||
orig = R.load_sprite
|
||
|
||
def spy(id, chtab=R.CHTAB_ENV, reason=""):
|
||
img = orig(id, chtab, reason)
|
||
if img is not None and id > 0:
|
||
used.add((chtab, id))
|
||
return img
|
||
|
||
R.load_sprite = spy
|
||
for lvl in range(1, 15):
|
||
fn = os.path.join(R.LEVEL_DIR, f"res{2000 + lvl}.bin")
|
||
if not os.path.exists(fn):
|
||
continue
|
||
lv = R.Level(open(fn, "rb").read())
|
||
for room in range(1, 25):
|
||
rm = R.Room(lv, room)
|
||
L = lv.links[room][0]
|
||
if L:
|
||
rm.left_room = R.Room(lv, L)
|
||
canvas = Image.new("RGBA", (320, 192), (0, 0, 0, 0))
|
||
for dr in (2, 1, 0):
|
||
for c in range(10):
|
||
R.draw_tile(canvas, rm, dr, c)
|
||
R.load_sprite = orig
|
||
return used
|
||
|
||
|
||
# ---- 2. Загрузка спрайта в 8bpp-блоб ---------------------------------
|
||
# Спрайты chtab_6, которые оригинал рисует БЕЗ прозрачности
|
||
# (blitters_0_no_transp): нижние грани тайлов (draw_tile_bottom, seg008:570) и
|
||
# нижние кадры loose (draw_loose, seg008:600). У 96/149 (грани кнопок) и 73/74
|
||
# (кадры loose) ПОСЛЕДНЯЯ строка — пиксель 0; в оригинале она заливается ЧЁРНЫМ
|
||
# поверх того, что сзади, а у нас 0 -> 0xFF делал её прозрачной, и сквозь неё
|
||
# просвечивал Kid (спуск/подъём у кромки кнопки). 43/36 полностью непрозрачны —
|
||
# для них флаг ничего не меняет, оставлены для полноты списка.
|
||
NO_TRANSP_ENV_IDS = {36, 43, 73, 74, 96, 149}
|
||
|
||
|
||
def load_indexed(chtab, id, opaque=False):
|
||
"""PNG (mode P, 16 цветов) -> (w, h, bytes 8bpp). Каскад VDUNGEON->VPALACE.
|
||
Пиксель 0 -> 0xFF (прозрач); i -> base+i (env 0x50 / wall 0x60).
|
||
|
||
opaque=True — пиксель 0 остаётся ЦВЕТОМ (base+0 = чёрный), а не ключом
|
||
прозрачности: порт blitters_0_no_transp (см. NO_TRANSP_ENV_IDS)."""
|
||
base_res = 360 if chtab == R.CHTAB_WALL else 200
|
||
pal_base = 0x60 if chtab == R.CHTAB_WALL else 0x50
|
||
fname = f"res{base_res + id}.png"
|
||
path = None
|
||
for d in (R.VDUNGEON_DIR, R.VPALACE_DIR):
|
||
p = os.path.join(d, fname)
|
||
if os.path.exists(p):
|
||
path = p
|
||
break
|
||
if path is None:
|
||
die(f"нет файла {fname}")
|
||
im = Image.open(path)
|
||
if im.mode != "P":
|
||
die(f"{fname}: mode {im.mode}, ожидался P (16-цветный)")
|
||
src = im.tobytes()
|
||
if opaque:
|
||
out = bytes((pal_base + v) for v in src)
|
||
else:
|
||
out = bytes((0xFF if v == 0 else pal_base + v) for v in src)
|
||
return im.width, im.height, out
|
||
|
||
|
||
def load_potion(id, mono=None):
|
||
"""chtab_1: res(150+id).png из data/PRINCE. mono=None — обычный спрайт
|
||
(палитра chtab_1 -> слоты POT_PAL_BASE+i); mono=цвет — силуэт этим цветом
|
||
(порт blitters_40h_mono)."""
|
||
path = os.path.join(os.path.dirname(R.VDUNGEON_DIR), POT_DIR_NAME,
|
||
f"res{150 + id}.png")
|
||
if not os.path.exists(path):
|
||
die(f"нет файла {path}")
|
||
im = Image.open(path)
|
||
if im.mode != "P":
|
||
die(f"res{150+id}: mode {im.mode}, ожидался P")
|
||
src = im.tobytes()
|
||
if mono is None:
|
||
out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src)
|
||
else:
|
||
out = bytes((0xFF if v == 0 else mono) for v in src)
|
||
return im.width, im.height, out
|
||
|
||
|
||
def blob(w, h, pix):
|
||
"""getimage-блоб: u16 w, u16 h (LE) + пиксели."""
|
||
return w.to_bytes(2, "little") + h.to_bytes(2, "little") + pix
|
||
|
||
|
||
# ---- 3. Пакер .atl (каталог любого размера) --------------------------
|
||
def pack_atlas(path, entries):
|
||
"""entries: dict idx -> (w, h, pix). Пустые idx (0..maxidx) заполняются
|
||
фейковой ссылкой на общий 0×0-блоб. Каталог count записей с 0x68,
|
||
блобы — сразу после каталога."""
|
||
if not entries:
|
||
die(f"{path}: пустой атлас")
|
||
count = max(entries) + 1
|
||
if count > 255:
|
||
die(f"{path}: count {count} > 255")
|
||
data_off = ATL_DIR_OFF + count * 8
|
||
|
||
blobs = bytearray()
|
||
empty_off = data_off + len(blobs) # общий 0×0-блоб для дырок
|
||
blobs += blob(0, 0, b"")
|
||
dir_bytes = bytearray(count * 8)
|
||
for idx in range(count):
|
||
e = entries.get(idx)
|
||
if e is None:
|
||
off = empty_off
|
||
fw = fh = 0
|
||
else:
|
||
w, h, pix = e
|
||
off = data_off + len(blobs)
|
||
blobs += blob(w, h, pix)
|
||
fw = min(w, 255)
|
||
fh = min(h, 255)
|
||
d = idx * 8
|
||
dir_bytes[d:d + 2] = off.to_bytes(2, "little")
|
||
dir_bytes[d + 2] = fw
|
||
dir_bytes[d + 3] = fh
|
||
dir_bytes[d + 4] = 1 # nx
|
||
dir_bytes[d + 5] = 1 # ny
|
||
# d+6,d+7 резерв
|
||
|
||
out = bytearray(data_off)
|
||
out[0:4] = ATL_MAGIC
|
||
out[4] = count
|
||
out[ATL_DIR_OFF:ATL_DIR_OFF + len(dir_bytes)] = dir_bytes
|
||
out += blobs
|
||
if len(out) < ATL_MIN_SIZE:
|
||
out += bytes(ATL_MIN_SIZE - len(out)) # atlas_load: n >= 0x100
|
||
if len(out) > PAGE_SIZE:
|
||
die(f"{path}: {len(out)} Б > {PAGE_SIZE} (одна EMM-страница)")
|
||
with open(path, "wb") as f:
|
||
f.write(out)
|
||
return count, len(out)
|
||
|
||
|
||
# ---- 4. Палитра ------------------------------------------------------
|
||
def scale6to8(v):
|
||
return (v * 255) // 63
|
||
|
||
|
||
def build_palette(out_path):
|
||
"""256-записная палитра B,G,R,0: env@0x50..0x5F, wall@0x60..0x6F."""
|
||
def read_pal(res, d):
|
||
b = open(os.path.join(d, res), "rb").read()
|
||
return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)]
|
||
|
||
env = read_pal("res200.pal", R.VDUNGEON_DIR)
|
||
wall = read_pal("res360.pal", R.VDUNGEON_DIR)
|
||
pot = read_pal("res150.pal", os.path.join(os.path.dirname(R.VDUNGEON_DIR),
|
||
POT_DIR_NAME))
|
||
pal = bytearray(256 * 4)
|
||
for i, (r, g, b) in enumerate(env):
|
||
o = (0x50 + i) * 4
|
||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||
for i, (r, g, b) in enumerate(wall):
|
||
o = (0x60 + i) * 4
|
||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||
for i, (r, g, b) in enumerate(pot):
|
||
o = (POT_PAL_BASE + i) * 4
|
||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||
for i, (r, g, b) in enumerate(VGA16):
|
||
o = (VGA16_PAL_BASE + i) * 4
|
||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||
with open(out_path, "wb") as f:
|
||
f.write(pal)
|
||
|
||
|
||
# ---- 5. C-заголовок --------------------------------------------------
|
||
def write_header(out_path, env_pages, wall_name, fore_name):
|
||
lines = [
|
||
"/* pop_bg_atlas.h — раскладка атласов статического фона PoP.",
|
||
" * Сгенерировано toolchain/pop_pack_bg.py — НЕ править вручную.",
|
||
" *",
|
||
" * Прямая адресация (ноль remap-таблиц в W2):",
|
||
" * ENV фон id N -> atlas env_bg[N>>%d], idx N&%d" % (ENV_SHIFT, ENV_MASK),
|
||
" * WALL id N -> atlas wall, idx N",
|
||
" * FORE id N -> atlas fore, idx N",
|
||
" */",
|
||
"#ifndef POP_BG_ATLAS_H",
|
||
"#define POP_BG_ATLAS_H",
|
||
"",
|
||
"#define POP_ENV_SHIFT %d" % ENV_SHIFT,
|
||
"#define POP_ENV_MASK %d" % ENV_MASK,
|
||
"#define POP_ENV_PAGES %d" % len(env_pages),
|
||
"",
|
||
"/* Палитра: env-слоты, wall-слоты (сприйт-пиксель i -> база+i). */",
|
||
"#define POP_PAL_ENV 0x50",
|
||
"#define POP_PAL_WALL 0x60",
|
||
"",
|
||
"/* Имена файлов атласов (грузятся atlas_load). */",
|
||
"static const char *const pop_env_atl[POP_ENV_PAGES] = {",
|
||
]
|
||
for pg in range(len(env_pages)):
|
||
nm = env_pages[pg]
|
||
lines.append(' %s,' % ('"%s"' % nm if nm else "0 /* пусто */"))
|
||
lines += [
|
||
"};",
|
||
'#define POP_WALL_ATL "%s"' % wall_name,
|
||
'#define POP_FORE_ATL "%s"' % fore_name,
|
||
'#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья */',
|
||
"#define POP_PAL_POT 0x%02X" % POT_PAL_BASE,
|
||
'#define POP_BG_PAL "pop_bg.pal"',
|
||
"",
|
||
"#endif",
|
||
"",
|
||
]
|
||
with open(out_path, "w") as f:
|
||
f.write("\n".join(lines))
|
||
|
||
|
||
def main():
|
||
os.makedirs(OUT_DIR, exist_ok=True)
|
||
used = collect_used()
|
||
env_ids = sorted(set(id for ch, id in used if ch != R.CHTAB_WALL)
|
||
| CLIMB_OVERLAY_ENV_IDS # + пол-оверлей подъёма
|
||
| LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose
|
||
| SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик
|
||
| GATE_ANIM_ENV_IDS # + кадры открытия ворот
|
||
| TORCH_ENV_IDS # + основание факела
|
||
| STUCK_ENV_IDS # + нажатая кнопка-closer
|
||
| OPENER_NOFLOOR_ENV_IDS) # + opener без пола слева
|
||
wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL)
|
||
|
||
env_bg = [i for i in env_ids if i not in FORE_ENV_IDS]
|
||
env_fore = [i for i in env_ids if i in FORE_ENV_IDS]
|
||
|
||
# ENV фон -> страницы по id>>SHIFT
|
||
pages = {}
|
||
for id in env_bg:
|
||
w, h, pix = load_indexed(R.CHTAB_ENV, id, id in NO_TRANSP_ENV_IDS)
|
||
pages.setdefault(id >> ENV_SHIFT, {})[id & ENV_MASK] = (w, h, pix)
|
||
max_pg = max(pages) if pages else -1
|
||
env_page_names = []
|
||
total_bytes = 0
|
||
for pg in range(max_pg + 1):
|
||
if pg in pages:
|
||
nm = f"pop_env{pg}.atl"
|
||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, nm), pages[pg])
|
||
total_bytes += sz
|
||
env_page_names.append(nm)
|
||
print(f" {nm}: {len(pages[pg])} спрайтов, каталог {cnt}, {sz} Б")
|
||
else:
|
||
env_page_names.append(None)
|
||
print(f" (env стр {pg}: пусто)")
|
||
|
||
# WALL -> idx=id
|
||
wall_entries = {id: load_indexed(R.CHTAB_WALL, id) for id in wall_ids}
|
||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_wall.atl"), wall_entries)
|
||
total_bytes += sz
|
||
print(f" pop_wall.atl: {len(wall_ids)} спрайтов, каталог {cnt}, {sz} Б")
|
||
|
||
# FORE -> idx=id
|
||
fore_entries = {id: load_indexed(R.CHTAB_ENV, id) for id in env_fore}
|
||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_fore.atl"), fore_entries)
|
||
total_bytes += sz
|
||
print(f" pop_fore.atl: {len(env_fore)} спрайтов, каталог {cnt}, {sz} Б")
|
||
|
||
# POTION (chtab_1) -> idx=id: склянки + кадры пузырька + маска
|
||
# chtab_1 целиком (res151..res173 = id 1..23): пламя факелов, меч-предмет,
|
||
# склянки зелий. Пузырьки и их маска рисуются MONO-блитом — пакуем
|
||
# силуэтами в нужном цвете (см. POT_*_COLOR).
|
||
pot_entries = {}
|
||
for id in range(1, 24):
|
||
if id in POT_BUBBLE_IDS:
|
||
pot_entries[id] = load_potion(id, POT_BUBBLE_COLOR)
|
||
elif id == POT_MASK_ID:
|
||
pot_entries[id] = load_potion(id, POT_MASK_COLOR)
|
||
else:
|
||
pot_entries[id] = load_potion(id)
|
||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries)
|
||
total_bytes += sz
|
||
print(f" pop_pot.atl (chtab_1: факелы+зелья): {len(pot_entries)} спрайтов, каталог {cnt}, {sz} Б")
|
||
|
||
build_palette(os.path.join(OUT_DIR, "pop_bg.pal"))
|
||
write_header(os.path.join(OUT_DIR, "pop_bg_atlas.h"),
|
||
env_page_names, "pop_wall.atl", "pop_fore.atl")
|
||
|
||
n_pages = sum(1 for n in env_page_names if n) + 2
|
||
print(f"ИТОГО: {len(env_bg)} env-фон + {len(wall_ids)} wall + "
|
||
f"{len(env_fore)} fore = {len(env_bg)+len(wall_ids)+len(env_fore)} спрайтов")
|
||
print(f" {n_pages} EMM-страниц, {total_bytes} Б на диске "
|
||
f"(+1КБ палитра), W2: {n_pages}×atlas_t = {n_pages*3} Б хэндлов")
|
||
print(f" -> {OUT_DIR}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|