Files
Sprinter-SDCC/applications/PoP/roomtest/pop_map.c
T
snark13 8b30dc20c8 roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME:

- Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57)
  теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный
  хвост-тень на полу 2,7 ПОСЛЕ падения.
- Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты
  перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край
  прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1).
- Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq ->
  флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80).
  KNOCK_DOWN в land-seq (приземление) и runcyc (footstep).
- Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0)
  перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) —
  иначе на одной странице застревает дрожащий кадр правой грани (живёт в
  тайлах col и col+1) -> мерцание через флип.

Инфраструктура/документация:
- app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME).
- docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация
  форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT.
- README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP —
  источник истины», порядок слоёв, режим отладки freeze 1/2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:27:47 +03:00

751 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 — перерисовка */
/* ---- Тайлы / действия / 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 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 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 */
void pop_map_set(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;
}
/* 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++; }
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, 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(); /* следующий ряд ВНУТРИ комнаты */
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 (dx4 назад + кадры 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 || 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;
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();
}
/* ---- 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 = обе стр.) */
/* 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;
if (frame == 79) { /* прыжок вверх — тайл сверху */
if (get_tile_above_char() == TILE_LOOSE)
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;
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);
}
/* 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 < 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;
}
}
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
void pop_loose_tick(void)
{
int pos;
for (pos = 0; pos < 30; pos++) {
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
pop_loose_bake_empty(pos / 10, pos % 10);
loose_bake[pos]--;
continue;
}
if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
* страницы дабл-буфера — иначе на одной остаётся застывший
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
pop_loose_shake_draw(pos / 10, pos % 10);
loose_rest[pos]--;
continue;
}
if (pop_loose_modif[pos] == 0) continue;
{
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 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(pos / 10, pos % 10); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
pop_loose_mob_spawn(pos / 10, pos % 10); /* отрыв падающего куска (L4) */
continue;
}
pop_loose_shake_draw(pos / 10, pos % 10); /* дрожащий кадр */
}
}
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
}
void pop_phys_tick(void)
{
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 */
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
}