From e1846369ad4e8483961698e6053463ad327fe39b Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 29 Jul 2026 17:22:22 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest:=20=D0=B1=D0=BB=D0=B5=D1=81?= =?UTF-8?q?=D0=BA=20=D0=BB=D0=B5=D0=B6=D0=B0=D1=89=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=87=D0=B0,=20=D0=BA=D0=BB=D0=B8=D0=BD=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=B2=20=D1=80=D1=83=D0=BA=D0=B5,=20=D0=B2=D1=81?= =?UTF-8?q?=D0=BF=D1=8B=D1=88=D0=BA=D0=B0=20=D1=84=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три расхождения с оригиналом на сцене подъёма меча (комната 15): 1. Лежащий меч не блестел. Порт animate_sword (seg007:0425) + start_anim_sword (seg007:087C): при входе в комнату тайлу даётся случайная фаза (prandom & 0x1F), каждый кадр счётчик вниз, на 0 — новый период 0x28..0x67. Кадр блеска рисуется РОВНО на modif==1 ((modif==1)+10 в draw_tile), т.е. одиночная вспышка раз в 40..103 тика. Перерисовка тайла — по смене видимого кадра, схемой кнопки (текущая страница сразу, вторая через rest-цикл). 2. В кадрах «нашёл меч» клинка не было видно. Порт add_sword_to_objtable (seg006:1798): клинок — ОТДЕЛЬНЫЙ спрайт chtab_0 поверх Kid со смещением из sword_tbl. Смещения в ЭКРАННЫХ пикселях (оригинал применяет их после calc_screen_x_coord), поэтому берём уже масштабированный obj_x. Из таблицы взяты только строки 35..42 (кадры 229..236) — бой не портирован. Новый атлас sword.atl (8 спрайтов, 1.6 КБ) + палитра chtab_0 в слотах 0x80..0x8F. Прямоугольник heal расширяется объединением с клинком, иначе он оставлял след за габаритом Kid. 3. Не было вспышки фона. do_flash = set_bg_attr(0, color) — оригинал подменяет НУЛЕВУЮ запись палитры, вспыхивает всё чёрное поле экрана; proc_get_object ставит flash_color=14, flash_time=8. У нас gfx_pal_set на обе страницы. ВАЖНО: вызов gfx_pal_set(0,0,0,0,0) пятью литералами ломает SDCC 4.5 (эмитит невалидный `ld hl, a`) — обёрнуто в функцию с параметрами. Попутно: TROBS_MAX 24 -> 30 (как в оригинале) + при входе в комнату из списка выбрасываются «декоративные» trob ДРУГИХ комнат (факелы/зелья/ меч анимируются только в отрисованной). Без этого отладочный обход всех 24 комнат забивал список, и новые анимации молча не заводились. Проверено в MAME: блеск (брейк на редрое тайла срабатывает), клинок в руке виден, фон вспыхивает жёлтым. Co-Authored-By: Claude Opus 5 --- applications/PoP/poc/res/kid/kid.pal | Bin 1024 -> 1024 bytes applications/PoP/poc/res/kid/kid_atlas.h | 2 + applications/PoP/poc/res/kid/sword.atl | Bin 0 -> 1646 bytes applications/PoP/roomtest/Makefile | 2 +- applications/PoP/roomtest/pop_kid.c | 59 +++++++++++++++++++++ applications/PoP/roomtest/pop_kid.h | 6 +++ applications/PoP/roomtest/pop_map.c | 9 +++- applications/PoP/roomtest/pop_map.h | 3 ++ applications/PoP/roomtest/pop_trob.c | 48 ++++++++++++++++- applications/PoP/roomtest/roomtest.c | 31 ++++++++++- applications/PoP/toolchain/pop_pack_kid.py | 36 ++++++++++++- 11 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 applications/PoP/poc/res/kid/sword.atl diff --git a/applications/PoP/poc/res/kid/kid.pal b/applications/PoP/poc/res/kid/kid.pal index 10a46bd91d7d1bcd7ff4dfe8d58c1ec88f32ef4a..370772316143712c1edbb8a2d833816289e2a1f1 100644 GIT binary patch delta 23 fcmZqRXyDi&!Ni=~cyA)3BJ-{P|2H!-9b^OmU^@t~ delta 15 WcmZqRXyDi&!L&JmX#?X#0Sf>m!~|*p diff --git a/applications/PoP/poc/res/kid/kid_atlas.h b/applications/PoP/poc/res/kid/kid_atlas.h index 067cbd1..0fb8fa3 100644 --- a/applications/PoP/poc/res/kid/kid_atlas.h +++ b/applications/PoP/poc/res/kid/kid_atlas.h @@ -36,4 +36,6 @@ static const char *const kid_atl[KID_PAGES] = { "kid27.atl", }; #define KID_PAL_FILE "kid.pal" +#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */ +#define KID_SWORD_ID0 20 /* индекс в атласе = sword_tbl.id - ID0 */ #endif diff --git a/applications/PoP/poc/res/kid/sword.atl b/applications/PoP/poc/res/kid/sword.atl new file mode 100644 index 0000000000000000000000000000000000000000..45bbaef5b3bd1e7c126985fd69fbf2aaa44d1c2e GIT binary patch literal 1646 zcmcgrOAdlC5FJE;grp`IcAUY)y&ErJOuSRKy@-eLG;Zrmhf?Si)CD$8ADzzcy_Wm! za;jkc@qn_)GJs>I7&q!b`*W`Lgw3|A2w&SSC%o?3g7CRhH6e`>8ZgGj!~D;tu!Fe# zn)?}QLId=ZB7AK`N3HdghqPF9QE*tBzu;zVk_cO+w{uQJIFdpntrMn53KS+*hfNCh z5H)~RA|1v4gCUV&8bOyesUfy{5`YxP?!tR~NJKlRX7AW%8gTgNvkde67O;anaKz}PIeVt9aTELSvxOq$^}=#@qm?QDWN>$A`jrwhdATa6We2{K bFELz6{}>TyU{OXCEc`g*=g9rIGRe<323RZn literal 0 HcmV?d00001 diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index 7d3d6df..1ae97e0 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -21,7 +21,7 @@ BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2. # не рисуются (kid_draw пропускает незагруженную страницу). Палитра — # ЕДИНАЯ игровая (env+wall+kid). KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl) -KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal +KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(LVL_DATA) include $(PROJ_ROOT)/app.mk diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index 261dab7..7861582 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -30,6 +30,25 @@ static const int16_t y_land[5] = {-8, 55, 118, 181, 244}; static atlas_t kidp[KID_MAXPAGES]; static uint8_t kid_npages; +/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): отдельный + * спрайт клинка поверх Kid в кадрах «нашёл меч»/убирание в ножны (229..237). + * Из sword_tbl взяты только строки 35..42 — те, что адресуют эти кадры (бой + * не портирован); индекс в атласе = sword_frame − 35. Смещения таблицы — + * в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ calc_screen_x_coord. */ +#define SWORD_SF_LO 35 +#define SWORD_SF_HI 42 +static const int8_t SWORD_DX[8] = { 7, 14, 14, 14, 28, 28, 21, 14 }; +static const int8_t SWORD_DY[8] = {-32,-32,-31,-29,-28,-28,-25,-22 }; +static atlas_t swordp; +static uint8_t sword_ok; + +int pop_sword_load(void) +{ + if (atlas_load(&swordp, "sword.atl") != 0) return -1; + sword_ok = 1; + return 0; +} + int pop_kid_load(uint8_t npages) { static const char *const names[KID_MAXPAGES] = { @@ -324,5 +343,45 @@ void kid_draw(void) kid_lvalid[dp] = (skip < (int)h); kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */ } + /* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх + * Kid. Прямоугольник heal расширяем объединением — иначе клинок, выйдя + * за габарит Kid, оставит след. */ + if (sword_ok && Kid.frame >= 229 && Kid.frame < 238) { + uint8_t sf = (uint8_t)(fr.sword & 0x3F); + if (sf >= SWORD_SF_LO && sf <= SWORD_SF_HI) { + uint8_t si = (uint8_t)(sf - SWORD_SF_LO); + const uint8_t *simg = (const uint8_t *)atlas_image(&swordp, si); + uint16_t sw, sh; + gfx_w0_map(swordp.page); + sw = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8)); + sh = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8)); + if (sw && sh) { + uint8_t sflip = (Kid.direction >= 0); + int sbx = obj_x + ((Kid.direction < 0) ? -(int)SWORD_DX[si] + : (int)SWORD_DX[si]); + int stop = obj_y + (int)SWORD_DY[si] - (int)sh + 1; + uint8_t dp = gfx_get_draw_page() & 1; + if (sflip) sbx -= (int)sw; + gfx_set_bank(GFX_BANK_SPRITE); + gfx_blit_cols_part(sbx, stop + POP_YOFF, simg, sflip, 0, 0); + gfx_set_bank(GFX_BANK_NORMAL); + if (!kid_lvalid[dp]) { + kid_lx[dp] = sbx; kid_ly[dp] = stop; + kid_lw[dp] = sw; kid_lh[dp] = sh; + kid_lvalid[dp] = 1; + } else { + int x0 = kid_lx[dp], y0 = kid_ly[dp]; + int x1 = x0 + (int)kid_lw[dp], y1 = y0 + (int)kid_lh[dp]; + if (sbx < x0) x0 = sbx; + if (stop < y0) y0 = stop; + if (sbx + (int)sw > x1) x1 = sbx + (int)sw; + if (stop + (int)sh > y1) y1 = stop + (int)sh; + kid_lx[dp] = x0; kid_ly[dp] = y0; + kid_lw[dp] = (uint16_t)(x1 - x0); + kid_lh[dp] = (uint16_t)(y1 - y0); + } + } + } + } gfx_w0_unmap(); } diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index e33698d..e79d929 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -33,6 +33,12 @@ extern int8_t knock; /* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph. * 0 — OK, -1 — ошибка. */ int pop_kid_load(uint8_t npages); + +/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах + * подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798). + * Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда + * кадры рисуются без клинка). */ +int pop_sword_load(void); void pop_kid_free(void); /* Инициализировать Kid: последовательность seq_id, клетка col/row, diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 0f1a9ca..c2071ac 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -774,6 +774,7 @@ uint8_t pop_down_action(void) * и возвратом в обычную стойку — как в оригинале до встречи со стражем. */ uint8_t pop_have_sword; uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 поднятого */ +uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */ static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */ /* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить @@ -789,8 +790,14 @@ static void do_pickup(int8_t obj_type, uint8_t tilepos) * play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */ void pop_proc_get_object(void) { - if (pickup_obj_type == -1) + if (pickup_obj_type == -1) { pop_have_sword = 1; /* в оригинале have_sword = -1 */ + /* Вспышка фона: flash_color = color_14_brightyellow, flash_time = 8 + * (seg006 proc_get_object). do_flash = set_bg_attr(0, color) — + * оригинал подменяет НУЛЕВУЮ запись палитры, т.е. вспыхивает всё + * чёрное поле экрана. У нас так же (gfx_pal_set 0), см. roomtest. */ + pop_flash_time = 8; + } pickup_obj_type = 0; /* зелья (типы 1..6) не портированы */ } diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 317d4ae..6cf7e02 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -43,6 +43,9 @@ void pop_kid_hp_reset(void); * (он стал полом) на ОБЕИХ страницах и записать override комнаты. */ extern uint8_t pop_have_sword; extern uint8_t pop_item_taken; +/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока + * НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */ +extern uint8_t pop_flash_time; uint8_t pop_get_item_action(void); /* 0 нет / 1 присесть / 2 поднять меч */ void pop_proc_get_object(void); /* эффект — из play_seq (опкод GET_ITEM) */ diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index 5cea81f..63dd189 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -14,6 +14,7 @@ #define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */ #define TILE_OPENER 0x0F #define TILE_POTION 0x0A +#define TILE_SWORD 0x16 /* меч-предмет на полу */ #define TILE_TORCH 0x13 #define TILE_TORCH_DEBRIS 0x1E @@ -23,7 +24,7 @@ static uint8_t room_seen[POP_NROOMS]; /* modif проинициализиро /* ---- Список анимируемых тайлов ------------------------------------ */ typedef struct { uint8_t room, tilepos; int8_t type; } trob_t; -#define TROBS_MAX 24 +#define TROBS_MAX 30 /* как в оригинале (types.h) */ static trob_t trobs[TROBS_MAX]; static uint8_t trobs_count; static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */ @@ -121,6 +122,25 @@ void pop_trob_anim_room(uint8_t room) uint8_t i, code; if (!mod) return; pop_level_access_begin(); + /* Освободить список от «декоративных» trob ДРУГИХ комнат: факел, зелье и + * блеск меча анимируются только в ОТРИСОВАННОЙ комнате, а место в списке + * занимают. Оригинал их копит и на 30-й выдаёт «Trobs Overflow»; у нас + * отладочный обход всех 24 комнат упирается в лимит за пару минут, после + * чего новые анимации молча не заводятся. Механику (ворота/пики/кнопки, + * в т.ч. кросс-комнатные) чистка не трогает. */ + { + uint8_t k, wr = 0; + for (k = 0; k < trobs_count; k++) { + uint8_t c = (uint8_t)(pop_level_tile_raw(trobs[k].room, + trobs[k].tilepos) & 0x1F); + if (trobs[k].room != room && + (c == TILE_TORCH || c == TILE_TORCH_DEBRIS || + c == TILE_POTION || c == TILE_SWORD)) + continue; + trobs[wr++] = trobs[k]; + } + trobs_count = wr; + } for (i = 0; i < POP_ROOMTILES; i++) { code = (uint8_t)(pop_level_tile_raw(room, i) & 0x1F); if (code == TILE_TORCH || code == TILE_TORCH_DEBRIS) { @@ -129,6 +149,10 @@ void pop_trob_anim_room(uint8_t room) } else if (code == TILE_POTION) { mod[i] = (uint8_t)((mod[i] & 0xF8) | (trob_prandom(6) + 1)); pop_add_trob(room, i, 1); + } else if (code == TILE_SWORD) { + /* start_anim_sword (seg007:087C): случайная фаза счётчика блеска. */ + mod[i] = (uint8_t)(trob_prandom(0xFF) & 0x1F); + pop_add_trob(room, i, 1); } } pop_level_access_end(); @@ -375,6 +399,15 @@ void pop_process_trobs(uint8_t cur_room) if (room == cur_room) mod[tp] = (uint8_t)((mod[tp] & 0xF8) | bubble_next_frame(mod[tp] & 7)); break; + case TILE_SWORD: + /* animate_sword (seg007:0425): счётчик вниз; на 0 — новый период + * 0x28..0x67. Блеск рисуется РОВНО на modif==1 (draw_tile берёт + * кадр (modif==1)+10), т.е. одиночная вспышка раз в 40..103 тика. */ + if (room == cur_room) { + if (--mod[tp] == 0) + mod[tp] = (uint8_t)((trob_prandom(255) & 0x3F) + 0x28); + } + break; default: type = -1; break; @@ -406,6 +439,19 @@ void pop_process_trobs(uint8_t cur_room) * его уже никто не положит: ставим отложенный редрой. */ if (type < 0) { spike_rest[tp] = 1; rest_pending++; } } + if (room == cur_room && code == TILE_SWORD) { + /* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1): + * спрайт запечён в фон (draw_tile), поэтому перерисовываем тайл + * той же схемой, что и кнопку — текущая страница сразу, вторая + * следующим кадром через rest-цикл. */ + uint8_t sig = (uint8_t)(mod[tp] == 1); + if (sig != trob_drawn[tp]) { + uint8_t trow = tp / 10, tcol = tp % 10; + pop_button_redraw(trow, tcol); + button_rest[tp] = 1; rest_pending++; + trob_drawn[tp] = sig; + } + } if (room == cur_room && (code == TILE_OPENER || code == TILE_CLOSER)) { /* КНОПКА — редрой только при смене видимого состояния (нажата = * таймер связи >1): пока Кид её держит, картинка стабильна. diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 4f91d1b..2949b16 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -89,8 +89,19 @@ static uint8_t room_label_redraw; #define OVR_MAX 8 static uint8_t ovr_room[OVR_MAX], ovr_pos[OVR_MAX], ovr_tile[OVR_MAX], ovr_cnt; -/* Поднятый предмет (меч): tilepos и счётчик «запечь пол» по страницам. */ -static uint8_t item_pos, item_bake; +/* Поднятый предмет (меч): tilepos и счётчик «запечь пол» по страницам. + * flash_on — вспышка фона активна (нулевая запись палитры подменена). */ +static uint8_t item_pos, item_bake, flash_on; + +/* Подмена НУЛЕВОЙ записи палитры на обеих страницах (do_flash/set_bg_attr). + * Через функцию с параметрами, а не пятью литералами в месте вызова: на + * gfx_pal_set(0,0,0,0,0) SDCC 4.5 выдаёт невалидный `ld hl, a` + * (см. .sprinter-cc-roomtest/roomtest.asm) и ассемблер падает. */ +static void flash_bg(uint8_t r, uint8_t g, uint8_t b) +{ + gfx_pal_set(0, 0, r, g, b); + gfx_pal_set(1, 0, r, g, b); +} static void add_tile_override(uint8_t room, uint8_t pos, uint8_t tile) { @@ -191,6 +202,7 @@ int main(void) puts("pop_bg_load failed"); return 1; } + pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */ if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */ puts("kid atlas load failed"); return 1; @@ -386,12 +398,27 @@ int main(void) item_pos = (uint8_t)(pop_item_taken - 1); item_bake = 2; /* запечь на ОБЕИХ страницах */ add_tile_override(cur_room, item_pos, TILE_FLOOR); + pop_add_trob(cur_room, item_pos, -1); /* меч убран — блеска больше нет */ pop_item_taken = 0; } if (item_bake) { /* стереть спрайт меча из фона страницы */ pop_floor_bake(item_pos / 10, item_pos % 10); item_bake--; } + if (pop_flash_time) { + /* do_flash (seg001:0908) = set_bg_attr(0, color): оригинал подменяет + * НУЛЕВУЮ запись палитры — вспыхивает всё чёрное поле экрана. + * У нас то же самое, но правим обе страницы (у каждой своя + * палитра). color_14_brightyellow = VGA 14. */ + if (!flash_on) { + flash_bg(255, 255, 85); /* color_14_brightyellow */ + flash_on = 1; + } + if (--pop_flash_time == 0) { + flash_bg(0, 0, 0); /* вернуть чёрный фон */ + flash_on = 0; + } + } io_border = 4; /* зеленый */ update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */ kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */ diff --git a/applications/PoP/toolchain/pop_pack_kid.py b/applications/PoP/toolchain/pop_pack_kid.py index 3f49e51..9703582 100644 --- a/applications/PoP/toolchain/pop_pack_kid.py +++ b/applications/PoP/toolchain/pop_pack_kid.py @@ -24,6 +24,8 @@ import pop_pack_bg as B # pack_atlas, blob, scale6to8, ENV_SHIFT KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/KID" OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid") PAL_BASE = 0x70 # слоты палитры Sprinter под Kid +SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F +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 @@ -62,6 +64,22 @@ def load_kid(aidx): return im.width, im.height, transpose_cols(im.width, im.height, rowmajor) +def load_sword(id): + """chtab_0 (меч в руке): sword_tbl[].id = N -> res(700+N+1).png в data/PRINCE + (в оригинале add_midtable(chtab, obj_id+1) -> get_image(chtab, obj_id)). + Пиксели, как у Kid, COLUMN-MAJOR (gfx_blit_cols + бесплатный флип).""" + path = os.path.join(os.path.dirname(B.R.VDUNGEON_DIR), B.POT_DIR_NAME, + f"res{700 + id + 1}.png") + if not os.path.exists(path): + B.die(f"нет файла {path}") + im = Image.open(path) + if im.mode != "P": + B.die(f"res{700+id+1}: mode {im.mode}, ожидался P") + src = im.tobytes() + rowmajor = bytes((0xFF if v == 0 else SWORD_PAL_BASE + v) for v in src) + return im.width, im.height, transpose_cols(im.width, im.height, rowmajor) + + def _read16(path): b = open(path, "rb").read() return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)] @@ -79,6 +97,8 @@ def build_palette(out_path): (PAL_BASE, os.path.join(KID_DIR, "res400.pal")), # kid 0x70 (B.POT_PAL_BASE, os.path.join(os.path.dirname(B.R.VDUNGEON_DIR), B.POT_DIR_NAME, "res150.pal")), # chtab_1 0x40 + (SWORD_PAL_BASE, os.path.join(os.path.dirname(B.R.VDUNGEON_DIR), + B.POT_DIR_NAME, "res700.pal")), # chtab_0 0x80 ] # 16 базовых цветов экрана (mono-блит красит ими: пламя, пузырьки зелья) for i, (r, g, bl) in enumerate(B.VGA16): @@ -121,6 +141,17 @@ def main(): names.append(None) print(f" (kid стр {pg}: пусто)") + # chtab_0: меч в руке. Пакуем ТОЛЬКО кадры подъёма/ножен (sword_tbl + # 35..42 -> id 20..27); индекс в атласе = id - 20 (бой не портирован). + sw = {} + for i, id in enumerate(SWORD_IDS): + w, h, pix = load_sword(id) + sw[i] = (w, h, pix) + cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, "sword.atl"), sw) + total += sz + print(f" sword.atl (chtab_0, id {SWORD_IDS[0]}..{SWORD_IDS[-1]}): " + f"{len(sw)} спрайтов, каталог {cnt}, {sz} Б") + build_palette(os.path.join(OUT_DIR, "kid.pal")) # C-заголовок @@ -133,7 +164,10 @@ def main(): f.write("static const char *const kid_atl[KID_PAGES] = {\n") for nm in names: f.write(" %s,\n" % ('"%s"' % nm if nm else "0")) - f.write('};\n#define KID_PAL_FILE "kid.pal"\n#endif\n') + f.write('};\n#define KID_PAL_FILE "kid.pal"\n') + f.write('#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */\n') + f.write(f"#define KID_SWORD_ID0 {SWORD_IDS[0]} /* индекс в атласе = sword_tbl.id - ID0 */\n") + f.write("#endif\n") print(f"ИТОГО: {len(ids)} спрайтов Kid, {len(names)} EMM-страниц, " f"{total} Б диск (+1КБ палитра) -> {OUT_DIR}")