Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв
Уровень 12 (тень) — порт seg002/seg006: - check_shadow: подъём тени в комнате 15 по условию «меч подобран», init_shad_12, вход падением (seq 7); - autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ; - общий урон (ранил тень — ранил себя) и check_killed_shadow (убил тень — убил себя); - таймер вспышки слияния 42 -> -1, sword_disappears при уходе из комнаты 18, появление плит в комнатах 2/13 после слияния. Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт сбросить HP. Чит навигации по комнатам этот триггер придерживает — иначе комнату 23 двенадцатого уровня не посмотреть в принципе. Уровень 13 (Джафар): guard_notice_timer (фора после встречи), on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три исключения loose-механики. Плюс спрайты визиря VIZIER.DAT — без них он рисовался обычным стражем; заодно цвет из данных комнаты применяется только к обычному стражу, как в оригинале. Падающие плиты — семь дефектов, найденных прогонами: - MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние терялись без щебня; - одиночные сигналы посадки/провала больше не затираются в кадре; - честный loose_fall: сбитая плита рождается в том же кадре, от места удара, с половинной скоростью; - при снятии плиты метится и сосед справа (висел передний торец); - heal и отрисовка кусков разнесены на два прохода; - куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y (compare_curr_objs для пары 0x80 сортирует наоборот); - wall_pattern убран из ceil_over_kid_tile: у оригинала узор из draw_tile_bottom идёт в фон, а не в передний слой. Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44). TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам. Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами тени при слиянии, чит навигации не запускает бесшовный переход, чит K убивает через штатный путь смерти. Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER, GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -236,6 +236,16 @@ void pop_kid_hp_reset(void) __banked
|
||||
pop_kid_dead = 0;
|
||||
}
|
||||
|
||||
/* Бесшовный переход (seamless, seg003:158): уровень стартует, НЕ трогая HP —
|
||||
* Кид уносит на следующий и потолок, и полученный урон, потому что для него
|
||||
* это не «новый уровень», а продолжение того же бега. Всё остальное, что
|
||||
* делает pop_kid_hp_reset, нужно и здесь: дельта кадра и признак смерти. */
|
||||
void pop_kid_hp_carry(void) __banked
|
||||
{
|
||||
hitp_delta = 0;
|
||||
pop_kid_dead = 0;
|
||||
}
|
||||
|
||||
/* init_game (seg003:0043): начало ИГРЫ, а не уровня — стартовое HP. */
|
||||
void pop_kid_hp_newgame(void) __banked { hitp_beg_lev = POP_START_HITP; }
|
||||
|
||||
@@ -249,25 +259,39 @@ static void pop_kid_add_life(void)
|
||||
hitp_delta = (int8_t)(hitp_max - hitp_curr);
|
||||
}
|
||||
|
||||
/* Тот же add_life наружу: слияние с тенью (уровень 12) даёт ровно ту же
|
||||
* прибавку, что большая склянка, а живёт ИИ тени в другом банке. */
|
||||
void pop_add_life(void) __banked { pop_kid_add_life(); }
|
||||
|
||||
/* Урон — через общий pop_take_hp (резидент pop_guard.c): физика работает
|
||||
* над Char, значит и HP обязаны убавляться у ТОГО, кто сейчас в Char. Своя
|
||||
* копия take_hp здесь была Kid-only и правила hitp_curr мимо дельты. */
|
||||
|
||||
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down) __banked
|
||||
/* Перечитать fg соседей из уровня, НЕ трогая историю коллизий. Отдельно от
|
||||
* pop_map_set_edges, потому что нужен и посреди жизни в комнате: тайл соседа
|
||||
* может измениться, пока тот не отрисован (плита, брошенная Кидом на
|
||||
* полпути, доваливается заочно — animate_loose в pop_trob.c). g_lcol/g_rcol
|
||||
* до этого были снимком НА МОМЕНТ ВХОДА, и коллизия у шва продолжала считать
|
||||
* провалившуюся плиту полом. */
|
||||
void pop_map_refresh_edges(void) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
if (!g_link_l && !g_link_r) return;
|
||||
pop_level_access_begin();
|
||||
for (i = 0; i < 30; i++) {
|
||||
if (g_link_l) g_lcol[i] = pop_level_tile_raw(g_link_l, i);
|
||||
if (g_link_r) g_rcol[i] = pop_level_tile_raw(g_link_r, i);
|
||||
}
|
||||
pop_level_access_end();
|
||||
}
|
||||
|
||||
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down) __banked
|
||||
{
|
||||
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
|
||||
/* fg соседей — прямо из уровня, одной скобкой доступа на обе комнаты.
|
||||
* Держать ещё одну копию в приложении незачем: карта — единственный её
|
||||
* потребитель (отрисовке швов хватает кромочных колонок pop_room_load). */
|
||||
if (left || right) {
|
||||
pop_level_access_begin();
|
||||
for (i = 0; i < 30; i++) {
|
||||
if (left) g_lcol[i] = pop_level_tile_raw(left, i);
|
||||
if (right) g_rcol[i] = pop_level_tile_raw(right, i);
|
||||
}
|
||||
pop_level_access_end();
|
||||
}
|
||||
pop_map_refresh_edges();
|
||||
/* смена комнаты — колонки перепроецированы, прошлые флаги перекрытия
|
||||
* недействительны (в оригинале prev_coll_room != curr_row_coll_room и
|
||||
* бампа на кадре входа нет). БОКОВОЙ переход это отменяет: вызывающий
|
||||
@@ -1349,13 +1373,52 @@ uint8_t pop_get_item_action(void) __banked
|
||||
}
|
||||
}
|
||||
|
||||
/* Спецсобытие уровня 12 «появление плит» (seg006:1063, ветка внутри
|
||||
* check_on_floor). После слияния с тенью (pop_united_shadow == −1) в
|
||||
* верхнем ряду комнат 2 и 13 пропасти становятся проходимыми: персонаж,
|
||||
* который ДОЛЖЕН БЫЛ провалиться, вместо этого достраивает под собой пол.
|
||||
* Плита появляется ровно под ним, шаг за шагом — это не «включили этаж», а
|
||||
* дорожка, возникающая под ногами.
|
||||
*
|
||||
* Возврат 1 = «пол появился, падать не надо». Перерисовываем сам тайл и
|
||||
* соседа справа на ОБЕИХ страницах — тем же приёмом, что pop_skel_wake_tile
|
||||
* (у оригинала это пара set_redraw_full/set_wipe на curr_tilepos и +1).
|
||||
*
|
||||
* Комната берётся РЕЗОЛВНУТОЙ (tile_room_of): персонаж может стоять в шве,
|
||||
* и тогда пол обязан появиться в соседней комнате, как и у оригинала —
|
||||
* там get_tile переставляет curr_room/curr_tilepos до присваивания. */
|
||||
#define HIDDEN_FLOOR_ROOM_A 2
|
||||
#define HIDDEN_FLOOR_ROOM_B 13
|
||||
#define HIDDEN_FLOOR_COL_B 6 /* в комнате 13 — только правая половина */
|
||||
|
||||
static uint8_t floors_appear(void)
|
||||
{
|
||||
uint8_t room, tp;
|
||||
if (pop_current_level != SHADOW_FIGHT_LEVEL) return 0;
|
||||
if (pop_united_shadow >= 0) return 0; /* только ПОСЛЕ слияния */
|
||||
if (Char.curr_row != 0) return 0;
|
||||
if (Char.room != HIDDEN_FLOOR_ROOM_A &&
|
||||
!(Char.room == HIDDEN_FLOOR_ROOM_B && Char.curr_col >= HIDDEN_FLOOR_COL_B))
|
||||
return 0;
|
||||
room = tile_room_of(Char.curr_col, Char.curr_row, &tp);
|
||||
if (!room) return 0;
|
||||
pop_level_set_tile(room, tp, TILE_FLOOR);
|
||||
if (room == g_room) {
|
||||
g_fg[tp] = TILE_FLOOR;
|
||||
pop_set_redraw(tp, POP_RD_FLOOR, 2);
|
||||
if ((tp % 10) < 9)
|
||||
pop_set_redraw((uint8_t)(tp + 1), POP_RD_FLOOR, 2);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- Диспетчер ----------------------------------------------------- */
|
||||
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))
|
||||
if (!tile_is_floor(g_curr_tile) && !floors_appear())
|
||||
start_fall();
|
||||
}
|
||||
}
|
||||
@@ -1466,6 +1529,40 @@ static void bumped_floor(void)
|
||||
is_guard_notice = 1; /* bumped_sound (seg004:05F1) */
|
||||
}
|
||||
|
||||
/* bump_into_opponent (seg003:08AA). Зовётся из play_kid_frame (seg000:1238)
|
||||
* между set_char_collision и check_collisions, и ТОЛЬКО у Кида — у стража
|
||||
* этой ветки нет (см. комментарий к guard_phys ниже).
|
||||
*
|
||||
* ЗАЧЕМ. Это единственная защита оригинала от «вошёл в комнату — сразу
|
||||
* труп». Стража при входе никуда не отбрасывает: enter_guard (seg002:0112)
|
||||
* ставит его ровно в его тайл из данных уровня, и Кид вполне может войти
|
||||
* вплотную. Спасает то, что БЕЗОРУЖНЫЙ Кид, столкнувшись с вооружённым
|
||||
* соперником лицом к лицу, не получает укол, а ОТСКАКИВАЕТ (seq_47).
|
||||
* Остальные три страховки у нас уже есть: Кид сам достаёт меч
|
||||
* (control_standing, pop_ctrl.c), страж поднимается в стойке покоя с мечом
|
||||
* в ножнах (pop_guard_cold.c) и не колет ближе 12 (guards.c).
|
||||
*
|
||||
* Расхождений с оригиналом нет. Опциональные фиксы SDLPoP
|
||||
* (fix_painless_fall_on_guard — падение на стража с высоты перестаёт быть
|
||||
* безболезненным; fix_jumping_over_guard — нельзя перепрыгнуть) намеренно НЕ
|
||||
* портированы: базовое поведение движка 1989 года. */
|
||||
static void bump_into_opponent(void)
|
||||
{
|
||||
int16_t distance;
|
||||
if (can_guard_see_kid < 2) return;
|
||||
if (Char.sword != 0) return; /* Кид уже в боевой стойке */
|
||||
if (Opp.sword == 0) return; /* у соперника меч в ножнах */
|
||||
if (Opp.action >= 2) return; /* только stand / run_jump */
|
||||
if (Char.direction == Opp.direction) return; /* должны быть лицом к лицу */
|
||||
distance = pop_char_opp_dist();
|
||||
if (distance < 0) distance = -distance;
|
||||
if (distance > 15) return;
|
||||
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||
Char.fall_y = 0;
|
||||
pop_char_set_seq(SEQ_47_BUMP);
|
||||
play_seq();
|
||||
}
|
||||
|
||||
/* ---- check_collisions (seg004:0004) -------------------------------- *
|
||||
* ПОЛНЫЙ порт: флаги перекрытия считаются для ВСЕХ колонок ряда, а колонка
|
||||
* удара выбирается обходом 9→0 по переходу флага 0→1.
|
||||
@@ -2112,6 +2209,12 @@ static void check_press(void)
|
||||
static void do_knock(int tile_row)
|
||||
{
|
||||
int col, pos;
|
||||
/* СПЕЦКЕЙС УРОВНЯ 13 (loose_make_shake, seg007:949): сотрясение НЕ
|
||||
* трясёт плиты вовсе. Причина та же, что у первого спецкейса: тряска
|
||||
* ставит фазу 0x80, а на этом уровне у плит уже лежит СВОЙ отложенный
|
||||
* старт (0xF0..0xFF), и приземление Кида сбросило бы его на 0x80 —
|
||||
* гряда посыпалась бы разом вместо вразнобой. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) return;
|
||||
if (tile_row == -1) { /* ряд 2 комнаты сверху — плиты-потолки */
|
||||
if (!g_above || !g_link_u) return;
|
||||
for (col = 0; col < 10; col++)
|
||||
@@ -2139,18 +2242,120 @@ void pop_check_knock(void) __banked
|
||||
}
|
||||
}
|
||||
|
||||
/* Сдать НЕЗАКОНЧЕННЫЕ loose уходящей комнаты в персистентное хранилище —
|
||||
* звать ПЕРЕД сменой комнаты, пока g_room/g_fg/g_link_u ещё старые.
|
||||
*
|
||||
* pop_loose_modif[30] — фаза тайлов ТЕКУЩЕЙ комнаты, и раньше смена комнаты
|
||||
* её просто теряла: плита, по которой Кид пробежал и ушёл вправо, оставалась
|
||||
* целой (уровень 10, комната 2, тайл 2,9). В оригинале терять нечего —
|
||||
* там loose это trob в общем списке, а фаза лежит в curr_room_modif, то есть
|
||||
* в самой таблице уровня. Порт того же: фаза уезжает в room_modif (он
|
||||
* персистентен per-room), а досчитывает её animate_loose в pop_trob.c.
|
||||
*
|
||||
* Пишем фазу ВСЕМ loose-тайлам, включая нулевые: room_modif — источник
|
||||
* истины, а pop_loose_modif лишь его копия на время пребывания в комнате,
|
||||
* и оставить там прошлое значение значило бы «оживить» уже отдрожавшую
|
||||
* плиту при следующем входе. trob заводим только на незаконченные. */
|
||||
void pop_loose_leave_room(void) __banked
|
||||
{
|
||||
uint8_t *mod;
|
||||
int i;
|
||||
if (!g_fg || !g_room) return;
|
||||
mod = pop_trob_modif(g_room);
|
||||
if (mod) {
|
||||
for (i = 0; i < 30; i++) {
|
||||
if ((g_fg[i] & 0x1F) != TILE_LOOSE) continue;
|
||||
mod[i] = pop_loose_modif[i];
|
||||
if (pop_loose_modif[i]) pop_add_trob(g_room, (uint8_t)i, 0);
|
||||
}
|
||||
}
|
||||
/* Плиты-ПОТОЛКИ — ряд 2 комнаты сверху, tilepos 20+col. */
|
||||
if (g_above && g_link_u) {
|
||||
mod = pop_trob_modif(g_link_u);
|
||||
if (mod) {
|
||||
for (i = 0; i < 10; i++) {
|
||||
if ((g_above[i] & 0x1F) != TILE_LOOSE) continue;
|
||||
mod[20 + i] = pop_ceil_modif[i];
|
||||
if (pop_ceil_modif[i])
|
||||
pop_add_trob(g_link_u, (uint8_t)(20 + i), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Решётка ворот должна идти ПОВЕРХ персонажа? Порт первого условия
|
||||
* draw_tile_fore (seg008:0D15):
|
||||
*
|
||||
* if (tile_left == tiles_4_gate && Kid.curr_row == drawn_row &&
|
||||
* Kid.curr_col == drawn_col - 1 && Kid.room != room_R)
|
||||
* draw_gate_fore();
|
||||
*
|
||||
* То есть: рисуя тайл (row, col), оригинал кладёт бары ворот в FOREtable —
|
||||
* поверх всего, что уже нарисовано, — если тайл СЛЕВА это ворота и персонаж
|
||||
* стоит ИМЕННО НА НИХ. Графика ворот принадлежит правому краю своего тайла
|
||||
* и рисуется в проходе СЛЕДУЮЩЕЙ колонки, отсюда сдвиг на единицу.
|
||||
*
|
||||
* Живёт в карте, а не в слое фона, по двум причинам: спрашивает она про
|
||||
* ТАЙЛ и позицию персонажа (это знание карты), и её линкует хост-харнесс —
|
||||
* решение проверяется тестом без эмулятора (t_char: gate_fore_over_char).
|
||||
* Слою фона остаётся только рисование (draw_gate_fore в pop_bg.c).
|
||||
*
|
||||
* РАСХОЖДЕНИЕ С ОРИГИНАЛОМ (осознанное, см. docs/impl_diff.md): оригинал
|
||||
* жёстко спрашивает про Kid, потому что foretable у него ОДНА на проход
|
||||
* тайлов; у нас fore-проход идёт по персонажу, поэтому спрашиваем про того,
|
||||
* кого рисуем. Наш вариант — надмножество: страж, стоящий в проёме ворот,
|
||||
* у нас тоже уходит ЗА решётку, а в оригинале остался бы перед ней. */
|
||||
uint8_t pop_gate_over_char(const pop_char_t *ch) __banked
|
||||
{
|
||||
if (!ch || ch->room != g_room) return 0;
|
||||
if (ch->curr_row < 0 || ch->curr_row > 2) return 0;
|
||||
/* Колонка 9: бары легли бы в колонку 10, то есть в СОСЕДНЮЮ комнату —
|
||||
* у оригинала это отсекает `Kid.room != room_R`. */
|
||||
if (ch->curr_col < 0 || ch->curr_col > 8) return 0;
|
||||
return (uint8_t)(get_tile(ch->curr_col, ch->curr_row) == TILE_GATE);
|
||||
}
|
||||
|
||||
/* СТАРТ/РЕСТАРТ уровня: забыть фазы, НЕ сдавая их в room_modif — в
|
||||
* оригинале всё состояние сносит сам load_level (seg003:57). Без этого
|
||||
* pop_loose_leave_room у первого же enter_room занесла бы в только что
|
||||
* восстановленный уровень дрожь из прошлой жизни: pop_trob_reset уже
|
||||
* отработал, room_modif свежий, а pop_loose_modif — ещё старый. */
|
||||
void pop_loose_forget(void) __banked
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 30; i++) pop_loose_modif[i] = 0;
|
||||
for (i = 0; i < 10; i++) pop_ceil_modif[i] = 0;
|
||||
}
|
||||
|
||||
/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest
|
||||
* индексируются позицией тайла и не должны утечь из старой комнаты в новую.
|
||||
* Уже ПАДАЮЩИЕ куски при этом не трогаем — они привязаны к своей комнате и
|
||||
* должны долететь (do_mobs, seg007:1063; см. pop_loose_mob_room_changed). */
|
||||
* должны долететь (do_mobs, seg007:1063; см. pop_loose_mob_room_changed).
|
||||
*
|
||||
* Вместо нулей забираем фазу НОВОЙ комнаты из room_modif — обратная
|
||||
* половина pop_loose_leave_room: расшатанная и брошенная плита, к которой
|
||||
* Кид вернулся, продолжает дрожать с той же фазы, а не с покоя. trob,
|
||||
* который крутил её заочно, снимет сам animate_loose (room == cur_room). */
|
||||
void pop_loose_reset(void) __banked
|
||||
{
|
||||
uint8_t *mod;
|
||||
int i;
|
||||
|
||||
mod = g_room ? pop_trob_modif(g_room) : 0;
|
||||
for (i = 0; i < 30; i++) {
|
||||
pop_loose_modif[i] = 0;
|
||||
pop_loose_modif[i] = (mod && g_fg && (g_fg[i] & 0x1F) == TILE_LOOSE)
|
||||
? mod[i] : 0;
|
||||
}
|
||||
mod = (g_above && g_link_u) ? pop_trob_modif(g_link_u) : 0;
|
||||
for (i = 0; i < 10; i++) {
|
||||
pop_ceil_modif[i] = 0;
|
||||
pop_ceil_modif[i] = (mod && (g_above[i] & 0x1F) == TILE_LOOSE)
|
||||
? mod[20 + i] : 0;
|
||||
/* ...и СНЯТЬ заочный trob: плиту-потолок отсюда снова ведёт
|
||||
* pop_loose_tick. Без этого её крутили бы ДВОЕ — trob по
|
||||
* room_modif и мы по pop_ceil_modif, — и она проваливалась бы
|
||||
* дважды (animate_loose снимает сам только trob'ы cur_room, а
|
||||
* комната сверху — не cur_room). */
|
||||
if (pop_ceil_modif[i]) pop_add_trob(g_link_u, (uint8_t)(20 + i), -1);
|
||||
}
|
||||
pop_ceil_fell = 0;
|
||||
pop_debris_at = 0;
|
||||
@@ -2158,6 +2363,82 @@ void pop_loose_reset(void) __banked
|
||||
pop_loose_mob_room_changed();
|
||||
}
|
||||
|
||||
/* remove_loose (seg007:0EB8) для ОДНОГО тайла, наружу — ради честного
|
||||
* loose_fall: падающий кусок сбивает плиту НЕМЕДЛЕННО, в том же кадре
|
||||
* (pop_room.c, mob_break_loose). Здесь, а не в pop_room, потому что тут
|
||||
* живут и рабочая копия тайлов комнаты (g_fg), и фазы плит.
|
||||
*
|
||||
* Перерисовываем ДВЕ клетки — саму и соседа справа. Передний торец пола
|
||||
* заезжает в соседний тайл (правая грань соседа всегда в backtable), и без
|
||||
* второй пометки от исчезнувшей плиты оставался висеть торец: тайл пустой,
|
||||
* а его передняя грань нарисована. Тем же приёмом, что pop_skel_wake_tile
|
||||
* и что set_redraw_full+set_wipe на curr_tilepos и curr_tilepos+1 в
|
||||
* оригинале. */
|
||||
void pop_loose_remove(uint8_t room, uint8_t tilepos) __banked
|
||||
{
|
||||
uint8_t *mod = pop_trob_modif(room);
|
||||
if (tilepos >= 30) return;
|
||||
if (room == g_room) {
|
||||
g_fg[tilepos] = TILE_EMPTY;
|
||||
pop_loose_modif[tilepos] = 0;
|
||||
pop_set_redraw(tilepos, POP_RD_LOOSE_GONE, 2);
|
||||
if ((tilepos % 10) < 9)
|
||||
pop_set_redraw((uint8_t)(tilepos + 1), POP_RD_FLOOR, 2);
|
||||
} else {
|
||||
/* Комната не на экране, но её кромочная колонка видна в шве — те же
|
||||
* снимки, что обновляет animate_loose. */
|
||||
pop_neigh_dirty = 1;
|
||||
}
|
||||
/* Модификатор пустой клетки = ТИП УРОВНЯ (по нему draw_tile_right
|
||||
* выбирает blueline у дыры), тайл в странице уровня — сразу. */
|
||||
if (mod) mod[tilepos] = pop_palace;
|
||||
pop_level_set_tile(room, tilepos, TILE_EMPTY);
|
||||
}
|
||||
|
||||
/* check_fall_flo (seg000:1317) — СПЕЦСОБЫТИЕ уровня 13: гряда плит над
|
||||
* комнатой сыплется на Кида сама, вразнобой.
|
||||
*
|
||||
* Как это устроено в оригинале: при входе в комнату 23 или 16 шести плитам
|
||||
* ряда 2 комнаты СВЕРХУ раздаётся ОТРИЦАТЕЛЬНЫЙ модификатор −(rnd & 15).
|
||||
* Отрицательное значение — это старший бит, то есть для animate_loose оно
|
||||
* выглядит как «тряска», а на уровне 13 тряска намеренно не гасится
|
||||
* (спецкейс в pop_loose_tick). Фаза считает вверх, доходит до нуля и
|
||||
* дальше идёт обычным отсчётом до провала — получается разная задержка у
|
||||
* каждой плиты.
|
||||
*
|
||||
* Плиты ряда 2 верхней комнаты у нас — это pop_ceil_modif (плиты-потолки),
|
||||
* так что раздаём фазу именно туда.
|
||||
*
|
||||
* ОДНО отличие от оригинала, вынужденное представлением: у нас фаза 0 = «не
|
||||
* анимируется», плита с такой фазой в цикле пропускается вовсе. Значит
|
||||
* счёт, дойдя до нуля, оборвался бы — поэтому, во-первых, тик перескакивает
|
||||
* ноль (см. pop_loose_tick), а во-вторых, стартовое значение берётся на
|
||||
* единицу «отрицательнее» (−n−1): вместе это даёт ровно ту же суммарную
|
||||
* задержку n + 11 кадров, что и у оригинала.
|
||||
*
|
||||
* Звать при входе в комнату, после pop_loose_reset (тот забирает фазы из
|
||||
* room_modif) — как check_the_end зовёт его после start_chompers. */
|
||||
static pop_rnd_t loose_seed; /* свой домен последовательности, см. pop_geom.h */
|
||||
|
||||
void pop_check_fall_flo(void) __banked
|
||||
{
|
||||
uint8_t col;
|
||||
if (pop_current_level != POP_LOOSE_TILES_LEVEL) return;
|
||||
if (g_room != POP_LOOSE_TILES_ROOM1 && g_room != POP_LOOSE_TILES_ROOM2) return;
|
||||
if (!g_above || !g_link_u) return;
|
||||
for (col = POP_LOOSE_TILES_FIRST - 20; col <= POP_LOOSE_TILES_LAST - 20; col++) {
|
||||
uint8_t t = g_above[col];
|
||||
/* Те же отсевы, что в make_loose_fall: не loose и «solid» loose
|
||||
* пропускаются молча (в комнате 1 тайл 24 пустой — диапазон 22..27
|
||||
* намеренно надмножество), уже идущий отсчёт не перебиваем. */
|
||||
if ((t & 0x1F) != TILE_LOOSE || (t & 0x20)) continue;
|
||||
if ((int8_t)pop_ceil_modif[col] > 0) continue;
|
||||
pop_ceil_modif[col] =
|
||||
(uint8_t)(-(int8_t)(pop_prandom(&loose_seed, 255) & 0x0F) - 1);
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* fell_on_your_head (seg007:1205): плита прилетела Киду по голове. В беге
|
||||
* (кадры 5..14) не бьёт (кроме уровня 13 — его тут нет); в висе/подъёме тоже
|
||||
* (action >= hang_climb, кроме turn). Иначе −1 HP: смерть → seq_22 (crushed),
|
||||
@@ -2169,7 +2450,10 @@ void pop_loose_reset(void) __banked
|
||||
static void fell_on_your_head(void)
|
||||
{
|
||||
uint8_t frame = Char.frame, action = Char.action;
|
||||
if ((frame < 5 || frame >= 15) &&
|
||||
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:1218): там плита бьёт и В БЕГЕ. На прочих
|
||||
* уровнях бегущего она пропускает — иначе гряда падающих плит, сквозь
|
||||
* которую и надо пробегать, не представляла бы никакой угрозы. */
|
||||
if ((pop_current_level == POP_LOOSE_TILES_LEVEL || frame < 5 || frame >= 15) &&
|
||||
(action < ACT_HANGCLIMB || action == ACT_TURN)) {
|
||||
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||
if (pop_take_hp(1)) { /* «брызги» даст pop_do_delta_hp */
|
||||
@@ -2221,8 +2505,22 @@ void pop_loose_tick(void) __banked
|
||||
for (pos = 0; pos < 30; pos++) {
|
||||
if (pop_loose_modif[pos] != 0) {
|
||||
uint8_t m = ++pop_loose_modif[pos];
|
||||
/* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас
|
||||
* означает «не анимируется», и плита застряла бы навсегда —
|
||||
* перескакиваем на 1. Стартовое значение выбрано с учётом
|
||||
* этого перескока (см. pop_check_fall_flo). */
|
||||
if (m == 0) m = pop_loose_modif[pos] = 1;
|
||||
if (m & 0x80) { /* тряска (do_knock) */
|
||||
if (m >= 0x84) { /* конец тряски: сброс + покой */
|
||||
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ
|
||||
* гасим и не трясём. На нём check_fall_flo раздаёт плитам
|
||||
* ОТРИЦАТЕЛЬНЫЙ старт (0xF0..0xFF) как отложенный таймер, и
|
||||
* обычная ветка «тряска кончилась на 0x84» убила бы его в
|
||||
* первом же кадре — плита не упала бы никогда. Пропущенная
|
||||
* инкрементом фаза сама дойдёт до 0x00, там старший бит
|
||||
* снимется, и дальше пойдёт обычный отсчёт до провала. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||
/* ждём молча */
|
||||
} else if (m >= 0x84) { /* конец тряски: сброс + покой */
|
||||
pop_loose_modif[pos] = 0;
|
||||
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
|
||||
* правая грань loose живёт в соседе). ОБЕ страницы —
|
||||
@@ -2231,6 +2529,13 @@ void pop_loose_tick(void) __banked
|
||||
} else {
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
|
||||
}
|
||||
} else if (m >= 11 && pop_loose_fell) {
|
||||
/* Сигнал прошлого провала ещё не разобран главным циклом (он
|
||||
* ОДИН на кадр) — придержим плиту кадр, откатив фазу к 10.
|
||||
* Иначе из двух плит, провалившихся в одном кадре, дыру
|
||||
* запекала бы только вторая, а первая оставалась нарисованной
|
||||
* целой (уровень 13: check_fall_flo роняет гряду разом). */
|
||||
pop_loose_modif[pos] = 10;
|
||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
||||
@@ -2244,6 +2549,11 @@ void pop_loose_tick(void) __banked
|
||||
pop_trob_modif(g_room)[pos] = pop_palace;
|
||||
pop_loose_modif[pos] = 0;
|
||||
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
|
||||
/* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его
|
||||
* клетку, и без этой пометки он оставался висеть над пустым
|
||||
* тайлом. То же, что делает pop_skel_wake_tile. */
|
||||
if ((pos % 10) < 9)
|
||||
pop_set_redraw((uint8_t)(pos + 1), POP_RD_FLOOR, 2);
|
||||
pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
|
||||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
|
||||
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||||
@@ -2258,10 +2568,19 @@ void pop_loose_tick(void) __banked
|
||||
for (col = 0; col < 10; col++) {
|
||||
if (pop_ceil_modif[col] != 0) {
|
||||
uint8_t m = ++pop_ceil_modif[col];
|
||||
if (m == 0) m = pop_ceil_modif[col] = 1; /* см. pop_loose_tick выше */
|
||||
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
||||
if (m >= 0x84) { pop_ceil_modif[col] = 0;
|
||||
/* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты,
|
||||
* которым check_fall_flo раздал отложенный старт, лежат
|
||||
* именно в ряду 2 комнаты СВЕРХУ, то есть ведёт их этот
|
||||
* цикл, а не соседний. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||
/* ждём молча */
|
||||
} else if (m >= 0x84) { pop_ceil_modif[col] = 0;
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
|
||||
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
||||
} else if (m >= 11 && pop_ceil_fell) {
|
||||
pop_ceil_modif[col] = 10; /* сигнал занят — кадр подождём */
|
||||
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
||||
pop_ceil_modif[col] = 0;
|
||||
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
||||
@@ -2629,6 +2948,7 @@ static void kid_phys(void)
|
||||
fall_accel();
|
||||
fall_speed();
|
||||
determine_col();
|
||||
bump_into_opponent(); /* seg003: безоружный Кид отскакивает от стража */
|
||||
check_collisions(); /* seg004: флаги перекрытия по колонкам ряда */
|
||||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||||
check_action();
|
||||
|
||||
Reference in New Issue
Block a user