diff --git a/applications/PoP/poc/res/guard/g0.atl b/applications/PoP/poc/res/guard/g0.atl new file mode 100644 index 0000000..87ab285 Binary files /dev/null and b/applications/PoP/poc/res/guard/g0.atl differ diff --git a/applications/PoP/poc/res/guard/g1.atl b/applications/PoP/poc/res/guard/g1.atl new file mode 100644 index 0000000..db8440b Binary files /dev/null and b/applications/PoP/poc/res/guard/g1.atl differ diff --git a/applications/PoP/poc/res/guard/g2.atl b/applications/PoP/poc/res/guard/g2.atl new file mode 100644 index 0000000..cd1d58d Binary files /dev/null and b/applications/PoP/poc/res/guard/g2.atl differ diff --git a/applications/PoP/poc/res/guard/g3.atl b/applications/PoP/poc/res/guard/g3.atl new file mode 100644 index 0000000..c7eeae7 Binary files /dev/null and b/applications/PoP/poc/res/guard/g3.atl differ diff --git a/applications/PoP/poc/res/guard/g4.atl b/applications/PoP/poc/res/guard/g4.atl new file mode 100644 index 0000000..8f8cae0 Binary files /dev/null and b/applications/PoP/poc/res/guard/g4.atl differ diff --git a/applications/PoP/poc/res/kid/kid.pal b/applications/PoP/poc/res/kid/kid.pal index 3707723..22ab080 100644 Binary files a/applications/PoP/poc/res/kid/kid.pal and b/applications/PoP/poc/res/kid/kid.pal differ diff --git a/applications/PoP/poc/res/kid/kid_data.bin b/applications/PoP/poc/res/kid/kid_data.bin index cc643fe..73a735f 100644 Binary files a/applications/PoP/poc/res/kid/kid_data.bin and b/applications/PoP/poc/res/kid/kid_data.bin differ diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index 3a46ca8..7ed1b91 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -30,8 +30,11 @@ KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl # Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его # делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже. KID_BIN := $(KID_DIR)/kid_data.bin +GUARD_DIR := $(CURDIR)/../poc/res/guard +GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \ + $(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl -EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) +EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(LVL_DATA) include $(PROJ_ROOT)/app.mk # Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS), @@ -45,11 +48,15 @@ hdd: $(EXAMPLE).exe $(EXAMPLE).exe \ $(foreach f,$(BG_DATA),BG:$(f)) \ $(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \ + $(foreach f,$(GUARD_DATA),GUARD:$(f)) \ LEVELS:$(LVL_DATA) TC := $(PROJ_ROOT)/applications/PoP/toolchain $(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py cd $(TC) && python3 pop_pack_bg.py +$(GUARD_DATA): $(TC)/pop_pack_guard.py + cd $(TC) && python3 pop_pack_guard.py + $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py cd $(TC) && python3 pop_pack_kid.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py diff --git a/applications/PoP/roomtest/kid_data.h b/applications/PoP/roomtest/kid_data.h index 8a0bdee..e5034eb 100644 --- a/applications/PoP/roomtest/kid_data.h +++ b/applications/PoP/roomtest/kid_data.h @@ -18,6 +18,10 @@ typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe; /* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */ #define KID_BIN_FRAMES_OFF 0 #define KID_BIN_SEQTBL_OFF 1205 +/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс + * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */ +#define KID_NGFRAMES 41 +#define KID_BIN_GFRAMES_OFF 3515 #define KID_NSEQ 115 static const uint16_t kid_seq_off[KID_NSEQ] = { diff --git a/applications/PoP/roomtest/pop_guard.c b/applications/PoP/roomtest/pop_guard.c index f2246e9..d2bc06a 100644 --- a/applications/PoP/roomtest/pop_guard.c +++ b/applications/PoP/roomtest/pop_guard.c @@ -2,7 +2,15 @@ * pop_guard.c — состояние стража (W1/W2). См. pop_guard.h. * Логика — в банке (guards.c), отрисовка — из главного цикла. */ +#include +#include +#include #include "pop_guard.h" +#include "pop_geom.h" +#include "pop_level.h" +#include "pop_kid.h" /* play_seq/kid_set_seq пока общие (seqtbl один) */ +#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ +#include "kid_data.h" pop_char_t Guard; uint8_t guardhp_curr, guardhp_max; @@ -36,3 +44,122 @@ void pop_guard_kill(void) guardhp_curr = 0; Guard.alive = 0; } + +/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */ +#define GUARD_PAGES 5 +static atlas_t gp[GUARD_PAGES]; +static uint8_t g_ok; + +/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid: + * heal обязан стирать спрайт именно той страницы, в которую рисуем). */ +static int gx_l[2], gy_l[2]; +static uint16_t gw_l[2], gh_l[2]; +static uint8_t gvalid[2]; + +/* Кадр стража (порт cur_frame для Char): таблица у стражей СВОЯ + * (frame_tbl_guard), и индексируется как frame + add_frame − 149, где + * add_frame = 70 для кадров 102..106 (seg006:535). */ +static kframe gframe; + +int pop_guard_load(void) +{ + static const char *const names[GUARD_PAGES] = { + "GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl", + "GUARD\\g3.atl", "GUARD\\g4.atl" + }; + uint8_t i; + for (i = 0; i < GUARD_PAGES; i++) + if (atlas_load(&gp[i], names[i]) != 0) return -1; + g_ok = 1; + return 0; +} + +void pop_guard_free(void) +{ + uint8_t i; + if (!g_ok) return; + for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]); + g_ok = 0; +} + +/* load_frame для стража (seg006:0293 ветка charid_2_guard). */ +static void guard_load_frame(void) +{ + int16_t idx = (int16_t)Guard.frame; + if (idx >= 102 && idx < 107) idx += 70; /* add_frame */ + idx -= 149; + if (idx < 0 || idx >= KID_NGFRAMES) { gframe.image = 255; return; } + pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&gframe); +} + +void pop_guard_enter(uint8_t room) +{ + uint8_t tile, color, skill; + int8_t dir; + pop_guard_reset(); + if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return; + /* enter_guard (seg002:0112) + pos_guards (seg003): X считается из + * колонки тайла, а не берётся из файла. */ + Guard.room = room; + Guard.curr_row = (int8_t)(tile / 10); + Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1]; + Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX); + Guard.curr_col = (int8_t)(tile % 10); + Guard.direction = dir; + Guard.charid = CHARID_2_GUARD; + Guard.sword = 0; /* sword_0_sheathed */ + Guard.alive = -1; + Guard.action = 1; /* actions_1_run_jump */ + Guard.fall_x = Guard.fall_y = 0; + /* seq_77_guard_stand_inactive даёт кадр 166 (frame_166_stand_inactive, + * seqtbl.c:266). Интерпретатор последовательностей для стража придёт + * вместе с ИИ (следующая фаза) — сейчас ставим кадр напрямую, чтобы + * страж просто СТОЯЛ и было что рисовать/убивать читом. */ + Guard.frame = 166; + guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */ + guardhp_delta = 0; + guard_load_frame(); +} + +void pop_guard_heal(void) +{ + uint8_t p = gfx_get_draw_page() & 1; + if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]); + gvalid[p] = 0; +} + +void pop_guard_draw(void) +{ + int obj_x, obj_y, top, fwd; + uint8_t page, idx, p, flip; + const uint8_t *img; + uint16_t w, h; + + if (!g_ok || Guard.charid == 0 || gframe.image == 255) return; + /* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */ + fwd = (int)Guard.x + ((Guard.direction < 0) ? -gframe.dx : gframe.dx); + obj_x = (fwd << 1) - 116; + obj_y = (int)Guard.y + gframe.dy; + if ((int8_t)(gframe.flags ^ Guard.direction) >= 0) obj_x++; + obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */ + + page = (uint8_t)(gframe.image >> 3); + idx = (uint8_t)(gframe.image & 7); + if (page >= GUARD_PAGES) return; + img = (const uint8_t *)atlas_image(&gp[page], idx); + gfx_w0_map(gp[page].page); + w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); + h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); + if (w && h) { + int bx = obj_x; + flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */ + if (flip) bx -= (int)w; + top = obj_y - (int)h + 1; + p = gfx_get_draw_page() & 1; + gfx_set_bank(GFX_BANK_SPRITE); + gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0); + gfx_set_bank(GFX_BANK_NORMAL); + gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1; + } + gfx_w0_unmap(); +} diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index 98b4d38..58193d1 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -48,6 +48,22 @@ extern int8_t guardhp_delta; /* <0 = в этом кадре отняли /* Есть ли живой страж в отрисованной комнате (для главного цикла). */ uint8_t pop_guard_present(void); +/* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid. + * 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */ +int pop_guard_load(void); +void pop_guard_free(void); + +/* Вход в комнату: поднять стража из данных уровня (порт enter_guard, + * seg002:0112) либо убрать его, если в комнате стража нет. */ +void pop_guard_enter(uint8_t room); + +/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE, + * как kid_draw) и стереть прошлый (heal). Звать из главного цикла рядом + * с kid_draw: рисовать из БАНКА нельзя только резидентом W3, а тут мы в + * W1/W2. */ +void pop_guard_heal(void); +void pop_guard_draw(void); + /* Убрать стража со сцены (смена комнаты / инициализация). */ void pop_guard_reset(void); diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index ff0f5a1..c898c66 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -145,6 +145,23 @@ void pop_kid_data_free(void) if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; } } +/* Прочитать кадр из ЛЮБОЙ таблицы страницы данных (у Kid и у стража они + * разные: KID_BIN_FRAMES_OFF / KID_BIN_GFRAMES_OFF). Сама маппит страницу, + * поэтому годится для вызова извне (pop_guard) — но НЕ в горячем цикле. */ +void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5) +{ + const uint8_t *f; + uint16_t off = (uint16_t)idx; + if (!kdat_ok) { out5[0] = 255; return; } + off += (uint16_t)(off << 2); /* idx*5, без умножения */ + off += (uint16_t)(KD_DATA_OFF + tbl_off); + gfx_w0_map(kdat_page); + f = (const uint8_t *)off; + out5[0] = f[0]; out5[1] = f[1]; out5[2] = f[2]; + out5[3] = f[3]; out5[4] = f[4]; + gfx_w0_unmap(); +} + /* load_frame (seg006:0293): скопировать кадр Kid.frame из таблицы в * cur_frame. Звать ТОЛЬКО когда страница данных замаплена в W0. */ static void load_frame(void) diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index 47847ac..a7493bd 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -39,6 +39,11 @@ int pop_kid_load(uint8_t npages); * первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт). * Страница маппится в W0 внутри play_seq, раз за тик. */ int pop_kid_data_load(const char *path); + +/* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF + * (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard). + * Маппит страницу сама; для горячего цикла есть cur_frame. */ +void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5); void pop_kid_data_free(void); /* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах diff --git a/applications/PoP/roomtest/pop_level.c b/applications/PoP/roomtest/pop_level.c index 53e7ca0..97b2ce4 100644 --- a/applications/PoP/roomtest/pop_level.c +++ b/applications/PoP/roomtest/pop_level.c @@ -23,6 +23,12 @@ extern void _gfx_w0_isr(void); #define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */ #define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */ #define BP_LINKS 1952u +/* Стражи (level_type, types.h:228): по байту на комнату в каждом массиве. */ +#define BP_GTILE 2119u +#define BP_GDIR 2143u +#define BP_GX 2167u +#define BP_GSKILL 2215u +#define BP_GCOLOR 2263u #define BP_START 2112u #define ROOM_TILES 30u #define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */ @@ -234,6 +240,28 @@ static uint8_t start_byte(uint16_t off) return v; } +/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат + * 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из + * колонки (pos_guards), поэтому guards_x из файла не читаем. */ +uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, + uint8_t *color, uint8_t *skill) +{ + const uint8_t *p; + uint8_t t; + if (!lvl_ok || room < 1 || room > 24) return 0; + gfx_w0_map(lvl_page); + p = (const uint8_t *)LVL_DATA_OFF; + t = p[BP_GTILE + room - 1]; + if (t < 30) { + *tile = t; + *dir = (int8_t)p[BP_GDIR + room - 1]; + *color = (uint8_t)(p[BP_GCOLOR + room - 1] & 0x0F); + *skill = p[BP_GSKILL + room - 1]; + } + gfx_w0_unmap(); + return (uint8_t)(t < 30); +} + uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); } uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); } int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); } diff --git a/applications/PoP/roomtest/pop_level.h b/applications/PoP/roomtest/pop_level.h index ff435ac..a10594c 100644 --- a/applications/PoP/roomtest/pop_level.h +++ b/applications/PoP/roomtest/pop_level.h @@ -73,6 +73,11 @@ void pop_doorlink2_set(uint8_t i, uint8_t v); int8_t pop_room_col_landing(uint8_t room, uint8_t col); /* Стартовая позиция уровня (блок @2112): комната / поза / направление. */ +/* Страж комнаты: 1 = есть (заполнит tile/dir/color/skill), 0 = нет. + * Порт enter_guard (seg002:0112). */ +uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, + uint8_t *color, uint8_t *skill); + uint8_t pop_level_start_room(void); uint8_t pop_level_start_pos(void); int8_t pop_level_start_dir(void); diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index e83fab3..0df7b07 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -169,6 +169,7 @@ static void enter_room(uint8_t room) pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ + pop_guard_enter(room); /* страж комнаты (порт enter_guard) */ /* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ * per-room (room_modif живёт между входами); рисуем и коллидируем по ним, * а не по свежему backtable. Фаза P0. */ @@ -219,6 +220,7 @@ int main(void) return 1; } pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */ + pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */ if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */ puts("kid atlas load failed"); return 1; @@ -331,6 +333,7 @@ int main(void) pop_ctrl_tick(); /* ввод -> control(): смена seq */ kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */ + pop_guard_heal(); /* и прошлый кадр стража */ pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */ kid_tick(); /* play_seq: следующий кадр */ pop_phys_tick(); /* K3: падение/приземление/стена */ @@ -448,6 +451,7 @@ int main(void) } io_border = 4; /* зеленый */ update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */ + pop_guard_draw(); /* страж — ДО Kid (порядок как в оригинале) */ kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */ pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */ if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */ @@ -509,6 +513,7 @@ int main(void) closegraph(); pop_bg_free(); pop_kid_free(); + pop_guard_free(); pop_kid_data_free(); pop_level_free(); return 0; diff --git a/applications/PoP/toolchain/pop_extract_kid_data.py b/applications/PoP/toolchain/pop_extract_kid_data.py index a13b55c..ebca96d 100644 --- a/applications/PoP/toolchain/pop_extract_kid_data.py +++ b/applications/PoP/toolchain/pop_extract_kid_data.py @@ -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}") diff --git a/applications/PoP/toolchain/pop_pack_guard.py b/applications/PoP/toolchain/pop_pack_guard.py new file mode 100644 index 0000000..1624c2e --- /dev/null +++ b/applications/PoP/toolchain/pop_pack_guard.py @@ -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() diff --git a/applications/PoP/toolchain/pop_pack_kid.py b/applications/PoP/toolchain/pop_pack_kid.py index 9703582..97c5b4c 100644 --- a/applications/PoP/toolchain/pop_pack_kid.py +++ b/applications/PoP/toolchain/pop_pack_kid.py @@ -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)