Уровни 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:
@@ -29,6 +29,7 @@
|
||||
#define TILE_MIDX 7 /* середина тайла в координатах персонажа */
|
||||
#define SCREENSPACE_X 58 /* = pop_x_bump[FIRST_ONSCREEN_COLUMN] */
|
||||
|
||||
#define TILE_FLOOR 1
|
||||
#define TILE_SPIKE 2
|
||||
#define TILE_GATE 4
|
||||
#define TILE_DOORTOP_FLOOR 7
|
||||
@@ -270,18 +271,28 @@ static uint8_t demo_index; /* текущая запись
|
||||
#define SHADOW_SKILL 3
|
||||
#define SHADOW_HP 4
|
||||
#define SEQ_2_STAND 2
|
||||
#define SEQ_7_FALL 7
|
||||
#define TILE_POTION 10
|
||||
#define TILE_SWORD 22
|
||||
|
||||
/* Таблицы init_shad_N (data:0EEA/0EF2) — ПЕРВЫЕ СЕМЬ полей Char в том же
|
||||
* порядке, в каком их копирует оригинал (`memcpy(&Char, source, 7)`):
|
||||
* frame, x, y, direction, curr_col, curr_row, action.
|
||||
* 5: x 55, y 55, вправо, колонка −1 (ещё за левым краем экрана), ряд 0;
|
||||
* 6: x 81, y 118, вправо, колонка 0, ряд 1 — тень стоит на границе
|
||||
* (1,0)-(1,1), одного осторожного шага хватает, чтобы встать на плиту. */
|
||||
static const uint8_t init_shad_5[7] = { 0x0F, 0x37, 0x37, 0, 0xFF, 0, 0 };
|
||||
static const uint8_t init_shad_6[7] = { 0x0F, 0x51, 0x76, 0, 0x00, 1, 0 };
|
||||
* (1,0)-(1,1), одного осторожного шага хватает, чтобы встать на плиту;
|
||||
* 12: x 81, y 232, вправо, колонка 0, ряд 0 — и это НЕ ошибка данных: 232
|
||||
* лежит НИЖЕ поля (192), поле y беззнаковое, и тень с seq_7 «падает»
|
||||
* из-под комнаты, переполняя байт, то есть влетает в кадр сверху.
|
||||
* Ряд при этом берётся ИЗ ТАБЛИЦЫ (0), а не пересчитывается из y
|
||||
* (иначе был бы −1): семь полей копируются как есть, и «поправить»
|
||||
* их согласованность нельзя (см. ../docs/levels_12_15_plan.md §7.5). */
|
||||
static const uint8_t init_shad_5[7] = { 0x0F, 0x37, 0x37, 0, 0xFF, 0, 0 };
|
||||
static const uint8_t init_shad_6[7] = { 0x0F, 0x51, 0x76, 0, 0x00, 1, 0 };
|
||||
static const uint8_t init_shad_12[7] = { 0x0F, 0x51, 0xE8, 0, 0x00, 0, 0 };
|
||||
|
||||
static void do_init_shad(const uint8_t *tbl, uint8_t room)
|
||||
/* seq — поза, с которой тень входит в сцену: 5/6 встают в стойку, 12 падает. */
|
||||
static void do_init_shad(const uint8_t *tbl, uint8_t room, uint8_t seq)
|
||||
{
|
||||
pop_loadshad();
|
||||
Char.frame = tbl[0];
|
||||
@@ -296,7 +307,7 @@ static void do_init_shad(const uint8_t *tbl, uint8_t room)
|
||||
Char.alive = Kid.alive; /* наследование от Кида, см. шапку */
|
||||
Char.sword = Kid.sword;
|
||||
Char.fall_x = Char.fall_y = 0;
|
||||
pop_char_set_seq(SEQ_2_STAND);
|
||||
pop_char_set_seq(seq);
|
||||
pop_saveshad();
|
||||
|
||||
demo_time = 0;
|
||||
@@ -309,6 +320,25 @@ static void do_init_shad(const uint8_t *tbl, uint8_t room)
|
||||
|
||||
uint8_t pop_check_shadow(uint8_t room) __banked
|
||||
{
|
||||
/* Уровень 12 (seg002:0070) — встреча с тенью в комнате 15. Три отличия
|
||||
* от 5/6, и все три существенные:
|
||||
* — событие одноразовое: после слияния (pop_united_shadow != 0) тень
|
||||
* больше не поднимается ни при каком входе в комнату;
|
||||
* — условие на тайл ОБРАТНОЕ уровню 5: там тень поднимается, ПОКА
|
||||
* зелье на месте, здесь — только когда меча (15,1,0) уже НЕТ, то
|
||||
* есть Кид его подобрал. Меч на месте — return 0, и это не «ничего
|
||||
* не делать»: стража в комнате 15 в данных нет, так что вызывающий
|
||||
* спокойно идёт в pop_guard_enter и убирает слот;
|
||||
* — поза входа не стойка, а ПАДЕНИЕ (seq 7).
|
||||
* pop_shadow_init сбрасываем здесь же: тень начинает «ждать Кида»
|
||||
* заново при каждом подъёме (см. autocontrol_shadow_level12). */
|
||||
if (pop_current_level == SHADOW_FIGHT_LEVEL) {
|
||||
if (pop_united_shadow || room != SHADOW_FIGHT_ROOM) return 0;
|
||||
if (pop_level_tile(room, SHADOW_SWORD_TILEPOS) == TILE_SWORD) return 0;
|
||||
pop_shadow_init = 0;
|
||||
do_init_shad(init_shad_12, room, SEQ_7_FALL);
|
||||
return 1;
|
||||
}
|
||||
/* Уровень 6 (seg002:0090): условий на содержимое комнаты нет вовсе —
|
||||
* тень встаёт при КАЖДОМ входе в комнату 1. Оригинал здесь ещё играет
|
||||
* sound_25_presentation один раз, отмечая это `leveldoor_open = 0x4D`
|
||||
@@ -316,7 +346,7 @@ uint8_t pop_check_shadow(uint8_t room) __banked
|
||||
* переменную портить незачем — TODO вместе со звуком. */
|
||||
if (pop_current_level == SHADOW_STEP_LEVEL) {
|
||||
if (room != SHADOW_STEP_ROOM) return 0;
|
||||
do_init_shad(init_shad_6, room);
|
||||
do_init_shad(init_shad_6, room, SEQ_2_STAND);
|
||||
return 1;
|
||||
}
|
||||
if (pop_current_level != SHADOW_STEAL_LEVEL) return 0;
|
||||
@@ -324,10 +354,108 @@ uint8_t pop_check_shadow(uint8_t room) __banked
|
||||
/* Зелья уже нет — тень своё дело сделала. Возвращаем 1 и здесь: стража
|
||||
* комнаты 24 оригинал не поднимает НИКОГДА (return до enter_guard). */
|
||||
if (pop_level_tile(room, SHADOW_POTION_TILEPOS) != TILE_POTION) return 1;
|
||||
do_init_shad(init_shad_5, room);
|
||||
do_init_shad(init_shad_5, room, SEQ_2_STAND);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* sword_disappears (seg002:0536) — спецсобытие уровня 12: меч, лежащий в
|
||||
* комнате 15, пропадает с карты, как только Кид ушёл ВПРАВО из комнаты 18.
|
||||
* Смысл событийный: дальше по уровню возврата за мечом уже нет, и сцена с
|
||||
* тенью обязана быть однозначной — либо Кид пришёл к ней вооружённым, либо
|
||||
* нет. Модификатор гасим вместе с тайлом (как SDLPoP): ненулевой modif на
|
||||
* полу рисует «фальшивые» узоры.
|
||||
*
|
||||
* Звать при уходе ВПРАВО, ДО смены комнаты, — там же, где level3_set_chkp
|
||||
* (оригинал держит оба в leave_room). */
|
||||
void pop_sword_disappears(void) __banked
|
||||
{
|
||||
uint8_t *m;
|
||||
if (pop_current_level != SHADOW_FIGHT_LEVEL) return;
|
||||
if (Kid.room != SWORD_GONE_ROOM) return;
|
||||
pop_level_set_tile(SHADOW_FIGHT_ROOM, SHADOW_SWORD_TILEPOS, TILE_FLOOR);
|
||||
m = pop_trob_modif(SHADOW_FIGHT_ROOM);
|
||||
if (m) m[SHADOW_SWORD_TILEPOS] = 0;
|
||||
}
|
||||
|
||||
/* meet_Jaffar (seg002:0544) — спецсобытие уровня 13: Кид уходит ВПРАВО из
|
||||
* комнаты 3 и попадает прямо к Джафару. Всё событие — фора: пока
|
||||
* pop_guard_notice_timer идёт, Джафар не встаёт в боевую стойку
|
||||
* (autocontrol_guard_inactive), то есть у Кида есть ~2 секунды достать меч.
|
||||
* В оригинале здесь же играет sound_29_meet_Jaffar — звука у нас нет.
|
||||
*
|
||||
* `pop_leveldoor_open == 0` в условии не косметика: вернувшись к Джафару
|
||||
* после его смерти (дверь уже открыта), форы быть не должно.
|
||||
*
|
||||
* Звать при уходе ВПРАВО, ДО смены комнаты, — рядом с pop_sword_disappears
|
||||
* (оригинал держит оба в одной ветке leave_room). */
|
||||
void pop_meet_jaffar(void) __banked
|
||||
{
|
||||
if (pop_current_level != JAFFAR_LEVEL) return;
|
||||
if (pop_leveldoor_open != 0) return;
|
||||
if (Kid.room != JAFFAR_MEET_ROOM) return;
|
||||
pop_guard_notice_timer = JAFFAR_NOTICE_TIME;
|
||||
}
|
||||
|
||||
/* Jaffar_exit (seg002:0517) — парная половина победы: смерть Джафара сама
|
||||
* дверь НЕ открывает, она лишь ставит pop_leveldoor_open = 2. Дверь
|
||||
* открывает КНОПКА комнаты 24 (tilepos 0 — настоящий opener, проверено по
|
||||
* res2013.bin), «нажимаемая» в момент ухода Кида ВЛЕВО.
|
||||
*
|
||||
* Звать при уходе ВЛЕВО, ДО смены комнаты, — рядом с чекпойнтом уровня 3. */
|
||||
void pop_jaffar_exit(void) __banked
|
||||
{
|
||||
uint8_t *m;
|
||||
if (pop_leveldoor_open != 2) return;
|
||||
m = pop_trob_modif(JAFFAR_EXIT_ROOM);
|
||||
pop_trigger_button(JAFFAR_EXIT_ROOM, JAFFAR_EXIT_TILEPOS,
|
||||
pop_level_tile(JAFFAR_EXIT_ROOM, JAFFAR_EXIT_TILEPOS),
|
||||
m ? m[JAFFAR_EXIT_TILEPOS] : 0);
|
||||
}
|
||||
|
||||
/* on_guard_killed (seg006:1927) — соперник только что потерял последнее HP.
|
||||
* Уровень 13 — единственный, где у этого есть последствия помимо звука:
|
||||
* белая вспышка и pop_leveldoor_open = 2, то есть «выход разрешён» (сама
|
||||
* дверь откроется кнопкой из pop_jaffar_exit).
|
||||
*
|
||||
* Ветку `is_show_time = 1` не портируем: это показ ОСТАВШЕГОСЯ ВРЕМЕНИ, а
|
||||
* часов на 60 минут у нас пока нет вовсе — появятся вместе с таймером игры.
|
||||
* Демо-уровень (0) сюда тоже не входит: его ветка — часть аттракта. */
|
||||
static void on_guard_killed(void)
|
||||
{
|
||||
if (pop_current_level != JAFFAR_LEVEL) return;
|
||||
pop_flash_color = POP_FLASH_WHITE;
|
||||
pop_flash_time = JAFFAR_FLASH_TIME;
|
||||
pop_leveldoor_open = 2;
|
||||
}
|
||||
|
||||
/* check_killed_shadow (seg006:2033) — «убил тень = убил себя». На уровне 12
|
||||
* Кид и тень связаны насмерть: как только один из них перестал быть живым
|
||||
* (alive >= 0), второму отсчитывается 100 урона, то есть гарантированная
|
||||
* смерть, и обоих накрывает белая вспышка.
|
||||
*
|
||||
* Условие оригинала `(Char.charid | Opp.charid) == charid_1_shadow` читается
|
||||
* так: в паре ровно Кид (0) и тень (1) — со стражем или скелетом связи нет.
|
||||
* Оригинал зовёт это ДВАЖДЫ за кадр (из тика Кида и из тика соперника, с
|
||||
* уже переставленными окнами); нам хватает одного вызова на кадр с явным
|
||||
* разбором обеих сторон — окна Char/Opp здесь не нужны, кроме как для
|
||||
* маршрутизации урона внутри pop_take_hp. */
|
||||
void pop_check_killed_shadow(void) __banked
|
||||
{
|
||||
if (pop_current_level != SHADOW_FIGHT_LEVEL) return;
|
||||
if ((uint8_t)(Kid.charid | Guard.charid) != CHARID_1_SHADOW) return;
|
||||
if (Kid.alive < 0 && Guard.alive >= 0) {
|
||||
pop_flash_color = POP_FLASH_WHITE;
|
||||
pop_flash_time = 5;
|
||||
pop_loadkid();
|
||||
pop_take_hp(100);
|
||||
} else if (Guard.alive < 0 && Kid.alive >= 0) {
|
||||
pop_flash_color = POP_FLASH_WHITE;
|
||||
pop_flash_time = 5;
|
||||
pop_loadshad();
|
||||
pop_take_hp(100);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Синтетический ввод (seg002:0706..) --------------------------- */
|
||||
static void move_0_nothing(void)
|
||||
{
|
||||
@@ -399,8 +527,11 @@ static void autocontrol_guard_inactive(void)
|
||||
}
|
||||
}
|
||||
if (can_guard_see_kid) {
|
||||
/* Увидел — переходит в боевую стойку (вниз+вперёд = достать меч). */
|
||||
move_down_forw();
|
||||
/* Увидел — переходит в боевую стойку (вниз+вперёд = достать меч).
|
||||
* Уровень 13: пока идёт фора после встречи (pop_meet_jaffar), Джафар
|
||||
* стоит и ждёт — это его единственное отличие от прочих стражей. */
|
||||
if (pop_current_level != JAFFAR_LEVEL || pop_guard_notice_timer == 0)
|
||||
move_down_forw();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,6 +817,82 @@ static void autocontrol_shadow_level6(void)
|
||||
move_1_forward();
|
||||
}
|
||||
|
||||
/* autocontrol_shadow_level12 (seg002:1184) — единственная тень, с которой
|
||||
* можно и драться, и слиться. Четыре режима подряд, порядок важен.
|
||||
*
|
||||
* 1. «Ждать Кида» (Char.room 15, pop_shadow_init == 0). Пока Кид не дошёл
|
||||
* до x >= 150, тень КАЖДЫЙ КАДР роняется заново — то есть висит в первом
|
||||
* кадре падения у потолка. Дошёл — флаг поднят, падение пущено. Смысл:
|
||||
* Кид обязан УВИДЕТЬ, как она сваливается сверху, а не застать готовую.
|
||||
* 2. У тени вынут меч → обычный боевой ИИ стража. Исключение: если Кид
|
||||
* намеренно убрал клинок (offguard) и тень ещё не отдышалась после
|
||||
* попадания (guard_refrac) — она тоже убирает меч (move_4_down).
|
||||
* 3. У тени меч в ножнах, но Кид ПРИ мече (или не убирал его намеренно):
|
||||
* близко и видно (< 90) — выхватить клинок из стойки (кадр 15); далеко
|
||||
* или не видно — стоять, а если Кид зашёл за спину, попятиться.
|
||||
* 4. Оба без мечей — СЛИЯНИЕ: сойтись ближе 10 и объединиться.
|
||||
*
|
||||
* xdiff инициализируется заведомо большим числом (как в SDLPoP): при
|
||||
* can_guard_see_kid < 2 расстояние не считается вовсе, и ветка «попятиться»
|
||||
* не должна срабатывать на мусоре. */
|
||||
#define FRAME_15_STAND 15
|
||||
#define FRAME_3_START_RUN 3
|
||||
#define FRAME_127_STEPPING_7 127
|
||||
#define FRAME_133_AFTER_STEP 133
|
||||
#define SHADOW_WAIT_KID_X 150
|
||||
#define SHADOW_SWORD_DIST 90
|
||||
|
||||
static void autocontrol_shadow_level12(void)
|
||||
{
|
||||
int16_t distance;
|
||||
|
||||
if (Char.room == SHADOW_FIGHT_ROOM && pop_shadow_init == 0) {
|
||||
if (Opp.x >= SHADOW_WAIT_KID_X) {
|
||||
do_init_shad(init_shad_12, Char.room, SEQ_7_FALL);
|
||||
return;
|
||||
}
|
||||
pop_shadow_init = 1;
|
||||
}
|
||||
if (Char.sword >= SWORD_2_DRAWN) {
|
||||
if (offguard == 0 || guard_refrac == 0) autocontrol_guard_active();
|
||||
else move_4_down();
|
||||
return;
|
||||
}
|
||||
if (Opp.sword >= SWORD_2_DRAWN || offguard == 0) {
|
||||
distance = 0x7000;
|
||||
if (can_guard_see_kid < 2 ||
|
||||
(distance = pop_char_opp_dist()) >= SHADOW_SWORD_DIST) {
|
||||
if (distance < 0) move_2_backward();
|
||||
return;
|
||||
}
|
||||
if (Char.frame == FRAME_15_STAND) move_down_forw();
|
||||
return;
|
||||
}
|
||||
if (pop_char_opp_dist() < SHADOW_UNITE_DIST) {
|
||||
/* Слияние. Кид ПЕРЕЕЗЖАЕТ на место тени: оригинал переписывает
|
||||
* charid активного персонажа (а это тень) в Кида и savekid'ит его
|
||||
* целиком — с позицией, кадром и последовательностью тени. Отсюда
|
||||
* и «дёрганый» шаг в оригинале: Кид оказывается там, где стояла
|
||||
* тень. Слот соперника после этого гасится clear_char. */
|
||||
pop_flash_color = POP_FLASH_WHITE;
|
||||
pop_flash_time = 18;
|
||||
pop_add_life(); /* +1 к потолку HP */
|
||||
pop_united_shadow = SHADOW_UNITE_TIME;
|
||||
Char.charid = CHARID_0_KID;
|
||||
pop_savekid();
|
||||
clear_char();
|
||||
return;
|
||||
}
|
||||
if (can_guard_see_kid == 2) {
|
||||
/* Кид бежит к тени — тень бежит навстречу (кадры бега 3..14 и
|
||||
* шага 127..132). */
|
||||
uint8_t of = Opp.frame;
|
||||
if ((of >= FRAME_3_START_RUN && of < FRAME_15_STAND) ||
|
||||
(of >= FRAME_127_STEPPING_7 && of < FRAME_133_AFTER_STEP))
|
||||
move_1_forward();
|
||||
}
|
||||
}
|
||||
|
||||
/* autocontrol_mouse (seg002:07EB). Весь «ИИ» мыши — два порога по X:
|
||||
* добежала влево до POP_MOUSE_TURN_X → seq_107 (встать, поднять голову,
|
||||
* развернуться и уйти), вернулась вправо до POP_MOUSE_LEAVE_X → исчезнуть.
|
||||
@@ -706,13 +913,13 @@ static void autocontrol_mouse(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* autocontrol_shadow (seg002:081D) — по уровням. Ветка 12 появится со своим
|
||||
* уровнем. */
|
||||
/* autocontrol_shadow (seg002:081D) — по уровням. */
|
||||
static void autocontrol_shadow(void)
|
||||
{
|
||||
if (pop_current_level == MIRROR_LEVEL) autocontrol_shadow_level4();
|
||||
if (pop_current_level == SHADOW_STEAL_LEVEL) autocontrol_shadow_level5();
|
||||
if (pop_current_level == SHADOW_STEP_LEVEL) autocontrol_shadow_level6();
|
||||
if (pop_current_level == SHADOW_FIGHT_LEVEL) autocontrol_shadow_level12();
|
||||
}
|
||||
|
||||
/* autocontrol_guard (seg002:701) + autocontrol_opponent (seg002:628). */
|
||||
@@ -857,8 +1064,7 @@ static void play_guard(void)
|
||||
if (Char.charid == CHARID_24_MOUSE) { autocontrol_opponent(); return; }
|
||||
if (Char.alive < 0 && guardhp_curr == 0) {
|
||||
Char.alive = 0;
|
||||
/* on_guard_killed (seg006:1927) — у нас пока только звук победы,
|
||||
* которого нет; для уровня 1 других эффектов там нет. */
|
||||
on_guard_killed(); /* уровень 13: вспышка + «выход открыт» */
|
||||
}
|
||||
autocontrol_opponent();
|
||||
pop_control();
|
||||
|
||||
Reference in New Issue
Block a user