/* * 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 — перерисовка */ #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 */ /* Состояние edge-триггера бампа (порт prev_coll_flags, seg004): колонка, для * которой сохранены флаги перекрытия, и сами флаги (бит0 = левая грань стены, * бит1 = правая). BUMP_COL_NONE — данных нет (смена комнаты/старт). */ #define BUMP_COL_NONE ((int8_t)-128) static int8_t bump_prev_col; static uint8_t bump_prev_flags; /* Связи комнаты (0 = нет соседа) + кромки швов: g_lcol[0..2] = col9 левого * соседа (за col0), g_lcol[3..5] = его col8 (колонка −2); g_rcol симметрично * (col0 / col1 правого соседа, колонки 10 и 11). Для коллизии на швах — * иначе Kid, войдя в соседнюю комнату у самой кромки (curr_col=-1/10), стоял * бы «в стене» уровня и in_wall выбрасывал бы его. ВТОРАЯ колонка нужна, * когда Kid стоит В шве и смотрит НАРУЖУ: передняя колонка тогда −2 (или 11), * и мнимая стена там выпихивала его сквозь решётку (баг разворота у ворот). * Оригинал резолвит любую колонку через find_room_of_tile (seg006:005D). * Задаются pop_map_set_edges. */ static uint8_t g_link_l, g_link_r, g_link_u, g_link_d; static uint8_t g_lcol[6], g_rcol[6]; /* ---- Плита-ПОТОЛОК: 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) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; } if (rcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_rcol[i] = rcol_fg[i]; } /* смена комнаты — колонки перепроецированы, прошлые флаги перекрытия * недействительны (в оригинале prev_coll_room != curr_row_coll_room и * бампа на кадре входа нет). */ bump_prev_col = BUMP_COL_NONE; } 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 == -2 && g_link_l) /* на тайл глубже в комнату слева (col8) */ g_curr_tile = g_lcol[3 + row] & 0x1F; else if (col == 10 && g_link_r) /* правый шов: тайл col0 комнаты справа */ g_curr_tile = g_rcol[row] & 0x1F; else if (col == 11 && g_link_r) /* на тайл глубже в комнату справа (col1) */ g_curr_tile = g_rcol[3 + 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); /* EDGE-триггер (порт check_collisions + get_row_collision_data, seg004:0004). * Оригинал считает флаги перекрытия от ГАБАРИТА персонажа, БЕЗ направления: * 0x0F = left_wall_xpos < char_x_right_coll («в левую грань стены») * 0xF0 = right_wall_xpos > char_x_left_coll («в правую грань») * и зовёт bumped() ТОЛЬКО когда нужный флаг перешёл 0 -> 1 (взгляд вправо * смотрит на 0x0F, влево — на 0xF0). Без этого проверка «уровнем» ловит * ЛЮБОЕ перекрытие: Kid, стоящий В проёме опущенной решётки, при развороте * на месте получал ложный бамп и выпихивался сквозь неё в соседнюю * комнату. С флагами от габарита разворот их не меняет — бампа нет. */ { uint8_t f = 0, prev, need; if (wt) { int coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX; int lw = wall_dl[wt] + coll_left; /* get_left_wall_xpos */ int rw = coll_left - wall_dr[wt] + TILE_RIGHTX; /* get_right_wall_xpos */ int e = char_x_forward_edge(), wh = ((int)kid_fp_width() + 1) >> 1; int xl, xr; if (Kid.direction < 0) { xl = e; xr = e + wh; } /* габарит персонажа */ else { xr = e; xl = e - wh; } if (kid_cur_flags() & FRAME_THIN) { xl += 4; xr -= 4; } if (lw < xr) f |= 1; if (rw > xl) f |= 2; } /* прошлый кадр: та же колонка — её флаги; другая — считаем, что не * перекрывали; «нет данных» (смена комнаты) — считаем, что перекрывали * всё, чтобы на кадре входа бампа не было (в оригинале это даёт * несовпадение prev_coll_room и curr_row_coll_room). */ prev = (bump_prev_col == BUMP_COL_NONE) ? 3u : (bump_prev_col == tcol ? bump_prev_flags : 0u); bump_prev_col = tcol; bump_prev_flags = f; if (!wt) return; need = (Kid.direction < 0) ? 2u : 1u; if (!(f & need) || (prev & need)) 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); } /* ---- clip_char (seg006:1749), первый блок: обрезка спрайта СВЕРХУ ----- * * Оригинал перед add_objtable кладёт в objtable прямоугольник клипа; блиттер * рисует персонажа только внутри него. Верхняя граница: если тайл НАД * головой (ряд char_top_row) — стена или пол, всё выше линии своей полосы * (y_clip[curr_row+1]) не рисуется. Без этого Kid, ушедший головой под пол * (спуск с кромки/кнопки, вис), просвечивает сквозь щели кладки. * * Хитрость исходника: `X == wall || tile_is_floor(curr_tile2)` — curr_tile2 * ставит сам get_tile, т.е. это просто «тайл X = стена ИЛИ пол». * * Метрики габарита — порт set_char_collision (seg006:0723); в отличие от * check_spike_below колонки берутся get_tile_div_mod БЕЗ −7. * * Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) { static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 }; int wh = ((int)w + 1) >> 1; /* char_width_half */ int xl = obj_x / 2 + 58; /* char_x_left */ int xr, top_y, clip_y; int8_t cL, cR, trow, clip_row; uint8_t t; if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ xr = xl + wh; /* char_x_right */ top_y = obj_y - (int)h + 1; /* char_top_y */ if (top_y >= 192) top_y = 0; /* весь спрайт ниже комнаты */ trow = y_to_row((int16_t)top_y); /* char_top_row */ cL = get_tile_div_mod(xl); if (cL < 0) cL = 0; cR = get_tile_div_mod(xr); if (cR > 9) cR = 9; t = get_tile(cL, trow); if (t != TILE_WALL && !tile_is_floor(t)) return 0; /* кадр 79 (прыжок вверх) / 81 (зацеп) в стойке клипятся и по одной грани */ if (!(Kid.action == 0 && (Kid.frame == 79 || Kid.frame == 81))) { t = get_tile(cR, trow); if (t != TILE_WALL && !tile_is_floor(t)) return 0; } clip_row = (int8_t)(Kid.curr_row + 1); if (clip_row < 0 || clip_row > 4) return 0; clip_y = y_clip[clip_row]; /* ряд 0 — клип безусловный; иначе только когда линия реально режет спрайт */ if (clip_row == 1 || (clip_y < obj_y && clip_y - 15 < top_y)) return clip_y; return 0; } /* 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: уход вбок → переход в соседнюю комнату */ } }