diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index d7babc0..2ad0b66 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -5,14 +5,14 @@ PROJ_ROOT := $(abspath $(CURDIR)/../../..) EXAMPLE := roomtest # small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе # CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge). -MEMORY ?= small +MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи) # --w3 берёт ОДИН файл на флаг. В W3-резиденте держим ТОЛЬКО pop_bg — # отрисовку тайлов: она большая и её зовёт лишь главный цикл. Всё # остальное живёт в W1/W2, чтобы оставаться достижимым из будущих # __banked-модулей (стражи): из банка резидентная страница W3 не видна — # ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4. -EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c -EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c +EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --bank 1=guards.c +EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c BG_DIR := $(CURDIR)/../poc/res/bg KID_DIR := $(CURDIR)/../poc/res/kid @@ -42,4 +42,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py cd $(TC) && python3 pop_extract_kid_data.py -$(EXAMPLE).exe: pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) +$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c new file mode 100644 index 0000000..4787d65 --- /dev/null +++ b/applications/PoP/roomtest/guards.c @@ -0,0 +1,22 @@ +/* + * guards.c — ЛОГИКА стража, собирается в БАНК (--bank 1=guards.c, окно W3). + * + * Правила банка (проверено пробником tests/w3bankgfx): + * - состояние держим в W1/W2 (pop_guard.c): статики банка линкуются в + * страницу банка и снаружи не читаются; + * - звать можно W1/W2-код (pop_map, pop_kid, pop_geom, libc, примитивы + * libbgi) — скобка _bgi_begin/_bgi_end возвращает страницу БАНКА; + * - звать НЕЛЬЗЯ резидент W3 (pop_bg) — ни напрямую, ни транзитивно: + * пока исполняется банк, резидентной страницы в адресном пространстве + * нет. Поэтому фон/окклюзию за стража рисует главный цикл. + * + * Сейчас — каркас: тик есть, ИИ и боёвка (порт seg005 control_guard*, + * seg004 fight/collision) добавляются сюда. + */ +#include "pop_guard.h" + +void pop_guard_tick(void) __banked +{ + if (Guard.charid == 0) return; /* стража в комнате нет */ + /* TODO: порт control_guard/fight (seg005/seg004) — состояние в Guard. */ +} diff --git a/applications/PoP/roomtest/pop_cheat.h b/applications/PoP/roomtest/pop_cheat.h new file mode 100644 index 0000000..7a2b43f --- /dev/null +++ b/applications/PoP/roomtest/pop_cheat.h @@ -0,0 +1,21 @@ +/* + * pop_cheat.h — режим читов (порт cheats_enabled, seg000:111). + * + * В оригинале включается ключом командной строки megahit/debug и меняет + * набор доступных клавиш. У нас конфигурации/UI пока нет, поэтому это + * просто глобальный флаг: main ставит его при старте (на время разработки — + * ВКЛЮЧЁН). Клавиши читов обрабатывает главный цикл. + * + * Реализовано: K — убить стража (seg000:786). Остальные читы оригинала + * (время, перо, воскрешение, переход по комнатам) пока не портированы. + */ +#ifndef POP_CHEAT_H +#define POP_CHEAT_H + +#include + +extern uint8_t pop_cheats; /* 0 = выключены, иначе включены */ + +#define KBD_CHEAT_KILL 0x42 /* K (PS/2 set 2) — kill guard */ + +#endif diff --git a/applications/PoP/roomtest/pop_guard.c b/applications/PoP/roomtest/pop_guard.c new file mode 100644 index 0000000..f2246e9 --- /dev/null +++ b/applications/PoP/roomtest/pop_guard.c @@ -0,0 +1,38 @@ +/* + * pop_guard.c — состояние стража (W1/W2). См. pop_guard.h. + * Логика — в банке (guards.c), отрисовка — из главного цикла. + */ +#include "pop_guard.h" + +pop_char_t Guard; +uint8_t guardhp_curr, guardhp_max; +int8_t guardhp_delta; + +uint8_t pop_guard_present(void) +{ + return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0); +} + +void pop_guard_reset(void) +{ + Guard.charid = 0; + Guard.alive = 0; + Guard.direction = 0; + guardhp_curr = 0; + guardhp_delta = 0; +} + +/* kill guard cheat (seg000:786): + * if (Guard.charid != charid_4_skeleton) { + * guardhp_delta = -guardhp_curr; + * Guard.alive = 0; + * } + * Скелета чит не убивает — он и в оригинале бессмертен. */ +void pop_guard_kill(void) +{ + if (Guard.charid == CHARID_4_SKELETON) return; + if (!pop_guard_present()) return; + guardhp_delta = (int8_t)-guardhp_curr; + guardhp_curr = 0; + Guard.alive = 0; +} diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h new file mode 100644 index 0000000..98b4d38 --- /dev/null +++ b/applications/PoP/roomtest/pop_guard.h @@ -0,0 +1,61 @@ +/* + * pop_guard.h — СОСТОЯНИЕ стража и точки входа в его логику. + * + * Разделение по окнам (docs/layout_plan_v2.md §4, подтверждено пробником + * tests/w3bankgfx): + * - СОСТОЯНИЕ живёт здесь, в W1/W2. Писучие статики __banked-модуля + * линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк — + * только код; + * - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c; + * - ОТРИСОВКА стража — из W1/W2 (рядом с kid_draw): из банка примитивы + * libbgi звать можно, но резидент W3 (pop_bg) — нельзя, поэтому слой + * фона/окклюзии за стража делает главный цикл. + * + * Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006 + * ложился один в один. + */ +#ifndef POP_GUARD_H +#define POP_GUARD_H + +#include + +/* 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 + +typedef struct { + uint8_t frame; + uint8_t x, y; + int8_t direction; + int8_t curr_col, curr_row; + uint8_t action; + int8_t fall_x, fall_y; + uint8_t room; + uint8_t repeat; + uint8_t charid; + uint8_t sword; + int8_t alive; /* -1 = жив (как в оригинале), 0 = убит */ + uint16_t curr_seq; +} pop_char_t; + +extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */ +extern uint8_t guardhp_curr, guardhp_max; +extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */ + +/* Есть ли живой страж в отрисованной комнате (для главного цикла). */ +uint8_t pop_guard_present(void); + +/* Убрать стража со сцены (смена комнаты / инициализация). */ +void pop_guard_reset(void); + +/* Чит «убить стража» (seg000:786, K): скелета не берёт. Вызывать только + * при включённых читах — проверку делает вызывающий. */ +void pop_guard_kill(void); + +/* Тик логики стража — В БАНКЕ (guards.c). Состояние берёт отсюда. */ +void pop_guard_tick(void) __banked; + +#endif diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index ec00f53..c0f2099 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -16,9 +16,17 @@ #include "pop_level.h" #include "pop_trob.h" #include "pop_redraw.h" +#include "pop_guard.h" +#include "pop_cheat.h" __sfr __at (0xFE) io_border; +/* Сколько банков грузит crt0_banked (читает его стартовый код). */ +const uint8_t n_banks = 1; + +/* Режим читов (pop_cheat.h): на время разработки включаем в main. */ +uint8_t pop_cheats; + /* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */ #define SEQ_STAND 2 #define ROOM_1 1 @@ -198,11 +206,14 @@ int main(void) #ifdef ROOMNAV uint8_t nav_prev = 0; #endif + uint8_t cheat_prev = 0; if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */ puts("pop_bg_load failed"); return 1; } + pop_cheats = 1; /* режим разработки: читы включены */ + pop_guard_reset(); if (pop_kid_data_load("kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */ printf("kid_data.bin load failed\n"); return 1; @@ -271,6 +282,12 @@ int main(void) * стоит, можно спокойно разбирать позу Kid и окклюзию. */ if (kbd_raw_down(0x16)) frozen = 1; if (kbd_raw_down(0x1E)) frozen = 0; + { /* Читы (seg000: switch под if (cheats_enabled)). K — убить + * стража; по фронту нажатия, чтобы не срабатывало каждый кадр. */ + uint8_t k = kbd_raw_down(KBD_CHEAT_KILL); + if (pop_cheats && k && !cheat_prev) pop_guard_kill(); + cheat_prev = k; + } if (frozen) { io_border = 0; /* черный: ждём vsync */ gfx_wait_vsync(); @@ -317,6 +334,7 @@ int main(void) pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */ kid_tick(); /* play_seq: следующий кадр */ pop_phys_tick(); /* K3: падение/приземление/стена */ + pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */ if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */ uint8_t down = pop_room_link(cur_room, LINK_DOWN); pop_fell_out = 0;