/* * pop_guard.h — СОСТОЯНИЕ стража и точки входа в его логику. * * Разделение по окнам (docs/layout_plan_v2.md §4, подтверждено пробником * tests/w3bankgfx): * - СОСТОЯНИЕ живёт здесь, в W1/W2. Писучие статики __banked-модуля * линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк — * только код; * - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c; * - ОТРИСОВКА стража — в pop_cdraw.c (банк 4), общая на всех Char: её зовёт * только главный цикл, а из банка резидент недостижим. Так код * отрисовки не занимает дефицитные W1/W2 (куча). * * Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006 * ложился один в один. */ #ifndef POP_GUARD_H #define POP_GUARD_H #include #include "kid_data.h" /* kframe */ #include "pop_char.h" #include "pop_geom.h" /* pop_rnd_t — сид бросков боёвки */ /* seqids (types.h:1052+) — последовательности боя и стойки. */ #define SEQ_55_DRAW_SWORD 55 #define SEQ_56_GUARD_FORWARD 56 /* шаг вперёд с мечом (страж) */ #define SEQ_57_BACK_WITH_SWORD 57 #define SEQ_58_GUARD_STRIKE 58 #define SEQ_60_TURN_WITH_SWORD 60 #define SEQ_61_PARRY_AFTER_STRIKE 61 #define SEQ_62_PARRY 62 #define SEQ_66_STRIKE_AFTER_PARRY 66 #define SEQ_71_DYING 71 #define SEQ_75_STRIKE 75 /* удар (Кид) */ #define SEQ_63_GUARD_ACTIVE_AFTER_FALL 63 /* стойка АКТИВНОГО (скелет) */ #define SEQ_77_GUARD_STAND_INACTIVE 77 #define SEQ_88_SKEL_WAKE_UP 88 /* скелет встаёт (спецсобытие) */ #define SEQ_80_STAND_FLIPPED 80 #define SEQ_86_FORWARD_WITH_SWORD 86 /* шаг вперёд с мечом (Кид) */ #define SEQ_87_GUARD_BECOME_INACT 87 #define SEQ_90_EN_GARDE 90 #define SEQ_92_PUT_SWORD_AWAY 92 #define SEQ_93_PUT_SWORD_AWAY_FAST 93 /* frameids (types.h:911+) и состояния меча (types.h:332). */ #define FRAME_150_PARRY 150 #define FRAME_151_STRIKE_1 151 #define FRAME_152_STRIKE_2 152 #define FRAME_153_STRIKE_3 153 #define FRAME_157_WALK_WITH_SWORD 157 #define FRAME_158_STAND_WITH_SWORD 158 #define FRAME_161_PARRY 161 #define FRAME_162_BLOCK_TO_STRIKE 162 #define FRAME_165_WALK_WITH_SWORD 165 #define FRAME_166_STAND_INACTIVE 166 #define FRAME_167_BLOCKED 167 #define FRAME_168_BACK 168 #define FRAME_169_BEGIN_BLOCK 169 #define FRAME_170_STAND_WITH_SWORD 170 #define FRAME_171_STAND_WITH_SWORD 171 #define SWORD_0_SHEATHED 0 #define SWORD_2_DRAWN 2 /* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */ #define CHARID_0_KID 0 #define CHARID_1_SHADOW 1 #define CHARID_2_GUARD 2 #define CHARID_4_SKELETON 4 #define CHARID_5_PRINCESS 5 /* dir_56_none (types.h:402): «направления нет» — им clear_char убирает * персонажа со сцены. Значение 0x56 в int8_t = 86, то есть заведомо не * dir_0_right (0) и не dir_FF_left (-1). */ #define DIR_56_NONE 0x56 /* Спецсобытие «зеркало» (custom->mirror_*, дефолты SDLPoP): уровень 4, * комната 4, колонка 4, верхний ряд. Здесь, а не в pop_trob.c: константы * нужны и постановке тайла (банк 6), и ИИ тени (банк 1). */ #define MIRROR_LEVEL 4 #define MIRROR_ROOM 4 #define MIRROR_COL 4 #define MIRROR_ROW 0 #define MIRROR_TILEPOS (MIRROR_ROW * 10 + MIRROR_COL) /* Спецсобытие «тень крадёт зелье» (custom->shadow_steal_*, дефолты SDLPoP): * уровень 5, комната 24. Тень появляется при ВХОДЕ в комнату, если зелье * (колонка 3, верхний ряд) ещё на месте; ждёт, пока откроются ворота * (колонка 1, верхний ряд), идёт к зелью, выпивает его и уходит за левый * край. Боя нет. Как и MIRROR_*, константы нужны в двух банках. */ #define SHADOW_STEAL_LEVEL 5 #define SHADOW_STEAL_ROOM 24 #define SHADOW_POTION_TILEPOS 3 /* (кол 3, ряд 0) — зелье */ #define SHADOW_DOOR_TILEPOS 1 /* (кол 1, ряд 0) — ворота */ #define SHADOW_DOOR_OPEN_MIN 80 /* openness, при которой тень идёт */ /* Спецсобытие «тень захлопывает решётку» (custom->shadow_step_*, дефолты * SDLPoP): уровень 6, комната 1. Кид жмёт opener (1,7), решётка (1,2) * поднимается, он прыгает через пропасть и цепляется за её порожек — и * ровно в этот момент тень делает ОДИН осторожный шаг с границы (1,0)-(1,1) * на closer (1,1), роняя решётку. Обе плиты комнаты ведут на тот же тайл * 12 (проверено по LINKLOC/LINKMAP уровня). */ #define SHADOW_STEP_LEVEL 6 #define SHADOW_STEP_ROOM 1 /* Спецсобытие «выход/вход падением» (custom->falling_*, дефолты SDLPoP). * Уровень 6 не заканчивается дверью: Кид проваливается вниз из комнаты 1 и * этим переходит на уровень 7, где появляется в комнате 17 — но экран сразу * переводится на комнату ПОД ней, так что он влетает сверху. Комната 3 * (шахта под комнатой 1) в переходе не участвует: leave_room перехватывает * уход раньше, чем сработала бы связь вниз. */ #define FALLING_EXIT_LEVEL 6 #define FALLING_EXIT_ROOM 1 #define FALLING_ENTRY_LEVEL 7 #define FALLING_ENTRY_ROOM 17 /* Единственный уровень, где тень — БОЕВОЙ персонаж (seg003:702). На всех * остальных (4 зеркало, 5 зелье, 6 плита) она Кида «не видит»: см. * pop_check_can_guard_see_kid. */ #define SHADOW_FIGHT_LEVEL 12 /* Градаций мастерства стража (seg002: таблицы вероятностей по 12 записей). */ #define NUM_GUARD_SKILLS 12 /* Тип персонажа — в pop_char.h (общий с Кидом и Char). */ extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */ extern uint8_t guardhp_curr, guardhp_max; extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */ /* take_hp (seg006:06BD): отнять count HP у АКТИВНОГО персонажа (Char) — * Киду из `hitp_*`, любому другому из `guardhp_*`. Возврат 1 = HP кончились * (для вызывающего это «умер»); саму дельту применяет pop_do_delta_hp раз в * кадр, как в оригинале. * * Живёт в резиденте W1/W2, потому что зовут ОБА банка: боёвка (guards.c, * банк 1) и физика (pop_map.c, банк 3) — раньше у каждого была своя копия, * причём у pop_map неверная (правила hitp_curr напрямую, мимо дельты, и не * знала про не-Кидов). Чит бессмертия ловится здесь же. */ uint8_t pop_take_hp(uint8_t count); /* Взводится в кадре, когда СТРАЖУ убавили HP (guardhp_delta < 0 оригинала): * по нему pop_char_draw рисует «брызги» удара, как draw_guard в seg008:1654. * Снимает главный цикл после отрисовки — стража могло не оказаться в * отрисованной комнате, и тогда флаг обязан просто пропасть. */ extern uint8_t pop_guard_hurt; /* Видит ли страж Кида (порт can_guard_see_kid, seg003:688): * 0 — не видит, 1 — видит, но не пойдёт (между ними loose/чомпер/дыра), * 2 — видит и пойдёт. Считает pop_check_can_guard_see_kid (банк) раз в * кадр ДО логики персонажей; читают ИИ (autocontrol_*) и боёвка. */ extern int8_t can_guard_see_kid; /* Кид «нашумел» (порт is_guard_notice, seg000:364): заставляет неактивного * стража обернуться, даже если Кид у него за спиной. Ставит источник шума, * снимает сам ИИ. */ extern int8_t is_guard_notice; /* Боевые таймеры/уровень мастерства (seg000:295..311). Все убывают раз в * кадр в autocontrol_opponent — как в оригинале. * guard_skill — 0..11, из данных уровня; индекс в таблицы вероятностей; * guard_refrac — «отдышка» стража: пока >0, он не атакует и не наступает; * justblocked — Кид только что парировал (влияет на вероятность блока); * kid_sword_strike — Кид только что бил (стража это придерживает). */ extern uint8_t guard_skill; extern int8_t guard_refrac, justblocked, kid_sword_strike; /* Состояние меча КИДА (seg005/seg006): * offguard — меч убран НАМЕРЕННО (вниз в бою): пока стоит, Кид не * достаёт клинок автоматически, даже видя стража; * holding_sword — клинок в руке (для индикатора HP стража). */ extern int8_t offguard, holding_sword; /* Сид бросков боёвки (свой домен последовательности, см. pop_geom.h). */ extern pop_rnd_t pop_fight_seed; /* Луч видимости стража — раз в кадр, ДО тиков персонажей (в оригинале * check_can_guard_see_kid стоит в play_frame перед play_kid_frame). */ void pop_check_can_guard_see_kid(void) __banked; /* check_skel (seg002:1042) — СПЕЦСОБЫТИЕ уровня 3: скелета нет в данных * уровня (guards_tile пуст во всех комнатах), он поднимается сам, когда Кид * доходит до нужной колонки комнаты 1 при открытой двери уровня. Звать раз * в кадр, ДО логики персонажей (порядок play_frame, seg000:868). */ void pop_check_skel(void) __banked; /* check_shadow (seg002:0064) — СПЕЦСОБЫТИЕ входа в комнату: на «шадоу»- * уровнях слот соперника занимает ТЕНЬ, а не страж из данных. Звать ВМЕСТО * pop_guard_enter при входе в комнату; вернула 1 — событие обработано и * стража поднимать НЕ надо. * * Тонкость оригинала, которую легко потерять: на уровне 5 в комнате 24 * страж В ДАННЫХ ЕСТЬ (`guards_tile[23] = 8`), но enter_guard там не * вызывается НИКОГДА — ни когда тень поднимается, ни когда зелье уже * выпито (в обоих случаях seg002:0064 делает return). Поэтому 1 * возвращается и во втором случае тоже. */ uint8_t pop_check_shadow(uint8_t room) __banked; /* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в * кадре как в play_frame: hurting -> hurt -> do_delta_hp. */ void pop_check_sword_hurting(void) __banked; void pop_check_sword_hurt(void) __banked; /* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2, * читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */ extern kframe pop_gframe; void pop_guard_load_frame(void); /* check_guard_fallout (seg002:0241): убрать персонажа слота Guard, если он * провалился ниже комнаты. Звать раз в кадр после физики и HP — как * play_frame (seg000:884). */ void pop_guard_fallout(void); /* Есть ли живой страж в отрисованной комнате (для главного цикла). */ uint8_t pop_guard_present(void); /* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid. * 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */ int pop_guard_load(void) __banked; void pop_guard_free(void) __banked; /* Вход в комнату: поднять стража из данных уровня (порт enter_guard, * seg002:0112) либо убрать его, если в комнате стража нет. */ void pop_guard_enter(uint8_t room); /* Запомнить стража УХОДЯЩЕЙ комнаты (порт leave_guard). Звать в начале * enter_room, пока Guard ещё держит прежнего. */ void pop_guard_leave(void); /* Страж идёт ЗА КИДОМ в соседнюю комнату — порт ветки стража из exit_room * (seg002:0340) + follow_guard (seg002:039E). Звать ВМЕСТО пары * leave/enter при смене комнаты: 1 = страж уже перенесён в new_room (ни * leave, ни enter звать нельзя), 0 = остаётся в старой (всё как раньше). * side — куда ушёл КИД: 0 left, 1 right, 2 up, 3 down. */ uint8_t pop_guard_follow(uint8_t new_room, uint8_t side); /* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл * ПОСЛЕ персонажей — она ничего не перекрывает. */ void pop_hp_draw(void) __banked; /* Полоса стёрта перерисовкой фона (вход в комнату) — обновить обе страницы. */ void pop_hp_invalidate(void) __banked; /* Цвет стража — подменой 16 слотов палитры (порт set_chtab_palette, * seg003:257): спрайты лежат в индексах POP_GUARD_PAL_BASE + v, а цвет * задаёт level.guards_color[room-1] (1..7; 0 = не менять). Звать ДО * отрисовки комнаты, как это делает redraw_screen. */ void pop_guard_set_palette(uint8_t color) __banked; /* Отрисовка соперника — общая на всех Char: pop_char_draw(POP_CH_OPP) и * компания, см. pop_cdraw.h. Метрики его последнего кадра (в т.ч. ширина * спрайта для char_width_half) — pop_cd[POP_CH_OPP]. */ /* Убрать стража со сцены (смена комнаты / инициализация). */ void pop_guard_reset(void); /* Чит «убить стража» (seg000:786, K): скелета не берёт. Вызывать только * при включённых читах — проверку делает вызывающий. */ void pop_guard_kill(void); /* do_delta_hp (seg003): применить дельту HP, накопленную за кадр. Звать * раз в кадр ПОСЛЕ тиков персонажей (в оригинале — в play_frame). */ void pop_do_delta_hp(void); /* Тик логики стража — В БАНКЕ (guards.c). Состояние берёт отсюда. */ void pop_guard_tick(void) __banked; #endif