b5d2a81ee3
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём. Разбор каждого — bug_closed.md.
Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
(POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
роняло Кида сквозь пол.
Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
(redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.
По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
нарисовался поверх передних столбов (kid_fore_clip_restore);
2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
настоящей X. Теперь колонка выводится из X, как в оригинале;
3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
«упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
габарита, поэтому бегущий Кид уходит за объекты справа. Считается
для обоих персонажей — enter_guard ставит action=1 и стражу.
Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража. make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
170 lines
9.3 KiB
C
170 lines
9.3 KiB
C
/*
|
||
* pop_guard.h — СОСТОЯНИЕ стража и точки входа в его логику.
|
||
*
|
||
* Разделение по окнам (docs/layout_plan_v2.md §4, подтверждено пробником
|
||
* tests/w3bankgfx):
|
||
* - СОСТОЯНИЕ живёт здесь, в W1/W2. Писучие статики __banked-модуля
|
||
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
|
||
* только код;
|
||
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
|
||
* - ОТРИСОВКА стража — в pop_gdraw.c, РЕЗИДЕНТЕ W3 (--w3): её зовёт
|
||
* только главный цикл, а из банка резидент недостижим. Так код
|
||
* отрисовки не занимает дефицитные W1/W2 (куча).
|
||
*
|
||
* Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006
|
||
* ложился один в один.
|
||
*/
|
||
#ifndef POP_GUARD_H
|
||
#define POP_GUARD_H
|
||
|
||
#include <stdint.h>
|
||
#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_77_GUARD_STAND_INACTIVE 77
|
||
#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
|
||
|
||
/* Градаций мастерства стража (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 */
|
||
|
||
/* Видит ли страж Кида (порт 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;
|
||
|
||
/* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в
|
||
* кадре как в 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);
|
||
|
||
/* Есть ли живой страж в отрисованной комнате (для главного цикла). */
|
||
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);
|
||
|
||
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
|
||
* как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
|
||
* (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
|
||
/* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл
|
||
* ПОСЛЕ персонажей — она ничего не перекрывает. */
|
||
void pop_hp_draw(void) __banked;
|
||
/* Полоса стёрта перерисовкой фона (вход в комнату) — обновить обе страницы. */
|
||
void pop_hp_invalidate(void) __banked;
|
||
|
||
void pop_guard_heal(void) __banked;
|
||
void pop_guard_draw(void) __banked;
|
||
|
||
/* Ширина спрайта последнего нарисованного кадра стража (char_width_half
|
||
* в set_char_collision). Нужна для его ТАЙЛА ОБЪЕКТА — от него зависит
|
||
* порядок отрисовки относительно Кида. */
|
||
uint16_t pop_guard_fp_width(void) __banked;
|
||
|
||
/* Убрать стража со сцены (смена комнаты / инициализация). */
|
||
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
|