/* * t_jaffar — спецсобытия УРОВНЯ 13: встреча с Джафаром, победа, выход и * три исключения loose-механики (падающая гряда плит). * * Своего ИИ у Джафара нет — это обычный страж (seg002:085A), поэтому * проверять тут нечего кроме СОБЫТИЙ и ветвлений по номеру уровня. А * именно они и опасны: три из них — это `if (level == 13)` посреди чужого * кода (тряска плит, удар плитой в беге, «страж не встаёт в стойку»), и * сломать их можно правкой, вообще не думая про 13-й уровень. * * Порт: seg002:0544 meet_Jaffar, seg002:0517 Jaffar_exit, * seg006:1927 on_guard_killed, seg000:1317 check_fall_flo, * seg007:823/949 исключения animate_loose/loose_make_shake. */ #include #include "tcheck.h" #include "pop_kid.h" #include "pop_map.h" #include "pop_guard.h" #include "pop_geom.h" #include "pop_state.h" #include "pop_level.h" #include "pop_tune.h" #include "scene.h" #include "stubs.h" #define E 0 /* empty */ #define F 1 /* floor */ #define L 11 /* loose */ #define W 20 /* wall */ /* Комната с полом внизу и грядой loose в ряду 2 — она же будет играть роль * «комнаты сверху» для check_fall_flo. */ static const uint8_t room_loose[30] = { F, F, F, F, F, F, F, F, F, F, W, W, W, W, W, W, W, W, W, W, F, F, L, L, L, L, L, L, F, F, }; /* Комната, в которую Кид входит снизу: ровный пол во всех рядах. */ static const uint8_t room_flat[30] = { F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, }; static uint8_t above_row[10]; static void scene_level13(uint8_t room) { pop_guard_reset(); pop_current_level = JAFFAR_LEVEL; pop_leveldoor_open = 0; pop_guard_notice_timer = 0; sc_room(room_flat, room); sc_kid_at(1, 2, 1); tk_log_clear(); } /* ---- meet_Jaffar ----------------------------------------------------- */ TC_TEST(jaffar_notice_timer_set_leaving_room3_right) { scene_level13(JAFFAR_MEET_ROOM); Kid.room = JAFFAR_MEET_ROOM; pop_meet_jaffar(); TC_EQ(pop_guard_notice_timer, JAFFAR_NOTICE_TIME); } /* Дверь уровня уже открыта — значит Джафар мёртв и Кид возвращается. * Второй форы не даётся. */ TC_TEST(jaffar_notice_not_given_after_victory) { scene_level13(JAFFAR_MEET_ROOM); Kid.room = JAFFAR_MEET_ROOM; pop_leveldoor_open = 2; pop_meet_jaffar(); TC_EQ(pop_guard_notice_timer, 0); } TC_TEST(jaffar_notice_not_given_from_other_room) { scene_level13(JAFFAR_MEET_ROOM); Kid.room = (uint8_t)(JAFFAR_MEET_ROOM + 1); pop_meet_jaffar(); TC_EQ(pop_guard_notice_timer, 0); } TC_TEST(jaffar_notice_is_level_gated) { scene_level13(JAFFAR_MEET_ROOM); pop_current_level = 12; Kid.room = JAFFAR_MEET_ROOM; pop_meet_jaffar(); TC_EQ(pop_guard_notice_timer, 0); } /* Счётчик убывает по кадру и останавливается на нуле. */ TC_TEST(jaffar_notice_timer_counts_down) { uint8_t i; pop_united_shadow = 0; pop_guard_notice_timer = 3; for (i = 0; i < 5; i++) pop_frame_timers(); TC_EQ(pop_guard_notice_timer, 0); } /* ---- Джафар ЖДЁТ, пока идёт фора ------------------------------------- */ /* Поднять соперника с УБРАННЫМ мечом в одном ряду с Кидом и «показать» его: * дальше решение принимает autocontrol_guard_inactive. */ static void jaffar_faces_kid(void) { sc_guard_at(CHARID_2_GUARD, 6, 2, -1, 6); Guard.sword = 0; can_guard_see_kid = 2; is_guard_notice = 0; } TC_TEST(jaffar_holds_still_while_notice_timer_runs) { uint8_t i; scene_level13(1); jaffar_faces_kid(); pop_guard_notice_timer = JAFFAR_NOTICE_TIME; for (i = 0; i < 6; i++) { can_guard_see_kid = 2; pop_guard_tick(); } /* Стойку не сменил: меч так и в ножнах. */ TC_EQ(Guard.sword, 0); } /* Фора кончилась — обычное поведение стража возвращается. */ TC_TEST(jaffar_draws_sword_after_notice_timer) { uint8_t i; scene_level13(1); jaffar_faces_kid(); pop_guard_notice_timer = 0; for (i = 0; i < 12; i++) { can_guard_see_kid = 2; pop_guard_tick(); } TC_EQ(Guard.sword, SWORD_2_DRAWN); } /* На прочих уровнях таймер ни на что не влияет: условие в оригинале * двойное (`level != 13 || timer == 0`), и потерять первую половину легко. */ TC_TEST(jaffar_notice_timer_ignored_on_other_levels) { uint8_t i; scene_level13(1); pop_current_level = 12; jaffar_faces_kid(); pop_guard_notice_timer = JAFFAR_NOTICE_TIME; for (i = 0; i < 12; i++) { can_guard_see_kid = 2; pop_guard_tick(); } TC_EQ(Guard.sword, SWORD_2_DRAWN); } /* ---- победа и выход --------------------------------------------------- */ /* Смерть Джафара не открывает дверь, а ставит флаг «выход разрешён». */ TC_TEST(jaffar_death_opens_the_exit_flag) { scene_level13(1); /* charid у Джафара обычный: своим charid оригинал метит только скелета * (enter_guard, seg002:196), а визиря отличают спрайты и события. */ sc_guard_at(CHARID_2_GUARD, 6, 2, -1, 6); Guard.sword = SWORD_2_DRAWN; guardhp_curr = 0; /* последнее HP только что снято */ pop_flash_time = 0; pop_guard_tick(); TC_EQ(pop_leveldoor_open, 2); TC_EQ(pop_flash_time, JAFFAR_FLASH_TIME); TC_EQ(pop_flash_color, POP_FLASH_WHITE); } /* На других уровнях смерть стража флага не ставит (иначе дверь открывалась * бы от любого убитого). */ TC_TEST(jaffar_death_flag_is_level_gated) { scene_level13(1); pop_current_level = 12; sc_guard_at(CHARID_2_GUARD, 6, 2, -1, 6); Guard.sword = SWORD_2_DRAWN; guardhp_curr = 0; pop_guard_tick(); TC_EQ(pop_leveldoor_open, 0); } /* Уход ВЛЕВО при поднятом флаге жмёт кнопку комнаты 24. */ TC_TEST(jaffar_exit_presses_button_in_room24) { scene_level13(1); tk_room_fg[JAFFAR_EXIT_ROOM][JAFFAR_EXIT_TILEPOS] = 15; /* opener */ pop_leveldoor_open = 2; tk_log_clear(); pop_jaffar_exit(); TC_TRUE(tk_log_count(TK_BUTTON) >= 1); } TC_TEST(jaffar_exit_silent_before_victory) { scene_level13(1); tk_room_fg[JAFFAR_EXIT_ROOM][JAFFAR_EXIT_TILEPOS] = 15; pop_leveldoor_open = 0; tk_log_clear(); pop_jaffar_exit(); TC_EQ(tk_log_count(TK_BUTTON), 0); } /* ---- три исключения loose-механики ----------------------------------- */ /* Сотрясение (приземление Кида) на 13-м уровне плиты НЕ трясёт: иначе оно * сбило бы им отложенный старт, и гряда посыпалась бы разом. */ TC_TEST(jaffar_knock_does_not_shake_tiles) { scene_level13(1); sc_room(room_loose, 1); sc_kid_at(1, 2, 1); Kid.curr_row = 2; pop_loadkid(); knock = -1; /* KNOCK_DOWN: ряд Кида */ pop_check_knock(); TC_EQ(pop_loose_modif[22], 0); TC_EQ(pop_loose_modif[27], 0); } /* А на прочих уровнях трясёт — контроль, что выключен именно 13-й. */ TC_TEST(jaffar_knock_still_shakes_on_other_levels) { scene_level13(1); pop_current_level = 12; sc_room(room_loose, 1); sc_kid_at(1, 2, 1); Kid.curr_row = 2; pop_loadkid(); knock = -1; pop_check_knock(); TC_EQ(pop_loose_modif[22], 0x80); } /* Фаза с ОТРИЦАТЕЛЬНЫМ стартом на 13-м уровне не гасится, а досчитывает * до провала: это и есть падающая гряда. Проверяем на плите ПОЛА (тот же * код, что у плит-потолков, но без mob-обвязки, которая в набор не * линкуется). */ TC_TEST(jaffar_negative_phase_counts_through_to_fall) { uint8_t i; scene_level13(1); sc_room(room_loose, 1); sc_kid_at(1, 0, 1); pop_loose_modif[22] = (uint8_t)-4; /* старт «через 4 кадра» */ /* 4 кадра — на выход из отрицательной зоны, дальше 10 — обычный отсчёт; * 30 с запасом. Ванильная ветка «тряска кончилась» обнулила бы фазу на * первом же кадре, и плита осталась бы стоять. */ for (i = 0; i < 30; i++) pop_loose_tick(); TC_EQ(pop_tile_at(2, 2), 0); /* провалилась: тайл стал пустым */ } /* На прочих уровнях та же фаза читается как тряска и гаснет — плита цела. */ TC_TEST(jaffar_negative_phase_is_shake_on_other_levels) { uint8_t i; scene_level13(1); pop_current_level = 12; sc_room(room_loose, 1); sc_kid_at(1, 0, 1); pop_loose_modif[22] = (uint8_t)-4; for (i = 0; i < 30; i++) pop_loose_tick(); TC_EQ(pop_tile_at(2, 2), L); TC_EQ(pop_loose_modif[22], 0); } /* ---- check_fall_flo --------------------------------------------------- */ /* Вход в комнату 23: шести плитам ряда 2 комнаты СВЕРХУ раздаётся * отрицательная фаза, остальным — ничего. */ static void fall_flo_scene(uint8_t room) { uint8_t i; scene_level13(room); for (i = 0; i < 10; i++) above_row[i] = room_loose[20 + i]; pop_map_set_above(above_row); pop_map_set_edges(0, 0, 1 /* up */, 0); pop_map_set_above(above_row); pop_loose_reset(); } TC_TEST(jaffar_fall_flo_seeds_the_row_above) { uint8_t c, seeded = 0; fall_flo_scene(POP_LOOSE_TILES_ROOM1); pop_check_fall_flo(); for (c = 2; c <= 7; c++) if ((int8_t)pop_ceil_modif[c] < 0) seeded++; TC_EQ(seeded, 6); /* Колонки вне диапазона 2..7 не тронуты (там в комнате пол, не loose). */ TC_EQ(pop_ceil_modif[0], 0); TC_EQ(pop_ceil_modif[9], 0); } TC_TEST(jaffar_fall_flo_ignores_other_rooms) { uint8_t c; fall_flo_scene((uint8_t)(POP_LOOSE_TILES_ROOM1 + 1)); pop_check_fall_flo(); for (c = 0; c < 10; c++) TC_EQ(pop_ceil_modif[c], 0); } TC_TEST(jaffar_fall_flo_is_level_gated) { uint8_t c; fall_flo_scene(POP_LOOSE_TILES_ROOM2); pop_current_level = 12; pop_check_fall_flo(); for (c = 0; c < 10; c++) TC_EQ(pop_ceil_modif[c], 0); } /* Повторный вход не перебивает уже идущий отсчёт (иначе гряда «зависала» * бы, пока игрок бегает туда-сюда). */ TC_TEST(jaffar_fall_flo_does_not_restart_running_countdown) { fall_flo_scene(POP_LOOSE_TILES_ROOM2); pop_ceil_modif[3] = 5; /* плита уже падает */ pop_check_fall_flo(); TC_EQ(pop_ceil_modif[3], 5); } void main(void) { sc_init(); TC_RUN(jaffar_notice_timer_set_leaving_room3_right); TC_RUN(jaffar_notice_not_given_after_victory); TC_RUN(jaffar_notice_not_given_from_other_room); TC_RUN(jaffar_notice_is_level_gated); TC_RUN(jaffar_notice_timer_counts_down); TC_RUN(jaffar_holds_still_while_notice_timer_runs); TC_RUN(jaffar_draws_sword_after_notice_timer); TC_RUN(jaffar_notice_timer_ignored_on_other_levels); TC_RUN(jaffar_death_opens_the_exit_flag); TC_RUN(jaffar_death_flag_is_level_gated); TC_RUN(jaffar_exit_presses_button_in_room24); TC_RUN(jaffar_exit_silent_before_victory); TC_RUN(jaffar_knock_does_not_shake_tiles); TC_RUN(jaffar_knock_still_shakes_on_other_levels); TC_RUN(jaffar_negative_phase_counts_through_to_fall); TC_RUN(jaffar_negative_phase_is_shake_on_other_levels); TC_RUN(jaffar_fall_flo_seeds_the_row_above); TC_RUN(jaffar_fall_flo_ignores_other_rooms); TC_RUN(jaffar_fall_flo_is_level_gated); TC_RUN(jaffar_fall_flo_does_not_restart_running_countdown); }