6673279cef
Скелет (L3-SKEL, ассеты + механика): - pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3 curr_guard_color = 0, оригинал палитру не подменяет); - pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом стража и был невидим; - load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень — только в кадрах 150..189. Пока ветка была одна (charid_2_guard), скелет получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался; - check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в комнате 3 при падении (seg002:252), autocontrol_skeleton; - leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня. Цвета стражей (BUG-GUARD-COLOR-1, закрыт): - все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257). Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP меняется вместе со стражем. Грабля: gfx_pal_load отдаёт указатель в BIOS, а тот читает только #4000-#BFFF — таблицу из банка копируем в стек. Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт): - pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19. Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать обратно. +48 байт W2. Окклюзия соперника: - pop_fore_over_char получил проход other_overlay_tile (порядок midtable, seg008:1B06) и расширение перебора объединённым прямоугольником «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх кладки и верхней грани пола; - клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP; - ROOMNAV после смерти Кида делает честный pop_start_level: телепорт «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с вернувшейся кучей костей. Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком (101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) — render_room анимированные тайлы пропускает, и в атласе не было ни одного. Число EMM-страниц не изменилось. Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок (резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен гард «код наехал на данные» (DATA_LOC). Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3: level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT). В roomtest.c временно оставлен автостоп на падении соперника (отладка падений скелета) — помечен ВРЕМЕННО. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
461 lines
21 KiB
C
461 lines
21 KiB
C
/*
|
||
* t_char — физика над Char для РАЗНЫХ ПЕРСОНАЖЕЙ.
|
||
*
|
||
* Зачем отдельный набор. После GUARD-PHYS физика — один набор функций над
|
||
* глобалом `Char`, а кто именно в Char, решают окна loadkid/savekid и
|
||
* loadshad/saveshad (как в оригинале, seg006:809). В слоте Guard живёт не
|
||
* только страж: тем же кодом ходят скелет (ур. 3), тень (4/5/6/12), визирь
|
||
* (13) и мышь (8) — `autocontrol_opponent` (seg002:628) разводит их только
|
||
* по ИИ, а play_guard_frame у всех общий. Значит расхождения «у Кида так,
|
||
* у прочих иначе» — это ВЕТКИ ПО charid внутри общих функций, и проверять
|
||
* их надо именно так: одна сцена, разные персонажи.
|
||
*
|
||
* t_phys страхует, что перенос ничего не сломал КИДУ (характеризационные
|
||
* трассы). Здесь — противоположное: что не-Кид получает СВОЮ ветку.
|
||
*
|
||
* Проверки точечные (значение поля после N кадров), а не трассы: ветки
|
||
* различаются исходом, а не траекторией, и эталон тут только запутал бы.
|
||
*/
|
||
#include <stdint.h>
|
||
|
||
#include "tcheck.h"
|
||
#include "pop_kid.h"
|
||
#include "pop_map.h"
|
||
#include "pop_guard.h"
|
||
#include "pop_geom.h"
|
||
#include "pop_state.h"
|
||
#include "scene.h"
|
||
#include "stubs.h"
|
||
|
||
#define E 0 /* empty */
|
||
#define F 1 /* floor */
|
||
#define W 20 /* wall */
|
||
|
||
/* Ровный пол на ряду 1 (как room_flat в t_phys). */
|
||
static const uint8_t room_flat[30] = {
|
||
W, E, E, E, E, E, E, E, E, E,
|
||
W, F, F, F, F, F, F, F, F, F,
|
||
W, W, W, W, W, W, W, W, W, W,
|
||
};
|
||
|
||
/* Пол ряда 0 обрывается после колонки 3; ряд 2 — пол. Персонаж, сошедший
|
||
* с кромки (0,3), падает ДВА ряда: ровно та высота, на которой ветки
|
||
* land() по charid расходятся (seg005:189). */
|
||
static const uint8_t room_drop2[30] = {
|
||
W, F, F, F, E, E, E, E, E, E,
|
||
W, E, E, E, E, E, E, E, E, E,
|
||
W, F, F, F, F, F, F, F, F, F,
|
||
};
|
||
|
||
/* ---- take_hp: кому достаётся урон ----------------------------------- */
|
||
|
||
TC_TEST(char_take_hp_goes_to_active)
|
||
{
|
||
sc_room(room_flat, 1);
|
||
sc_kid_at(3, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 6, 1, -1, 3);
|
||
|
||
/* Окно Кида: урон обязан уйти в hitp_delta и не тронуть стража. */
|
||
pop_loadkid();
|
||
hitp_delta = 0; guardhp_delta = 0;
|
||
tc_check(pop_take_hp(1), 0, "kid: не последнее HP", __LINE__);
|
||
tc_check(hitp_delta, -1, "kid: hitp_delta", __LINE__);
|
||
tc_check(guardhp_delta, 0, "kid: страж не тронут", __LINE__);
|
||
pop_savekid();
|
||
|
||
/* Окно стража: тот же вызов — уже в guardhp_delta. */
|
||
pop_loadshad();
|
||
hitp_delta = 0; guardhp_delta = 0;
|
||
tc_check(pop_take_hp(1), 0, "guard: не последнее HP", __LINE__);
|
||
tc_check(guardhp_delta, -1, "guard: guardhp_delta", __LINE__);
|
||
tc_check(hitp_delta, 0, "guard: Кид не тронут", __LINE__);
|
||
/* Смертельный удар: возврат 1 и дельта на всё оставшееся. */
|
||
guardhp_delta = 0;
|
||
tc_check(pop_take_hp(100), 1, "guard: смертельный удар", __LINE__);
|
||
tc_check(guardhp_delta, -3, "guard: дельта на все HP", __LINE__);
|
||
pop_saveshad();
|
||
}
|
||
|
||
/* Чит бессмертия — только Киду: страж обязан гибнуть и с ним (иначе
|
||
* отладочный режим ломает боёвку). */
|
||
TC_TEST(char_immortal_covers_kid_only)
|
||
{
|
||
sc_room(room_flat, 1);
|
||
sc_kid_at(3, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 6, 1, -1, 3);
|
||
pop_immortal = 1;
|
||
|
||
pop_loadkid();
|
||
hitp_delta = 0;
|
||
tc_check(pop_take_hp(100), 0, "kid: бессмертен", __LINE__);
|
||
tc_check(hitp_delta, 0, "kid: HP не тронуто", __LINE__);
|
||
pop_savekid();
|
||
|
||
pop_loadshad();
|
||
guardhp_delta = 0;
|
||
tc_check(pop_take_hp(100), 1, "guard: чит на него не действует", __LINE__);
|
||
pop_saveshad();
|
||
|
||
pop_immortal = 0;
|
||
}
|
||
|
||
/* ---- do_delta_hp: дельты применяются каждому своему счётчику --------- */
|
||
|
||
TC_TEST(char_delta_hp_applies_per_character)
|
||
{
|
||
sc_room(room_flat, 1);
|
||
sc_kid_at(3, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 6, 1, -1, 3);
|
||
|
||
hitp_delta = -1;
|
||
guardhp_delta = -2;
|
||
pop_kid_hurt = 0;
|
||
pop_guard_hurt = 0;
|
||
pop_do_delta_hp();
|
||
|
||
tc_check(hitp_curr, 2, "kid: HP 3-1", __LINE__);
|
||
tc_check(guardhp_curr, 1, "guard: HP 3-2", __LINE__);
|
||
/* Флаги «брызг» — по одному на персонажа, и только на УРОН. */
|
||
tc_check(pop_kid_hurt, 1, "kid: брызги", __LINE__);
|
||
tc_check(pop_guard_hurt, 1, "guard: брызги", __LINE__);
|
||
|
||
/* Плюсовая дельта (зелье) брызг и вспышки давать НЕ должна. */
|
||
pop_kid_hurt = 0;
|
||
hitp_delta = 1;
|
||
pop_do_delta_hp();
|
||
tc_check(hitp_curr, 3, "kid: зелье вернуло HP", __LINE__);
|
||
tc_check(pop_kid_hurt, 0, "kid: зелье — не урон", __LINE__);
|
||
}
|
||
|
||
/* ---- land(): ветки по charid (seg005:173) --------------------------- */
|
||
|
||
/* Кид с двух рядов теряет 1 HP и остаётся жив. */
|
||
TC_TEST(char_kid_survives_two_row_fall)
|
||
{
|
||
sc_room(room_drop2, 1);
|
||
sc_kid_at(3, 0, 0);
|
||
sc_trace_clear();
|
||
sc_run(SC_R, 30); /* шагнуть с кромки и упасть */
|
||
|
||
pop_do_delta_hp();
|
||
tc_check(Kid.curr_row, 2, "kid: приземлился на ряд 2", __LINE__);
|
||
tc_check(hitp_curr, 2, "kid: −1 HP за два ряда", __LINE__);
|
||
tc_check(pop_kid_dead, 0, "kid: жив", __LINE__);
|
||
}
|
||
|
||
/* СТРАЖ с той же высоты гибнет: seg005:190 уводит charid_2_guard прямо в
|
||
* take_hp(100). Это не «баланс», а буквальная ветка оригинала. */
|
||
TC_TEST(char_guard_dies_from_two_row_fall)
|
||
{
|
||
sc_room(room_drop2, 1);
|
||
sc_kid_at(1, 2, 0); /* Кид в стороне, не мешает */
|
||
/* Ставим сразу НАД ПУСТОТОЙ: ИИ в наборе нет, сам с кромки страж не
|
||
* сойдёт, а физике довольно того, что под ним не пол. Колонка 5, а не
|
||
* 4: решение принимается по ВЕСОВОЙ ТОЧКЕ кадра (dx_weight, seg006),
|
||
* а она у стойки стража уходит на 9 px назад — с колонки 4 точка
|
||
* попадала бы ещё на пол колонки 3. */
|
||
sc_guard_at(CHARID_2_GUARD, 5, 0, 0, 3);
|
||
sc_guard_run(30);
|
||
|
||
pop_do_delta_hp();
|
||
tc_check(Guard.curr_row, 2, "guard: приземлился на ряд 2", __LINE__);
|
||
tc_check(guardhp_curr, 0, "guard: два ряда = смерть", __LINE__);
|
||
}
|
||
|
||
/* ТЕНЬ (charid_1_shadow) с тех же двух рядов не теряет ничего: seg005:189
|
||
* уводит её в ветку «упал с одного ряда». */
|
||
TC_TEST(char_shadow_survives_two_row_fall)
|
||
{
|
||
sc_room(room_drop2, 1);
|
||
sc_kid_at(1, 2, 0);
|
||
sc_guard_at(CHARID_1_SHADOW, 5, 0, 0, 3);
|
||
sc_guard_run(30);
|
||
|
||
pop_do_delta_hp();
|
||
tc_check(Guard.curr_row, 2, "shadow: приземлилась на ряд 2", __LINE__);
|
||
tc_check(guardhp_curr, 3, "shadow: HP не тронуто", __LINE__);
|
||
}
|
||
|
||
/* ---- физика слота Guard вообще работает ----------------------------- */
|
||
|
||
/* До GUARD-PHYS у стража не было ни fall_accel, ни check_action: сойдя с
|
||
* кромки, он просто висел в воздухе. Тест ловит именно это. */
|
||
TC_TEST(char_guard_falls_at_all)
|
||
{
|
||
uint8_t y0;
|
||
sc_room(room_drop2, 1);
|
||
sc_kid_at(1, 2, 0);
|
||
sc_guard_at(CHARID_2_GUARD, 5, 0, 0, 3);
|
||
y0 = Guard.y;
|
||
|
||
sc_guard_run(6);
|
||
tc_check(Guard.y > y0, 1, "guard: пошёл вниз", __LINE__);
|
||
}
|
||
|
||
/* Гард по X (seg000:1254): за пределами комнаты физика молчит — именно так
|
||
* оригинал не выпускает стража из комнаты. */
|
||
TC_TEST(char_guard_physics_gated_by_x)
|
||
{
|
||
uint8_t y0;
|
||
sc_room(room_drop2, 1);
|
||
sc_kid_at(1, 2, 0);
|
||
sc_guard_at(CHARID_2_GUARD, 5, 0, 0, 3);
|
||
Guard.x = 220; /* правее 211 — вне комнаты */
|
||
y0 = Guard.y;
|
||
|
||
sc_guard_run(6);
|
||
tc_check(Guard.y, y0, "guard вне комнаты: физика не трогает", __LINE__);
|
||
}
|
||
|
||
/* check_guard_fallout (seg002:0241): провалившегося ниже комнаты убирают
|
||
* совсем — в комнату снизу он не переходит. */
|
||
TC_TEST(char_guard_fallout_removes_him)
|
||
{
|
||
sc_room(room_drop2, 1);
|
||
sc_kid_at(1, 2, 0);
|
||
sc_guard_at(CHARID_2_GUARD, 5, 0, 0, 3);
|
||
|
||
Guard.y = 211;
|
||
pop_guard_fallout();
|
||
tc_check(Guard.charid, 0, "guard: убран из комнаты", __LINE__);
|
||
tc_check(guardhp_curr, 0, "guard: HP обнулены", __LINE__);
|
||
}
|
||
|
||
/* ---- окна Char не путают персонажей ---------------------------------- */
|
||
|
||
/* Регресс на класс ошибок, ради которого затевался перенос: физика Кида не
|
||
* должна трогать стража, и наоборот. Гоняем оба слота в одной сцене. */
|
||
TC_TEST(char_windows_do_not_leak)
|
||
{
|
||
uint8_t gx, gy;
|
||
sc_room(room_flat, 1);
|
||
sc_kid_at(3, 1, 0);
|
||
sc_guard_at(CHARID_2_GUARD, 7, 1, -1, 3);
|
||
gx = Guard.x; gy = Guard.y;
|
||
|
||
sc_run(SC_R, 10); /* Кид бежит вправо */
|
||
|
||
tc_check(Guard.x, gx, "страж не поехал за Кидом", __LINE__);
|
||
tc_check(Guard.y, gy, "страж остался на своей высоте", __LINE__);
|
||
tc_check(Guard.charid, CHARID_2_GUARD, "страж не подменён Кидом", __LINE__);
|
||
tc_check(Kid.charid, CHARID_0_KID, "Кид не подменён стражем", __LINE__);
|
||
}
|
||
|
||
|
||
/* ---- follow_guard: страж идёт ЗА КИДОМ вбок (seg002:0340/039E) ------- *
|
||
* Своим ходом персонаж слота Guard комнату не меняет, но когда её меняет
|
||
* КИД, exit_room может перенести стража следом. Здесь проверяются все
|
||
* условия отбора — они и есть механика. Порт зовётся из enter_room, а
|
||
* тестируем прямо pop_guard_follow: сцена комнат в наборе одна. */
|
||
|
||
/* Базовый случай: бой, Кид ушёл ВЛЕВО, страж прижат к левому краю (x < 91),
|
||
* в комнате 2 своего стража нет → идёт следом, X переносится через шов. */
|
||
TC_TEST(char_follow_guard_left)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(1, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 2, 1, -1, 3);
|
||
Guard.x = 80; /* < 91: у левого края */
|
||
Guard.room = 3;
|
||
|
||
tc_check(pop_guard_follow(2, 0), 1, "страж пошёл следом", __LINE__);
|
||
tc_check(Guard.room, 2, "страж в новой комнате", __LINE__);
|
||
tc_check(Guard.x, 80 + 140, "X перенесён через шов (+140)", __LINE__);
|
||
/* follow_guard стирает guards_tile ОБЕИХ комнат — иначе страж
|
||
* раздвоится: старую поднимут при возврате, новую при входе. */
|
||
tc_check(tk_gs_tile[3 - 1], 30, "в старой комнате стража нет", __LINE__);
|
||
tc_check(tk_gs_tile[2 - 1], 30, "в новой комнате нет второго", __LINE__);
|
||
}
|
||
|
||
/* Тот же бой, но страж ДАЛЕКО от края (x >= 91) — остаётся. */
|
||
TC_TEST(char_follow_guard_needs_edge)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(1, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 5, 1, -1, 3);
|
||
Guard.x = 120; /* >= 91 */
|
||
Guard.room = 3;
|
||
|
||
tc_check(pop_guard_follow(2, 0), 0, "далеко от края — остаётся", __LINE__);
|
||
tc_check(Guard.room, 3, "комната не менялась", __LINE__);
|
||
}
|
||
|
||
/* Порог для ухода ВПРАВО — свой (x >= 165). */
|
||
TC_TEST(char_follow_guard_right_threshold)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(8, 1, 0);
|
||
sc_guard_at(CHARID_2_GUARD, 7, 1, 0, 3);
|
||
Guard.room = 3;
|
||
|
||
Guard.x = 160; /* < 165 — не идёт */
|
||
tc_check(pop_guard_follow(4, 1), 0, "x=160: остаётся", __LINE__);
|
||
Guard.x = 170; /* >= 165 — идёт */
|
||
tc_check(pop_guard_follow(4, 1), 1, "x=170: идёт следом", __LINE__);
|
||
tc_check(Guard.x, 170 - 140, "X перенесён через шов (−140)", __LINE__);
|
||
}
|
||
|
||
/* Не бой — не идёт. Меч в ножнах = страж Кида не преследует. */
|
||
TC_TEST(char_follow_guard_needs_sword_drawn)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(1, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 2, 1, -1, 3);
|
||
Guard.x = 80;
|
||
Guard.room = 3;
|
||
Guard.sword = 0; /* sword_0_sheathed */
|
||
|
||
tc_check(pop_guard_follow(2, 0), 0, "без меча — остаётся", __LINE__);
|
||
}
|
||
|
||
/* Мёртвый (и умирающий) не идёт никуда. */
|
||
TC_TEST(char_follow_guard_dead_stays)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(1, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 2, 1, -1, 3);
|
||
Guard.x = 80;
|
||
Guard.room = 3;
|
||
Guard.alive = 0; /* alive >= 0 — умирает/мёртв */
|
||
|
||
tc_check(pop_guard_follow(2, 0), 0, "мёртвый остаётся", __LINE__);
|
||
}
|
||
|
||
/* Вверх и вниз оригинал не пускает никогда (условия leave всегда истинны). */
|
||
TC_TEST(char_follow_guard_never_vertical)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(1, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 2, 1, -1, 3);
|
||
Guard.x = 80;
|
||
Guard.room = 3;
|
||
|
||
tc_check(pop_guard_follow(2, 2), 0, "вверх — не идёт", __LINE__);
|
||
tc_check(pop_guard_follow(2, 3), 0, "вниз — не идёт", __LINE__);
|
||
tc_check(Guard.room, 3, "комната не менялась", __LINE__);
|
||
}
|
||
|
||
/* В целевой комнате СВОЙ живой страж — перехода нет (иначе их станет два). */
|
||
TC_TEST(char_follow_guard_blocked_by_own_guard)
|
||
{
|
||
tk_guard_state_clear();
|
||
sc_room(room_flat, 3);
|
||
sc_kid_at(1, 1, -1);
|
||
sc_guard_at(CHARID_2_GUARD, 2, 1, -1, 3);
|
||
Guard.x = 80;
|
||
Guard.room = 3;
|
||
tk_guard_room = 2; /* в комнате 2 есть свой страж */
|
||
|
||
tc_check(pop_guard_follow(2, 0), 0, "чужая комната занята", __LINE__);
|
||
tc_check(Guard.room, 3, "остался у себя", __LINE__);
|
||
|
||
/* А если там ТРУП (запомнена последовательность) — идти можно. */
|
||
tk_gs_tile[2 - 1] = 11;
|
||
tk_gs_seq[2 - 1] = 0x1234;
|
||
tc_check(pop_guard_follow(2, 0), 1, "к трупу — можно", __LINE__);
|
||
}
|
||
|
||
/* Тайлы СОСЕДНИХ комнат: колонка персонажа считается из x и на диапазоне
|
||
* x 0..255 лежит в −5..13, то есть регулярно уходит за комнату глубже кромки.
|
||
* Там get_tile обязан отдавать настоящий тайл соседа — мнимая стена стоила
|
||
* BUG-SWORD-GHOST-1 (страж после follow_guard оказывался в col 12, луч
|
||
* видимости упирался в неё, и Кид посреди боя прятал меч, а достать обратно
|
||
* уже не мог: draw_sword требует can_guard_see_kid >= 2). */
|
||
static void char_tiles_resolve_across_rooms(void)
|
||
{
|
||
static const uint8_t floor30[30] = {
|
||
E,E,E,E,E,E,E,E,E,E,
|
||
E,E,E,E,E,E,E,E,E,E,
|
||
F,F,F,F,F,F,F,F,F,F,
|
||
};
|
||
uint8_t i;
|
||
/* Комнаты-соседи «уровня»: карта заберёт их fg сама (pop_map_set_edges). */
|
||
for (i = 0; i < 30; i++) { tk_room_fg[4][i] = F; tk_room_fg[6][i] = F; }
|
||
tk_room_fg[4][10 + 5] = W; /* сосед слева: ряд 1, col 5 */
|
||
tk_room_fg[6][10 + 2] = W; /* сосед справа: ряд 1, col 2 */
|
||
|
||
sc_room(floor30, 5);
|
||
pop_map_set_edges(4, 6, 0, 0);
|
||
tc_check(pop_tile_at(-1, 1), F, "col -1 = col9 соседа слева", __LINE__);
|
||
tc_check(pop_tile_at(-5, 1), W, "col -5 = col5 соседа слева", __LINE__);
|
||
tc_check(pop_tile_at(10, 1), F, "col 10 = col0 соседа справа", __LINE__);
|
||
tc_check(pop_tile_at(12, 1), W, "col 12 = col2 соседа справа", __LINE__);
|
||
tc_check(pop_tile_at(13, 1), F, "col 13 = col3 соседа справа", __LINE__);
|
||
/* Дальше комнаты соседа оригинал ушёл бы ещё через roomlink; у нас
|
||
* кэш на одну комнату в каждую сторону, и это край — стена. */
|
||
tc_check(pop_tile_at(20, 1), W, "за комнатой соседа — стена", __LINE__);
|
||
tc_check(pop_tile_at(-11, 1), W, "и слева тоже", __LINE__);
|
||
|
||
/* Соседа нет (край уровня) — стена в обе стороны, как раньше. */
|
||
pop_map_set_edges(0, 0, 0, 0);
|
||
tc_check(pop_tile_at(12, 1), W, "без соседа справа — стена", __LINE__);
|
||
tc_check(pop_tile_at(-1, 1), W, "без соседа слева — стена", __LINE__);
|
||
}
|
||
|
||
/* ---- скелет: падение его не убивает (check_guard_fallout, seg002:252) --- */
|
||
|
||
/* Если под комнатой лежит комната 3, упавший скелет ВОЗРОЖДАЕТСЯ там же —
|
||
* это единственный способ «убрать» его с экрана, убить его нельзя. */
|
||
TC_TEST(char_skeleton_reappears_below)
|
||
{
|
||
sc_room(room_flat, 1);
|
||
sc_kid_at(1, 2, 0);
|
||
sc_guard_at(CHARID_4_SKELETON, 5, 1, -1, 3);
|
||
Guard.room = 5;
|
||
Guard.y = 220; /* ниже нижней границы комнаты (211) */
|
||
tk_link_down = 3; /* под комнатой 5 — комната 3 */
|
||
|
||
pop_guard_fallout();
|
||
|
||
tc_check(Guard.charid, CHARID_4_SKELETON, "скелет остался в слоте", __LINE__);
|
||
tc_check(Guard.room, 3, "перенесён в комнату 3", __LINE__);
|
||
tc_check(Guard.x, 133, "и на свою позицию", __LINE__);
|
||
tc_check(Guard.curr_row, 1, "ряд 1", __LINE__);
|
||
tc_check(Guard.alive, -1, "жив", __LINE__);
|
||
}
|
||
|
||
/* А если снизу НЕ комната 3 — обычная судьба выпавшего: слот освобождается. */
|
||
TC_TEST(char_skeleton_falls_out_elsewhere)
|
||
{
|
||
sc_room(room_flat, 1);
|
||
sc_kid_at(1, 2, 0);
|
||
sc_guard_at(CHARID_4_SKELETON, 5, 1, -1, 3);
|
||
Guard.room = 5;
|
||
Guard.y = 220;
|
||
tk_link_down = 7; /* снизу другая комната */
|
||
|
||
pop_guard_fallout();
|
||
tc_check(Guard.charid, 0, "скелет убран, как обычный выпавший", __LINE__);
|
||
}
|
||
|
||
void main(void)
|
||
{
|
||
sc_init();
|
||
TC_RUN(char_take_hp_goes_to_active);
|
||
TC_RUN(char_immortal_covers_kid_only);
|
||
TC_RUN(char_delta_hp_applies_per_character);
|
||
TC_RUN(char_kid_survives_two_row_fall);
|
||
TC_RUN(char_guard_dies_from_two_row_fall);
|
||
TC_RUN(char_shadow_survives_two_row_fall);
|
||
TC_RUN(char_guard_falls_at_all);
|
||
TC_RUN(char_guard_physics_gated_by_x);
|
||
TC_RUN(char_guard_fallout_removes_him);
|
||
TC_RUN(char_windows_do_not_leak);
|
||
TC_RUN(char_follow_guard_left);
|
||
TC_RUN(char_follow_guard_needs_edge);
|
||
TC_RUN(char_follow_guard_right_threshold);
|
||
TC_RUN(char_follow_guard_needs_sword_drawn);
|
||
TC_RUN(char_follow_guard_dead_stays);
|
||
TC_RUN(char_follow_guard_never_vertical);
|
||
TC_RUN(char_follow_guard_blocked_by_own_guard);
|
||
TC_RUN(char_tiles_resolve_across_rooms);
|
||
TC_RUN(char_skeleton_reappears_below);
|
||
TC_RUN(char_skeleton_falls_out_elsewhere);
|
||
}
|