L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях

Порт SDLPoP:
  animate_chomper / start_chompers / start_anim_chomper /
  next_chomper_timing (seg007) -> pop_trob.c;
  draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) ->
  pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой);
  check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c.

Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N,
старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда).
Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c):
их читают обе половины слоя фона, а const-таблица банка из чужого банка
не видна.

start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq
(pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c).
Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале.

check_chomped_guard у оригинала отдельное тело (страж не проходит через
check_collisions).  У нас та же формула уже есть в get_row_collision_data,
поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below —
это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание
сломало бы ему бамп.

Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале).

Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked,
хотя функция давно переехала в резидент — всплыло при пересборке.

Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07).  Банк 6: 20.0 %,
банк 7: 39.7 %, банк 3: 53.8 %.  tests-host зелёные (5 наборов).
Зубья в MAME рисуются; анимация и смерть — на ручной приёмке.
This commit is contained in:
2026-08-08 19:05:16 +03:00
parent 4310f94795
commit dc0bd47368
14 changed files with 238 additions and 3 deletions
+7
View File
@@ -332,6 +332,13 @@ static void fore_only_tile(int row, int col)
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf]) if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
pop_env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2); 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) { /* зелье: склянка (пузырёк — динамика) */ if (code == 10) { /* зелье: склянка (пузырёк — динамика) */
pop_potion_flask(row, col, pop_tile_mod(row, col)); pop_potion_flask(row, col, pop_tile_mod(row, col));
} else if (t->fore_id) { } else if (t->fore_id) {
+4
View File
@@ -109,6 +109,10 @@ void pop_ceil_bake_empty(int col) __banked;
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */ * modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
void pop_spike_redraw(int row, int col) __banked; void pop_spike_redraw(int row, int col) __banked;
/* Чомпер — то же самое для его кадра анимации (все куски в СВОЕЙ ячейке,
* поэтому соседа перерисовывать не надо). */
void pop_chomp_redraw(int row, int col) __banked;
/* Анимация факела/зелья (динамика каждый кадр; фон запечён без них): /* Анимация факела/зелья (динамика каждый кадр; фон запечён без них):
* modif — живой room_modif тайла. Для факела col — колонка САМОГО факела * modif — живой room_modif тайла. Для факела col — колонка САМОГО факела
* (пламя рисуется в ячейке правого соседа). */ * (пламя рисуется в ячейке правого соседа). */
+3
View File
@@ -10,6 +10,7 @@
#include <gfx.h> #include <gfx.h>
#include <sprite.h> #include <sprite.h>
#include "pop_kid.h" #include "pop_kid.h"
#include "pop_trob.h" /* pop_start_chompers — смена ряда будит чомперы */
#include "pop_guard.h" /* Guard/pop_gframe/CHARID_* — интерпретатор общий */ #include "pop_guard.h" /* Guard/pop_gframe/CHARID_* — интерпретатор общий */
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ #include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */ #include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
@@ -424,9 +425,11 @@ void play_seq(void)
} }
case 0xFD: /* UP */ case 0xFD: /* UP */
Char.curr_row--; Char.curr_row--;
pop_start_chompers(Char.room, Char.curr_row); /* seg006:0603 */
break; break;
case 0xFC: /* DOWN */ case 0xFC: /* DOWN */
Char.curr_row++; Char.curr_row++;
pop_start_chompers(Char.room, Char.curr_row); /* seg006:0607 */
break; break;
case 0xF9: /* ACTION */ case 0xF9: /* ACTION */
Char.action = SEQ(Char.curr_seq); Char.action = SEQ(Char.curr_seq);
+62 -2
View File
@@ -90,6 +90,7 @@
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */ #define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */ #define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */ #define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
#define SEQ_54_CHOMPED 54 /* попасть в челюсти чомпера (смерть) */
#define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */ #define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */
#define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */ #define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */
#define SEQ_65_BUMP_FWD_SWORD 65 /* стража прижали к стене (с мечом) */ #define SEQ_65_BUMP_FWD_SWORD 65 /* стража прижали к стене (с мечом) */
@@ -99,6 +100,7 @@
#define TILE_FLOOR 1 #define TILE_FLOOR 1
#define FRAME_109_CROUCH 109 #define FRAME_109_CROUCH 109
#define FRAME_178_CHOMPED 178
/* Кадры анимации для check_spiked (seg006:0658). */ /* Кадры анимации для check_spiked (seg006:0658). */
#define FRAME_7_RUN 7 #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) if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
Char.x = (uint8_t)char_dx_forward(-3); Char.x = (uint8_t)char_dx_forward(-3);
pop_start_chompers(Char.room, Char.curr_row); /* seg005:0148 */
/* Урон от падения (seg005:173) — ветвление по ПЕРСОНАЖУ, а не только по /* Урон от падения (seg005:173) — ветвление по ПЕРСОНАЖУ, а не только по
* высоте. Порт построчно: * высоте. Порт построчно:
* 1 этаж (fall_y < 22): не-Кид (charid >= guard) ИЛИ меч вынут → * 1 этаж (fall_y < 22): не-Кид (charid >= guard) ИЛИ меч вынут →
@@ -575,7 +578,7 @@ static void start_fall(void)
* control_with_sword разбирает кадры падения как боевые. */ * control_with_sword разбирает кадры падения как боевые. */
Char.sword = SWORD_0_SHEATHED; Char.sword = SWORD_0_SHEATHED;
inc_curr_row(); 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)) */ /* seg006:1044 start_fall: frame 9 -> seq_7, 13 -> seq_19 (лишний dx(1)) */
if (frame == 13) seq = SEQ_19_FALL; if (frame == 13) seq = SEQ_19_FALL;
else if (frame == 26) seq = SEQ_18_FALL_STANDJUMP; 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, а кто именно в * Оригинал держит физику ОДНИМ набором функций над Char, а кто именно в
* Char — решают окна loadkid/savekid и loadshad/saveshad (seg006:809). * Char — решают окна loadkid/savekid и loadshad/saveshad (seg006:809).
@@ -2126,6 +2184,7 @@ static void kid_phys(void)
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */ check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */ check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
check_spiked(); /* seg006: напоролся на вредные пики → смерть */ check_spiked(); /* seg006: напоролся на вредные пики → смерть */
check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */
if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */ if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */ pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
/* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой /* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой
@@ -2144,7 +2203,7 @@ static void kid_phys(void)
/* play_guard_frame (seg000:1252..1266). Чего у стража НЕТ по сравнению с /* play_guard_frame (seg000:1252..1266). Чего у стража НЕТ по сравнению с
* Кидом: bump_into_opponent, check_collisions/check_bumped (у него свой * Кидом: bump_into_opponent, check_collisions/check_bumped (у него свой
* check_guard_bumped), check_gate_push, check_knock и весь уход из комнаты. * check_guard_bumped), check_gate_push, check_knock и весь уход из комнаты.
* check_chomped_guard появится вместе с чомперами (L3-CHOMP). */ * check_chomped_guard — то же тело, что у Кида (у нас check_chomped). */
static void guard_phys(void) static void guard_phys(void)
{ {
fall_accel(); fall_accel();
@@ -2155,6 +2214,7 @@ static void guard_phys(void)
check_press(); /* страж тоже давит кнопки и проваливает loose */ check_press(); /* страж тоже давит кнопки и проваливает loose */
check_spike_below(); check_spike_below();
check_spiked(); check_spiked();
check_chomped_guard();/* seg000:1265 */
} }
/* Физика КИДА — своё окно Char: главный цикл зовёт нас вне окон, а физика /* Физика КИДА — своё окно Char: главный цикл зовёт нас вне окон, а физика
+1
View File
@@ -60,6 +60,7 @@ void pop_redraw_needed(void)
case POP_RD_FLOOR: pop_floor_bake(row, col); break; case POP_RD_FLOOR: pop_floor_bake(row, col); break;
case POP_RD_LEVELDOOR: pop_leveldoor_redraw(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_GATE: pop_gate_redraw(row, col); break;
case POP_RD_CHOMP: pop_chomp_redraw(row, col); break;
default: break; default: break;
} }
if (--rd_cnt[i]) left++; if (--rd_cnt[i]) left++;
+1
View File
@@ -30,6 +30,7 @@
#define POP_RD_FLOOR 4 /* пол изменился: кнопка/щебень/поднятый меч */ #define POP_RD_FLOOR 4 /* пол изменился: кнопка/щебень/поднятый меч */
#define POP_RD_LEVELDOOR 5 /* створка двери уровня (ПРАВАЯ половина) */ #define POP_RD_LEVELDOOR 5 /* створка двери уровня (ПРАВАЯ половина) */
#define POP_RD_GATE 6 /* решётка ворот (tilepos = САМИ ворота) */ #define POP_RD_GATE 6 /* решётка ворот (tilepos = САМИ ворота) */
#define POP_RD_CHOMP 7 /* чомпер: кадр смыкания челюстей */
/* Виды перерисовки полосы у потолка (по колонке). */ /* Виды перерисовки полосы у потолка (по колонке). */
#define POP_RDA_CEIL 1 /* плита-потолок: дрожащий кадр / покой */ #define POP_RDA_CEIL 1 /* плита-потолок: дрожащий кадр / покой */
+42
View File
@@ -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}; 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 uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126};
static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0}; static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45}; 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]) if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2); 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): /* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3. * chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой * В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
@@ -326,6 +355,19 @@ void pop_spike_redraw(int row, int col) __banked
gfx_set_bank(GFX_BANK_NORMAL); 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 соседа — /* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей * ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]). * комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]).
+14
View File
@@ -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_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}; 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): индекс дрожащего кадра по модификатору. /* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору.
* В режиме ЗАПЕЧКИ фона отдаёт кадр ПОКОЯ — транзиентный кадр нельзя * В режиме ЗАПЕЧКИ фона отдаёт кадр ПОКОЯ — транзиентный кадр нельзя
* консервировать в ОЗУ-копию (см. pop_t_bake_rest в pop_bg.c). */ * консервировать в ОЗУ-копию (см. pop_t_bake_rest в pop_bg.c). */
+5
View File
@@ -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_SPIKES_FRAM_LEFT[10]; /* кадр пик в своей ячейке */
extern const uint8_t POP_LOOSE_FRAM_LEFT[12]; /* верх дрожащей плиты */ extern const uint8_t POP_LOOSE_FRAM_LEFT[12]; /* верх дрожащей плиты */
extern const uint8_t POP_LOOSE_FRAM_BOTTOM[12]; /* низ дрожащей плиты */ extern const uint8_t POP_LOOSE_FRAM_BOTTOM[12]; /* низ дрожащей плиты */
extern const uint8_t POP_CHOMP_FRAM_FOR[5]; /* чомпер: передний слой */
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора, /* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры * из которой потом восстанавливает heal). В этом режиме дрожащие кадры
@@ -91,6 +92,10 @@ extern uint8_t pop_t_bake_rest;
/* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */ /* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */
uint8_t pop_loose_frame(uint8_t modif); 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 добавляется внутри), левый * НИЗ спрайта на ybottom (room-space, POP_YOFF добавляется внутри), левый
* край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в * край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в
+81
View File
@@ -15,6 +15,7 @@
* нельзя звать из __banked-кода. */ * нельзя звать из __banked-кода. */
#include "pop_bg.h" #include "pop_bg.h"
#include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */ #include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */
#include "pop_kid.h" /* Kid.curr_row/alive — условие остановки чомпера */
#include "pop_tune.h" /* POP_DBG_GATE_HOLD — отладочное растягивание окна */ #include "pop_tune.h" /* POP_DBG_GATE_HOLD — отладочное растягивание окна */
/* Коды тайлов (fg & 0x1F), которые анимируются. */ /* Коды тайлов (fg & 0x1F), которые анимируются. */
@@ -27,6 +28,7 @@
#define TILE_LOOSE 0x0B /* проваливающийся пол (фаза тряски в modif) */ #define TILE_LOOSE 0x0B /* проваливающийся пол (фаза тряски в modif) */
#define TILE_SWORD 0x16 /* меч-предмет на полу */ #define TILE_SWORD 0x16 /* меч-предмет на полу */
#define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */ #define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */
#define TILE_CHOMP 0x12 /* чомпер (челюсти) */
#define TILE_TORCH 0x13 #define TILE_TORCH 0x13
#define TILE_TORCH_DEBRIS 0x1E #define TILE_TORCH_DEBRIS 0x1E
@@ -196,6 +198,75 @@ static uint8_t bubble_next_frame(uint8_t curr)
return (next >= 8) ? 1 : next; 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) static void animate_spike(uint8_t *m, int8_t *type)
{ {
uint8_t mod = *m; uint8_t mod = *m;
@@ -429,6 +500,9 @@ void pop_process_trobs(uint8_t cur_room) __banked
case TILE_SPIKE: case TILE_SPIKE:
animate_spike(&mod[tp], &type); animate_spike(&mod[tp], &type);
break; break;
case TILE_CHOMP:
animate_chomper(&mod[tp], &type, room, tp, cur_room);
break;
case TILE_OPENER: case TILE_OPENER:
case TILE_CLOSER: case TILE_CLOSER:
animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */ animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */
@@ -490,6 +564,13 @@ void pop_process_trobs(uint8_t cur_room) __banked
* страницы: живой trob метит каждый кадр по одной. */ * страницы: живой trob метит каждый кадр по одной. */
pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1)); 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) { if (room == cur_room && code == TILE_GATE) {
/* draw_trob (seg007:01E6), которым заканчивается animate_door: /* draw_trob (seg007:01E6), которым заканчивается animate_door:
* пока trob жив — решётка едет каждый кадр, помечаем ТЕКУЩУЮ * пока trob жив — решётка едет каждый кадр, помечаем ТЕКУЩУЮ
+5
View File
@@ -62,4 +62,9 @@ void pop_trob_room_changed(void) __banked;
* зелья) со случайной стартовой фазой. Звать в enter_room. */ * зелья) со случайной стартовой фазой. Звать в enter_room. */
void pop_trob_anim_room(uint8_t room) __banked; 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 #endif
+6
View File
@@ -63,4 +63,10 @@
* enhancements. */ * enhancements. */
#define POP_ENABLE_JUMP_GRAB 1 #define POP_ENABLE_JUMP_GRAB 1
/* ---- Чомперы (SDLPoP: custom->chomper_speed) ------------------------ *
* Период смыкания челюстей в кадрах: фаза крутится 1..N и сбрасывается в 1.
* В оригинале 15; SDLPoP выносит это в настройку, потому что на разных
* уровнях моды любят делать чомперы быстрее. Меньше — злее. */
#define POP_CHOMPER_SPEED 15
#endif /* POP_TUNE_H */ #endif /* POP_TUNE_H */
+1
View File
@@ -310,6 +310,7 @@ static void enter_room_side(uint8_t room, uint8_t side)
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */
if (!follow) if (!follow)
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */ pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */ pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */
+6 -1
View File
@@ -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 void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) __banked
{ log_put(TK_TROB, room, tilepos, (uint8_t)type); } { 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, void pop_trigger_button(uint8_t room, uint8_t tilepos,
uint8_t button_type, uint8_t modifier) __banked uint8_t button_type, uint8_t modifier) __banked
{ (void)modifier; log_put(TK_BUTTON, room, tilepos, button_type); } { (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)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)x; (void)y; (void)w; (void)h; }
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
{ (void)x; (void)w; (void)sy_top; (void)h; } { (void)x; (void)w; (void)sy_top; (void)h; }