PoP: roomtest — объекты/обломки/переходы комнат + арт
Порт Prince of Persia (applications/PoP/roomtest): развитие уровня, объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка; планы (room_model/size_optimization), bug_list, pop_trob. Арт third_party/16x16-RPG-characters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "pop_map.h"
|
||||
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
|
||||
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */
|
||||
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
|
||||
|
||||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||||
#define TILE_EMPTY 0
|
||||
@@ -31,6 +32,9 @@
|
||||
#define TILE_WALL 20
|
||||
#define TILE_LOOSE 11 /* проваливающийся пол */
|
||||
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
|
||||
#define TILE_SPIKE 2 /* пики */
|
||||
#define TILE_CLOSER 6 /* кнопка-закрывалка (DROP) */
|
||||
#define TILE_OPENER 15 /* кнопка-открывалка (RAISE, 0x0F) */
|
||||
#define ACT_STAND 0
|
||||
#define ACT_RUNJUMP 1
|
||||
#define ACT_HANGCLIMB 2
|
||||
@@ -38,6 +42,7 @@
|
||||
#define ACT_FREEFALL 4
|
||||
#define ACT_BUMPED 5
|
||||
#define ACT_HANGSTRAIGHT 6
|
||||
#define ACT_TURN 7
|
||||
|
||||
#define SEQ_7_FALL 7 /* stepfall (stand/run/step/crouch) */
|
||||
#define SEQ_17_SOFT_LAND 17
|
||||
@@ -58,6 +63,13 @@
|
||||
#define SEQ_23_RELEASE_LEDGE_FALL 23 /* отпустить уступ и падать */
|
||||
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
|
||||
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
|
||||
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
|
||||
|
||||
/* Кадры анимации для check_spiked (seg006:0658). */
|
||||
#define FRAME_7_RUN 7
|
||||
#define FRAME_26_STANDJUMP_LAND 26
|
||||
#define FRAME_34_RUNJUMP_START 34
|
||||
#define FRAME_43_RUNJUMP_LAND 43
|
||||
|
||||
#define FRAME_NEEDS_FLOOR 0x40
|
||||
#define FRAME_WEIGHT_X 0x1F
|
||||
@@ -86,7 +98,11 @@ static int8_t g_infrontx; /* колонка «перед персонаже
|
||||
/* ---- Карта комнаты ------------------------------------------------- */
|
||||
uint8_t pop_fell_out; /* 1 = Kid пересёк НИЖНЮЮ границу (см. pop_map.h) */
|
||||
uint8_t pop_leave_dir; /* уход вбок/вверх: 0=нет; 1=left,2=right,3=up (side+1) */
|
||||
uint8_t pop_leave_timer; /* exit_room_timer (seg002): >0 = leave заблокирован N кадров после входа в комнату */
|
||||
uint8_t pop_loose_fell; /* 0=нет; иначе tilepos+1 упавшего loose (кусок улетел вниз) */
|
||||
uint8_t pop_kid_dead; /* 1 = Kid мёртв (напоролся на пики и т.п.) */
|
||||
uint8_t hitp_curr; /* текущее HP (0 = мёртв); pop_kid_hp_reset ставит старт */
|
||||
static uint8_t g_room; /* номер ТЕКУЩЕЙ комнаты (для room_modif пик/ворот) */
|
||||
static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ — loose→empty) */
|
||||
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
||||
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
|
||||
@@ -100,6 +116,18 @@ static uint8_t g_lcol[3], g_rcol[3];
|
||||
|
||||
void pop_map_set(uint8_t *fg) { g_fg = fg; }
|
||||
|
||||
void pop_map_set_room(uint8_t room) { g_room = room; }
|
||||
|
||||
void pop_kid_hp_reset(void) { hitp_curr = 3; pop_kid_dead = 0; }
|
||||
|
||||
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
|
||||
static uint8_t take_hp(uint8_t count)
|
||||
{
|
||||
if (count >= hitp_curr) { hitp_curr = 0; return 1; }
|
||||
hitp_curr = (uint8_t)(hitp_curr - count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
|
||||
const uint8_t *lcol_fg, const uint8_t *rcol_fg)
|
||||
{
|
||||
@@ -123,6 +151,28 @@ static uint8_t get_tile(int8_t col, int8_t row)
|
||||
return g_curr_tile;
|
||||
}
|
||||
|
||||
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
|
||||
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
|
||||
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
|
||||
* tile_is_floor это ПОЛ), поэтому Kid НЕ падает на них. Раньше get_tile ради
|
||||
* проходимости подменял ворота шва на empty — но подмену видел и check_on_floor
|
||||
* → start_fall (Kid проваливался при проходе через ОТКРЫТЫЕ ворота шва). Теперь
|
||||
* проходимость живёт ТОЛЬКО в bump-логике (как в SDLPoP), не трогая проверку пола.
|
||||
* Модификатор: шов слева (col=-1) — сосед g_link_l col9; шов справа (col=10) —
|
||||
* g_link_r col0; ВНУТРИ комнаты (0..9) — текущая комната g_room (иначе после
|
||||
* перехода Kid встаёт на ту же створку уже как на внутрикомнатную и его отбивает
|
||||
* обратно). Открыто ⇔ (modif>>2)+6 >= char_height (бары выше головы). */
|
||||
static uint8_t gate_passable(int8_t col, int8_t row)
|
||||
{
|
||||
uint8_t gm;
|
||||
if (row < 0 || row > 2) return 0;
|
||||
if (col == -1 && g_link_l) gm = pop_trob_modif(g_link_l)[row * 10 + 9];
|
||||
else if (col == 10 && g_link_r) gm = pop_trob_modif(g_link_r)[row * 10 + 0];
|
||||
else if (col >= 0 && col <= 9) gm = pop_trob_modif(g_room)[row * 10 + col];
|
||||
else return 0;
|
||||
return (uint8_t)((gm >> 2) + 6) >= (uint8_t)kid_fp_height();
|
||||
}
|
||||
|
||||
static uint8_t tile_is_floor(uint8_t t)
|
||||
{
|
||||
switch (t) {
|
||||
@@ -225,13 +275,31 @@ static int distance_to_edge(int xpos)
|
||||
static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); }
|
||||
|
||||
/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
|
||||
* Передний край персонажа char_x_*_coll = char_dx_forward(cur_frame.dx)
|
||||
* (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для
|
||||
* THIN-кадров. dist_from_wall_forward — расстояние переднего края до грани
|
||||
* стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */
|
||||
* Передний край персонажа = char_x_right (взгляд вправо) / char_x_left
|
||||
* (влево) из set_char_collision (seg006:0723): obj_x/2 + 58, где obj_x =
|
||||
* 2*char_dx_forward(cur_frame.dx) - 116 и load_frame_to_obj (seg008:1728)
|
||||
* добавляет +1 для кадров «чётного пикселя» (бит 0x80 флагов XOR знак
|
||||
* направления). Деление obj_x/2 в C усечено К НУЛЮ, поэтому в зоне левого
|
||||
* шва (e<=57, obj_x<0) поправка +1 сдвигает край на пиксель ВПРАВО: e -> e+1.
|
||||
* Это НЕ мелочь: Kid, осевший после отскока от ворот шва на x=57, имеет
|
||||
* char_x_right=58 и НЕ пересекает порог leave-left (<=57) — иначе пинг-понг
|
||||
* drawn_room у закрытых ворот (BUG-SEAM-PINGPONG; подтверждено потиковой
|
||||
* трассой живого SDLPoP 1.23). При e>=58 формула сокращается к чистому
|
||||
* char_dx_forward. ∓4 для THIN-кадров поверх. dist_from_wall_forward —
|
||||
* расстояние переднего края до грани стены tiletype в колонке tcol: >0
|
||||
* зазор, <=0 достал/зашёл (насколько). */
|
||||
static int char_x_forward_edge(void)
|
||||
{
|
||||
int e = char_dx_forward(kid_cur_dx());
|
||||
uint8_t fl = kid_cur_flags();
|
||||
if (Kid.direction < 0) fl ^= 0x80;
|
||||
if (!(fl & 0x80) && e <= 57) e++; /* odd pixel + C-трунк к нулю */
|
||||
return e;
|
||||
}
|
||||
|
||||
static int char_front_coll(void)
|
||||
{
|
||||
int cx = char_dx_forward(kid_cur_dx());
|
||||
int cx = char_x_forward_edge();
|
||||
if (kid_cur_flags() & FRAME_THIN)
|
||||
cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */
|
||||
return cx;
|
||||
@@ -270,10 +338,36 @@ static void in_wall(void)
|
||||
/* ---- Падение ------------------------------------------------------- */
|
||||
static void inc_curr_row(void) { Kid.curr_row++; }
|
||||
|
||||
/* Пики (определены ниже, перед pop_phys_tick) — нужны в land() для смерти
|
||||
* при ПАДЕНИИ на выдвинутые пики (seg005 loc_5EE6). */
|
||||
static int is_spike_harmful(uint8_t tilepos);
|
||||
static void spiked(uint8_t tilepos);
|
||||
|
||||
/* fell_on_spikes (seg005 loc_5EE6): при приземлении тайл ПОД персонажем =
|
||||
* пика (или пика СЗАДИ у самой кромки) и вредна → напороться (смерть).
|
||||
* Возвращает 1, если умер (land прерывается). */
|
||||
static uint8_t fell_on_spikes(void)
|
||||
{
|
||||
uint8_t tp;
|
||||
if (get_tile_at_char() == TILE_SPIKE) {
|
||||
tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||
} else if (distance_to_edge_weight() >= 12 &&
|
||||
get_tile_behind_char() == TILE_SPIKE) {
|
||||
int8_t bcol = (int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col);
|
||||
if (bcol < 0 || bcol > 9) return 0;
|
||||
tp = (uint8_t)(Kid.curr_row * 10 + bcol);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if (is_spike_harmful(tp)) { spiked(tp); return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void land(void)
|
||||
{
|
||||
uint8_t seq;
|
||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
||||
if (fell_on_spikes()) return; /* упал на вредные пики — смерть */
|
||||
/* к краю пола — чуть назад (как оригинал) */
|
||||
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
|
||||
Kid.x = (uint8_t)char_dx_forward(-3);
|
||||
@@ -618,7 +712,7 @@ static void check_action(void)
|
||||
* выравниваем, без рестарта. В падении/висе не трогаем. */
|
||||
static void check_bumped(void)
|
||||
{
|
||||
uint8_t t; int8_t tcol; int d;
|
||||
uint8_t t, wt; int8_t tcol; int d;
|
||||
/* Не бампим в падении/висе/подтягивании (seg004 check_bumped guard'ы:
|
||||
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
|
||||
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
|
||||
@@ -635,8 +729,19 @@ static void check_bumped(void)
|
||||
* underflow X и уход в стену. fell_out ловит do_fall. */
|
||||
if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
|
||||
t = get_tile_at_char(); tcol = Kid.curr_col;
|
||||
if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; }
|
||||
if (!wall_type(t)) return;
|
||||
wt = wall_type(t);
|
||||
/* seg004: bump проверяется по колонке СБОКУ от стены (bump_col_left/right_
|
||||
* of_wall), а НЕ по тайлу ПОД персонажем. Стоя́ на односторонней стене
|
||||
* (ворота = стена справа / зеркало = стена слева) лицом ПРОЧЬ от её
|
||||
* блокирующей стороны — она не бампит. Иначе разворот на тайле ворот у
|
||||
* шва выталкивает Kid к дальней грани ворот (сквозь закрытую решётку в
|
||||
* соседнюю комнату — баг «телепорта» при развороте). */
|
||||
if ((wt == 1 && Kid.direction < 0) || (wt == 2 && Kid.direction >= 0))
|
||||
wt = 0;
|
||||
if (!wt) { t = get_tile_infrontof_char(); tcol = g_infrontx; wt = wall_type(t); }
|
||||
if (!wt) return;
|
||||
if (t == TILE_GATE && gate_passable(tcol, Kid.curr_row))
|
||||
return; /* открытые ворота (шов/внутри) — Kid проходит (не бамп) */
|
||||
d = dist_from_wall_forward(t, tcol);
|
||||
if (d >= 0) return; /* ещё не зашёл за грань */
|
||||
Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */
|
||||
@@ -676,11 +781,20 @@ static void check_press(void)
|
||||
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
||||
return;
|
||||
}
|
||||
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR */
|
||||
if (action != ACT_BUMPED && action >= ACT_HANGCLIMB) return;
|
||||
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR.
|
||||
* ACT_TURN обязателен (SDLPoP check_press: actions_7_turn): при развороте
|
||||
* на кнопке иначе trigger_button не зовётся кадр-два → таймер связи падает
|
||||
* <2 → кнопка ложно отжимается и жмётся снова. */
|
||||
if (action != ACT_BUMPED && action != ACT_TURN && action >= ACT_HANGCLIMB) return;
|
||||
if (!(kid_cur_flags() & FRAME_NEEDS_FLOOR)) return;
|
||||
if (get_tile_at_char() == TILE_LOOSE)
|
||||
make_loose_fall(Kid.curr_row * 10 + Kid.curr_col, 1);
|
||||
{
|
||||
uint8_t t = get_tile_at_char();
|
||||
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||
if (t == TILE_OPENER || t == TILE_CLOSER)
|
||||
pop_trigger_button(g_room, tp, t, pop_trob_modif(g_room)[tp]);
|
||||
else if (t == TILE_LOOSE)
|
||||
make_loose_fall(tp, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* do_knock (seg007:0FE0) + loose_make_shake (seg007:0FB4): сотрясение от
|
||||
@@ -729,29 +843,32 @@ void pop_loose_reset(void)
|
||||
void pop_loose_tick(void)
|
||||
{
|
||||
int pos;
|
||||
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
|
||||
* нет аппаратного деления, и SDCC выносил оба деления (__divuchar/
|
||||
* __moduchar) в начало КАЖДОЙ итерации (общее подвыражение аргументов
|
||||
* вызовов), т.е. 30*2=60 вызовов деления за кадр — даже в покое и в
|
||||
* комнатах без loose-плит (условия if к тому моменту ещё не проверены).
|
||||
* Инкремент+сравнение считают row/col без деления. */
|
||||
uint8_t row = 0, col = 0;
|
||||
for (pos = 0; pos < 30; pos++) {
|
||||
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
|
||||
pop_loose_bake_empty(pos / 10, pos % 10);
|
||||
pop_loose_bake_empty(row, col);
|
||||
loose_bake[pos]--;
|
||||
continue;
|
||||
}
|
||||
if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||||
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||||
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
||||
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
||||
* страницы дабл-буфера — иначе на одной остаётся застывший
|
||||
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
||||
pop_loose_shake_draw(pos / 10, pos % 10);
|
||||
pop_loose_shake_draw(row, col);
|
||||
loose_rest[pos]--;
|
||||
continue;
|
||||
}
|
||||
if (pop_loose_modif[pos] == 0) continue;
|
||||
{
|
||||
} else if (pop_loose_modif[pos] != 0) {
|
||||
uint8_t m = ++pop_loose_modif[pos];
|
||||
if (m & 0x80) { /* тряска (do_knock) */
|
||||
if (m >= 0x84) { /* конец тряски: сброс + перерисовать покой */
|
||||
pop_loose_modif[pos] = 0;
|
||||
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */
|
||||
continue;
|
||||
} else {
|
||||
pop_loose_shake_draw(row, col); /* дрожащий кадр */
|
||||
}
|
||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||
@@ -761,14 +878,15 @@ void pop_loose_tick(void)
|
||||
* (стена-вниз от тайла снизу + чёрный фон). */
|
||||
g_fg[pos] = TILE_EMPTY;
|
||||
pop_loose_modif[pos] = 0;
|
||||
pop_loose_bake_empty(pos / 10, pos % 10); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||||
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||||
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
|
||||
pop_loose_mob_spawn(pos / 10, pos % 10); /* отрыв падающего куска (L4) */
|
||||
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */
|
||||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
|
||||
continue;
|
||||
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||||
pop_loose_shake_draw(row, col);
|
||||
}
|
||||
pop_loose_shake_draw(pos / 10, pos % 10); /* дрожащий кадр */
|
||||
}
|
||||
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
||||
}
|
||||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||
}
|
||||
@@ -799,9 +917,27 @@ static void check_leave_below(void)
|
||||
* удержит у шва). */
|
||||
static void check_leave(void)
|
||||
{
|
||||
int fx = char_dx_forward(kid_cur_dx());
|
||||
int fx;
|
||||
uint8_t f = Kid.frame;
|
||||
/* seg002 leave_room: комнату НЕ покидаем во время разворота (action turn),
|
||||
* подъёма-с-зацепа (climb, кадры 135..149) и вставания из приседа
|
||||
* (110..119). Без этого гарда разворот у правого шва (взгляд флипается
|
||||
* право→лево, char_x_left проскакивает >=198) телепортирует Kid сквозь
|
||||
* закрытые ворота в соседнюю комнату (leave_room возвращает −1). */
|
||||
if (Kid.action == ACT_TURN ||
|
||||
(f >= 135 && f < 150) ||
|
||||
(f >= 110 && f < 120))
|
||||
return;
|
||||
/* Передний край — ТОЛЬКО через char_x_forward_edge (obj_x-арифметика
|
||||
* set_char_collision): в зоне шва (<=57) он на пиксель правее чистого
|
||||
* Kid.x+dx, и Kid, осевший у ворот на x=57, порога leave-left НЕ достигает
|
||||
* (эталон SDLPoP: экран не переключается, BUG-SEAM-PINGPONG). */
|
||||
fx = char_x_forward_edge();
|
||||
if (Kid.direction >= 0) { /* взгляд вправо: fx = char_x_right */
|
||||
if (g_link_r && fx >= 201) {
|
||||
uint8_t t9 = get_tile(9, Kid.curr_row);
|
||||
if (g_link_r && fx >= 201 &&
|
||||
t9 != TILE_DOORTOP_FLOOR && t9 != TILE_DOORTOP) {
|
||||
/* leave_room (seg002): под doortop у правой кромки выхода нет */
|
||||
Kid.x = (uint8_t)(Kid.x - 140); determine_col(); pop_leave_dir = 2;
|
||||
} else if (g_link_l && fx <= 57) {
|
||||
Kid.x = (uint8_t)(Kid.x + 140); determine_col(); pop_leave_dir = 1;
|
||||
@@ -815,8 +951,105 @@ static void check_leave(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Пики (spike, seg006/seg007) ---------------------------------- *
|
||||
* Состояние пики в room_modif[g_room][tilepos] (pop_trob), автомат —
|
||||
* animate_spike (pop_trob). Здесь: триггер выдвижения (check_spike_below),
|
||||
* решение о смерти (check_spiked) + spiked(). */
|
||||
|
||||
/* start_anim_spike (seg007:08E3): взвести выдвижение, если пика в покое
|
||||
* (modif 0 → trob type1) или ушла не полностью (modif<0, кроме 0xFF disabled
|
||||
* → мгновенно 0x8F «вышли»). */
|
||||
static void start_anim_spike(uint8_t tilepos)
|
||||
{
|
||||
uint8_t *mod = pop_trob_modif(g_room);
|
||||
int8_t old;
|
||||
if (!mod) return;
|
||||
old = (int8_t)mod[tilepos];
|
||||
if (old <= 0) {
|
||||
if (old == 0)
|
||||
pop_add_trob(g_room, tilepos, 1); /* (TODO: sound_49_spikes) */
|
||||
else if ((uint8_t)old != 0xFF)
|
||||
mod[tilepos] = 0x8F;
|
||||
}
|
||||
}
|
||||
|
||||
/* is_spike_harmful (seg007:1178): 0/-1 → 0 (безопасно); <0 → 1; 1..4 → 2;
|
||||
* >=5 → 0. */
|
||||
static int is_spike_harmful(uint8_t tilepos)
|
||||
{
|
||||
int8_t m = (int8_t)pop_trob_modif(g_room)[tilepos];
|
||||
if (m == 0 || m == -1) return 0;
|
||||
if (m < 0) return 1;
|
||||
if (m < 5) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* spiked (seg005:0888): напоролся — пика становится безвредной (0xFF),
|
||||
* выровнять по колонке, take_hp(100) = смерть, seq_51 (импейл). */
|
||||
static void spiked(uint8_t tilepos)
|
||||
{
|
||||
pop_trob_modif(g_room)[tilepos] = 0xFF;
|
||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
||||
Kid.x = (uint8_t)(x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10);
|
||||
Kid.x = (uint8_t)char_dx_forward(8);
|
||||
Kid.fall_y = 0;
|
||||
take_hp(100); /* (TODO: sound_48_spiked) */
|
||||
kid_set_seq(SEQ_51_SPIKED);
|
||||
play_seq();
|
||||
determine_col();
|
||||
pop_kid_dead = 1;
|
||||
}
|
||||
|
||||
/* check_spiked (seg006:0658): под персонажем пика И вредна И кадр
|
||||
* бега(7..14)/старта прыжка(34..39) при harmful>=2, ЛИБО кадр приземления
|
||||
* (43/26) при harmful!=0 → smert'. */
|
||||
static void check_spiked(void)
|
||||
{
|
||||
uint8_t frame = Kid.frame, tp;
|
||||
int h;
|
||||
if (Kid.curr_col < 0 || Kid.curr_col > 9 || Kid.curr_row < 0 || Kid.curr_row > 2)
|
||||
return;
|
||||
if (get_tile_at_char() != TILE_SPIKE) return;
|
||||
tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||
h = is_spike_harmful(tp);
|
||||
if ((h >= 2 && ((frame >= FRAME_7_RUN && frame < 15) ||
|
||||
(frame >= FRAME_34_RUNJUMP_START && frame < 40))) ||
|
||||
((frame == FRAME_43_RUNJUMP_LAND || frame == FRAME_26_STANDJUMP_LAND) && h != 0))
|
||||
spiked(tp);
|
||||
}
|
||||
|
||||
/* check_spike_below (seg006:1199): для каждой колонки РЕАЛЬНОГО габарита
|
||||
* спрайта (char_x_left..char_x_right, порт set_char_collision) скан ВНИЗ от
|
||||
* curr_row через НЕ-floor тайлы; встретил пику → start_anim_spike. Габарит,
|
||||
* а не «curr_col + колонка спереди»: иначе пики выдвигаются на тайл раньше
|
||||
* (когда тело Кида ещё не над шахтой). Метрики кадра — kid_fp (габарит
|
||||
* последнего kid_draw; отставание в 1 кадр несущественно, ошибка в позднюю
|
||||
* сторону). */
|
||||
static void check_spike_below(void)
|
||||
{
|
||||
int wh = ((int)kid_fp_width() + 1) >> 1; /* char_width_half */
|
||||
int xl = kid_fp_obj_x() / 2 + 58; /* char_x_left */
|
||||
int xr;
|
||||
int8_t c0, c1, col;
|
||||
if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||||
xr = xl + wh; /* char_x_right */
|
||||
c0 = get_tile_div_mod_m7(xl);
|
||||
c1 = get_tile_div_mod_m7(xr);
|
||||
for (col = c0; col <= c1; col++) {
|
||||
int8_t row = Kid.curr_row;
|
||||
if (col < 0 || col > 9) continue;
|
||||
for (;;) {
|
||||
uint8_t t = get_tile(col, row);
|
||||
if (t == TILE_SPIKE) { start_anim_spike((uint8_t)(row * 10 + col)); break; }
|
||||
if (!tile_is_floor(t) && row <= 2) { row++; continue; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pop_phys_tick(void)
|
||||
{
|
||||
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
|
||||
if (grab_timer) grab_timer--;
|
||||
fall_accel();
|
||||
fall_speed();
|
||||
@@ -824,7 +1057,19 @@ void pop_phys_tick(void)
|
||||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||||
check_action();
|
||||
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
|
||||
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
|
||||
check_spiked(); /* seg006: напоролся на вредные пики → смерть */
|
||||
if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */
|
||||
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
|
||||
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
|
||||
check_leave(); /* seg002: уход вбок (право) → переход в соседнюю комнату */
|
||||
/* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой
|
||||
* leave заблокирован. Без него транзиент отскока о ворота шва (seq_47
|
||||
* роняет char_x за порог) сразу же выкидывает Kid в соседнюю комнату
|
||||
* (ping-pong / баг #4). С таймером первый бамп попадает в окно блокировки,
|
||||
* Kid оседает у грани (char_x стабильно > порога) — экран не перескакивает. */
|
||||
if (pop_leave_timer) {
|
||||
pop_leave_timer--;
|
||||
} else {
|
||||
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
|
||||
check_leave(); /* seg002: уход вбок → переход в соседнюю комнату */
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user