ea57a03543
Порт SDLPoP seg004/005/006 «hang state»: - pop_map: check_grab (зацеп за уступ в падении по Shift → seq_15 → вис), can_grab/can_grab_front_above + tile-запросы над/за персонажем; pop_jump_up_seq (check_jump_up: ↑ в стойке = чистый прыжок seq_28/14 ЛИБО прыжок-с-зацепом seq_8/24/16 за уступ выше → запрыгнуть на этаж); pop_hang_* (climb_up seq_10/73, hang_fall seq_23/11, hang-у-стены); pop_down_action (спуск seq_68 у края лицом от края / отступ / присед). - pop_ctrl: control_hanging/can_climb_up/hang_fall, control_jumpup, jump_up через pop_jump_up_seq, down_pressed через pop_down_action; pop_ctrl_shift_held() для check_grab. Ключевой фикс check_bumped (seg004 guard'ы): не бампить при action hang_climb/hang_straight, на кадрах подъёма/спуска 135..148 И на кадрах виса 87..99. Без последнего спуск (seq_68) на кадре frame_91 (action ещё midair, act(hang_climb) идёт следующим опкодом) ловил отскок у стены и рвал цепочку hang→hang_fall→seq_11, приземляя не туда. Проверено в MAME (HDD-тест, покадровая трасса через мост): прыжок-с- зацепом [row2 col4]→вис→подтягивание→[row1 col3]; спуск [row1 col3]→ [row2 col4] с корректной позицией у стены (x=122, совпадает с SDLPoP). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
612 lines
26 KiB
C
612 lines
26 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 */
|
||
|
||
/* ---- Тайлы / действия / 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 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 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 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 упал за низ комнаты (сброс приложением) */
|
||
static const uint8_t *g_fg; /* fg[30], row*10+col */
|
||
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 */
|
||
|
||
void pop_map_set(const uint8_t *fg) { g_fg = fg; }
|
||
|
||
static uint8_t get_tile(int8_t col, int8_t row)
|
||
{
|
||
if (col < 0 || col > 9 || row < 0 || row > 2)
|
||
g_curr_tile = TILE_WALL; /* край уровня = стена */
|
||
else
|
||
g_curr_tile = g_fg[row * 10 + col] & 0x1F;
|
||
return g_curr_tile;
|
||
}
|
||
|
||
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_*_coll = char_dx_forward(cur_frame.dx)
|
||
* (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для
|
||
* THIN-кадров. dist_from_wall_forward — расстояние переднего края до грани
|
||
* стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */
|
||
static int char_front_coll(void)
|
||
{
|
||
int cx = char_dx_forward(kid_cur_dx());
|
||
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;
|
||
}
|
||
|
||
/* Выровнять передний край РОВНО к грани стены, если он зашёл за неё (bumped:
|
||
* Char.x += dist; char_dx_forward отражает знак для обоих направлений).
|
||
* Стена ищется под персонажем, иначе перед ним. */
|
||
static void in_wall(void)
|
||
{
|
||
uint8_t t; int8_t tcol; int d;
|
||
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;
|
||
d = dist_from_wall_forward(t, tcol);
|
||
if (d < 0) {
|
||
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++; }
|
||
|
||
static void land(void)
|
||
{
|
||
uint8_t seq;
|
||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
||
/* к краю пола — чуть назад (как оригинал) */
|
||
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;
|
||
inc_curr_row();
|
||
if (frame == 9 || 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; /* stand/step/crouch */
|
||
kid_set_seq(seq);
|
||
play_seq();
|
||
determine_col();
|
||
if (get_tile_at_char() == TILE_WALL)
|
||
in_wall();
|
||
}
|
||
|
||
/* ---- Зацеп (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(); /* следующий ряд ВНУТРИ комнаты */
|
||
else
|
||
pop_fell_out = 1; /* упал за низ комнаты (нет пола,
|
||
комнаты снизу нет) — приложение
|
||
сбросит Kid (K-later: переход) */
|
||
}
|
||
}
|
||
|
||
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; 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) 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;
|
||
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;
|
||
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();
|
||
}
|
||
|
||
void pop_phys_tick(void)
|
||
{
|
||
if (grab_timer) grab_timer--;
|
||
fall_accel();
|
||
fall_speed();
|
||
determine_col();
|
||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||
check_action();
|
||
}
|