diff --git a/applications/PoP/poc/res/kid/kid_atlas.h b/applications/PoP/poc/res/kid/kid_atlas.h index 0fb8fa3..709400b 100644 --- a/applications/PoP/poc/res/kid/kid_atlas.h +++ b/applications/PoP/poc/res/kid/kid_atlas.h @@ -37,5 +37,5 @@ static const char *const kid_atl[KID_PAGES] = { }; #define KID_PAL_FILE "kid.pal" #define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */ -#define KID_SWORD_ID0 20 /* индекс в атласе = sword_tbl.id - ID0 */ +#define KID_SWORD_ID0 0 /* индекс в атласе = sword_tbl.id - ID0 */ #endif diff --git a/applications/PoP/poc/res/kid/sword.atl b/applications/PoP/poc/res/kid/sword.atl index 45bbaef..917470a 100644 Binary files a/applications/PoP/poc/res/kid/sword.atl and b/applications/PoP/poc/res/kid/sword.atl differ diff --git a/applications/PoP/roomtest/kid_data.h b/applications/PoP/roomtest/kid_data.h index e5034eb..9899e4c 100644 --- a/applications/PoP/roomtest/kid_data.h +++ b/applications/PoP/roomtest/kid_data.h @@ -42,4 +42,31 @@ static const uint16_t kid_seq_off[KID_NSEQ] = { 0x21FC,0x2240,0x2257, }; +/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0. + * Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет. + * Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord), + * x — «вперёд» по направлению персонажа. */ +#define KID_NSWORD 51 +#define KID_SWORD_ID_INIT { \ + 0xFF,0x00,0x05,0x01,0x02,0x06,0x07,0x03,0x04,0x1E,0x08,0x09, \ + 0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x13,0x11, \ + 0x11,0x07,0x01,0x1C,0x08,0x04,0x04,0x0A,0x0D,0x0D,0x0D,0x14, \ + 0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0xFF,0xFF,0x1D,0x08,0x1F, \ + 0x20,0x21,0x08, \ +} +#define KID_SWORD_DX_INIT { \ + 0,0,-9,7,17,7,0,17,16,12,13,7, \ + 10,10,22,28,13,0,0,21,14,21,22,7, \ + 15,0,7,14,7,6,9,11,24,19,21,7, \ + 14,14,14,28,28,21,14,14,21,0,8,14, \ + 14,7,8, \ +} +#define KID_SWORD_DY_INIT { \ + 0,-9,-29,-25,-26,-14,-5,-16,-19,-9,-34,-25, \ + -16,-11,-21,-23,-35,-38,-29,-19,-23,-22,-23,-13, \ + -18,-8,-27,-28,-27,-23,-21,-18,-23,-23,-23,-32, \ + -32,-31,-29,-28,-28,-25,-22,-25,-25,-16,-37,-24, \ + -24,-14,-37, \ +} + #endif diff --git a/applications/PoP/roomtest/pop_gdraw.c b/applications/PoP/roomtest/pop_gdraw.c index 4342f25..bf325d9 100644 --- a/applications/PoP/roomtest/pop_gdraw.c +++ b/applications/PoP/roomtest/pop_gdraw.c @@ -15,6 +15,7 @@ #include #include #include "pop_guard.h" +#include "pop_kid.h" /* pop_sword_draw — клинок общий с Кидом */ #include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */ #include "kid_data.h" @@ -29,6 +30,13 @@ static int gx_l[2], gy_l[2]; static uint16_t gw_l[2], gh_l[2]; static uint8_t gvalid[2]; +/* Клинок — ОТДЕЛЬНЫМ прямоугольником, не объединением со стражем: объединение + * почти вдвое больше суммы двух (клинок уходит вперёд-вверх), а heal стоит + * ровно по площади. Объединение считаем только для окна fore-клипа. */ +static int sx_l[2], sy_l[2]; +static uint16_t sw_l[2], sh_l[2]; +static uint8_t svalid[2]; + int pop_guard_load(void) __banked { static const char *const names[GUARD_PAGES] = { @@ -54,15 +62,20 @@ void pop_guard_heal(void) __banked { 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; + if (svalid[p]) gfx_heal(sx_l[p], sy_l[p] + POP_YOFF, sw_l[p], sh_l[p]); + gvalid[p] = svalid[p] = 0; } void pop_guard_draw(void) __banked { - int obj_x, obj_y, top, fwd, fp_x, bx0 = 0, top0 = 0; + int obj_x, obj_y, top, fwd, fp_x; uint8_t page, idx, p, flip; const uint8_t *img; uint16_t w, h; + /* Окно fore-клипа — по ОБЪЕДИНЁННОМУ габариту «страж + клинок»; сам + * футпринт (pop_fore_over_char) считается по спрайту стража, поэтому + * w/h ниже НЕ трогаем. */ + int clip_x = 0, clip_y = 0; uint16_t clip_w = 0, clip_h = 0; if (!g_ok || Guard.charid == 0 || pop_gframe.image == 255) return; /* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */ @@ -85,17 +98,41 @@ void pop_guard_draw(void) __banked flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */ if (flip) bx -= (int)w; top = obj_y - (int)h + 1; - bx0 = bx; top0 = top; 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; + clip_x = bx; clip_y = top; clip_w = w; clip_h = h; + } + /* Клинок стража — тот же chtab_0, что у Кида (add_sword_to_objtable, + * seg006:1798): у ЖИВОГО стража меч в руке всегда. Свой прямоугольник + * heal; окно fore-клипа — объединение (без него клинок полез бы поверх + * переднего столба). */ + { + int sx, sy; uint16_t sw, sh; + if (pop_sword_draw(&Guard, &pop_gframe, obj_x, obj_y, + &sx, &sy, &sw, &sh)) { + p = gfx_get_draw_page() & 1; + sx_l[p] = sx; sy_l[p] = sy; sw_l[p] = sw; sh_l[p] = sh; + svalid[p] = 1; + if (!clip_w) { + clip_x = sx; clip_y = sy; clip_w = sw; clip_h = sh; + } else { + int x1 = clip_x + (int)clip_w, y1 = clip_y + (int)clip_h; + if (sx < clip_x) clip_x = sx; + if (sy < clip_y) clip_y = sy; + if (sx + (int)sw > x1) x1 = sx + (int)sw; + if (sy + (int)sh > y1) y1 = sy + (int)sh; + clip_w = (uint16_t)(x1 - clip_x); + clip_h = (uint16_t)(y1 - clip_y); + } + } } gfx_w0_unmap(); - /* Окно fore-слоя = прямоугольник стража (см. pop_bg.c, fclip_on): - * без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов на кадр. */ - pop_fore_set_clip(bx0, top0 + POP_YOFF, (int)w, (int)h); + /* Окно fore-слоя = прямоугольник стража с клинком (см. pop_bg.c, + * fclip_on): без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов. */ + pop_fore_set_clip(clip_x, clip_y + POP_YOFF, (int)clip_w, (int)clip_h); /* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись * midtable, а foretable рисуется после всех midtable-записей, поэтому * колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index a2235c8..77ef83e 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -28,15 +28,15 @@ int8_t knock; 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 }; +/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): клинок — + * ОТДЕЛЬНЫЙ спрайт поверх персонажа, один и тот же атлас у Кида и у стража. + * Таблица sword_tbl (kid_data.h, генерится из seg006) индексируется + * sword_frame = кадр.sword & 0x3F; id 255 = клинка в этом кадре нет. + * Смещения — в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ + * calc_screen_x_coord, x — «вперёд» по направлению персонажа. */ +static const uint8_t SWORD_ID[KID_NSWORD] = KID_SWORD_ID_INIT; +static const int8_t SWORD_DX[KID_NSWORD] = KID_SWORD_DX_INIT; +static const int8_t SWORD_DY[KID_NSWORD] = KID_SWORD_DY_INIT; static atlas_t swordp; static uint8_t sword_ok; @@ -47,6 +47,46 @@ int pop_sword_load(void) return 0; } +/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в + * кадрах подъёма/ножен, ИЛИ держит меч, ИЛИ это ЖИВОЙ страж (у стража меч + * в руке всегда, даже пока sword ещё sheathed). Страницу W0 оставляем + * замапленной — вызывающий делает gfx_w0_unmap сам. */ +uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr, + int obj_x, int obj_y, + int *rx, int *ry, uint16_t *rw, uint16_t *rh) +{ + uint8_t sf, id, flip; + const uint8_t *simg; + uint16_t w, h; + int bx, top; + + if (!sword_ok) return 0; + if (!((ch->frame >= 229 && ch->frame < 238) || + ch->sword != 0 || + (ch->charid == CHARID_2_GUARD && ch->alive < 0))) + return 0; + sf = (uint8_t)(fr->sword & 0x3F); + if (sf == 0 || sf >= KID_NSWORD) return 0; + id = SWORD_ID[sf]; + if (id == 0xFF) return 0; + + simg = (const uint8_t *)atlas_image(&swordp, id); + gfx_w0_map(swordp.page); + w = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8)); + h = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8)); + if (!w || !h) return 0; + + flip = (uint8_t)(ch->direction >= 0); + bx = obj_x + ((ch->direction < 0) ? -(int)SWORD_DX[sf] : (int)SWORD_DX[sf]); + top = obj_y + (int)SWORD_DY[sf] - (int)h + 1; + if (flip) bx -= (int)w; + gfx_set_bank(GFX_BANK_SPRITE); + gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, 0, 0); + gfx_set_bank(GFX_BANK_NORMAL); + *rx = bx; *ry = top; *rw = w; *rh = h; + return 1; +} + int pop_kid_load(uint8_t npages) { static const char *const names[KID_MAXPAGES] = { @@ -371,6 +411,64 @@ static int kid_lx[2], kid_ly[2]; static uint16_t kid_lw[2], kid_lh[2]; static uint8_t kid_lvalid[2]; +/* ОТДЕЛЬНЫЙ прямоугольник накладных спрайтов (клинок chtab_0, брызги урона). + * Раньше он объединялся с прямоугольником персонажа — но объединение сильно + * больше суммы двух: клинок уходит вперёд-вверх, и общий габарит выходил + * почти вдвое шире и выше самого Kid, а heal стоит ровно по площади. + * Поэтому heal делаем двумя вызовами по своим прямоугольникам, а объединение + * оставляем ТОЛЬКО для окна fore-клипа (там это 4 сравнения, без рисования — + * зато клинок/брызги честно уходят под передние грани тайлов). */ +static int kid_ox[2], kid_oy[2]; +static uint16_t kid_ow[2], kid_oh[2]; +static uint8_t kid_ovalid[2]; + +/* Добавить накладной спрайт в overlay-прямоугольник страницы dp. Клинок и + * брызги в одном кадре встречаются редко и оба маленькие — их объединение + * дешевле третьего слота. */ +static void kid_overlay_add(uint8_t dp, int x, int y, uint16_t w, uint16_t h) +{ + if (!kid_ovalid[dp]) { + kid_ox[dp] = x; kid_oy[dp] = y; kid_ow[dp] = w; kid_oh[dp] = h; + kid_ovalid[dp] = 1; + return; + } + { + int x0 = kid_ox[dp], y0 = kid_oy[dp]; + int x1 = x0 + (int)kid_ow[dp], y1 = y0 + (int)kid_oh[dp]; + if (x < x0) x0 = x; + if (y < y0) y0 = y; + if (x + (int)w > x1) x1 = x + (int)w; + if (y + (int)h > y1) y1 = y + (int)h; + kid_ox[dp] = x0; kid_oy[dp] = y0; + kid_ow[dp] = (uint16_t)(x1 - x0); kid_oh[dp] = (uint16_t)(y1 - y0); + } +} + +/* Окно fore-клипа = объединение «персонаж + накладные». Считается по + * прямоугольникам, ничего не рисует. */ +static void kid_fore_clip(uint8_t dp) +{ + int x0, y0, x1, y1; + if (!kid_lvalid[dp] && !kid_ovalid[dp]) { pop_fore_set_clip(0, 0, 0, 0); return; } + if (!kid_ovalid[dp]) { + pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF, + (int)kid_lw[dp], (int)kid_lh[dp]); + return; + } + if (!kid_lvalid[dp]) { + pop_fore_set_clip(kid_ox[dp], kid_oy[dp] + POP_YOFF, + (int)kid_ow[dp], (int)kid_oh[dp]); + return; + } + x0 = kid_lx[dp] < kid_ox[dp] ? kid_lx[dp] : kid_ox[dp]; + y0 = kid_ly[dp] < kid_oy[dp] ? kid_ly[dp] : kid_oy[dp]; + x1 = kid_lx[dp] + (int)kid_lw[dp]; + y1 = kid_ly[dp] + (int)kid_lh[dp]; + if (kid_ox[dp] + (int)kid_ow[dp] > x1) x1 = kid_ox[dp] + (int)kid_ow[dp]; + if (kid_oy[dp] + (int)kid_oh[dp] > y1) y1 = kid_oy[dp] + (int)kid_oh[dp]; + pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0); +} + /* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg): * obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */ static int kid_fp_x, kid_fp_y; @@ -417,23 +515,12 @@ void kid_draw_splash(void) gfx_blit_cols(bx, top + POP_YOFF, img, flip); gfx_set_bank(GFX_BANK_NORMAL); pop_clip_sprite(bx, w, top + POP_YOFF, (int)h); - /* Прямоугольник splash расширяет область heal ЭТОЙ страницы: иначе - * брызги останутся на экране после кадра. */ + /* Брызги — накладной спрайт: свой прямоугольник heal (см. + * kid_overlay_add), окно fore пересчитываем объединением. */ { uint8_t dp = gfx_get_draw_page(); - if (kid_lvalid[dp]) { - int x0 = kid_lx[dp] < bx ? kid_lx[dp] : bx; - int y0 = kid_ly[dp] < top ? kid_ly[dp] : top; - int x1 = kid_lx[dp] + (int)kid_lw[dp]; - int y1 = kid_ly[dp] + (int)kid_lh[dp]; - if (bx + (int)w > x1) x1 = bx + (int)w; - if (top + (int)h > y1) y1 = top + (int)h; - kid_lx[dp] = x0; kid_ly[dp] = y0; - kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0); - /* Окно fore-слоя расширяем тем же объединением: брызги - * тоже должны уходить под передние грани тайлов. */ - pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0); - } + kid_overlay_add(dp, bx, top, w, h); + kid_fore_clip(dp); } } gfx_w0_unmap(); @@ -445,7 +532,9 @@ void kid_heal(void) uint8_t p = gfx_get_draw_page(); if (kid_lvalid[p]) gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */ - kid_lvalid[p] = 0; + if (kid_ovalid[p]) + gfx_heal(kid_ox[p], kid_oy[p] + POP_YOFF, kid_ow[p], kid_oh[p]); + kid_lvalid[p] = kid_ovalid[p] = 0; } void kid_draw(void) @@ -518,57 +607,16 @@ void kid_draw(void) 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); - } - } - } + * Kid — со СВОИМ прямоугольником heal (kid_overlay_add). */ + { + int sx, sy; uint16_t sw, sh; + if (pop_sword_draw(&Kid, &fr, obj_x, obj_y, &sx, &sy, &sw, &sh)) + kid_overlay_add(gfx_get_draw_page() & 1, sx, sy, sw, sh); } gfx_w0_unmap(); - /* Окно fore-слоя = итоговый прямоугольник спрайта (с клинком и - * обрезкой clip_char): fore-проход вернёт куски тайлов ТОЛЬКО поверх - * него, а не целыми тайлами — см. pop_bg.c, fclip_on. Ставим и когда - * рисовать нечего (kid_lvalid=0): нулевое окно = fore-проход не - * трогает экран, что и правильно — перекрывать нечего. */ - { - uint8_t dp = gfx_get_draw_page() & 1; - if (kid_lvalid[dp]) - pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF, - (int)kid_lw[dp], (int)kid_lh[dp]); - else - pop_fore_set_clip(0, 0, 0, 0); - } + /* Окно fore-слоя = объединение «Kid + клинок» (с обрезкой clip_char): + * fore-проход вернёт куски тайлов ТОЛЬКО поверх него, а не целыми + * тайлами — см. pop_bg.c, fclip_on. Ставим и когда рисовать нечего: + * нулевое окно = fore-проход не трогает экран, что и правильно. */ + kid_fore_clip(gfx_get_draw_page() & 1); } diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index e4b62ad..b26ff30 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -12,6 +12,7 @@ #include #include "pop_char.h" +#include "kid_data.h" /* kframe — тип кадра для pop_sword_draw */ /* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */ extern pop_char_t Kid; @@ -43,6 +44,15 @@ void pop_kid_data_free(void); int pop_sword_load(void); void pop_kid_free(void); +/* Нарисовать клинок chtab_0 поверх персонажа (порт add_sword_to_objtable, + * seg006:1798) — общая точка для Кида и стража: меч в оригинале ОДИН на + * всех. obj_x/obj_y — та же опорная точка, что у спрайта персонажа. + * Возвращает 1 и прямоугольник клинка (для heal), 0 — клинка нет. + * Страницу W0 оставляет замапленной: вызывающий делает gfx_w0_unmap. */ +uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr, + int obj_x, int obj_y, + int *rx, int *ry, uint16_t *rw, uint16_t *rh); + /* Инициализировать Kid: последовательность seq_id, клетка col/row, * направление dir (-1/0). Ставит x/y по x_bump/y_land. */ void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir); diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 6136872..5ad8cbf 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -73,6 +73,12 @@ uint8_t pop_cheats; * Убирается закомментированием ОДНОЙ строки ниже. */ #define ROOMNAV 1 +/* ОТЛАДКА БОЁВКИ: выдавать Киду меч сразу при входе в комнату с этим + * номером (в комнате 3 стоит страж уровня 1). Иначе каждый прогон надо + * бежать за мечом в комнату 15. Убрать, когда боёвку можно будет + * проверять штатным путём. */ +#define DEBUG_SWORD_ROOM 3 + /* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы * соседей для кромок. Заполняются pop_room_load из уровня. File-scope, * чтобы enter_room переключал комнату без длинного списка параметров. */ @@ -196,6 +202,9 @@ static void enter_room(uint8_t room) * Kid.room == Guard.room нужно уже сейчас: без него луч видимости * стража никогда не срабатывает. */ Kid.room = room; +#ifdef DEBUG_SWORD_ROOM + if (room == DEBUG_SWORD_ROOM) pop_have_sword = 1; +#endif pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ diff --git a/applications/PoP/toolchain/pop_extract_kid_data.py b/applications/PoP/toolchain/pop_extract_kid_data.py index ebca96d..de4f36b 100644 --- a/applications/PoP/toolchain/pop_extract_kid_data.py +++ b/applications/PoP/toolchain/pop_extract_kid_data.py @@ -14,7 +14,7 @@ seqtbl адресуется базой SEQTBL_BASE=0x196E: элемент = kid_ import os import re -SRC = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/src" +SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "SDLPoP", "src") OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "poc", "res", "kid") OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), @@ -44,6 +44,18 @@ def parse_frame_table(txt, name="frame_table_kid"): return frames +def parse_sword_tbl(txt): + """sword_tbl[] (seg006:2047) -> список (id, x, y). id 255 = клинка нет. + Индекс — sword_frame (cur_frame.sword & 0x3F); один и тот же chtab_0 и у + Кида, и у стража.""" + m = re.search(r'const sword_table_type sword_tbl\[\]\s*=\s*\{(.*?)\n\};', txt, re.S) + rows = [] + for row in re.findall(r'\{([^}]*)\}', m.group(1)): + a, b, c = [int(p.strip(), 0) for p in row.split(',')] + rows.append((a & 0xFF, b & 0xFF, c & 0xFF)) + return rows + + def parse_byte_array(txt, name): m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S) return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))] @@ -69,6 +81,7 @@ def main(): gframes = parse_frame_table(seg006, "frame_tbl_guard") seqtbl = parse_byte_array(seqc, "original_seqtbl") offs = parse_word_array(seqc, "original_seqtbl_offsets") + swtbl = parse_sword_tbl(seg006) with open(OUT, "w") as f: f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n" @@ -102,7 +115,22 @@ def main(): f.write("#define KID_NSEQ %d\n" % len(offs)) f.write("static const uint16_t kid_seq_off[KID_NSEQ] = {\n") f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n") - f.write("#endif\n") + + # sword_tbl — макро-инициализаторами, а не готовыми массивами: так + # 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто + # включил заголовок. + f.write("/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.\n" + " * Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.\n" + " * Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),\n" + " * x — «вперёд» по направлению персонажа. */\n") + f.write("#define KID_NSWORD %d\n" % len(swtbl)) + for i, (nm, fmt) in enumerate((("ID", "0x%02X"), ("DX", "%d"), ("DY", "%d"))): + # dx/dy — знаковые: печатаем со знаком, иначе int8_t = overflow + vals = [r[i] if i == 0 else (r[i] - 256 if r[i] > 127 else r[i]) + for r in swtbl] + f.write("#define KID_SWORD_%s_INIT { \\\n" % nm) + f.write(emit_c(vals, 12, fmt).replace("\n", " \\\n") + " \\\n}\n") + f.write("\n#endif\n") # kid_data.bin: кадры (по 5 байт) + seqtbl подряд os.makedirs(OUT_BIN_DIR, exist_ok=True) diff --git a/applications/PoP/toolchain/pop_pack_kid.py b/applications/PoP/toolchain/pop_pack_kid.py index 97c5b4c..33ec649 100644 --- a/applications/PoP/toolchain/pop_pack_kid.py +++ b/applications/PoP/toolchain/pop_pack_kid.py @@ -26,7 +26,10 @@ 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 (найти-убрать) +# chtab_0 целиком: sword_tbl (seg006:2047) адресует id 0..33 — подъём меча, +# ножны И вся боёвка. Меч в оригинале ОДИН на всех: и Кид, и страж рисуют +# клинок из этого же chtab_0 (add_sword_to_objtable, seg006:1798). +SWORD_IDS = tuple(range(0, 34)) SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ) MASK = (1 << SHIFT) - 1 @@ -150,8 +153,7 @@ def main(): names.append(None) print(f" (kid стр {pg}: пусто)") - # chtab_0: меч в руке. Пакуем ТОЛЬКО кадры подъёма/ножен (sword_tbl - # 35..42 -> id 20..27); индекс в атласе = id - 20 (бой не портирован). + # chtab_0: меч в руке — весь chtab (id 0..33), индекс в атласе = id. sw = {} for i, id in enumerate(SWORD_IDS): w, h, pix = load_sword(id)