diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c index 660ba8e..f45b223 100644 --- a/applications/PoP/roomtest/pop_bg.c +++ b/applications/PoP/roomtest/pop_bg.c @@ -332,6 +332,13 @@ static void fore_only_tile(int row, int col) if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf]) pop_env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2); } + if (code == 0x12) { /* чомпер: передний слой челюстей */ + uint8_t cm = pop_tile_mod(row, col); + uint8_t pose = pop_chomp_pose(cm); + pop_env_b(POP_CHOMP_FRAM_FOR[pose], x, dmy); + if (cm & 0x80) /* кровь поверх переднего слоя */ + pop_env_b((uint8_t)(pose + 119), x + 8, dmy - 6); + } if (code == 10) { /* зелье: склянка (пузырёк — динамика) */ pop_potion_flask(row, col, pop_tile_mod(row, col)); } else if (t->fore_id) { diff --git a/applications/PoP/roomtest/pop_bg.h b/applications/PoP/roomtest/pop_bg.h index 463610d..e7b4d40 100644 --- a/applications/PoP/roomtest/pop_bg.h +++ b/applications/PoP/roomtest/pop_bg.h @@ -109,6 +109,10 @@ void pop_ceil_bake_empty(int col) __banked; * modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */ void pop_spike_redraw(int row, int col) __banked; +/* Чомпер — то же самое для его кадра анимации (все куски в СВОЕЙ ячейке, + * поэтому соседа перерисовывать не надо). */ +void pop_chomp_redraw(int row, int col) __banked; + /* Анимация факела/зелья (динамика каждый кадр; фон запечён без них): * modif — живой room_modif тайла. Для факела col — колонка САМОГО факела * (пламя рисуется в ячейке правого соседа). */ diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index e2d4d19..eec913a 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -10,6 +10,7 @@ #include #include #include "pop_kid.h" +#include "pop_trob.h" /* pop_start_chompers — смена ряда будит чомперы */ #include "pop_guard.h" /* Guard/pop_gframe/CHARID_* — интерпретатор общий */ #include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ #include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */ @@ -424,9 +425,11 @@ void play_seq(void) } case 0xFD: /* UP */ Char.curr_row--; + pop_start_chompers(Char.room, Char.curr_row); /* seg006:0603 */ break; case 0xFC: /* DOWN */ Char.curr_row++; + pop_start_chompers(Char.room, Char.curr_row); /* seg006:0607 */ break; case 0xF9: /* ACTION */ Char.action = SEQ(Char.curr_seq); diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 9dace8b..068e5bf 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -90,6 +90,7 @@ #define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */ #define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */ #define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */ +#define SEQ_54_CHOMPED 54 /* попасть в челюсти чомпера (смерть) */ #define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */ #define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */ #define SEQ_65_BUMP_FWD_SWORD 65 /* стража прижали к стене (с мечом) */ @@ -99,6 +100,7 @@ #define TILE_FLOOR 1 #define FRAME_109_CROUCH 109 +#define FRAME_178_CHOMPED 178 /* Кадры анимации для check_spiked (seg006:0658). */ #define FRAME_7_RUN 7 @@ -521,6 +523,7 @@ static void land(void) /* к краю пола — чуть назад (как оригинал) */ if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) Char.x = (uint8_t)char_dx_forward(-3); + pop_start_chompers(Char.room, Char.curr_row); /* seg005:0148 */ /* Урон от падения (seg005:173) — ветвление по ПЕРСОНАЖУ, а не только по * высоте. Порт построчно: * 1 этаж (fall_y < 22): не-Кид (charid >= guard) ИЛИ меч вынут → @@ -575,7 +578,7 @@ static void start_fall(void) * control_with_sword разбирает кадры падения как боевые. */ Char.sword = SWORD_0_SHEATHED; inc_curr_row(); - /* start_chompers() — чомперов ещё нет (L3-CHOMP). */ + pop_start_chompers(Char.room, Char.curr_row); /* seg006:08C1 */ /* seg006:1044 start_fall: frame 9 -> seq_7, 13 -> seq_19 (лишний dx(1)) */ if (frame == 13) seq = SEQ_19_FALL; else if (frame == 26) seq = SEQ_18_FALL_STANDJUMP; @@ -2107,6 +2110,61 @@ static void check_spike_below(void) } } +/* chomped (seg004:07BF): персонажа перемололо. Тайл помечается кровью + * (0x80 в modif), персонаж выравнивается по колонке чомпера, теряет все HP + * и уходит в seq_54. Условие «кадр ещё не 178» — чтобы не перезапускать + * последовательность каждый кадр, пока труп лежит в челюстях. + * + * Кровь пишется ДАЖЕ у скелета: SDLPoP чинит это опцией + * fix_skeleton_chomper_blood, но в ВАНИЛИ она есть, и мы держим ваниль. */ +static void chomped(uint8_t tilepos, int8_t col) +{ + pop_trob_modif(g_room)[tilepos] |= 0x80; + if (Char.frame == FRAME_178_CHOMPED) return; + Char.x = (uint8_t)(pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX); + Char.x = (uint8_t)char_dx_forward(7 - !Char.direction); + Char.y = (uint8_t)pop_y_land[Char.curr_row + 1]; + pop_take_hp(100); /* (TODO: sound_46_chomped) */ + pop_char_set_seq(SEQ_54_CHOMPED); + play_seq(); + if (Char.charid == CHARID_0_KID) pop_kid_dead = 1; +} + +/* check_chomped_kid (seg004:076B): по колонкам ряда — там, где персонаж + * перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF, + * у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (modif & 0x7F == 2), + * персонажа перемалывает. */ +static void check_chomped_flags(const uint8_t *flags) +{ + int8_t row = Char.curr_row, col; + uint8_t tp; + if (row < 0 || row > 2) return; + for (col = 0; col <= 9; col++) { + if (flags[COLL_IDX(col)] != 3) continue; + if (get_tile(col, row) != TILE_CHOMPER) continue; + tp = (uint8_t)(row * 10 + col); + if ((pop_trob_modif(g_room)[tp] & 0x7F) == 2) chomped(tp, col); + } +} + +static void check_chomped_kid(void) { check_chomped_flags(coll_curr); } + +/* check_chomped_guard (seg004:0989) — у оригинала ОТДЕЛЬНОЕ тело: страж не + * проходит через check_collisions, поэтому там перекрытие считается прямо по + * координатам граней (check_chomped_here). У нас та же формула уже живёт в + * get_row_collision_data, поэтому считаем флаги ряда во ВРЕМЕННЫЙ массив и + * идём общим телом. Именно во временный: coll_curr/above/below — это кадр + * КИДА, из них move_coll_to_prev берёт «прошлые» флаги на следующем кадре, и + * затирание сломало бы ему бамп. */ +static void check_chomped_guard(void) +{ + uint8_t flags[COLL_N]; + if (Char.curr_row < 0 || Char.curr_row > 2) return; + set_char_collision(); + get_row_collision_data(Char.curr_row, flags); + check_chomped_flags(flags); +} + /* ---- Кадровая физика: одна и та же над Char, два входа --------------- * * Оригинал держит физику ОДНИМ набором функций над Char, а кто именно в * Char — решают окна loadkid/savekid и loadshad/saveshad (seg006:809). @@ -2126,6 +2184,7 @@ static void kid_phys(void) check_press(); /* seg006: стойка/пробой loose → make_loose_fall */ check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */ check_spiked(); /* seg006: напоролся на вредные пики → смерть */ + check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */ if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */ pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */ /* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой @@ -2144,7 +2203,7 @@ static void kid_phys(void) /* play_guard_frame (seg000:1252..1266). Чего у стража НЕТ по сравнению с * Кидом: bump_into_opponent, check_collisions/check_bumped (у него свой * check_guard_bumped), check_gate_push, check_knock и весь уход из комнаты. - * check_chomped_guard появится вместе с чомперами (L3-CHOMP). */ + * check_chomped_guard — то же тело, что у Кида (у нас check_chomped). */ static void guard_phys(void) { fall_accel(); @@ -2155,6 +2214,7 @@ static void guard_phys(void) check_press(); /* страж тоже давит кнопки и проваливает loose */ check_spike_below(); check_spiked(); + check_chomped_guard();/* seg000:1265 */ } /* Физика КИДА — своё окно Char: главный цикл зовёт нас вне окон, а физика diff --git a/applications/PoP/roomtest/pop_redraw.c b/applications/PoP/roomtest/pop_redraw.c index b534739..ec9468b 100644 --- a/applications/PoP/roomtest/pop_redraw.c +++ b/applications/PoP/roomtest/pop_redraw.c @@ -60,6 +60,7 @@ void pop_redraw_needed(void) case POP_RD_FLOOR: pop_floor_bake(row, col); break; case POP_RD_LEVELDOOR: pop_leveldoor_redraw(row, col); break; case POP_RD_GATE: pop_gate_redraw(row, col); break; + case POP_RD_CHOMP: pop_chomp_redraw(row, col); break; default: break; } if (--rd_cnt[i]) left++; diff --git a/applications/PoP/roomtest/pop_redraw.h b/applications/PoP/roomtest/pop_redraw.h index 690d60b..822f72b 100644 --- a/applications/PoP/roomtest/pop_redraw.h +++ b/applications/PoP/roomtest/pop_redraw.h @@ -30,6 +30,7 @@ #define POP_RD_FLOOR 4 /* пол изменился: кнопка/щебень/поднятый меч */ #define POP_RD_LEVELDOOR 5 /* створка двери уровня (ПРАВАЯ половина) */ #define POP_RD_GATE 6 /* решётка ворот (tilepos = САМИ ворота) */ +#define POP_RD_CHOMP 7 /* чомпер: кадр смыкания челюстей */ /* Виды перерисовки полосы у потолка (по колонке). */ #define POP_RDA_CEIL 1 /* плита-потолок: дрожащий кадр / покой */ diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index 58f4fbc..8f95248 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -54,6 +54,22 @@ void pop_bg_free(void) __banked static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0}; +/* ---- Чомпер (seg008.c:0B2B draw_tile_anim, ветка tiles_18_chomper) ---- * + * Кадр берётся НЕ прямо из modif: chomper_fram1[min(modif & 0x7F, 6)] даёт + * номер позы 0..4, и уже он индексирует три набора кусков — нижнюю челюсть + * (backtable), верхнюю (backtable, со своим подъёмом) и передний слой + * (foretable, поверх персонажа). Кровь — тот же номер позы + 114 (back) / + * +119 (fore), рисуется mono-силуэтом цветом 12; в атласе она уже такой + * (pop_pack_bg.py, 2026-08-07). + * Старший бит modif (0x80) = «в этом чомпере кого-то перемололо». + * + * Номер позы (pop_chomp_pose) и передние куски живут в РЕЗИДЕНТЕ + * (pop_tile.c): их читает ещё и fore-проход из горячей половины, а + * const-таблицы банка из чужого банка не видны. */ +static const uint8_t CHOMP_FRAM_BOT[5] = {101, 102, 103, 104, 105}; +static const uint8_t CHOMP_FRAM_TOP[5] = {0, 0, 111, 112, 113}; +static const uint8_t CHOMP_FRAM_Y[5] = {0, 0, 0x25, 0x2F, 0x32}; + static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126}; static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0}; static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45}; @@ -258,6 +274,19 @@ static void draw_tile(int row, int col) if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf]) pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2); } + /* Чомпер (тот же draw_tile_anim, case tiles_18_chomper): нижняя челюсть + * на draw_main_y, поверх неё кровь (если кого-то перемололо), затем + * верхняя челюсть со своим подъёмом. Всё это BACKtable — то есть ПОД + * персонажем; передний слой рисует fore-проход. */ + if (row >= 0 && code == 0x12) { + uint8_t cm = pop_tile_mod(row, col); + uint8_t pose = pop_chomp_pose(cm); + pop_env_b(CHOMP_FRAM_BOT[pose], x, dmy); + if (cm & 0x80) + pop_env_b((uint8_t)(pose + 114), x + 8, dmy - 6); + if (CHOMP_FRAM_TOP[pose]) + pop_env_b(CHOMP_FRAM_TOP[pose], x, dmy - CHOMP_FRAM_Y[pose]); + } /* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword): * chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3. * В оригинале это midtable (рисуется вместе с объектами) — у нас статикой @@ -326,6 +355,19 @@ void pop_spike_redraw(int row, int col) __banked gfx_set_bank(GFX_BANK_NORMAL); } +/* Чомпер: перерисовка кадра анимации (порт redraw_20h/draw_trob). Челюсти + * поднимаются высоко над линией пола (верхний кусок уходит на 0x32 = 50 px + * вверх от dmy), поэтому heal берём на всю высоту тайла. Правый сосед НЕ + * нужен: в отличие от пик, все куски чомпера рисуются в СВОЕЙ ячейке. */ +void pop_chomp_redraw(int row, int col) __banked +{ + int x = POP_COL_XH[col] * 8; + pop_heal_off(x, 63 * row + 2, 32, 64); + gfx_set_bank(GFX_BANK_SPRITE); + draw_tile(row, col); + gfx_set_bank(GFX_BANK_NORMAL); +} + /* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа — * ворота соседней комнаты слева (portcullis room8) видны в col0 текущей * комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]). diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index da87c89..61fcdda 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -79,6 +79,20 @@ const uint8_t POP_SPIKES_FRAM_LEFT[10] = {0, 128, 129, 130, 131, 132, 131, 129, const uint8_t POP_LOOSE_FRAM_LEFT[12] = {41,69,41,70,70,41,41,41,70,70,70,0}; const uint8_t POP_LOOSE_FRAM_BOTTOM[12] = {43,73,43,74,74,43,43,43,74,74,74,0}; +/* ---- Чомпер: номер позы и передние куски (seg008 chomper_fram1/for) --- + * Резидент, а не банк: позу считает и полная отрисовка тайла (холодная + * половина, pop_room.c), и fore-проход поверх персонажа (горячая, + * pop_bg.c) — а const-таблица банка из чужого банка не читается вовсе. */ +static const uint8_t CHOMP_FRAM1[7] = {3, 2, 0, 1, 4, 3, 3}; +const uint8_t POP_CHOMP_FRAM_FOR[5] = {106, 107, 108, 109, 110}; + +uint8_t pop_chomp_pose(uint8_t modif) +{ + uint8_t m = (uint8_t)(modif & 0x7F); + if (m > 6) m = 6; + return CHOMP_FRAM1[m]; +} + /* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору. * В режиме ЗАПЕЧКИ фона отдаёт кадр ПОКОЯ — транзиентный кадр нельзя * консервировать в ОЗУ-копию (см. pop_t_bake_rest в pop_bg.c). */ diff --git a/applications/PoP/roomtest/pop_tile.h b/applications/PoP/roomtest/pop_tile.h index b4dc255..7ba4b79 100644 --- a/applications/PoP/roomtest/pop_tile.h +++ b/applications/PoP/roomtest/pop_tile.h @@ -79,6 +79,7 @@ extern const uint8_t POP_WALL_FRAM_MAIN[4]; /* основная грань extern const uint8_t POP_SPIKES_FRAM_LEFT[10]; /* кадр пик в своей ячейке */ extern const uint8_t POP_LOOSE_FRAM_LEFT[12]; /* верх дрожащей плиты */ extern const uint8_t POP_LOOSE_FRAM_BOTTOM[12]; /* низ дрожащей плиты */ +extern const uint8_t POP_CHOMP_FRAM_FOR[5]; /* чомпер: передний слой */ /* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора, * из которой потом восстанавливает heal). В этом режиме дрожащие кадры @@ -91,6 +92,10 @@ extern uint8_t pop_t_bake_rest; /* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */ uint8_t pop_loose_frame(uint8_t modif); +/* Номер позы чомпера по его модификатору (seg008 chomper_fram1): 0..4 — + * индекс во ВСЕ три набора кусков (низ/верх/перед) и в кровь (+114/+119). */ +uint8_t pop_chomp_pose(uint8_t modif); + /* ---- Блит куска атласа ---------------------------------------------- * * НИЗ спрайта на ybottom (room-space, POP_YOFF добавляется внутри), левый * край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index 3e22afc..b91479b 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -15,6 +15,7 @@ * нельзя звать из __banked-кода. */ #include "pop_bg.h" #include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */ +#include "pop_kid.h" /* Kid.curr_row/alive — условие остановки чомпера */ #include "pop_tune.h" /* POP_DBG_GATE_HOLD — отладочное растягивание окна */ /* Коды тайлов (fg & 0x1F), которые анимируются. */ @@ -27,6 +28,7 @@ #define TILE_LOOSE 0x0B /* проваливающийся пол (фаза тряски в modif) */ #define TILE_SWORD 0x16 /* меч-предмет на полу */ #define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */ +#define TILE_CHOMP 0x12 /* чомпер (челюсти) */ #define TILE_TORCH 0x13 #define TILE_TORCH_DEBRIS 0x1E @@ -196,6 +198,75 @@ static uint8_t bubble_next_frame(uint8_t curr) return (next >= 8) ? 1 : next; } +/* animate_chomper (seg007:0448). Модификатор: младшие 7 бит — фаза 1..N, + * старший — «перемололо кого-то» (кровь). Фаза крутится по кругу до + * POP_CHOMPER_SPEED (у оригинала 15), то есть челюсти смыкаются раз в N + * кадров; фаза 2 — момент удара (в оригинале там звук sound_47). + * + * Остановка (иначе чомперы щёлкали бы всю игру): если Кид ушёл из этой + * комнаты, или ушёл из этого РЯДА, или умер не в этом чомпере — trob + * снимается, но только после фазы 6, чтобы челюсти успели разомкнуться. + * Запускает их обратно start_chompers на смене ряда. Аргументы room/tp + * нужны ровно для этой проверки. */ +static void animate_chomper(uint8_t *m, int8_t *type, + uint8_t room, uint8_t tp, uint8_t drawn_room) +{ + uint8_t blood, frame; + if (*type >= 0) { + blood = (uint8_t)(*m & 0x80); + frame = (uint8_t)((*m & 0x7F) + 1); + if (frame > POP_CHOMPER_SPEED) frame = 1; + *m = (uint8_t)(blood | frame); + if ((room != drawn_room || tp / 10 != Kid.curr_row || + (Kid.alive >= 0 && blood == 0)) && frame >= 6) + *type = -1; + } +} + +/* next_chomper_timing (seg007:0F9A): 15,12,9,6,13,10,7,14,11,8 по кругу — + * чомперы одного ряда стартуют вразнобой, а не хлопают в унисон. */ +static uint8_t next_chomper_timing(uint8_t timing) +{ + timing -= 3; + if (timing < 6) timing += 10; + return timing; +} + +/* start_anim_chomper (seg007:08C7): запустить, только если стоит (0) или + * уже прошёл фазу размыкания (>=6). */ +static void start_anim_chomper(uint8_t room, uint8_t tp, uint8_t modifier) +{ + uint8_t *mod = pop_trob_modif(room); + uint8_t old = (uint8_t)(mod[tp] & 0x7F); + if (old == 0 || old >= 6) { + mod[tp] = modifier; + pop_add_trob(room, tp, 1); + } +} + +/* start_chompers (seg007:0F13): все чомперы РЯДА активного персонажа. + * Зовётся отовсюду, где персонаж сменил ряд (SEQ_UP/SEQ_DOWN, start_fall, + * приземление) и на входе в комнату. */ +void pop_start_chompers(uint8_t room, int8_t row) __banked +{ + uint8_t timing = 15, col, tp; + uint8_t *mod; + if ((uint8_t)row >= 3) return; /* вне комнаты — нечего */ + mod = pop_trob_modif(room); + pop_level_access_begin(); + for (col = 0; col < 10; col++) { + tp = (uint8_t)(row * 10 + col); + if ((pop_level_tile_raw(room, tp) & 0x1F) == TILE_CHOMP) { + uint8_t m = (uint8_t)(mod[tp] & 0x7F); + if (m == 0 || m >= 6) { + start_anim_chomper(room, tp, (uint8_t)(timing | (mod[tp] & 0x80))); + timing = next_chomper_timing(timing); + } + } + } + pop_level_access_end(); +} + static void animate_spike(uint8_t *m, int8_t *type) { uint8_t mod = *m; @@ -429,6 +500,9 @@ void pop_process_trobs(uint8_t cur_room) __banked case TILE_SPIKE: animate_spike(&mod[tp], &type); break; + case TILE_CHOMP: + animate_chomper(&mod[tp], &type, room, tp, cur_room); + break; case TILE_OPENER: case TILE_CLOSER: animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */ @@ -490,6 +564,13 @@ void pop_process_trobs(uint8_t cur_room) __banked * страницы: живой trob метит каждый кадр по одной. */ pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1)); } + if (room == cur_room && code == TILE_CHOMP) { + /* Кадр меняется каждый тик, пока trob жив — как у пик, метим + * текущую страницу; последний кадр (челюсти встали) — обе, + * иначе на второй странице дабл-буфера застынет предыдущая поза + * и чомпер дрожит через кадр. */ + pop_set_redraw(tp, POP_RD_CHOMP, (uint8_t)(type < 0 ? 2 : 1)); + } if (room == cur_room && code == TILE_GATE) { /* draw_trob (seg007:01E6), которым заканчивается animate_door: * пока trob жив — решётка едет каждый кадр, помечаем ТЕКУЩУЮ diff --git a/applications/PoP/roomtest/pop_trob.h b/applications/PoP/roomtest/pop_trob.h index f40e756..8264e7a 100644 --- a/applications/PoP/roomtest/pop_trob.h +++ b/applications/PoP/roomtest/pop_trob.h @@ -62,4 +62,9 @@ void pop_trob_room_changed(void) __banked; * зелья) со случайной стартовой фазой. Звать в enter_room. */ void pop_trob_anim_room(uint8_t room) __banked; +/* start_chompers (seg007:0F13): запустить чомперы РЯДА персонажа. Звать + * там же, где оригинал: смена ряда в play_seq (SEQ_UP/SEQ_DOWN), start_fall, + * приземление и вход в комнату. */ +void pop_start_chompers(uint8_t room, int8_t row) __banked; + #endif diff --git a/applications/PoP/roomtest/pop_tune.h b/applications/PoP/roomtest/pop_tune.h index f4a9879..ca13196 100644 --- a/applications/PoP/roomtest/pop_tune.h +++ b/applications/PoP/roomtest/pop_tune.h @@ -63,4 +63,10 @@ * enhancements. */ #define POP_ENABLE_JUMP_GRAB 1 +/* ---- Чомперы (SDLPoP: custom->chomper_speed) ------------------------ * + * Период смыкания челюстей в кадрах: фаза крутится 1..N и сбрасывается в 1. + * В оригинале 15; SDLPoP выносит это в настройку, потому что на разных + * уровнях моды любят делать чомперы быстрее. Меньше — злее. */ +#define POP_CHOMPER_SPEED 15 + #endif /* POP_TUNE_H */ diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 4939e7b..fe6caf6 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -310,6 +310,7 @@ static void enter_room_side(uint8_t room, uint8_t side) pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ + pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */ if (!follow) pop_guard_enter(room); /* страж комнаты (порт enter_guard) */ pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */ diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index 19e57bd..1c6d5ff 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -161,6 +161,11 @@ uint8_t tk_log_count(uint8_t kind) void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) __banked { log_put(TK_TROB, room, tilepos, (uint8_t)type); } +/* Чомперы будит смена ряда (SEQ_UP/SEQ_DOWN, падение, приземление) — в + * хост-тестах физики их состояние не проверяется, достаточно не падать. */ +void pop_start_chompers(uint8_t room, int8_t row) __banked +{ (void)room; (void)row; } + void pop_trigger_button(uint8_t room, uint8_t tilepos, uint8_t button_type, uint8_t modifier) __banked { (void)modifier; log_put(TK_BUTTON, room, tilepos, button_type); } @@ -181,7 +186,7 @@ uint8_t pop_loose_mob_pos(int *col, int *y) __banked { (void)col; (void)y; return 0; } /* Отрисовочные хвосты, которые физика дёргает напрямую. */ -void pop_fore_set_clip(int x, int y, int w, int h) __banked +void pop_fore_set_clip(int x, int y, int w, int h) { (void)x; (void)y; (void)w; (void)h; } void pop_clip_sprite(int x, int w, int sy_top, int h) __banked { (void)x; (void)w; (void)sy_top; (void)h; }