Files
Sprinter-SDCC/applications/PoP/roomtest/pop_guard_cold.c
T
snark13 67d62e3e8b Уровни 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>
2026-08-13 18:21:52 +03:00

173 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_guard_cold.c — переходы стража МЕЖДУ КОМНАТАМИ (банк 8): страж идёт
* за Кидом (follow_guard, seg002:039E) и вход стража в комнату
* (enter_guard, seg002:0112 + pos_guards, seg003).
*
* Обе зовутся РАЗ НА КОМНАТУ и обе — из roomtest_cold.c, который лежит в
* том же банке 8: значит в резиденте им делать нечего, а трамплин на
* вызове не появляется (объявления в pop_guard.h без __banked; условие
* корректности — оба модуля в одном банке, см. _pop_kdraw.h про ту же
* связку). Состояние стража (Guard, guard_skill, …) живёт в pop_guard.c
* и видно отсюда как обычные глобалы: данные банков — в общем _DATA.
*/
#include <stdint.h>
#include "pop_guard.h"
#include "pop_geom.h"
#include "pop_level.h"
#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */
#include "pop_map.h"
#include "pop_state.h"
#include "pop_tile.h" /* POP_TILE_DIV — колонка по X без деления */
#include "kid_data.h"
static int8_t guard_col_from_x(uint8_t xpos); /* определена ниже */
/* ---- Страж ИДЁТ ЗА КИДОМ в соседнюю комнату ------------------------- *
* Порт ветки стража из exit_room (seg002:0340..0382) + follow_guard
* (seg002:039E). Своим ходом персонаж слота Guard из комнаты не уходит
* (его физика гейтится x ∈ [44,211), см. pop_guard_phys_tick), но когда
* комнату меняет КИД, движок может перенести стража следом — именно так в
* оригинале страж из комнаты 3 оказывается в комнате 2 за отступающим
* Кидом.
*
* Условия оригинала, все обязательные:
* - страж есть, ЖИВ и с ВЫНУТЫМ МЕЧОМ (то есть идёт бой);
* - переход ВБОК: вверх и вниз оригинал не пускает никогда (там условия
* `curr_row >= 0` и `curr_row < 3` истинны всегда → leave_guard);
* - в целевой комнате НЕТ своего ЖИВОГО стража (иначе их стало бы два:
* проверка `guards_tile >= 30 || guards_seq_hi != 0` — «пусто или
* труп»);
* - страж прижат к тому краю, в который ушёл Кид: x < 91 при уходе
* влево, x >= 165 при уходе вправо.
*
* side — куда ушёл КИД (0 = left, 1 = right, 2 = up, 3 = down), то есть
* roomleave_result оригинала. Возврат: 1 — страж перенесён в new_room
* (вызывающий НЕ должен звать ни pop_guard_leave, ни pop_guard_enter),
* 0 — остаётся, всё как раньше. */
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side)
{
uint8_t tile, color, skill;
int8_t dir;
if (Guard.charid == 0) return 0; /* dir_56_none */
if (Guard.alive >= 0 || Guard.sword != SWORD_2_DRAWN) return 0;
if (side > 1) return 0; /* только вбок */
/* «В целевой комнате уже есть живой страж» = страж по данным есть И это
* не запомненный труп (у трупа сохранена последовательность). */
if (pop_level_guard(new_room, &tile, &dir, &color, &skill) &&
pop_guard_state_seq(new_room) == 0) return 0;
if (side == 0) { if (Guard.x >= 91) return 0; } /* Кид ушёл влево */
else { if (Guard.x < 165) return 0; } /* Кид ушёл вправо */
/* follow_guard: сначала «стража нет» в ОБЕИХ комнатах (в оригинале
* guards_tile = 0xFF). Без этого он раздвоится: в старой комнате его
* поднимут из данных при возврате, а в новой — при следующем входе. */
pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0);
pop_guard_state_save(new_room, 30, 0, 0, 0, 0, 0);
/* goto_other_room (seg002:0486) для стража: комната + перенос X через
* шов (влево +140, вправо −140). Ряд при боковом переходе не меняется;
* колонку пересчитываем из новой X, как enter_guard. */
Guard.room = new_room;
Guard.x = (uint8_t)(side == 0 ? Guard.x + 140 : Guard.x - 140);
Guard.curr_col = guard_col_from_x(Guard.x);
return 1;
}
/* get_tile_div_mod_m7 (seg006:697): колонка комнаты по X персонажа —
* floor((xpos 7 58) / 14), где 58 = pop_x_bump[FIRST_ONSCREEN_COLUMN]. */
static int8_t guard_col_from_x(uint8_t xpos)
{
/* Таблицей (резидентная POP_TILE_DIV: −58 уже внутри значений) — тот же
* приём, что в tile_at_kid/get_tile_div_mod. Здесь он кроет ПОЧТИ всё:
* xpos — байт, так что медленный хвост остаётся только для xpos < 7. */
if (xpos >= 7) return POP_TILE_DIV[xpos - 7];
{
int16_t x = (int16_t)xpos - 7 - pop_x_bump[FIRST_ONSCREEN_COLUMN];
int16_t col = x / TILE_SIZEX;
if (x % TILE_SIZEX < 0) --col;
return (int8_t)col;
}
}
void pop_guard_enter(uint8_t room)
{
uint8_t tile, color, skill;
int8_t dir;
pop_guard_reset();
/* load_lev_spr (seg000:1112): tbl_guard_type == 1 — стражей на уровне
* НЕТ вообще (14/15), их графика даже не грузится. Без этой проверки
* на таком уровне полезли бы стражи из данных комнат. */
if (pop_level_guard_type(pop_current_level) < 0) return;
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
/* Цвет — ДО отрисовки комнаты (как redraw_screen, seg003:255): слоты
* палитры общие на экран, и смена посреди кадра дала бы стража в новой
* палитре с полосой HP в старой.
*
* Цвет из данных комнаты берётся ТОЛЬКО у обычного стража (enter_guard,
* seg002:183: `curr_guard_color = 0`, если tbl_guard_type != 0). У
* скелета и Джафара палитра своя, из файла набора, и подменять её
* нельзя — иначе визирь на уровне 13 покрасился бы в цвет стража,
* записанный в его комнате. */
pop_guard_set_palette(pop_level_guard_type(pop_current_level) == 0
? color : 0);
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
* колонки тайла, а не берётся из файла. */
Guard.room = room;
Guard.curr_row = (int8_t)(tile / 10);
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
Guard.curr_col = (int8_t)(tile % 10);
Guard.direction = dir;
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
* покоя (seq_77). Стойку ставит ветка ниже, вместе с seq трупа. */
Guard.charid = (pop_level_guard_type(pop_current_level) == 2)
? CHARID_4_SKELETON : CHARID_2_GUARD;
Guard.sword = (Guard.charid == CHARID_4_SKELETON)
? SWORD_2_DRAWN : 0; /* sword_0_sheathed */
Guard.alive = -1;
Guard.action = 1; /* actions_1_run_jump */
Guard.fall_x = Guard.fall_y = 0;
/* enter_guard (seg002:214): мастерство из данных уровня, 12 градаций;
* всё, что вне диапазона, оригинал сводит к 3. */
guard_skill = (uint8_t)(skill < NUM_GUARD_SKILLS ? skill : 3);
/* get_guard_hp (seg002:54): extrastrength[skill] + tbl_guard_hp[уровень].
* extrastrength (seg002:38) ненулевой только у skill 4 (=1). */
guardhp_max = guardhp_curr =
(uint8_t)(pop_level_guard_hp(pop_current_level) + (guard_skill == 4 ? 1 : 0));
guardhp_delta = 0;
/* enter_guard (seg002:0208). Живого поднимаем стандартной стойкой; у
* ЗАПОМНЕННОГО МЁРТВОГО берём сохранённую последовательность — он лежит
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
{
uint16_t saved_seq = pop_guard_state_seq(room);
/* У ЗАПОМНЕННОГО трупа X берём сохранённую: pos_guards (seg003:620)
* пересчитывает её из колонки только при загрузке уровня, а дальше
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
* тайла. */
if (saved_seq) Guard.x = pop_guard_state_x(room);
/* ...и КОЛОНКУ пересчитываем из X (enter_guard, seg002:180:
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Брать её из
* тайла нельзя: leave_guard сохраняет get_tilepos(0, row), то
* есть колонку 0 всегда. У живого стража это незаметно (X там
* сам считается из колонки), а у ЗАПОМНЕННОГО ТРУПА X
* настоящая, и curr_col оставался нулём при теле, лежащем в
* колонке 2. Тайл трупа шёл в обходе раньше тайла Кида, и Кид
* рисовался ПОВЕРХ тела вместо того, чтобы пройти за ним
* (BUG-DRAWORDER-1). Колонка нужна не только отрисовке — на
* неё смотрят коллизия и check_can_guard_see_kid. */
Guard.curr_col = guard_col_from_x(Guard.x);
pop_loadshad();
if (saved_seq) Char.curr_seq = saved_seq;
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
pop_saveshad();
pop_guard_play(); /* до первого кадра */
/* enter_guard (seg002:218): кадр смерти -> страж поднят ТРУПОМ. */
if (Guard.frame == 185 || Guard.frame == 177 || Guard.frame == 178) {
Guard.alive = 1;
guardhp_curr = 0;
}
}
}