/* * 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 #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 (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 || 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 в ряду */ }