From 3dbad6120cdf6ea0d74eb582cd8c1568c2d168dd Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 29 Jul 2026 21:57:38 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest:=20=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=20=E2=80=94=20=D1=81=D0=BF=D1=80=D0=B0=D0=B9=D1=82=D1=8B?= =?UTF-8?q?,=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=B4=D1=80=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D0=BE=D1=8F=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BD=D0=B0=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Шаги 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 --- applications/PoP/poc/res/guard/g0.atl | Bin 0 -> 8307 bytes applications/PoP/poc/res/guard/g1.atl | Bin 0 -> 10536 bytes applications/PoP/poc/res/guard/g2.atl | Bin 0 -> 10639 bytes applications/PoP/poc/res/guard/g3.atl | Bin 0 -> 5806 bytes applications/PoP/poc/res/guard/g4.atl | Bin 0 -> 1605 bytes applications/PoP/poc/res/kid/kid.pal | Bin 1024 -> 1024 bytes applications/PoP/poc/res/kid/kid_data.bin | Bin 3515 -> 3720 bytes applications/PoP/roomtest/Makefile | 9 +- applications/PoP/roomtest/kid_data.h | 4 + applications/PoP/roomtest/pop_guard.c | 127 ++++++++++++++++++ applications/PoP/roomtest/pop_guard.h | 16 +++ applications/PoP/roomtest/pop_kid.c | 17 +++ applications/PoP/roomtest/pop_kid.h | 5 + applications/PoP/roomtest/pop_level.c | 28 ++++ applications/PoP/roomtest/pop_level.h | 5 + applications/PoP/roomtest/roomtest.c | 5 + .../PoP/toolchain/pop_extract_kid_data.py | 20 ++- applications/PoP/toolchain/pop_pack_guard.py | 76 +++++++++++ applications/PoP/toolchain/pop_pack_kid.py | 9 ++ 19 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 applications/PoP/poc/res/guard/g0.atl create mode 100644 applications/PoP/poc/res/guard/g1.atl create mode 100644 applications/PoP/poc/res/guard/g2.atl create mode 100644 applications/PoP/poc/res/guard/g3.atl create mode 100644 applications/PoP/poc/res/guard/g4.atl create mode 100644 applications/PoP/toolchain/pop_pack_guard.py diff --git a/applications/PoP/poc/res/guard/g0.atl b/applications/PoP/poc/res/guard/g0.atl new file mode 100644 index 0000000000000000000000000000000000000000..87ab285adfcf268c79c18a8fc835fbbbf073e6d2 GIT binary patch literal 8307 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/guard/g2.atl b/applications/PoP/poc/res/guard/g2.atl new file mode 100644 index 0000000000000000000000000000000000000000..cd1d58d7b41079b8eecc3e4debbb1b0b39953801 GIT binary patch literal 10639 zcmeI1v5w<55QcqC5FkjAD#dQGINaU=?(ztEg8%{YUS7rW8a7;{PMJ4IlZVMWDD(d_ zLk=ZUVgfI42u_7a4nNJ16#0v$fBxymAHLqb{q_59|MA_s-R|$N_Medc^6kSO>0jUN zKOp_%`}faCe|vmj{hv>dOuu=4Vm(^8-+kOo|Lf~=>FM5msbk_2J^nhd0yvRGG&0kb! z!}$eQE;n-bq^7$&o7z(0{gId}g5uQF1x*sKsZGh1WQ5x9K7GdDkFH*@AF*4leD9$2 zL|qeh#;1C$>VHt}sy7u{S3dY#!!#c6)d4YY8LN*{P zFbx^7sM{h07H5G$hFrK4mZ%h!m@Bt?YhYQr0rv#QIFySqjfKz5$n7%DGRJd1BS>pL-eT~Uk^cdv~c~>J9@)-=q zYSzeVT{J2eE!BJ{rah^8D2#MhSn36QJL)xswG+dqsCceX5Ui>VbG#G7_oGt;=Mk!9 z`g)MxD%ILA#;y^t?RjLY??^OAL|pEADSw%wg3DyK&&oKu2!a+?-aEyEBF;%~ej-Qo za!ER_Yt4l6WlqXmq9REL3{;gq%t_2TlA`7XiPd7w&?J!}=#d0il9~ zs^2i*lN)&oBu)8C;mYnlui2-Y&g#>}-OUxy$fWOvu94`Iysp!UY>jl`P!OnG7@tDC z@i;W5oJvkg1Z#^X^7Mur8c2}3wyL=lniZV zAi!G*`KbZ0%;1D1?{sDHq-cR*IFUabFjGn#iVXbv{+3JCo1LI2$Z2bf$8v+9lQfj7 z9hoSlg(L?*V5AM%Q_^2`$ zcB`A#;1x{gA-U>!EDjdyoh9aTT2DN~iuQ5S4SUA)WYpF){xs5S=1krBs>WYp^19{D z^&gz^MQ|}noYCim0C{DDW(-@|qwTgxIHrxr7)~EK!P25-P?Z1*1-%ugkB2K6KYmm_ z@t>)|TN}%#kK(C@`SvWW5np)zx0Z_-Mh?BvK9q}?oyc9ym^-tFuy-WpD;@4+5zR4| z7G-&t16#S=kEa2!ECY^1)dVD=;l$s>hnWhwX*ovXeRll4+vSQafM#2&v0 zgbau+9V8qT&R_p~0t5C|0-;NquVhUiY~^D?*vhX7gi`_Yvn=#%zNCri2A~gH6$p51 z3Ix_d?CP38fDKz1j15htjHn5f91_hm1VX(j!aD^*J;Zw;G>cI?M1uJRRuzav(EM28 zaa|x1x^kNJmiJN}_KT{vQ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/guard/g3.atl b/applications/PoP/poc/res/guard/g3.atl new file mode 100644 index 0000000000000000000000000000000000000000..c7eeae7b60b018846efc826c93b42a488f425634 GIT binary patch literal 5806 zcmcgwL5|x-5Tt+r4uW8fz_Bm(!GLYR@GS=)a?A@7An(XyC|ML~t)vg+33*5^c?(lj z-92nlB(+91o&z~ORoye}sh%C}@4x->)3??C-~Oy#z1wW6>aTBJyyg4vCmXb?`sc}q zmwf;F{l`1Le|~!Rp6@??c=m?x?|yv#ns2sO)w`xp2WxdUkv{ELN#r0@)1i$%4w1{(I*FW2B85$U$4n|LBRK1Lbka_dJXWG06gbDT zg-WCmr>?>2&&G1n{e9QXkStK2NI~18!2yB#9xObKbk9g=3`Wit2szONI!FB=1wn5j znbgm+NL36y`ejmK(Zd(T?zAzEjjfOdrw5TZpRzRg&Gfi!XfsG2dI*OKkk5>`RY_CdDN_7XrXxKQ_xoGFGkfs#XEhwchRBXY5V%ZHf zdYq_7u_%2s)V8yX6dYP!j}U)wG+P`HMsd?_HSPR!%B z&9tdDRsW%SIa46v_lWP~=fz;hHSF`E--_EZw0I6-M|n)oH174xFox>OWfdCSC^K(} z2Co89q=9iJgbXHt^Vtj{XbAykZev8EcZf&|XHfZ*e*x5v0s?}@a@Us;FKW8 z%u7h|GU#w<+xCP-z{w#>ks%MnPNx%*1a%#N?m*;pB4n7$954Iay1s@2S%bKK`{sxk zULBzc#>vDJX4wxvE|U!qLwF)F%4Vbp?O4A8kz!8e3&U%8^8`q94M3bSv5Jfwk95l; z(MORiAUH%8tFB*xR5wa_z* z&Le3gw(~fGE?>{8HxJT`W}TA@*3URSgqM_JL-tfU)YWE%NQPl65Q`&9=tC4fnkKr4 zBT7gk#zFC_$8zFlz3ui41v_{p2+`u`fDx3e1zZA*VRI!JSZ3{>?UBUE3J_-y6c27v zNnulp$Z?GT+HSQ;Gy87yb#2QIJ!iMvSx(Ue*N;NFhVqDLd0?);K9f9bxpU5COdka6 zdG-4CpYE#zZ$CcMl-Z=*{TR=()dD8CwuYL;38Rfo?yzm^T(H8U9$eM(ceOgsS7^R= zTvAOqXOub7X;3D37OB=5IR53tFC638rPc$XoQU(wO$X(GYDMvKQ)WmgF!~6=AENGV q5i*W#jGwde_lM2HXC6)cAf%yD_ZIG-GV&Q4E0Y^>d2xY9z<&Y%3ZlCJ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/guard/g4.atl b/applications/PoP/poc/res/guard/g4.atl new file mode 100644 index 0000000000000000000000000000000000000000..8f8cae06d86f3992fd318df97f779f228644527a GIT binary patch literal 1605 zcmcJPOKt-p42DVDRkl^K%{t2-p*N^hsVC|MfB>0?*!Gz8|81C8X3-Kb{?G<#O;!h)qq&l@^(RCl6k_SCqD5oyJ_oBclEMH@hnYKA z7NEv}Mk0DpCW+TYW6W-v5$q)270FT7bhc7=iWr4P4-f2HF|4RCn5J+y_Y`Ylha_67Fxa&vnIuFUkR!3&cLcl>#*p^e$MO%M8I^%o|;xMBQLF-7URgH!lDT69V)A 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)