63a3b60524
Фича — плита-ПОТОЛОК (loose ряда 2 комнаты сверху, комн.5 (2,5) над комн.6): - check_press (seg006:1683) портирован целиком: кадры виса 87..99 и подъёма 135..140 «нажимают» тайл, за который Kid держится; кадр 79 — удар снизу (с проверкой action, как в оригинале); стояние — тайл под ним; - ряд −1 в pop_map.get_tile = ряд 2 комнаты сверху (порт find_room_of_tile), состояние тряски/падения — pop_ceil_modif[10]; do_knock(-1) трясёт потолок; - падение: mob переведён на координаты оригинала (y_loose_land/y_something), спавнится в ряду −1, по move_loose находит пол → loose_land: debris + do_knock; heal коридора — по прошлой позиции ДЛЯ КАЖДОЙ страницы; - урон: check_loose_fall_on_kid + fell_on_your_head (seq_52/seq_22, −1 HP) и «брызги» draw_hurt_splash (kid image 218); - колодец наверх: leave_room «вверх» проверяется ПЕРВЫМ (иначе y≈248 при подтягивании ловил check_leave_below и ронял Kid из уровня); - персистентность: общий tile-override (щебень внизу, пустота в комнате сверху) вместо debris-only. Окклюзия — приведена к оригиналу (найдено трассировкой собранного SDLPoP): - draw_tile_right/anim_right/draw_loose кладут спрайты в add_backtable НАПРЯМУЮ, минуя ptr_add_table → правая грань соседа («шахматка» столба 93), blueline и грани loose/пик соседа ВСЕГДА под персонажем. draw_other_overlay поверх Kid даёт только: 42 при левом соседе-поле, base, свои пики, bottom (overlay_mid_tile); - порядок объектов: тайл Kid (set_objtile_at_char) и порядок обхода (ряды 2→0, колонки 0→9), внутри тайла — сортировка по y (sort_curr_objs); то же правило для падающей плиты (pop_loose_mob_draw_over); - fore-слой рисуется ПОСЛЕ оверлеев (foretable после midtable), иначе тёмный скос floor_left_overlay ложился поверх колонны; - fore_tile больше не рисует bottom_id (в оригинале он в backtable) — минус 2..4 блита за кадр; - полоса потолка поверх Kid = draw_tile_bottom(1)+draw_loose(1)+draw_tile_fore (arg_0=1 добавляет в foretable), остальное — под ним; - следы оверлея восстанавливаются на ОБЕИХ страницах (pop_fore_heal). Прочее: - control(): при action bumped/in_freefall управление игнорируется целиком (seg005) — иначе прерванный присед терял dy(1)+dy(1) из medland и Kid навсегда оставался на 2px выше пола; - check_bumped: стена ищется по колонке переднего края (а не curr_col) + гард разворота — Kid больше не проходит сквозь опущенную решётку шва; - pop_floor_bake для смены floor→debris (bake_empty стирал верх стены ряда ниже — чёрный прямоугольник); - в pop_room_redraw_seam_left восстанавливается полоса потолка над решёткой. Проверено в MAME (bridge); эталон — собранный SDLPoP из applications/PoP/SDLPoP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1246 lines
64 KiB
C
1246 lines
64 KiB
C
/*
|
||
* pop_map.c — коллизия Kid с картой (Фаза K3.2: падение). Порт SDLPoP
|
||
* seg005/seg006. Пер-кадровый physics-диспетч check_action() по Char.action:
|
||
* grounded (stand/run/step/crouch) -> check_on_floor(): кадр с флагом
|
||
* FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall();
|
||
* стена -> in_wall();
|
||
* freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed),
|
||
* достиг y_land[curr_row+1] -> land()/inc_curr_row.
|
||
*
|
||
* Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX);
|
||
* col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 =
|
||
* div_mod(xpos-7). Карта: fg[row*10+col] & 0x1F; col<0/>9,row<0/>2 = стена.
|
||
*
|
||
* MVP: без spike/HP/grab/gate — падение + мягкое/среднее приземление.
|
||
* Бег В стену (check_bumped) и осторожный шаг у края (get_edge_distance) —
|
||
* следующим шагом.
|
||
*/
|
||
#include <stdint.h>
|
||
#include "pop_kid.h"
|
||
#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
|
||
#define TILE_GATE 4
|
||
#define TILE_DOORTOP_FLOOR 7 /* tapestry (пол сверху) */
|
||
#define TILE_DOORTOP 12 /* tapestry top */
|
||
#define TILE_MIRROR 13
|
||
#define TILE_CHOMPER 18
|
||
#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
|
||
#define ACT_MIDAIR 3
|
||
#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
|
||
#define SEQ_18_FALL_STANDJUMP 18
|
||
#define SEQ_19_FALL 19 /* stepfall2 (run frames 9/13) */
|
||
#define SEQ_20_MEDIUM_LAND 20
|
||
#define SEQ_21_FALL_RUNJUMP 21
|
||
#define SEQ_47_BUMP 47 /* удар в стену: dx(-4) отскок + 50-52 + stand */
|
||
/* K4 — зацеп/вис/подтягивание/спуск */
|
||
#define SEQ_8_GRAB_STRAIGHT 8 /* прыжок вверх и зацеп (прямо) */
|
||
#define SEQ_14_JUMP_INTO_CEILING 14 /* прыжок вверх: сверху стена/пол */
|
||
#define SEQ_16_JUMP_UP_AND_GRAB 16 /* прыжок вверх и зацеп (нет пола сзади)*/
|
||
#define SEQ_24_GRAB_FORWARD 24 /* прыжок вверх и зацеп (пол сзади) */
|
||
#define SEQ_28_JUMP_NOTHING_ABOVE 28 /* прыжок вверх: сверху пусто */
|
||
#define SEQ_10_CLIMB_UP 10 /* подтягивание из виса */
|
||
#define SEQ_11_RELEASE_LEDGE_LAND 11 /* сорваться и встать (конец спуска) */
|
||
#define SEQ_15_GRAB_LEDGE_MIDAIR 15 /* зацеп за уступ в падении */
|
||
#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
|
||
#define TILE_SIZEX 14
|
||
#define TILE_RIGHTX 13
|
||
#define SCREENSPACE_X 58
|
||
#define FALL_ACCEL 3
|
||
#define FALL_MAX 33
|
||
|
||
#define TILE_MIDX 7
|
||
#define FIRST_ONSCREEN_COLUMN 5
|
||
#define FRAME_THIN 0x20
|
||
|
||
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
|
||
static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */
|
||
static const int8_t dir_behind[2] = {1, -1}; /* [dir+1]: назад по направлению */
|
||
static const int16_t x_bump[20] = {
|
||
-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
|
||
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
|
||
};
|
||
/* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */
|
||
static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0}; /* wall_dist_from_left */
|
||
static const int8_t wall_dr[6] = {0, 0, 10, 13, 0, 0}; /* wall_dist_from_right */
|
||
static int8_t g_infrontx; /* колонка «перед персонажем» (set в get_tile_infrontof) */
|
||
|
||
/* ---- Карта комнаты ------------------------------------------------- */
|
||
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 pop_kid_hurt; /* 1 = в этом кадре убавилось HP → «брызги» */
|
||
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) */
|
||
static uint8_t grab_timer; /* после зацепа (12): блокирует climb-up */
|
||
/* Связи комнаты (0 = нет соседа) + кромки швов: g_lcol = col9 левого соседа
|
||
* (за col0), g_rcol = col0 правого соседа (за col9). Для коллизии на швах —
|
||
* иначе Kid, войдя в соседнюю комнату у самой кромки (curr_col=-1/10), стоял
|
||
* бы «в стене» уровня и in_wall выбрасывал бы его. Задаются pop_map_set_edges. */
|
||
static uint8_t g_link_l, g_link_r, g_link_u, g_link_d;
|
||
static uint8_t g_lcol[3], g_rcol[3];
|
||
|
||
/* ---- Плита-ПОТОЛОК: loose ряда 2 комнаты СВЕРХУ ------------------- *
|
||
* В оригинале это тот же loose, просто get_tile ушёл в комнату сверху
|
||
* (find_room_of_tile) и tilepos получился отрицательным (set_redraw* уводит
|
||
* такие в redraw_frames_above). У нас ряд 2 верхнего соседа живёт отдельной
|
||
* копией (above_fg в приложении, наш «ряд −1»), поэтому и состояние
|
||
* отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. —
|
||
* тряска, 1..11 — отсчёт до падения). */
|
||
uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */
|
||
static uint8_t ceil_rest[10]; /* «перерисовать ПОКОЙ» (2 = обе страницы) */
|
||
static uint8_t ceil_bake[10]; /* «запечь колодец» на второй странице */
|
||
static uint8_t land_bake, land_bake_pos; /* щебень от приземления — вторая страница */
|
||
static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */
|
||
uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */
|
||
uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */
|
||
|
||
void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; }
|
||
|
||
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)
|
||
{
|
||
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
|
||
if (lcol_fg) { g_lcol[0] = lcol_fg[0]; g_lcol[1] = lcol_fg[1]; g_lcol[2] = lcol_fg[2]; }
|
||
if (rcol_fg) { g_rcol[0] = rcol_fg[0]; g_rcol[1] = rcol_fg[1]; g_rcol[2] = rcol_fg[2]; }
|
||
}
|
||
|
||
static uint8_t get_tile(int8_t col, int8_t row)
|
||
{
|
||
/* Ряд −1 — ряд 2 комнаты СВЕРХУ (порт find_room_of_tile, seg006:005D:
|
||
* отрицательный row уходит по links.up). Нужен для зацепа за край
|
||
* провалившейся плиты-потолка (колодец наверх) и выбора seq прыжка. */
|
||
if (row == -1 && g_above && g_link_u && col >= 0 && col <= 9)
|
||
g_curr_tile = (uint8_t)(g_above[col] & 0x1F);
|
||
else if (row < 0 || row > 2)
|
||
g_curr_tile = TILE_WALL;
|
||
else if (col == -1 && g_link_l) /* левый шов: тайл col9 комнаты слева */
|
||
g_curr_tile = g_lcol[row] & 0x1F;
|
||
else if (col == 10 && g_link_r) /* правый шов: тайл col0 комнаты справа */
|
||
g_curr_tile = g_rcol[row] & 0x1F;
|
||
else if (col < 0 || col > 9)
|
||
g_curr_tile = TILE_WALL; /* край уровня / нет соседа = стена */
|
||
else
|
||
g_curr_tile = g_fg[row * 10 + col] & 0x1F;
|
||
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) {
|
||
case 0: case 9: case 12: case 20:
|
||
case 26: case 27: case 28: case 29:
|
||
return 0;
|
||
default:
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
static uint8_t get_tile_at_char(void)
|
||
{
|
||
return get_tile(Kid.curr_col, Kid.curr_row);
|
||
}
|
||
|
||
static uint8_t get_tile_infrontof_char(void)
|
||
{
|
||
g_infrontx = (int8_t)(dir_front[Kid.direction + 1] + Kid.curr_col);
|
||
return get_tile(g_infrontx, Kid.curr_row);
|
||
}
|
||
|
||
/* ---- Тайлы НАД/ЗА персонажем (для зацепа/спуска, seg006) ----------- */
|
||
static uint8_t get_tile_above_char(void)
|
||
{
|
||
return get_tile(Kid.curr_col, (int8_t)(Kid.curr_row - 1));
|
||
}
|
||
|
||
static uint8_t get_tile_behind_char(void)
|
||
{
|
||
return get_tile((int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col),
|
||
Kid.curr_row);
|
||
}
|
||
|
||
static uint8_t get_tile_front_above_char(void)
|
||
{
|
||
g_infrontx = (int8_t)(dir_front[Kid.direction + 1] + Kid.curr_col);
|
||
return get_tile(g_infrontx, (int8_t)(Kid.curr_row - 1));
|
||
}
|
||
|
||
static uint8_t get_tile_behind_above_char(void)
|
||
{
|
||
return get_tile((int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col),
|
||
(int8_t)(Kid.curr_row - 1));
|
||
}
|
||
|
||
static uint8_t wall_type(uint8_t t)
|
||
{
|
||
switch (t) {
|
||
case 4: case 7: case 12: return 1; /* стена справа (gate/doortop) */
|
||
case 13: return 2; /* стена слева (mirror) */
|
||
case 18: return 3; /* chomper */
|
||
case 20: return 4; /* стена с обеих сторон */
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
/* ---- Координаты ---------------------------------------------------- */
|
||
static int char_dx_forward(int8_t dx)
|
||
{
|
||
int d = dx;
|
||
if (Kid.direction < 0) d = -d;
|
||
return (int)Kid.x + d;
|
||
}
|
||
|
||
/* col = (xpos-58)/14 округл. вниз; g_obj_xl = позиция в тайле (0..13). */
|
||
static int8_t get_tile_div_mod(int xpos)
|
||
{
|
||
int x = xpos - SCREENSPACE_X;
|
||
int xh = x / TILE_SIZEX;
|
||
int xl = x % TILE_SIZEX;
|
||
if (xl < 0) { --xh; xl += TILE_SIZEX; } /* округление вниз */
|
||
g_obj_xl = (uint8_t)xl;
|
||
return (int8_t)xh;
|
||
}
|
||
|
||
static int8_t get_tile_div_mod_m7(int xpos) { return get_tile_div_mod(xpos - 7); }
|
||
|
||
/* «весовая» точка кадра (dx с вычетом weight-битов флагов), в направлении. */
|
||
static int dx_weight(void)
|
||
{
|
||
int8_t offset = (int8_t)(kid_cur_dx() - (kid_cur_flags() & FRAME_WEIGHT_X));
|
||
return char_dx_forward(offset);
|
||
}
|
||
|
||
static void determine_col(void)
|
||
{
|
||
Kid.curr_col = get_tile_div_mod_m7(dx_weight());
|
||
}
|
||
|
||
/* расстояние до края тайла (для in_wall). */
|
||
static int distance_to_edge(int xpos)
|
||
{
|
||
int d;
|
||
get_tile_div_mod_m7(xpos); /* -> g_obj_xl */
|
||
d = g_obj_xl;
|
||
if (Kid.direction == 0) d = TILE_RIGHTX - d; /* dir_0_right */
|
||
return d;
|
||
}
|
||
static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); }
|
||
|
||
/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
|
||
* Передний край персонажа = 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_x_forward_edge();
|
||
if (kid_cur_flags() & FRAME_THIN)
|
||
cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */
|
||
return cx;
|
||
}
|
||
|
||
static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol)
|
||
{
|
||
uint8_t type = wall_type(tiletype);
|
||
int coll_left, cx;
|
||
if (type == 0) return 127;
|
||
coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||
cx = char_front_coll();
|
||
if (Kid.direction < 0) /* лицом влево */
|
||
return cx - (coll_left + TILE_RIGHTX - wall_dr[type]);
|
||
else /* лицом вправо */
|
||
return wall_dl[type] + coll_left - cx;
|
||
}
|
||
|
||
/* in_wall (seg006:1292): персонаж оказался ВНУТРИ стены (тайл под ним =
|
||
* стена) — вытолкнуть. Аутентично SDLPoP: если он глубоко (delta>=8) ИЛИ
|
||
* перед ним тоже стена — двигаем НАЗАД к грани (6-delta), иначе ВПЕРЁД из
|
||
* стены на соседний тайл (delta+4). Вызывать ТОЛЬКО когда
|
||
* get_tile_at_char()==стена (проверяет вызывающий). */
|
||
static void in_wall(void)
|
||
{
|
||
int d = distance_to_edge_weight();
|
||
if (d >= 8 || get_tile_infrontof_char() == TILE_WALL)
|
||
d = 6 - d;
|
||
else
|
||
d += 4;
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)d);
|
||
determine_col();
|
||
get_tile_at_char();
|
||
}
|
||
|
||
/* ---- Падение ------------------------------------------------------- */
|
||
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);
|
||
if (Kid.fall_y < 22)
|
||
seq = SEQ_17_SOFT_LAND; /* 1 этаж — мягко */
|
||
else
|
||
seq = SEQ_20_MEDIUM_LAND; /* 2+ этажа — присесть (HP/смерть — позже) */
|
||
Kid.fall_x = Kid.fall_y = 0;
|
||
kid_set_seq(seq);
|
||
play_seq();
|
||
determine_col();
|
||
}
|
||
|
||
static void start_fall(void)
|
||
{
|
||
uint8_t frame = Kid.frame, seq, tile;
|
||
inc_curr_row();
|
||
/* 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;
|
||
else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP;
|
||
else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */
|
||
kid_set_seq(seq);
|
||
play_seq();
|
||
determine_col();
|
||
/* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2).
|
||
* Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose)
|
||
* наш get_tile отдаёт WALL как СЕНТИНЕЛ кромки уровня, но в оригинале там
|
||
* КОМНАТА СНИЗУ (у дыры — пол/пусто, не стена). Без гарда in_wall() ложно
|
||
* срабатывал и откатывал X назад (6−d) → дайв падения смещался на тайл
|
||
* влево (Кид «клевал» рано над колонной вместо дыры). */
|
||
if (Kid.curr_row <= 2) {
|
||
if (get_tile_at_char() == TILE_WALL) {
|
||
in_wall();
|
||
return;
|
||
}
|
||
/* Хвост seg006:1099: тайл ПЕРЕД персонажем — стена → отодвинуть на 1
|
||
* назад (иначе передний край зайдёт в грань). (seq_104 для frame 44 с
|
||
* малым зазором не портирован — в этом тесте не встречается.) */
|
||
tile = get_tile_infrontof_char();
|
||
if (tile == TILE_WALL) {
|
||
if (frame != 44 || distance_to_edge_weight() >= 6)
|
||
Kid.x = (uint8_t)char_dx_forward(-1);
|
||
determine_col();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- Зацеп (seg006 can_grab/check_grab) --------------------------- *
|
||
* can_grab: можно ли зацепиться за g_curr_tile (curr_tile2) СКВОЗЬ through-
|
||
* тайл? Kid-only (без loose/opener-модификаторов). */
|
||
static uint8_t can_grab(uint8_t through)
|
||
{
|
||
if (through == TILE_WALL) return 0; /* сквозь стену нельзя */
|
||
if (through == TILE_DOORTOP && Kid.direction >= 0) return 0; /* верх двери, лицом вправо */
|
||
if (tile_is_floor(through)) return 0; /* сквозь пол нельзя */
|
||
if (g_curr_tile == TILE_DOORTOP_FLOOR && Kid.direction < 0) return 0;
|
||
if (!tile_is_floor(g_curr_tile)) return 0; /* не за что цепляться */
|
||
return 1;
|
||
}
|
||
|
||
static uint8_t can_grab_front_above(void)
|
||
{
|
||
uint8_t through = get_tile_above_char();
|
||
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
|
||
return can_grab(through);
|
||
}
|
||
|
||
/* check_grab (seg006:0A28): в падении при зажатом Shift — зацепиться за
|
||
* уступ спереди-сверху (seq_15), если скорость падения ещё мала и высота
|
||
* подходящая. Выравнивает передний край по кромке, гасит fall_y, взводит
|
||
* grab_timer (блок climb-up на несколько кадров). */
|
||
static void check_grab(void)
|
||
{
|
||
uint8_t old_x;
|
||
if (!pop_ctrl_shift_held()) return; /* Shift не зажат */
|
||
if ((uint8_t)Kid.fall_y >= 32) return; /* падает слишком быстро */
|
||
if ((uint16_t)y_land[Kid.curr_row + 1] > (uint16_t)(Kid.y + 25)) return;
|
||
old_x = Kid.x;
|
||
Kid.x = (uint8_t)char_dx_forward(-8);
|
||
determine_col();
|
||
if (!can_grab_front_above()) {
|
||
Kid.x = old_x; /* не за что — назад */
|
||
determine_col();
|
||
} else {
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)distance_to_edge_weight());
|
||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
||
Kid.fall_y = 0;
|
||
kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR);
|
||
play_seq();
|
||
determine_col();
|
||
grab_timer = 12; /* (TODO: sound grab) */
|
||
}
|
||
}
|
||
|
||
static void do_fall(void)
|
||
{
|
||
uint8_t nrow = (uint8_t)(Kid.curr_row + 1);
|
||
if (nrow > 4) nrow = 4; /* защита y_land[] от выхода */
|
||
if ((uint16_t)y_land[nrow] > (uint16_t)Kid.y) {
|
||
check_grab(); /* ещё летит — попытка зацепа */
|
||
} else {
|
||
if (get_tile_at_char() == TILE_WALL)
|
||
in_wall();
|
||
if (tile_is_floor(g_curr_tile))
|
||
land();
|
||
else if (Kid.curr_row < 2)
|
||
inc_curr_row(); /* следующий ряд ВНУТРИ комнаты */
|
||
/* curr_row==2, нет пола: продолжает падать (fall_speed двигает y);
|
||
* пересечение нижней границы комнаты ловит check_leave_below (y>=211,
|
||
* порт leave_room) — общий путь для провала И спуска-зацепа. */
|
||
}
|
||
}
|
||
|
||
static void fall_accel(void)
|
||
{
|
||
if (Kid.action == ACT_FREEFALL) {
|
||
int fy = (uint8_t)Kid.fall_y + FALL_ACCEL;
|
||
if (fy > FALL_MAX) fy = FALL_MAX;
|
||
Kid.fall_y = (int8_t)fy;
|
||
}
|
||
}
|
||
|
||
static void fall_speed(void)
|
||
{
|
||
Kid.y = (uint8_t)(Kid.y + (uint8_t)Kid.fall_y);
|
||
if (Kid.action == ACT_FREEFALL) {
|
||
Kid.x = (uint8_t)char_dx_forward(Kid.fall_x);
|
||
determine_col();
|
||
}
|
||
}
|
||
|
||
/* ---- get_edge_distance (seg004:067C) — ТОЧНО ---------------------- *
|
||
* Расстояние переднего края до стены/края впереди + тип g_edge_type: WALL
|
||
* (стена в пределах тайла), FLOOR (пол — далеко, 11), CLOSER (край уступа
|
||
* над ямой — distance_to_edge_weight). Стена сперва под персонажем, потом
|
||
* перед ним; dist<=TILE_RIGHTX -> WALL, иначе FLOOR. */
|
||
static uint8_t g_edge_type;
|
||
|
||
static int edge_classify(int d)
|
||
{
|
||
if (d <= TILE_RIGHTX) g_edge_type = EDGE_WALL;
|
||
else { g_edge_type = EDGE_FLOOR; d = 11; }
|
||
return d;
|
||
}
|
||
|
||
int pop_edge_distance(void)
|
||
{
|
||
uint8_t t; int d;
|
||
determine_col();
|
||
t = get_tile_at_char();
|
||
if (wall_type(t)) {
|
||
d = dist_from_wall_forward(t, Kid.curr_col);
|
||
if (d >= 0) return edge_classify(d);
|
||
}
|
||
t = get_tile_infrontof_char();
|
||
if (wall_type(t)) {
|
||
d = dist_from_wall_forward(t, g_infrontx);
|
||
if (d >= 0) return edge_classify(d);
|
||
}
|
||
if (tile_is_floor(t)) { g_edge_type = EDGE_FLOOR; return 11; }
|
||
g_edge_type = EDGE_CLOSER; /* яма впереди — до кромки уступа */
|
||
return distance_to_edge_weight();
|
||
}
|
||
|
||
uint8_t pop_edge_type(void) { return g_edge_type; }
|
||
|
||
/* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- *
|
||
* pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в
|
||
* пустоту / seq_14 в потолок) ЛИБО прыжок-и-зацеп за уступ выше (seq_8/
|
||
* 24 — с полом сзади, seq_16 — без), и выравнивает Kid.x. control_up
|
||
* гасит caller (pop_ctrl). Порт: check_jump_up/grab_up_with_floor_behind/
|
||
* jump_up_or_grab/grab_up_no_floor_behind/jump_up (без super-high-jump). */
|
||
static uint8_t grab_up_with_floor_behind(void)
|
||
{
|
||
int distance = distance_to_edge_weight();
|
||
int edge_distance = pop_edge_distance(); /* побочно ставит g_edge_type */
|
||
if (distance < 4 && edge_distance < 4 && g_edge_type != EDGE_WALL) {
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)distance);
|
||
return SEQ_8_GRAB_STRAIGHT;
|
||
}
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance - 4));
|
||
return SEQ_24_GRAB_FORWARD;
|
||
}
|
||
|
||
static uint8_t jump_up_plain(void) /* seg005 jump_up (без super) */
|
||
{
|
||
int distance = pop_edge_distance(); /* ставит g_edge_type */
|
||
int8_t col;
|
||
uint8_t t;
|
||
if (distance < 4 && g_edge_type == EDGE_WALL)
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance - 3));
|
||
col = get_tile_div_mod(dx_weight() - 6);
|
||
t = get_tile(col, (int8_t)(Kid.curr_row - 1));
|
||
if (t != TILE_WALL && !tile_is_floor(t))
|
||
return SEQ_28_JUMP_NOTHING_ABOVE;
|
||
return SEQ_14_JUMP_INTO_CEILING;
|
||
}
|
||
|
||
static uint8_t grab_up_no_floor_behind(void)
|
||
{
|
||
get_tile_above_char();
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance_to_edge_weight() - 10));
|
||
return SEQ_16_JUMP_UP_AND_GRAB;
|
||
}
|
||
|
||
static uint8_t jump_up_or_grab(void)
|
||
{
|
||
int distance = distance_to_edge_weight();
|
||
if (distance < 6)
|
||
return jump_up_plain();
|
||
if (!tile_is_floor(get_tile_behind_char()))
|
||
return grab_up_no_floor_behind();
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance - TILE_SIZEX));
|
||
determine_col(); /* load_fram_det_col */
|
||
return grab_up_with_floor_behind();
|
||
}
|
||
|
||
uint8_t pop_jump_up_seq(void)
|
||
{
|
||
uint8_t through = get_tile_above_char();
|
||
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
|
||
if (can_grab(through))
|
||
return grab_up_with_floor_behind();
|
||
through = get_tile_behind_above_char();
|
||
get_tile_above_char(); /* g_curr_tile = сверху */
|
||
if (can_grab(through))
|
||
return jump_up_or_grab();
|
||
return jump_up_plain();
|
||
}
|
||
|
||
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
|
||
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
|
||
int pop_wall_ahead(void)
|
||
{
|
||
int d = pop_edge_distance();
|
||
return (g_edge_type == EDGE_WALL && d <= 2);
|
||
}
|
||
|
||
/* ---- Вис (control_hanging из seg005) — тайловые решения ------------ *
|
||
* Управление держится в pop_ctrl (control_*), сюда вынесены только
|
||
* тайловые предикаты + выбор seq (+ выравнивание Kid.x). */
|
||
|
||
/* grab_timer истёк — можно подтягиваться? */
|
||
uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; }
|
||
|
||
/* can_climb_up: тайл над персонажем — обычный подъём (seq_10) либо подъём
|
||
* к закрытой калитке/зеркалу/чомперу (seq_73). */
|
||
uint8_t pop_hang_climb_up_seq(void)
|
||
{
|
||
uint8_t t = get_tile_above_char();
|
||
if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) ||
|
||
(t == TILE_GATE && Kid.direction != 0))
|
||
return SEQ_73_CLIMB_UP_TO_GATE;
|
||
return SEQ_10_CLIMB_UP;
|
||
}
|
||
|
||
/* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 =
|
||
* сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */
|
||
int8_t pop_hang_wall(void)
|
||
{
|
||
uint8_t t = get_tile_at_char();
|
||
if (Kid.action != ACT_HANGSTRAIGHT &&
|
||
(t == TILE_WALL ||
|
||
(Kid.direction < 0 && (t == TILE_DOORTOP_FLOOR || t == TILE_DOORTOP))))
|
||
return 1;
|
||
if (!tile_is_floor(get_tile_above_char()))
|
||
return -1;
|
||
return 0;
|
||
}
|
||
|
||
/* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под
|
||
* персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7)
|
||
* и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */
|
||
uint8_t pop_hang_fall_seq(void)
|
||
{
|
||
uint8_t behind = get_tile_behind_char();
|
||
uint8_t at;
|
||
if (!tile_is_floor(behind) && !tile_is_floor(get_tile_at_char()))
|
||
return SEQ_23_RELEASE_LEDGE_FALL;
|
||
at = get_tile_at_char();
|
||
if (at == TILE_WALL ||
|
||
(Kid.direction < 0 && (at == TILE_DOORTOP_FLOOR || at == TILE_DOORTOP)))
|
||
Kid.x = (uint8_t)char_dx_forward(-7);
|
||
return SEQ_11_RELEASE_LEDGE_LAND;
|
||
}
|
||
|
||
/* down_pressed (seg005:04C7) tile-часть: 1 = обработано (НЕ приседать —
|
||
* либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 =
|
||
* приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки
|
||
* пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */
|
||
uint8_t pop_down_action(void)
|
||
{
|
||
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) {
|
||
Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */
|
||
determine_col();
|
||
return 1;
|
||
}
|
||
if (!tile_is_floor(get_tile_behind_char()) && distance_to_edge_weight() >= 8) {
|
||
uint8_t through = get_tile_behind_char();
|
||
get_tile_at_char(); /* g_curr_tile = под персонажем */
|
||
if (can_grab(through) &&
|
||
(Kid.direction >= 0 || get_tile_at_char() != TILE_GATE)) {
|
||
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance_to_edge_weight() - 9));
|
||
kid_set_seq(SEQ_68_CLIMB_DOWN);
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ---- Диспетчер ----------------------------------------------------- */
|
||
static void check_on_floor(void)
|
||
{
|
||
if (kid_cur_flags() & FRAME_NEEDS_FLOOR) {
|
||
if (get_tile_at_char() == TILE_WALL)
|
||
in_wall(); /* забежал в стену -> выровнять */
|
||
if (!tile_is_floor(g_curr_tile))
|
||
start_fall();
|
||
}
|
||
}
|
||
|
||
static void check_action(void)
|
||
{
|
||
uint8_t action = Kid.action, frame = Kid.frame;
|
||
if (action == ACT_BUMPED || action == ACT_HANGSTRAIGHT) {
|
||
if (frame == 109 || (frame >= 110 && frame <= 119))
|
||
check_on_floor();
|
||
} else if (action == ACT_FREEFALL) {
|
||
do_fall();
|
||
} else if (action == ACT_MIDAIR) {
|
||
/* frames 102..105: check_grab — K4 */
|
||
} else if (action != ACT_HANGCLIMB) {
|
||
check_on_floor();
|
||
}
|
||
}
|
||
|
||
/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять
|
||
* к грани И проиграть отскок seq_47_bump (dx−4 назад + кадры 50-52 + stand),
|
||
* как в оригинале (bumped_floor). Это даёт «шаг назад со звуком» при беге/
|
||
* шаге в стену вместо падения. Уже в отскоке (action bumped) — только
|
||
* выравниваем, без рестарта. В падении/висе не трогаем. */
|
||
static void check_bumped(void)
|
||
{
|
||
uint8_t t, wt; int8_t tcol; int d;
|
||
/* Не бампим в падении/висе/подтягивании (seg004 check_bumped guard'ы:
|
||
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
|
||
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
|
||
* в падении держит do_fall/in_wall. */
|
||
if (Kid.action == ACT_FREEFALL || Kid.action == ACT_HANGCLIMB ||
|
||
Kid.action == ACT_HANGSTRAIGHT || Kid.action == ACT_MIDAIR) return;
|
||
if (Kid.frame >= 135 && Kid.frame < 149) return; /* кадры подъёма/спуска */
|
||
/* Кадры виса 87..99: в спуске (seq_68) кадр frame_91 идёт ДО act(hang_climb),
|
||
* т.е. action ещё midair — без этого guard'а bump у стены рвёт цепочку
|
||
* hang->hang_fall->seq_11 (правильное приземление). */
|
||
if (Kid.frame >= 87 && Kid.frame < 100) return;
|
||
/* Вне рядов комнаты (падение мимо пола): get_tile даёт WALL, а
|
||
* dist_from_wall_forward/x_bump[] — мусор с большим отриц. сдвигом →
|
||
* underflow X и уход в стену. fell_out ловит do_fall. */
|
||
if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
|
||
/* seg004 check_collisions: на кадрах разворота коллизии НЕ считаются
|
||
* (bump_col_* сбрасываются и функция выходит) — bump'а в развороте нет. */
|
||
if (Kid.action == ACT_TURN) return;
|
||
/* Стена ищется по колонке, в которую упирается ПЕРЕДНИЙ КРАЙ персонажа
|
||
* (порт seg004: bump_col_left/right_of_wall — колонка, где край пересёк
|
||
* грань; НЕ curr_col!). Через curr_col было ДВА расхождения: (1) весовая
|
||
* точка кадра заезжает за грань раньше края, curr_col становился −1 на
|
||
* шве, ворота оказывались «под персонажем» и гард односторонней стены их
|
||
* снимал — Kid уходил в соседнюю комнату СКВОЗЬ ОПУЩЕННУЮ РЕШЁТКУ;
|
||
* (2) тот же гард был нужен только из-за отсутствия проверки разворота
|
||
* (см. выше). Колонка переднего края даёт и то, и другое даром: стоя
|
||
* СПРАВА от ворот лицом вправо, край в соседней колонке — ворота не
|
||
* бампят; заехав краем за их грань лицом влево — бампят. */
|
||
tcol = get_tile_div_mod_m7(char_front_coll());
|
||
t = get_tile(tcol, Kid.curr_row);
|
||
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); /* край -> грань */
|
||
determine_col();
|
||
if (Kid.action == ACT_BUMPED) return; /* уже в отскоке — не рестартить */
|
||
Kid.fall_y = 0;
|
||
kid_set_seq(SEQ_47_BUMP); /* отскок (TODO: bumped_sound) */
|
||
play_seq();
|
||
determine_col();
|
||
}
|
||
|
||
/* ---- Loose floors (seg007) ---------------------------------------- *
|
||
* Состояние на тайл: 0 = покой; 0x80..0x83 = тряска (do_knock); 1..11 =
|
||
* отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/
|
||
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
|
||
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
|
||
static uint8_t loose_bake[30]; /* счётчик «запечь пусто» (2 = обе страницы) */
|
||
static uint8_t loose_rest[30]; /* счётчик «перерисовать ПОКОЙ» после тряски (2 = обе стр.) */
|
||
|
||
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
|
||
|
||
/* make_loose_fall (seg007:0EF6): взвести отсчёт падения, если ещё не идёт. */
|
||
static void make_loose_fall(int pos, uint8_t modifier)
|
||
{
|
||
if (pos < 0 || pos >= 30) return;
|
||
if ((g_fg[pos] & 0x1F) != TILE_LOOSE) return;
|
||
if ((g_fg[pos] & 0x20)) return; /* «solid» loose — не от шага */
|
||
if ((int8_t)pop_loose_modif[pos] <= 0) /* покой/тряска, не отсчёт */
|
||
pop_loose_modif[pos] = modifier;
|
||
}
|
||
|
||
/* check_press (seg006:0EC8, упрощ.): Kid стоит на loose → make_loose_fall;
|
||
* frame 79 (прыжок вверх) с loose СВЕРХУ → пробить сверху. */
|
||
static void check_press(void)
|
||
{
|
||
uint8_t frame = Kid.frame, action = Kid.action;
|
||
uint8_t above_press = 0; /* «нажимается» тайл СВЕРХУ */
|
||
/* Кадры виса (87..99) и начала подъёма (135..140): нажимается тайл, за
|
||
* КОТОРЫЙ ПЕРСОНАЖ ДЕРЖИТСЯ, т.е. верхний (seg006:1688). Отсюда падение
|
||
* плиты, за край которой Kid зацепился, — а не только той, по которой он
|
||
* ударил снизу прыжком (кадр 79). */
|
||
if ((frame >= 87 && frame < 100) || (frame >= 135 && frame < 141)) {
|
||
above_press = 1;
|
||
} else if (action == ACT_BUMPED || action == ACT_TURN || action < ACT_HANGCLIMB) {
|
||
if (frame == 79) above_press = 1; /* прыжок вверх — удар снизу */
|
||
} else {
|
||
return; /* seg006:1704 — прочее не жмёт */
|
||
}
|
||
if (above_press) {
|
||
if (Kid.curr_row == 0) {
|
||
/* Ряд 0: «сверху» — ряд 2 комнаты СВЕРХУ (в оригинале get_tile с
|
||
* row=−1 уходит по links.up, find_room_of_tile seg006:005D).
|
||
* Плита-ПОТОЛОК; кнопок на потолке в этом тесте нет. */
|
||
int8_t c = Kid.curr_col;
|
||
if (g_above && g_link_u && c >= 0 && c <= 9 &&
|
||
(g_above[c] & 0x1F) == TILE_LOOSE && !(g_above[c] & 0x20) &&
|
||
(int8_t)pop_ceil_modif[c] <= 0)
|
||
pop_ceil_modif[c] = 1; /* make_loose_fall(1) */
|
||
} else if (get_tile_above_char() == TILE_LOOSE) {
|
||
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
||
}
|
||
return;
|
||
}
|
||
/* Стояние: кадр обязан требовать пол; нажимается тайл ПОД персонажем.
|
||
* ACT_TURN обязателен (seg006:1691): при развороте на кнопке иначе
|
||
* trigger_button не зовётся кадр-два → таймер связи падает <2 → кнопка
|
||
* ложно отжимается и жмётся снова. */
|
||
if (!(kid_cur_flags() & FRAME_NEEDS_FLOOR)) return;
|
||
{
|
||
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): сотрясение от
|
||
* приземления/удара трясёт ВСЕ loose-полы в ряду tile_row (не роняя их —
|
||
* modif 0x80..0x83 = чистая тряска, сбрасывается в pop_loose_tick). Трясём
|
||
* только из ПОКОЯ (modif==0): если тайл уже дрожит/падает — не перебиваем. */
|
||
static void do_knock(int tile_row)
|
||
{
|
||
int col, pos;
|
||
if (tile_row == -1) { /* ряд 2 комнаты сверху — плиты-потолки */
|
||
if (!g_above || !g_link_u) return;
|
||
for (col = 0; col < 10; col++)
|
||
if ((g_above[col] & 0x1F) == TILE_LOOSE && pop_ceil_modif[col] == 0)
|
||
pop_ceil_modif[col] = 0x80;
|
||
return;
|
||
}
|
||
if (tile_row < 0 || tile_row > 2) return;
|
||
for (col = 0; col < 10; col++) {
|
||
pos = tile_row * 10 + col;
|
||
if ((g_fg[pos] & 0x1F) != TILE_LOOSE) continue;
|
||
if (pop_loose_modif[pos] == 0)
|
||
pop_loose_modif[pos] = 0x80;
|
||
}
|
||
}
|
||
|
||
/* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl-
|
||
* командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) →
|
||
* ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */
|
||
void pop_check_knock(void)
|
||
{
|
||
if (knock) {
|
||
do_knock(Kid.curr_row - (knock > 0));
|
||
knock = 0;
|
||
}
|
||
}
|
||
|
||
/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest
|
||
* индексируются позицией тайла и не должны утечь из старой комнаты в новую.
|
||
* Плюс останавливает падающий кусок (pop_bg). */
|
||
void pop_loose_reset(void)
|
||
{
|
||
int i;
|
||
for (i = 0; i < 30; i++) {
|
||
pop_loose_modif[i] = 0;
|
||
loose_bake[i] = 0;
|
||
loose_rest[i] = 0;
|
||
}
|
||
for (i = 0; i < 10; i++) {
|
||
pop_ceil_modif[i] = 0;
|
||
ceil_rest[i] = 0;
|
||
ceil_bake[i] = 0;
|
||
}
|
||
pop_ceil_fell = 0;
|
||
pop_debris_at = 0;
|
||
pop_loose_landed = 0;
|
||
land_bake = 0;
|
||
pop_loose_mob_reset();
|
||
}
|
||
|
||
/* fell_on_your_head (seg007:1205): плита прилетела Киду по голове. В беге
|
||
* (кадры 5..14) не бьёт (кроме уровня 13 — его тут нет); в висе/подъёме тоже
|
||
* (action >= hang_climb, кроме turn). Иначе −1 HP: смерть → seq_22 (crushed),
|
||
* живой → seq_52 (плита придавила: Kid приседает), с отступом на 2 назад,
|
||
* если сзади пусто. Кадр 109 (присед) последовательность не меняет. */
|
||
#define SEQ_22_CRUSHED 22
|
||
#define SEQ_52_LOOSE_FELL_ON_KID 52
|
||
#define FRAME_109_CROUCH 109
|
||
|
||
static void fell_on_your_head(void)
|
||
{
|
||
uint8_t frame = Kid.frame, action = Kid.action;
|
||
if ((frame < 5 || frame >= 15) &&
|
||
(action < ACT_HANGCLIMB || action == ACT_TURN)) {
|
||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
||
pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */
|
||
if (take_hp(1)) {
|
||
kid_set_seq(SEQ_22_CRUSHED);
|
||
play_seq();
|
||
determine_col();
|
||
pop_kid_dead = 1;
|
||
} else if (frame != FRAME_109_CROUCH) {
|
||
if (get_tile_behind_char() == TILE_EMPTY)
|
||
Kid.x = (uint8_t)char_dx_forward(-2);
|
||
kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID);
|
||
play_seq();
|
||
determine_col();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* check_loose_fall_on_kid (seg007:1192): падающий кусок в той же колонке и
|
||
* его низ уже в полосе 30px над ногами Kid → удар. */
|
||
static void check_loose_fall_on_kid(void)
|
||
{
|
||
int mcol, my;
|
||
if (pop_kid_dead) return;
|
||
if (!pop_loose_mob_pos(&mcol, &my)) return;
|
||
if (Kid.curr_col != (int8_t)mcol) return;
|
||
if (my < (int)Kid.y && (int)Kid.y - 30 < my)
|
||
fell_on_your_head();
|
||
}
|
||
|
||
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
|
||
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
|
||
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(row, col);
|
||
loose_bake[pos]--;
|
||
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
||
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
||
* страницы дабл-буфера — иначе на одной остаётся застывший
|
||
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
||
pop_loose_shake_draw(row, col);
|
||
loose_rest[pos]--;
|
||
} 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; /* покой на ОБЕИХ страницах */
|
||
} else {
|
||
pop_loose_shake_draw(row, col); /* дрожащий кадр */
|
||
}
|
||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
||
* кусок (mob), т.е. рядом ниже. Loose в нижнем ряду (2,6)
|
||
* — кусок улетает из комнаты вниз, на месте остаётся пусто
|
||
* (стена-вниз от тайла снизу + чёрный фон). */
|
||
g_fg[pos] = TILE_EMPTY;
|
||
pop_loose_modif[pos] = 0;
|
||
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
|
||
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */
|
||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
|
||
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||
pop_loose_shake_draw(row, col);
|
||
}
|
||
}
|
||
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
||
}
|
||
/* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры
|
||
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
||
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
||
for (col = 0; col < 10; col++) {
|
||
if (ceil_rest[col]) {
|
||
pop_ceil_shake_draw(col); /* modif уже 0 → кадр покоя */
|
||
ceil_rest[col]--;
|
||
} else if (pop_ceil_modif[col] != 0) {
|
||
uint8_t m = ++pop_ceil_modif[col];
|
||
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
||
if (m >= 0x84) { pop_ceil_modif[col] = 0; ceil_rest[col] = 2; }
|
||
else pop_ceil_shake_draw(col);
|
||
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
||
pop_ceil_modif[col] = 0;
|
||
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
||
pop_ceil_bake_empty(col); /* колодец наверх на ТЕКУЩЕЙ странице */
|
||
ceil_bake[col] = 1; /* + вторая страница следующим кадром */
|
||
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
||
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
|
||
} else {
|
||
pop_ceil_shake_draw(col); /* отсчёт 1..10 — дрожащий кадр */
|
||
}
|
||
} else if (ceil_bake[col]) {
|
||
pop_ceil_bake_empty(col);
|
||
ceil_bake[col]--;
|
||
}
|
||
}
|
||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||
check_loose_fall_on_kid(); /* seg007:1192 — плита падает Киду на голову */
|
||
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
|
||
uint8_t pos = (uint8_t)(pop_loose_landed - 1);
|
||
uint8_t r = 0, c = pos;
|
||
pop_loose_landed = 0;
|
||
while (c >= 10) { c -= 10; r++; }
|
||
g_fg[pos] = TILE_DEBRIS;
|
||
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не bake_empty: та стирает и верх стены
|
||
* ряда ниже — оставался чёрный прямоугольник под тайлом). */
|
||
pop_floor_bake(r, c);
|
||
land_bake_pos = pos; land_bake = 1; /* вторая страница дабл-буфера */
|
||
do_knock(r); /* удар плиты трясёт loose этого ряда */
|
||
pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */
|
||
} else if (land_bake) {
|
||
uint8_t r = 0, c = land_bake_pos;
|
||
while (c >= 10) { c -= 10; r++; }
|
||
pop_floor_bake(r, c);
|
||
land_bake = 0;
|
||
}
|
||
}
|
||
|
||
/* y_to_row_mod4 (seg006): ряд по y с оборотом mod4 (TILE_SIZEY=63). */
|
||
static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
|
||
|
||
/* check_leave_below (порт leave_room, seg002: leave_dir=3 при chary>=211):
|
||
* Kid пересёк нижнюю границу комнаты (провал в дыру ИЛИ спуск-зацеп/climbdown
|
||
* — единый триггер по y). Репроекция в кадр комнаты снизу (goto_other_room
|
||
* down: y-=189, curr_row=y_to_row_mod4) + сигнал pop_fell_out — приложение
|
||
* решит: переход (links.down) или выпал из уровня (нет комнаты снизу). */
|
||
static void check_leave_below(void)
|
||
{
|
||
/* leave_room (seg002:0504) проверяет ВВЕРХ ПЕРВЫМ: персонаж не в отскоке/
|
||
* полёте и его y (как signed) в полосе −16..10 у верха комнаты — он вылез
|
||
* в комнату сверху (через колодец от упавшей плиты). Порядок важен: при
|
||
* подтягивании y ≈ 248, и проверка «ниже комнаты» (y>=211) иначе
|
||
* перехватывает подъём и роняет Kid из уровня.
|
||
* goto_other_room(up): y += 189, curr_row = y_to_row_mod4. */
|
||
if (g_link_u && Kid.action != ACT_BUMPED && Kid.action != ACT_FREEFALL &&
|
||
Kid.action != ACT_MIDAIR &&
|
||
(int8_t)Kid.y < 10 && (int8_t)Kid.y > -16) {
|
||
Kid.y = (uint8_t)(Kid.y + 189);
|
||
Kid.curr_row = y_to_row((int16_t)Kid.y);
|
||
pop_leave_dir = 3;
|
||
return;
|
||
}
|
||
if (Kid.y >= 211) {
|
||
Kid.y = (uint8_t)(Kid.y - 189);
|
||
Kid.curr_row = y_to_row((int16_t)Kid.y);
|
||
pop_fell_out = 1;
|
||
}
|
||
}
|
||
|
||
/* check_leave вбок (порт leave_room, seg002): передний край персонажа
|
||
* (char_dx_forward(cur_frame.dx) = char_x_left при взгляде влево / char_x_right
|
||
* вправо) сравнивается с кромками комнаты. Взгляд вправо: >=201 → right,
|
||
* <=57 → left; взгляд влево: <=54 → left, >=198 → right. goto_other_room:
|
||
* x∓140 (ширина комнаты), пересчёт колонки; сигнал pop_leave_dir (2=right,
|
||
* 1=left). Уход только если сосед есть (иначе кромка = стена, check_bumped
|
||
* удержит у шва). */
|
||
static void check_leave(void)
|
||
{
|
||
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 */
|
||
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;
|
||
}
|
||
} else { /* взгляд влево: fx = char_x_left */
|
||
if (g_link_l && fx <= 54) {
|
||
Kid.x = (uint8_t)(Kid.x + 140); determine_col(); pop_leave_dir = 1;
|
||
} else if (g_link_r && fx >= 198) {
|
||
Kid.x = (uint8_t)(Kid.x - 140); determine_col(); pop_leave_dir = 2;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- Пики (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();
|
||
determine_col();
|
||
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 в ряду */
|
||
/* 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: уход вбок → переход в соседнюю комнату */
|
||
}
|
||
}
|