DRAW-CHAR: отрисовка одна на всех Char; разгрузка банка 2 (90.4% -> 72.9%)
DRAW-CHAR. Отрисовка персонажа сведена к одному набору функций над Char — как физика после GUARD-PHYS. В оригинале add_kid_to_objtable (seg008:22F0) и add_guard_to_objtable (seg008:2324) имеют идентичное тело и различаются окном (loadkid/loadshad), набором спрайтов и типом объекта, а redraw_at_char/redraw_at_char2 гейтов по charid не имеют вовсе. pop_gdraw.c -> pop_cdraw.c: pop_char_draw/heal/fore(who), слот POP_CH_KID / POP_CH_OPP; состояние слотов pop_cd[] в _DATA — читается из любого банка без трамплина. Проход окклюзии тоже один (pop_fore_over_char), pop_fore_over_kid больше нет. Починилось само (расхождения, которые и были ценой дублирования): у соперника не было clip_char; у Кида не было клипа полем 192 и ветки брызг «мёртв/падение»; char_width_half СТРАЖА считался по спрайту КИДА. Замер: _CODE 24 881 -> 20 524 (куча 2023 -> 6333), BANK2 -265, итого -3.2 КБ. Проверено пользователем в MAME; циан-полоса профиля подросла — оптимизация заведена отдельной задачей DRAW-COST. MEM-BANK2, шаг 1: общие «листья» слоя фона в РЕЗИДЕНТ (pop_tile.c/.h). Ограничение платформы: писучие данные банка лежат в _DATA и видны всем, а const-таблицы — в странице банка, из другого банка их не прочитать; трамплин же выбирается объявлением, то есть __banked на листе бьёт и по горячим вызывающим (654 такта). W1 замаплено всегда — оттуда обе половины зовут листья прямым call и читают таблицы напрямую. MEM-BANK2, шаг 2: дедуп внутри банка. wall_pattern 808 -> 394 и wall_rnd 786 -> 654: четыре ветки по виду стены отличались только набором кусков и числами в одной серии prandom — сведены к таблицам WP_PARTS и WR_RULE, порядок вызовов prandom сохранён дословно. Заодно: kid_seq_off больше не static const в kid_data.h (230 Б мёртвой копии в каждом из 9 модулей) — генератор pop_extract_kid_data.py отдаёт макро-инициализатор, массив определяет один pop_kid.c. Итог: BANK2 14 815 -> 11 942 (72.9 %, свободно 4442 Б), _CODE 22 556, куча 4301 Б. tests-host зелёные (65/39/53/1723/1); в MAME комната 1 совпала с дорефакторным снимком попиксельно (0 из 227 520), комната 3 — та же раскладка кладки. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,7 @@ roomtest — живой прототип порта PoP: комната 1 уро
|
|||||||
- `pop_room_link(room, side)` — связь (side 0=L,1=R,2=U,3=D; 0=нет).
|
- `pop_room_link(room, side)` — связь (side 0=L,1=R,2=U,3=D; 0=нет).
|
||||||
- `pop_level_start_room/pos/dir()`.
|
- `pop_level_start_room/pos/dir()`.
|
||||||
- `pop_bg.c/.h` — отрисовка тайлов (порт seg008 draw_tile), fore-окклюзия над
|
- `pop_bg.c/.h` — отрисовка тайлов (порт seg008 draw_tile), fore-окклюзия над
|
||||||
Kid (`pop_fore_over_kid`, порт set_char_collision+redraw_at_char/char2),
|
Kid (`pop_fore_over_char`, порт set_char_collision+redraw_at_char/char2),
|
||||||
**loose-полы** (shake/bake/mob). `draw_tile` — статическая, знает
|
**loose-полы** (shake/bake/mob). `draw_tile` — статическая, знает
|
||||||
`draw_gate_back` (грань гейта из левой комнаты).
|
`draw_gate_back` (грань гейта из левой комнаты).
|
||||||
- `pop_kid.c/.h` — анимация Kid (интерпретатор seqtbl `play_seq`, порт seg006),
|
- `pop_kid.c/.h` — анимация Kid (интерпретатор seqtbl `play_seq`, порт seg006),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
### 1.2 Журналирующий рендерер
|
### 1.2 Журналирующий рендерер
|
||||||
|
|
||||||
Вместо `pop_bg.c`/`pop_gdraw.c` в тестовый бинарь линкуется модуль с теми
|
Вместо `pop_bg.c`/`pop_cdraw.c` в тестовый бинарь линкуется модуль с теми
|
||||||
же прототипами, который **не рисует, а записывает вызовы**: какой тайл
|
же прототипами, который **не рисует, а записывает вызовы**: какой тайл
|
||||||
помечен к перерисовке, каким кодом, с каким счётчиком страниц.
|
помечен к перерисовке, каким кодом, с каким счётчиком страниц.
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ fore-клипа (`pop_fore_set_clip`) одно на всех, и его став
|
|||||||
рисует персонажа; когда порядок «Кид/страж» стал переменным, окно осталось
|
рисует персонажа; когда порядок «Кид/страж» стал переменным, окно осталось
|
||||||
стражьим, и Кид нарисовался поверх передних столбов. Это не «функция
|
стражьим, и Кид нарисовался поверх передних столбов. Это не «функция
|
||||||
вернула не то», unit-тест такое не видит. Ловится инвариантом,
|
вернула не то», unit-тест такое не видит. Ловится инвариантом,
|
||||||
вкомпилированным в safe-сборку: «в момент `pop_fore_over_kid` окно клипа
|
вкомпилированным в safe-сборку: «в момент `pop_fore_over_char` окно клипа
|
||||||
принадлежит Киду». Отдельный инструмент, дополняющий тесты.
|
принадлежит Киду». Отдельный инструмент, дополняющий тесты.
|
||||||
|
|
||||||
## Порядок работ
|
## Порядок работ
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
# roomtest — план v2: размер кода и раскладка по окнам/банкам/страницам
|
# roomtest — план v2: размер кода и раскладка по окнам/банкам/страницам
|
||||||
|
|
||||||
> **Замер 2026-08-01 (актуальная сборка).** `_CODE` 25 119 Б, `_DATA` 3 709,
|
> **Замер 2026-08-08 (актуальная сборка, ALLOCS=3000).** `_CODE` 20 524 Б,
|
||||||
|
> данные 4 375, куча 6 333. Банки: 1 (`guards.c`) 2 519, 2 (`pop_bg.c`)
|
||||||
|
> 15 045, 3 (`pop_map.c`) 8 713, 4 (`pop_cdraw.c`) 5 905, 5 (`pop_ctrl.c`)
|
||||||
|
> 2 270, 6 (`pop_trob.c`) 3 119 — все из 16 384. Резидент похудел на 4.3 КБ
|
||||||
|
> после DRAW-CHAR (отрисовка персонажей уехала из W1 в банк 4 и слилась в
|
||||||
|
> одну на всех Char); банк 2 при этом почти не разгрузился — свободно
|
||||||
|
> 1 339 Б.
|
||||||
|
>
|
||||||
|
> **Замер 2026-08-01 (историческая отметка).** `_CODE` 25 119 Б, `_DATA` 3 709,
|
||||||
> куча ~2.4 КБ. Банки: 1 (`guards.c`) 1 896, 2 (`pop_bg.c`) 13 792,
|
> куча ~2.4 КБ. Банки: 1 (`guards.c`) 1 896, 2 (`pop_bg.c`) 13 792,
|
||||||
> 3 (`pop_map.c`) 6 331, 4 (`pop_gdraw.c`) 2 236 — все из 16 384.
|
> 3 (`pop_map.c`) 6 331, 4 (отрисовка стража) 2 236 — все из 16 384.
|
||||||
> **Резидента `--w3` больше нет**: отрисовка уехала в банк 2, и это сняло
|
> **Резидента `--w3` больше нет**: отрисовка уехала в банк 2, и это сняло
|
||||||
> главное ограничение резидента (из банка его было не достать) — банк→банк
|
> главное ограничение резидента (из банка его было не достать) — банк→банк
|
||||||
> работает, трамплин сохраняет страницу окна на стеке. Отрисовка стража
|
> работает, трамплин сохраняет страницу окна на стеке. Отрисовка стража
|
||||||
|
|||||||
@@ -60,18 +60,25 @@ Kid — `kid_data.h` (генерится `pop_extract_kid_data.py`).
|
|||||||
| Файл | Роль (порт SDLPoP) |
|
| Файл | Роль (порт SDLPoP) |
|
||||||
|------|--------------------|
|
|------|--------------------|
|
||||||
| `roomtest.c` | Главный цикл: дабл-буфер (2 страницы + флип на vsync), tick→draw порядок слоёв. |
|
| `roomtest.c` | Главный цикл: дабл-буфер (2 страницы + флип на vsync), tick→draw порядок слоёв. |
|
||||||
| `pop_bg.c/.h` | Отрисовка фона композицией тайлов (`seg008` draw_tile/wall_pattern), fore-окклюзия поверх Kid (`seg003` redraw_at_char), loose-полы (shake/bake/mob, `seg007/008`). |
|
| `pop_tile.c/.h` | Общие ЛИСТЬЯ слоя фона — РЕЗИДЕНТ W1: блит куска атласа, чтение тайла комнаты, `tile_table`. Резидент потому, что их зовут обе половины слоя фона из разных банков, а `const`-таблицы банка из чужого банка не видны. |
|
||||||
| `pop_kid.c/.h` | Анимация/движение Kid: интерпретатор seqtbl `play_seq` + frame_table (`seg006`). |
|
| `pop_bg.c/.h` | Отрисовка фона композицией тайлов (`seg008` draw_tile/wall_pattern), fore-окклюзия поверх ЛЮБОГО персонажа (`seg003` redraw_at_char/char2 — один проход на всех Char), loose-полы (shake/bake/mob, `seg007/008`). |
|
||||||
|
| `pop_kid.c/.h` | Анимация/движение: интерпретатор seqtbl `play_seq` + frame_table (`seg006`), окна `Char` (loadkid/loadshad), атласы Кида. |
|
||||||
|
| `pop_cdraw.c/.h` | ОТРИСОВКА персонажей — одна на всех Char (порт `add_kid_to_objtable`/`add_guard_to_objtable`, `seg008:22F0/2324`): спрайт, clip_char, брызги, клинок, heal; + полоса HP и палитры соперника. |
|
||||||
| `pop_ctrl.c/.h` | Управление: диспетчер `control()` (`seg005`) + ввод `read_user_control` (`seg006`) поверх `<kbd_raw.h>`. |
|
| `pop_ctrl.c/.h` | Управление: диспетчер `control()` (`seg005`) + ввод `read_user_control` (`seg006`) поверх `<kbd_raw.h>`. |
|
||||||
| `pop_map.c/.h` | Коллизия с картой + физика падения/приземления/стены + loose-полы (`seg005/006/007`). |
|
| `pop_map.c/.h` | Коллизия с картой + физика падения/приземления/стены + loose-полы (`seg005/006/007`). |
|
||||||
|
|
||||||
## Порядок слоёв в кадре (важно для окклюзии)
|
## Порядок слоёв в кадре (важно для окклюзии)
|
||||||
|
|
||||||
`roomtest.c` каждый тик рисует в СКРЫТУЮ страницу: `kid_heal` (стереть
|
`roomtest.c` каждый тик рисует в СКРЫТУЮ страницу: `pop_char_heal` (стереть
|
||||||
прошлый кадр) → `kid_tick` → `pop_phys_tick` → `pop_loose_tick` (loose СЛОЙ
|
прошлый кадр — по слоту на персонажа) → `kid_tick` → `pop_phys_tick` →
|
||||||
ФОНА — ДО Kid, чтобы Kid был поверх плиты) → `kid_draw` → `pop_fore_over_kid`
|
`pop_loose_tick` (loose СЛОЙ ФОНА — ДО персонажей, чтобы они были поверх
|
||||||
(передние грани тайлов поверх Kid) → `pop_room_clip_borders` (чистка бортов,
|
плиты) → `pop_char_draw`/`pop_char_fore` для соперника и Кида (кто позже —
|
||||||
гейт по флагу — только в кадрах падения) → флип на vsync.
|
тот поверх, порядок задаёт обход тайлов) → `pop_room_clip_borders` (чистка
|
||||||
|
бортов, гейт по флагу — только в кадрах падения) → флип на vsync.
|
||||||
|
|
||||||
|
Отрисовка персонажа ОДНА на всех (`pop_cdraw.c`): слот выбирает окно
|
||||||
|
`Char`, набор атласов и heal-прямоугольники, всё остальное общее — как два
|
||||||
|
одинаковых входа `add_*_to_objtable` в оригинале.
|
||||||
|
|
||||||
**Дабл-буфер:** у каждой страницы своя видео-ОЗУ и теневая ОЗУ-копия; heal
|
**Дабл-буфер:** у каждой страницы своя видео-ОЗУ и теневая ОЗУ-копия; heal
|
||||||
берёт чистый фон из копии ТОЙ страницы, в которую рисуем. Любой динамический
|
берёт чистый фон из копии ТОЙ страницы, в которую рисуем. Любой динамический
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ EXAMPLE := roomtest
|
|||||||
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
|
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
|
||||||
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
|
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
|
||||||
MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи)
|
MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи)
|
||||||
# Резидента --w3 БОЛЬШЕ НЕТ: отрисовка уехала в БАНК 2 (pop_bg + pop_gdraw
|
# Резидента --w3 БОЛЬШЕ НЕТ: отрисовка уехала в БАНК 2 (pop_bg + pop_cdraw
|
||||||
# в одной странице — вызовы между ними прямые, если функция не помечена
|
# в одной странице — вызовы между ними прямые, если функция не помечена
|
||||||
# __banked). Так снято главное ограничение резидента: из банка его было
|
# __banked). Так снято главное ограничение резидента: из банка его было
|
||||||
# не достать ни прямо, ни транзитивно, а банк→банк работает (трамплин
|
# не достать ни прямо, ни транзитивно, а банк→банк работает (трамплин
|
||||||
@@ -35,14 +35,14 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
|||||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
|
EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
|
||||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
# MAME = время фазы в растровых строках.
|
# MAME = время фазы в растровых строках.
|
||||||
PROF ?= 1
|
PROF ?= 1
|
||||||
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
||||||
EXTRA_SRCS := pop_state.c pop_draw.c pop_kid.c pop_level.c pop_redraw.c pop_geom.c pop_guard.c
|
EXTRA_SRCS := pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_redraw.c pop_geom.c pop_guard.c
|
||||||
|
|
||||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||||
KID_DIR := $(CURDIR)/../poc/res/kid
|
KID_DIR := $(CURDIR)/../poc/res/kid
|
||||||
@@ -57,7 +57,7 @@ BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.
|
|||||||
$(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl \
|
$(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl \
|
||||||
$(BG_DIR)/pop_pot.atl
|
$(BG_DIR)/pop_pot.atl
|
||||||
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
|
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
|
||||||
# не рисуются (kid_draw пропускает незагруженную страницу). Палитра —
|
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
|
||||||
# ЕДИНАЯ игровая (env+wall+kid).
|
# ЕДИНАЯ игровая (env+wall+kid).
|
||||||
KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl)
|
KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl)
|
||||||
KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl
|
KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl
|
||||||
@@ -105,4 +105,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
|||||||
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
||||||
cd $(TC) && python3 pop_extract_kid_data.py
|
cd $(TC) && python3 pop_extract_kid_data.py
|
||||||
|
|
||||||
$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_gdraw.c pop_draw.c _pop_draw.h pop_state.c pop_state.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)
|
$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.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)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# roomtest — ЗАКРЫТЫЕ задачи (архив досок, обновлено 2026-08-07)
|
# roomtest — ЗАКРЫТЫЕ задачи (архив досок, обновлено 2026-08-08)
|
||||||
|
|
||||||
Сделанное — с протоколами замеров, граблями и причинами решений. Файл
|
Сделанное — с протоколами замеров, граблями и причинами решений. Файл
|
||||||
существует не ради истории: половина записей ниже — это ЧИСЛА (сколько тактов
|
существует не ради истории: половина записей ниже — это ЧИСЛА (сколько тактов
|
||||||
@@ -232,6 +232,95 @@ BUG-1 (провал на row 1 при переходе через открыты
|
|||||||
|
|
||||||
## Отрисовка и память
|
## Отрисовка и память
|
||||||
|
|
||||||
|
<a id="draw-char"></a>
|
||||||
|
### DRAW-CHAR. Отрисовка — ОДНА на всех Char — **СДЕЛАНО И ПРОВЕРЕНО 2026-08-08**
|
||||||
|
|
||||||
|
> **Итог.** Отрисовка персонажа сведена к одному набору функций над `Char`
|
||||||
|
> (`pop_cdraw.c`, банк 4) — как физика после GUARD-PHYS. Было два
|
||||||
|
> независимых куска кода: `kid_draw`/`kid_heal`/`kid_draw_splash`/
|
||||||
|
> `kid_fore_clip*` в резиденте W1 и `pop_guard_draw`/`pop_guard_heal` в
|
||||||
|
> банке 4, каждый со своей математикой кадра, своими heal-прямоугольниками
|
||||||
|
> и своим проходом окклюзии (`pop_fore_over_kid` / `pop_fore_over_char`).
|
||||||
|
>
|
||||||
|
> **Основание в оригинале** (сверено перед кодингом): `add_kid_to_objtable`
|
||||||
|
> (seg008:22F0) и `add_guard_to_objtable` (seg008:2324) имеют ИДЕНТИЧНОЕ
|
||||||
|
> тело и различаются окном (`loadkid`/`loadshad`), набором спрайтов и типом
|
||||||
|
> объекта; `redraw_at_char` / `redraw_at_char2` (seg003:0576/0645) гейтов по
|
||||||
|
> `charid` не имеют вовсе — единственная ветка по персонажу там это
|
||||||
|
> объединение с ПРОШЛЫМ футпринтом у Кида (пометки перерисовки, у нас их
|
||||||
|
> нет). `draw_hurt_splash` (seg006:2003) разводит Кида и остальных ровно
|
||||||
|
> двумя числами: chtab/image и подъём `((charid == kid) << 2) + 11`.
|
||||||
|
|
||||||
|
**Что теперь одно на всех.**
|
||||||
|
|
||||||
|
| Было (два места) | Стало |
|
||||||
|
|---|---|
|
||||||
|
| `kid_draw` / `pop_guard_draw` | `pop_char_draw(who)` |
|
||||||
|
| `kid_heal` / `pop_guard_heal` | `pop_char_heal(who)` |
|
||||||
|
| `kid_draw_splash` / ветка брызг внутри `pop_guard_draw` | `cd_splash` (порт целиком, все три ветки кадров) |
|
||||||
|
| `kid_fore_clip` + `kid_fore_clip_restore` / хвост `pop_guard_draw` | `pop_char_fore(who)` |
|
||||||
|
| `pop_fore_over_kid` / `pop_fore_over_char` | `pop_fore_over_char(ch, …)` |
|
||||||
|
| `kid_fp_*()` / `pop_guard_fp_width()` | `pop_cd[slot]` (структура в `_DATA`, читается из любого банка без трамплина) |
|
||||||
|
| `pop_kid_set_render_dx` | `pop_char_set_render_dx(who, dx)` |
|
||||||
|
|
||||||
|
Слот (`POP_CH_KID` / `POP_CH_OPP`) выбирает ровно то, что различается в
|
||||||
|
оригинале: окно `Char`, набор атласов и кадр (`kidp`+`kid_frame` /
|
||||||
|
`gp`+`pop_gframe`), heal-прямоугольники по страницам дабл-буфера.
|
||||||
|
|
||||||
|
**Что при этом ПОЧИНИЛОСЬ (расхождения, которые и были ценой дублирования).**
|
||||||
|
|
||||||
|
1. **`clip_char` у соперника не было вовсе** — теперь общий (в оригинале он
|
||||||
|
в обоих `add_*_to_objtable`).
|
||||||
|
2. **Клип полем 192 у Кида не было** (`reset_obj_clip`: `obj_clip_bottom =
|
||||||
|
192`): его спрайт рисовался на полосе HP, а следы за него подчищала
|
||||||
|
полоса `pop_room_clip_borders` по флагу `pop_clip_sprite`. Теперь
|
||||||
|
персонаж режется полем, как в оригинале, и `border_dirty` остался только
|
||||||
|
у падающей плиты.
|
||||||
|
3. **У Кида не было ветки брызг «мёртв / падение 106..110»** (`obj_y += 4`)
|
||||||
|
— была только у соперника.
|
||||||
|
4. **`char_width_half` СТРАЖА считался по спрайту КИДА**: `set_char_collision`
|
||||||
|
и `check_spike_below` (`pop_map.c`) читали `kid_fp_width()` безусловно, а
|
||||||
|
`pop_guard_fp_width()` использовался только для порядка отрисовки.
|
||||||
|
Теперь метрики берутся по `charid` активного персонажа (`CD_ACT`).
|
||||||
|
5. **Расширение перебора тайлов окном спрайта** (клинок и брызги уходят за
|
||||||
|
габарит кадра) было только у соперника — теперь общее. Заодно оно
|
||||||
|
перестало быть четырьмя лишними аргументами: окно fore-клипа уже лежит в
|
||||||
|
`pop_bg.c`, и проход читает его сам.
|
||||||
|
|
||||||
|
**Цена / выигрыш (ALLOCS=3000, замер до и после):**
|
||||||
|
|
||||||
|
```
|
||||||
|
было стало дельта
|
||||||
|
_CODE (W1) 24 881 20 524 −4 357 ← куча 2 023 → 6 333 Б (+4 310)
|
||||||
|
BANK2 pop_bg 15 310 15 045 − 265 ← свободно 1 074 → 1 339 Б
|
||||||
|
BANK3 pop_map 8 608 8 713 + 105 (CD_ACT — индекс по charid)
|
||||||
|
BANK4 pop_cdraw 4 625 5 905 +1 280 (сюда переехала отрисовка Кида)
|
||||||
|
итого −3 237
|
||||||
|
```
|
||||||
|
|
||||||
|
То есть слияние двух копий дало ~3.2 КБ, но **главный выигрыш не в банке 2,
|
||||||
|
а в резиденте**: отрисовка Кида уехала из W1 в банк, и куча выросла втрое
|
||||||
|
(2 023 → 6 333 Б). Банку 2 при этом досталось всего +265 Б свободного
|
||||||
|
места — **под [L3-CHOMP](TASKS_OPEN.md#l3-chomp) этого мало**, разгрузку
|
||||||
|
`pop_bg` придётся делать отдельно (свободные номера банков — 7+).
|
||||||
|
|
||||||
|
**Проверено:** `tests-host` — все 5 наборов зелёные, трассы физики не
|
||||||
|
изменились (`[phys] ok: 1723`, тот же эталон; `[char] ok: 65`). В MAME
|
||||||
|
(свежий образ, полный рестарт): уровень 1 — комната 1 (Кид, кладка,
|
||||||
|
факелы, окклюзия колонны), падение на нижний ярус, комната 3 — страж своего
|
||||||
|
цвета с клинком и своей полосой HP, бой и смерть Кида. Артефактов
|
||||||
|
отрисовки и мусора на полосе HP нет.
|
||||||
|
|
||||||
|
**Приёмка пользователем пройдена 2026-08-08.** Прогон вживую: отрисовка
|
||||||
|
персонажей, окклюзия и бой — без регрессий.
|
||||||
|
|
||||||
|
Единственное наблюдение — **циан-полоса профиля (спрайты + fore) выросла**:
|
||||||
|
проход над Кидом теперь расширяется окном спрайта, как раньше только у
|
||||||
|
соперника, то есть перебирает на колонку-другую больше. Решение
|
||||||
|
пользователя: **в пределах допустимого, оптимизация — ОТДЕЛЬНОЙ задачей**
|
||||||
|
([DRAW-COST](TASKS_OPEN.md#draw-cost)).
|
||||||
|
|
||||||
|
|
||||||
<a id="clip-1"></a>
|
<a id="clip-1"></a>
|
||||||
### CLIP-1. Аудит блитов: где клип не нужен — **СДЕЛАНО 2026-08-01**
|
### CLIP-1. Аудит блитов: где клип не нужен — **СДЕЛАНО 2026-08-01**
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-07)
|
# roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-08)
|
||||||
|
|
||||||
Что берём в работу сейчас и в каком порядке. Каждая запись: что сделать,
|
Что берём в работу сейчас и в каком порядке. Каждая запись: что сделать,
|
||||||
почему именно сейчас, чем подтверждать результат.
|
почему именно сейчас, чем подтверждать результат.
|
||||||
@@ -12,9 +12,18 @@
|
|||||||
**Состояние на 2026-08-07:** пользователь прогнал ВСЕ комнаты уровней 1 и 2 —
|
**Состояние на 2026-08-07:** пользователь прогнал ВСЕ комнаты уровней 1 и 2 —
|
||||||
крупных багов нет. Приёмки [L1-PASS](TASKS_CLOSED.md#l1-pass) и
|
крупных багов нет. Приёмки [L1-PASS](TASKS_CLOSED.md#l1-pass) и
|
||||||
[L2-PASS](TASKS_CLOSED.md#l2-pass) закрыты; с прогона открыты три записи по
|
[L2-PASS](TASKS_CLOSED.md#l2-pass) закрыты; с прогона открыты три записи по
|
||||||
уровню 2 (цвет стража, брызги по стражу, чит `+`/`−` в бою) — все в
|
уровню 2, из них цвет стража и брызги уже закрыты (см.
|
||||||
[`bug_list.md`](bug_list.md). Уровень 3 играется, но приёмки не было: сначала
|
[`bug_closed.md`](bug_closed.md)); открытым остался чит `+`/`−` в бою.
|
||||||
чомперы и скелет.
|
Уровень 3: **скелет сделан и проверен в MAME 2026-08-07** (бой, падение в
|
||||||
|
пропасть, окклюзия), чомперов ещё нет.
|
||||||
|
|
||||||
|
**Разгрузка банка 2 сделана 2026-08-08** ([MEM-BANK2](#mem-bank2)): 90.4 % →
|
||||||
|
**72.9 %, свободно 4442 Б** — чомперам места хватает.
|
||||||
|
|
||||||
|
**DRAW-CHAR сделана и ПРОВЕРЕНА 2026-08-08** (протокол и замеры —
|
||||||
|
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char)): отрисовка теперь одна на
|
||||||
|
всех `Char`. Банку 2 она дала всего +265 Б — место под чомперов дала уже
|
||||||
|
[MEM-BANK2](#mem-bank2).
|
||||||
|
|
||||||
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
|
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
|
||||||
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
|
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
|
||||||
@@ -40,9 +49,11 @@
|
|||||||
| # | Задача | Что | Блокирует |
|
| # | Задача | Что | Блокирует |
|
||||||
|---|--------|-----|-----------|
|
|---|--------|-----|-----------|
|
||||||
| 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME |
|
| 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME |
|
||||||
| 2 | [L3-CHOMP](#l3-chomp) | чомперы (5 шт) | прохождение ур. 3 |
|
| — | [DRAW-CHAR](TASKS_CLOSED.md#draw-char) | отрисовка одна на всех `Char` — **закрыта и проверена 2026-08-08** | — |
|
||||||
| 3 | [L3-SKEL](#l3-skel) | скелет (единственный противник ур. 3) | прохождение ур. 3 |
|
| — | [MEM-BANK2](#mem-bank2) | разгрузка банка 2 — **сделана 2026-08-08**: 90.4 % → 72.9 %, свободно 4442 Б | — |
|
||||||
| 4 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
|
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
|
||||||
|
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
|
||||||
|
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
|
||||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||||
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
||||||
|
|
||||||
@@ -173,6 +184,66 @@
|
|||||||
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
|
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
|
||||||
живой страж — переход не происходит.
|
живой страж — переход не происходит.
|
||||||
|
|
||||||
|
### <a id="mem-bank2"></a>MEM-BANK2. Разгрузка банка 2 — **шаги 1-2 сделаны 2026-08-08**
|
||||||
|
|
||||||
|
Банк 2 (`pop_bg.c`) упирался в потолок: 90.8 % ещё до чомперов. Разбор по
|
||||||
|
символам показал, что он держит ДВЕ разные вещи — горячий fore-проход (каждый
|
||||||
|
кадр) и холодную отрисовку тайлов (вход в комнату, точечные перерисовки), —
|
||||||
|
а общие «листья» (`blit_b`, `tile_code`, `tile_table`, `wall_pattern`) нужны
|
||||||
|
обеим.
|
||||||
|
|
||||||
|
**Ключевое ограничение платформы** (записано в memory
|
||||||
|
`sdcc_banked_call_rules`): писучие данные банка лежат в `_DATA`/W2 и видны
|
||||||
|
всем, а `const`-таблицы — в СТРАНИЦЕ банка, и из другого банка их не
|
||||||
|
прочитать вовсе. Плюс трамплин выбирается ОБЪЯВЛЕНИЕМ: пометить лист
|
||||||
|
`__banked` — значит заставить платить и горячих вызывающих (654 такта на
|
||||||
|
круг).
|
||||||
|
|
||||||
|
**Шаг 1 — общие листья в РЕЗИДЕНТ** (`pop_tile.c` + внутренний `pop_tile.h`).
|
||||||
|
W1 замаплен всегда, поэтому и горячая половина, и будущая холодная зовут их
|
||||||
|
прямым `call` и читают таблицы напрямую — без дублей и без трамплинов.
|
||||||
|
Уехали: `blit_b`, `env_b/wall_b/fore_b/pot_b`, `tile_code/tile_mod/
|
||||||
|
tile_code_drawn/wall_modifier`, `heal_off`, `get_loose_frame`,
|
||||||
|
`potion_flask`, `pop_fore_set_clip`, `pop_room_set_below/above` + таблицы
|
||||||
|
`tile_table`, `COL_XH`, `WALL_FRAM_*`, `SPIKES_FRAM_LEFT`,
|
||||||
|
`LOOSE_FRAM_LEFT/BOTTOM`. Заодно три функции перестали быть `__banked` —
|
||||||
|
минус трамплин на кадр.
|
||||||
|
|
||||||
|
**Шаг 2 — дедуп внутри банка.** `wall_pattern` (808 → 394) и `wall_rnd`
|
||||||
|
(786 → 654): четыре ветки по виду стены (SWS/SWW/WWS/WWW) отличались ТОЛЬКО
|
||||||
|
набором кусков и числами в одной и той же серии `prandom` — сведены к
|
||||||
|
таблицам `WP_PARTS` и `WR_RULE`. Порядок вызовов `prandom` сохранён
|
||||||
|
дословно (иначе разъедется раскладка кладки).
|
||||||
|
|
||||||
|
**Грабля, пойманная замером:** после выноса `tile_table` из `static const` в
|
||||||
|
глобальную SDCC перестала сворачивать `tile_table[10].fore_x/fore_y` в
|
||||||
|
константы и стала читать их в рантайме — `potion_flask` раздулся 115 → 265 Б.
|
||||||
|
Лечится макросами, которыми инициализируется та же запись таблицы (одно место
|
||||||
|
правки, дублирования данных нет): 265 → 230.
|
||||||
|
|
||||||
|
**Замер (ALLOCS=3000):**
|
||||||
|
|
||||||
|
```
|
||||||
|
было после ш.1 после ш.2
|
||||||
|
BANK2 14 815 12 460 11 942 (90.4 % -> 72.9 %, свободно 4442 Б)
|
||||||
|
_CODE 20 064 22 591 22 556 (куча 6793 -> 4301 Б)
|
||||||
|
```
|
||||||
|
|
||||||
|
С релизным `ALLOCS=100000` банк 2 будет ещё примерно на 500-600 Б меньше
|
||||||
|
(замер до разгрузки: 14 815 -> 14 176).
|
||||||
|
|
||||||
|
**Проверено:** `tests-host` зелёные; в MAME комната 1 уровня 1 после дедупа
|
||||||
|
совпала с дорефакторным снимком **попиксельно (0 различий из 227 520)**,
|
||||||
|
комната 3 (сплошная кладка, все четыре вида стены и марки) — та же раскладка
|
||||||
|
кирпича.
|
||||||
|
|
||||||
|
**Что осталось в запасе, если места снова не хватит** (не делаем, пока нет
|
||||||
|
причины): вынести ХОЛОДНУЮ половину (`draw_tile` 1800 + точечные
|
||||||
|
перерисовки 1438 + падающие плиты 1898 ≈ 5.1 КБ) в свободный банк 7. Теперь
|
||||||
|
это дёшево: листья уже в резиденте, а `wall_pattern`/`wall_rnd`/марки/
|
||||||
|
`draw_gate_back` нужны обеим половинам — им понадобится тонкая `__banked`-
|
||||||
|
обёртка (тело остаётся непомеченным, горячие вызывающие платят ноль).
|
||||||
|
|
||||||
### <a id="l3-chomp"></a>L3-CHOMP. Чомперы — механика уровня 3
|
### <a id="l3-chomp"></a>L3-CHOMP. Чомперы — механика уровня 3
|
||||||
|
|
||||||
**Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти
|
**Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти
|
||||||
@@ -183,10 +254,10 @@
|
|||||||
коллизия и смерть Кида в сомкнутых челюстях (`seg004`/`seg006`); отрисовка
|
коллизия и смерть Кида в сомкнутых челюстях (`seg004`/`seg006`); отрисовка
|
||||||
кадра по модификатору (`draw_tile_anim`, ветка `tiles_18_chomper`).
|
кадра по модификатору (`draw_tile_anim`, ветка `tiles_18_chomper`).
|
||||||
|
|
||||||
**Риск:** банк 2 (`pop_bg`) занят на **90.8 %, свободно 1503 Б** (замер
|
**Место в банке 2 расчищено** ([MEM-BANK2](#mem-bank2), 2026-08-08): 72.9 %,
|
||||||
2026-08-07, было 2213 Б) — считать
|
**свободно 4442 Б** (было 1074 Б). Считать место ДО кодинга всё равно
|
||||||
место ДО кодинга (`levels_plan.md` §5.1), иначе повторится «банк 2 упёрся
|
обязательно (`levels_plan.md` §5.1), и если не хватит — в запасе вынос
|
||||||
в потолок» (коммит 2f3e854). Свободные номера банков есть (5+).
|
холодной половины `pop_bg` в банк 7, там расписано как.
|
||||||
|
|
||||||
**Ассеты УЖЕ упакованы (2026-08-07):** `pop_pack_bg.py` кладёт в атлас весь
|
**Ассеты УЖЕ упакованы (2026-08-07):** `pop_pack_bg.py` кладёт в атлас весь
|
||||||
набор кадров чомпера явно (`CHOMPER_BOT_IDS` 101-105, `CHOMPER_TOP_IDS`
|
набор кадров чомпера явно (`CHOMPER_BOT_IDS` 101-105, `CHOMPER_TOP_IDS`
|
||||||
@@ -301,60 +372,36 @@ offset 22790, **240 байт** = 5 палитр × 16 цветов × 3 байт
|
|||||||
таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в
|
таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в
|
||||||
W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)).
|
W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)).
|
||||||
|
|
||||||
### <a id="draw-char"></a>DRAW-CHAR. Отрисовка — ОДНА на всех Char, как физика после GUARD-PHYS
|
### <a id="draw-cost"></a>DRAW-COST. Стоимость fore-прохода после DRAW-CHAR
|
||||||
|
|
||||||
**Почему заведено (пользователь, 2026-08-07).** После GUARD-PHYS физика стала
|
**Наблюдение пользователя на приёмке 2026-08-08:** циан-полоса профиля
|
||||||
общей над `Char`, а ОТРИСОВКА так и осталась продублированной: `kid_draw` и
|
(спрайты + fore) выросла — «в пределах, но оптимизация отдельной задачей».
|
||||||
`pop_guard_draw` считают `load_frame_to_obj`, флип и heal-прямоугольники
|
|
||||||
каждая по-своему, окклюзия — двумя проходами (`pop_fore_over_kid` /
|
|
||||||
`pop_fore_over_char`). Общее вынесено лишь частично (`char_footprint`,
|
|
||||||
`other_overlay_tile`, `pop_sword_draw`), а СПИСКИ ВЫЗОВОВ разные.
|
|
||||||
|
|
||||||
**В оригинале она общая — проверено.** `seg008` держит два входа с
|
**Отчего именно.** Раньше перебор тайлов у Кида шёл строго по футпринту
|
||||||
ИДЕНТИЧНЫМ телом:
|
кадра (`char_x_left/right`, seg006:1021), а он УЖЕ спрайта; расширение
|
||||||
|
перебора окном спрайта (клинок и брызги уходят за габарит кадра) было
|
||||||
|
только у соперника. DRAW-CHAR сделала его общим — то есть у Кида теперь на
|
||||||
|
колонку-другую больше `fore_tile` за кадр. Это не регрессия «лишней
|
||||||
|
работы», а недостающая ранее корректность: тайл, который спрайт задевает,
|
||||||
|
обязан вернуть свой передний слой поверх него.
|
||||||
|
|
||||||
```
|
**Куда копать, когда возьмёмся** (сначала МЕРИТЬ, потом резать —
|
||||||
add_kid_to_objtable (seg008:22F0) add_guard_to_objtable (seg008:2324)
|
[`pop_fore_layer_cost`](../../../docs) уже показывал 78 % кадра до кэша
|
||||||
loadkid() loadshad()
|
кладки):
|
||||||
load_fram_det_col() load_fram_det_col()
|
1. расширять перебор ТОЛЬКО по накладным спрайтам (клинок/брызги), а не по
|
||||||
load_frame_to_obj() load_frame_to_obj()
|
всему окну: габарит самого кадра уже покрыт футпринтом + правилом меча;
|
||||||
stuck_lower() stuck_lower()
|
2. кэш «в этом тайле fore-слоя нет вовсе» — половина колонок пустые, а за
|
||||||
set_char_collision() set_char_collision()
|
каждую платим `tile_in_fclip` + разбор кладки;
|
||||||
set_objtile_at_char() set_objtile_at_char()
|
3. считать окно клипа в тайловых координатах один раз, а не сравнивать
|
||||||
redraw_at_char() redraw_at_char()
|
пиксельные габариты в двух циклах.
|
||||||
redraw_at_char2() redraw_at_char2()
|
|
||||||
clip_char() clip_char()
|
|
||||||
add_objtable(0) add_objtable(1 тень / 2 страж)
|
|
||||||
```
|
|
||||||
|
|
||||||
Различаются ТОЛЬКО окно (`Kid`/`Guard`), тип объекта и спецкейс тени у
|
### <a id="draw-char"></a>DRAW-CHAR — **ЗАКРЫТА И ПРОВЕРЕНА 2026-08-08**
|
||||||
зеркала (уровень 4). То есть та же схема, что `play_kid_frame` /
|
|
||||||
`play_guard_frame` у физики.
|
|
||||||
|
|
||||||
**Цена дублирования уже видна.** Скелет, падающий в пропасть (ур. 3),
|
Отрисовка сведена к одному набору функций над `Char` (`pop_cdraw.c`), проход
|
||||||
рисовался ПОВЕРХ верхней грани пола соседней колонки: проход
|
окклюзии — один на всех (`pop_fore_over_char`). Разбор, таблица «было →
|
||||||
`other_overlay_tile` (порядок midtable — «тайлы позже персонажа ложатся
|
стало», пять починенных расхождений и замеры — в
|
||||||
поверх него») вызывался только у Кида. Симптом закрыт 2026-08-07
|
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char). Оттуда же список того, что
|
||||||
переносом прохода в `pop_fore_over_char`, но корень — именно дублирование.
|
надо потрогать вживую при приёмке уровней.
|
||||||
Раньше это не всплывало потому, что ОБЫЧНЫЙ страж в пропасть не падает: его
|
|
||||||
физика гейтится `x ∈ [44,211)`, а провалившегося убирает
|
|
||||||
`check_guard_fallout`. Тем же путём пойдут тень (ур. 4/5/6/12), визирь
|
|
||||||
(13), толстяк (12) — расхождение будет повторяться на каждом.
|
|
||||||
|
|
||||||
**Что сделать:** свести к одному набору функций над `Char`, оставив
|
|
||||||
параметрами ровно то, что различается в оригинале:
|
|
||||||
- окно (`loadkid`/`loadshad` — уже есть);
|
|
||||||
- набор атласов и таблица кадров (`kid*` / `GUARD` / `SKEL` — уже
|
|
||||||
выбирается в `pop_guard_load`);
|
|
||||||
- слот heal-прямоугольников (у каждого персонажа свой, по страницам
|
|
||||||
дабл-буфера);
|
|
||||||
- тип объекта для порядка (Kid / тень / страж).
|
|
||||||
|
|
||||||
**Не портированы вовсе и относятся сюда же:** `stuck_lower`, `redraw_at_char2`,
|
|
||||||
`clip_char` (см. memory `pop_clip_char_todo`).
|
|
||||||
|
|
||||||
**Чем подтверждать:** падение скелета в пропасть с разных X (сегодняшний
|
|
||||||
сценарий), бой стража на ур. 1-2 без регрессий, `tests-host` зелёные.
|
|
||||||
|
|
||||||
### <a id="l3-pass"></a>L3-PASS. Приёмка уровня 3 — обход всех комнат
|
### <a id="l3-pass"></a>L3-PASS. Приёмка уровня 3 — обход всех комнат
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* то есть из общего заголовка эти ~100 Б размножились бы десятком мёртвых
|
* то есть из общего заголовка эти ~100 Б размножились бы десятком мёртвых
|
||||||
* копий (замер CLIP-1, 2026-08-01: +1091 Б в _CODE и +636 Б в банке
|
* копий (замер CLIP-1, 2026-08-01: +1091 Б в _CODE и +636 Б в банке
|
||||||
* стража — за код, который там никто не вызывает). Здесь их видят ровно
|
* стража — за код, который там никто не вызывает). Здесь их видят ровно
|
||||||
* три файла, которые реально рисуют: pop_bg.c, pop_kid.c, pop_gdraw.c.
|
* три файла, которые реально рисуют: pop_bg.c, pop_kid.c, pop_cdraw.c.
|
||||||
*/
|
*/
|
||||||
#ifndef POP_DRAW_H
|
#ifndef POP_DRAW_H
|
||||||
#define POP_DRAW_H
|
#define POP_DRAW_H
|
||||||
|
|||||||
@@ -24,23 +24,23 @@ typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe;
|
|||||||
#define KID_BIN_GFRAMES_OFF 3515
|
#define KID_BIN_GFRAMES_OFF 3515
|
||||||
|
|
||||||
#define KID_NSEQ 115
|
#define KID_NSEQ 115
|
||||||
static const uint16_t kid_seq_off[KID_NSEQ] = {
|
#define KID_SEQ_OFF_INIT { \
|
||||||
0x0000,0x1973,0x19A0,0x1A93,0x1ACD,0x1B39,0x1B5A,0x1BDB,
|
0x0000,0x1973,0x19A0,0x1A93,0x1ACD,0x1B39,0x1B5A,0x1BDB, \
|
||||||
0x1C54,0x1CA1,0x1D04,0x1D25,0x1D49,0x1D4F,0x1D68,0x1DF6,
|
0x1C54,0x1CA1,0x1D04,0x1D25,0x1D49,0x1D4F,0x1D68,0x1DF6, \
|
||||||
0x1C84,0x202B,0x1C1C,0x1C01,0x205A,0x1C38,0x209C,0x1D36,
|
0x1C84,0x202B,0x1C1C,0x1C01,0x205A,0x1C38,0x209C,0x1D36, \
|
||||||
0x1C69,0x1CD1,0x1B04,0x1B1A,0x1D7D,0x1F9E,0x1F92,0x1F82,
|
0x1C69,0x1CD1,0x1B04,0x1B1A,0x1D7D,0x1F9E,0x1F92,0x1F82, \
|
||||||
0x1F72,0x1F5D,0x1F48,0x1F33,0x1F19,0x1F13,0x1EF7,0x1EDA,
|
0x1F72,0x1F5D,0x1F48,0x1F33,0x1F19,0x1F13,0x1EF7,0x1EDA, \
|
||||||
0x1EBB,0x1E9C,0x1E7D,0x1B53,0x1E59,0x1E06,0x1E3B,0x1DFC,
|
0x1EBB,0x1E9C,0x1E7D,0x1B53,0x1E59,0x1E06,0x1E3B,0x1DFC, \
|
||||||
0x1D9B,0x1FB3,0x1FA7,0x20BE,0x20D1,0x20D4,0x20C9,0x19C4,
|
0x1D9B,0x1FB3,0x1FA7,0x20BE,0x20D1,0x20D4,0x20C9,0x19C4, \
|
||||||
0x1A13,0x1A2E,0x1A3C,0x1A7C,0x1A83,0x1A63,0x1A5E,0x1A68,
|
0x1A13,0x1A2E,0x1A3C,0x1A7C,0x1A83,0x1A63,0x1A5E,0x1A68, \
|
||||||
0x1A6E,0x1A75,0x1A5A,0x19F9,0x1CEC,0x1A54,0x20DD,0x20AE,
|
0x1A6E,0x1A75,0x1A5A,0x19F9,0x1CEC,0x1A54,0x20DD,0x20AE, \
|
||||||
0x1E78,0x1CDC,0x19DC,0x1A42,0x1A07,0x19A8,0x2009,0x1B1B,
|
0x1E78,0x1CDC,0x19DC,0x1A42,0x1A07,0x19A8,0x2009,0x1B1B, \
|
||||||
0x1A8B,0x1B85,0x1BA1,0x1BBF,0x196E,0x20A9,0x1A22,0x19A6,
|
0x1A8B,0x1B85,0x1BA1,0x1BBF,0x196E,0x20A9,0x1A22,0x19A6, \
|
||||||
0x19AC,0x1B2D,0x19C1,0x1FCA,0x1FDA,0x1FFB,0x2195,0x2132,
|
0x19AC,0x1B2D,0x19C1,0x1FCA,0x1FDA,0x1FFB,0x2195,0x2132, \
|
||||||
0x214F,0x2166,0x2199,0x21A8,0x216D,0x226E,0x2136,0x21BC,
|
0x214F,0x2166,0x2199,0x21A8,0x216D,0x226E,0x2136,0x21BC, \
|
||||||
0x1BFA,0x2245,0x2253,0x225B,0x21C4,0x21C0,0x21E6,0x21EA,
|
0x1BFA,0x2245,0x2253,0x225B,0x21C4,0x21C0,0x21E6,0x21EA, \
|
||||||
0x21FC,0x2240,0x2257,
|
0x21FC,0x2240,0x2257, \
|
||||||
};
|
}
|
||||||
|
|
||||||
/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.
|
/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.
|
||||||
* Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.
|
* Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.
|
||||||
|
|||||||
+303
-560
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
|||||||
#define POP_BG_H
|
#define POP_BG_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "pop_char.h" /* pop_char_t — общий проход отрисовки поверх Char */
|
||||||
|
|
||||||
/* ---- Вертикальный layout (PoP-экран 320×200 в нашем 320×256) --------- *
|
/* ---- Вертикальный layout (PoP-экран 320×200 в нашем 320×256) --------- *
|
||||||
* Игровой экран 200px центрируем по высоте: сдвиг всего рисунка вниз на
|
* Игровой экран 200px центрируем по высоте: сдвиг всего рисунка вниз на
|
||||||
@@ -51,47 +52,40 @@ void pop_room_draw(uint8_t room_num,
|
|||||||
* room_B(0, col-1). ОДИННАДЦАТЬ байт: [0..9] — колонки комнаты снизу,
|
* room_B(0, col-1). ОДИННАДЦАТЬ байт: [0..9] — колонки комнаты снизу,
|
||||||
* [10] — тайл (0,9) комнаты СНИЗУ-СЛЕВА (сосед угла (2,0); см.
|
* [10] — тайл (0,9) комнаты СНИЗУ-СЛЕВА (сосед угла (2,0); см.
|
||||||
* load_rowbelow, seg008:368). 0 — нет комнаты снизу. */
|
* load_rowbelow, seg008:368). 0 — нет комнаты снизу. */
|
||||||
void pop_room_set_below(const uint8_t *below_row0_fg) __banked;
|
/* НЕ __banked: тело в резиденте (pop_tile.c) — из банка зовётся прямым call. */
|
||||||
|
void pop_room_set_below(const uint8_t *below_row0_fg);
|
||||||
|
|
||||||
/* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы
|
/* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы
|
||||||
* кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 0 — нет
|
* кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 0 — нет
|
||||||
* комнаты сверху (кромка уровня → пол). */
|
* комнаты сверху (кромка уровня → пол). */
|
||||||
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) __banked;
|
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod);
|
||||||
|
|
||||||
/* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него —
|
/* Перерисовать передний слой (fore) и оверлеи тайлов ПОВЕРХ персонажа —
|
||||||
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
* ОДИН проход на всех Char (Кид, страж, скелет, дальше тень/визирь): в
|
||||||
* спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики
|
* оригинале add_kid_to_objtable и add_guard_to_objtable имеют одинаковое
|
||||||
* берутся из pop_kid (kid_fp_*). См. redraw_at_char/set_char_collision. */
|
* тело, а redraw_at_char/redraw_at_char2 гейтов по charid не имеют.
|
||||||
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
* Звать ПОСЛЕ отрисовки спрайта, когда окно fore-клипа уже стоит
|
||||||
int8_t dir, uint8_t frame, uint8_t action) __banked;
|
* (pop_fore_set_clip) — перебор тайлов расширяется по нему.
|
||||||
|
*
|
||||||
|
* ch — персонаж (Kid/Guard): из него берутся direction/sword/frame/action и
|
||||||
|
* curr_col/curr_row. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
||||||
|
* спрайта, w/h — размер кадра. См. redraw_at_char/set_char_collision. */
|
||||||
|
void pop_fore_over_char(const pop_char_t *ch, int obj_x, int obj_y,
|
||||||
|
uint16_t w, uint16_t h) __banked;
|
||||||
|
|
||||||
/* Окно fore-слоя: прямоугольник СПРАЙТА (экранные координаты, y уже с
|
/* Окно fore-слоя: прямоугольник СПРАЙТА (экранные координаты, y уже с
|
||||||
* POP_YOFF), поверх которого fore-проход возвращает куски тайлов. Ставит
|
* POP_YOFF), поверх которого fore-проход возвращает куски тайлов. Ставит
|
||||||
* тот, кто нарисовал спрайт (kid_draw / pop_guard_draw) — он один знает
|
* тот, кто нарисовал спрайт (pop_char_draw) — он один знает
|
||||||
* итоговый габарит (у Kid — с учётом клинка и обрезки clip_char). Вне
|
* итоговый габарит (у Kid — с учётом клинка и обрезки clip_char). Вне
|
||||||
* fore-проходов окно игнорируется: фон рисуется целиком. Мотивация и
|
* fore-проходов окно игнорируется: фон рисуется целиком. Мотивация и
|
||||||
* замер — в pop_bg.c у fclip_on. */
|
* замер — в pop_bg.c у fclip_on. */
|
||||||
void pop_fore_set_clip(int x, int y, int w, int h) __banked;
|
void pop_fore_set_clip(int x, int y, int w, int h);
|
||||||
|
|
||||||
/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка,
|
|
||||||
* без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата
|
|
||||||
* (до ×8/7), как у kid_fp_obj_x(). */
|
|
||||||
/* __banked: отрисовка стража уехала в СВОЙ банк (pop_gdraw.c), поэтому
|
|
||||||
* вызов стал кроссбанковым и обязан идти через трамплин. Зовётся раз в
|
|
||||||
* кадр, других вызывающих нет — цена одного трамплина здесь не видна. */
|
|
||||||
/* ex/ey/ew/eh — ОБЪЕДИНЁННЫЙ экранный прямоугольник «персонаж + клинок +
|
|
||||||
* брызги» (тот же, что уходит в pop_fore_set_clip): перебор передних тайлов
|
|
||||||
* идёт по нему, иначе меч и звёздочка удара остаются поверх кладки.
|
|
||||||
* ew == 0 — расширения нет (только габарит кадра). */
|
|
||||||
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
|
||||||
int8_t dir, uint8_t sword,
|
|
||||||
int ex, int ey, uint16_t ew, uint16_t eh,
|
|
||||||
uint8_t action, int8_t ccol, int8_t crow) __banked;
|
|
||||||
|
|
||||||
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
|
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
|
||||||
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
|
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
|
||||||
* прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице
|
* прямоугольник персонажа, поэтому его heal этого не убирает — на второй
|
||||||
* дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */
|
* странице дабл-буфера остаётся след и мерцает. Звать рядом с
|
||||||
|
* pop_char_heal. */
|
||||||
void pop_fore_heal(void) __banked;
|
void pop_fore_heal(void) __banked;
|
||||||
|
|
||||||
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
|
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
|
||||||
@@ -154,7 +148,7 @@ void pop_loose_mob_reset(void) __banked; /* убрать все куски (С
|
|||||||
void pop_loose_mob_room_changed(void) __banked;
|
void pop_loose_mob_room_changed(void) __banked;
|
||||||
|
|
||||||
/* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs,
|
/* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs,
|
||||||
* seg008:1561). Звать после kid_draw, перед fore-слоем. */
|
* seg008:1561). Звать после отрисовки персонажей, перед fore-слоем. */
|
||||||
void pop_loose_mob_draw_over(void) __banked;
|
void pop_loose_mob_draw_over(void) __banked;
|
||||||
|
|
||||||
/* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */
|
/* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */
|
||||||
@@ -169,7 +163,7 @@ void pop_clip_sprite(int x, int w, int sy_top, int h) __banked;
|
|||||||
/* Почистить борта (верхний + статус-полоса/нижний) — но ТОЛЬКО в кадрах,
|
/* Почистить борта (верхний + статус-полоса/нижний) — но ТОЛЬКО в кадрах,
|
||||||
* где кто-то реально вышел за поле (гейт по флагу от pop_clip_sprite):
|
* где кто-то реально вышел за поле (гейт по флагу от pop_clip_sprite):
|
||||||
* даром в покое, O(1) при падении (не зависит от числа спрайтов). Звать
|
* даром в покое, O(1) при падении (не зависит от числа спрайтов). Звать
|
||||||
* КАЖДЫЙ кадр ПОСЛЕ kid_draw+fore на back-странице. */
|
* КАЖДЫЙ кадр ПОСЛЕ персонажей и fore на back-странице. */
|
||||||
void pop_room_clip_borders(void) __banked;
|
void pop_room_clip_borders(void) __banked;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,455 @@
|
|||||||
|
/*
|
||||||
|
* pop_cdraw.c — ОТРИСОВКА ПЕРСОНАЖЕЙ (Кид, страж, скелет; дальше тень,
|
||||||
|
* визирь, толстяк) + полоса HP. Банк 4.
|
||||||
|
*
|
||||||
|
* Одно тело на всех Char — порт add_kid_to_objtable/add_guard_to_objtable
|
||||||
|
* (seg008:22F0/2324), различия сведены к слоту; мотивация и порядок вызовов
|
||||||
|
* в кадре — в шапке pop_cdraw.h. До DRAW-CHAR это были kid_draw в
|
||||||
|
* резиденте и pop_guard_draw здесь же, каждый со своей математикой кадра,
|
||||||
|
* своими heal-прямоугольниками и своим проходом окклюзии.
|
||||||
|
*
|
||||||
|
* Почему банк, а не резидент: зовёт только главный цикл (раз в кадр на
|
||||||
|
* персонажа), горячих банк→банк переходов отсюда нет, а резидентное
|
||||||
|
* пространство W1+W2 делят код и данные — килобайт кода, уехавший в банк,
|
||||||
|
* это килобайт для кучи. Писучие статики банкового модуля (--dataseg не
|
||||||
|
* задаём) остаются в обычном _DATA, то есть в W2: pop_cd читают и физика, и
|
||||||
|
* фон из СВОИХ банков — без трамплина, обычной адресацией.
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_kid.h" /* атласы/кадр Кида, pop_sword_draw, окна Char */
|
||||||
|
#include "pop_cdraw.h"
|
||||||
|
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char, pop_fore_set_clip */
|
||||||
|
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||||||
|
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида; clip_char */
|
||||||
|
#include "pop_level.h" /* тип соперника уровня (набор спрайтов) */
|
||||||
|
#include "kid_data.h"
|
||||||
|
#include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */
|
||||||
|
|
||||||
|
/* Цвет стража (set_chtab_palette, seg003:257). Оригинал держит ОДИН набор
|
||||||
|
* спрайтов и подменяет 16 цветов палитры chtab_5 по level.guards_color —
|
||||||
|
* так на уровне 2 стражи комнат 4/7/11/15/24 разного цвета (1/3/1/1/6).
|
||||||
|
* Полоса HP стража рисуется тем же атласом, поэтому меняется заодно.
|
||||||
|
* color = 0 — палитру не трогаем (оригинал в этом случае set_chtab_palette
|
||||||
|
* не зовёт: тип уровня не «обычный страж»).
|
||||||
|
* Грузим В ОБЕ палитры: у страниц дабл-буфера они раздельные. */
|
||||||
|
void pop_guard_set_palette(uint8_t color) __banked
|
||||||
|
{
|
||||||
|
/* Таблица — rodata ЭТОГО модуля, то есть страница банка (0xC000+), а
|
||||||
|
* gfx_pal_load отдаёт указатель в BIOS ($A4 через rst #0x08). BIOS
|
||||||
|
* читает только #4000-#BFFF (см. корневой CLAUDE.md), поэтому копируем
|
||||||
|
* запись в локальный буфер — стек гарантированно в W2. Без копии BIOS
|
||||||
|
* забирал мусор с чужой страницы и страж становился невидимым. */
|
||||||
|
uint8_t buf[64];
|
||||||
|
const uint8_t *p;
|
||||||
|
uint8_t i;
|
||||||
|
if (color == 0 || color > POP_GUARD_NPAL) return;
|
||||||
|
p = pop_guard_pal[color - 1];
|
||||||
|
for (i = 0; i < 64; i++) buf[i] = p[i];
|
||||||
|
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
||||||
|
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
|
||||||
|
#define GUARD_PAGES 5
|
||||||
|
static atlas_t gp[GUARD_PAGES];
|
||||||
|
static uint8_t g_ok;
|
||||||
|
|
||||||
|
/* Состояние отрисовки по слотам (см. pop_cdraw.h): heal-прямоугольники по
|
||||||
|
* страницам дабл-буфера, габарит кадра и окно fore-клипа. */
|
||||||
|
pop_cdraw_t pop_cd[POP_CH_N];
|
||||||
|
|
||||||
|
/* «Брызги» урона Кида — спрайт chtab_2 id 218 (res619.png, в наших атласах
|
||||||
|
* страница 27). У соперника это image 1 его собственного набора. */
|
||||||
|
#define KID_SPLASH_IMAGE 218
|
||||||
|
|
||||||
|
/* Набор спрайтов соперника выбирает ТИП УРОВНЯ (load_lev_spr, seg000:1092):
|
||||||
|
* tbl_guard_type = {0 guard, 1 fat, 2 skeleton, 3 vizier, 4 shadow, −1 нет}.
|
||||||
|
* У нас пока два набора: обычный страж (уровни 1-2) и скелет (уровень 3);
|
||||||
|
* у скелета и кадров меньше (28 против 34), поэтому пятую страницу он не
|
||||||
|
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
|
||||||
|
int pop_guard_load(void) __banked
|
||||||
|
{
|
||||||
|
static const char *const guard_names[GUARD_PAGES] = {
|
||||||
|
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
|
||||||
|
"GUARD\\g3.atl", "GUARD\\g4.atl"
|
||||||
|
};
|
||||||
|
static const char *const skel_names[GUARD_PAGES] = {
|
||||||
|
"SKEL\\g0.atl", "SKEL\\g1.atl", "SKEL\\g2.atl",
|
||||||
|
"SKEL\\g3.atl", 0
|
||||||
|
};
|
||||||
|
const char *const *names;
|
||||||
|
int8_t type = pop_level_guard_type(pop_current_level);
|
||||||
|
uint8_t i;
|
||||||
|
if (type < 0) return 0; /* соперников на уровне нет */
|
||||||
|
names = (type == 2) ? skel_names : guard_names;
|
||||||
|
for (i = 0; i < GUARD_PAGES; i++) {
|
||||||
|
if (names[i] == 0) break;
|
||||||
|
if (atlas_load(&gp[i], names[i]) != 0) return -1;
|
||||||
|
}
|
||||||
|
g_ok = 1;
|
||||||
|
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
|
||||||
|
* из res10.bin по guards_color — это делает pop_guard_set_palette при
|
||||||
|
* входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */
|
||||||
|
if (type == 2) {
|
||||||
|
uint8_t buf[64], k;
|
||||||
|
for (k = 0; k < 64; k++) buf[k] = pop_skel_pal[k]; /* BIOS: буфер в W2 */
|
||||||
|
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
||||||
|
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_guard_free(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
if (!g_ok) return;
|
||||||
|
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
|
||||||
|
g_ok = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Добавить накладной спрайт (клинок / брызги) в overlay-прямоугольник
|
||||||
|
* страницы. Оба мелкие и в одном кадре встречаются редко — их объединение
|
||||||
|
* дешевле третьего heal-слота. */
|
||||||
|
static void cd_overlay_add(pop_cdraw_t *s, uint8_t dp, int x, int y,
|
||||||
|
uint16_t w, uint16_t h)
|
||||||
|
{
|
||||||
|
if (!s->ovalid[dp]) {
|
||||||
|
s->ox[dp] = x; s->oy[dp] = y; s->ow[dp] = w; s->oh[dp] = h;
|
||||||
|
s->ovalid[dp] = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int x0 = s->ox[dp], y0 = s->oy[dp];
|
||||||
|
int x1 = x0 + (int)s->ow[dp], y1 = y0 + (int)s->oh[dp];
|
||||||
|
if (x < x0) x0 = x;
|
||||||
|
if (y < y0) y0 = y;
|
||||||
|
if (x + (int)w > x1) x1 = x + (int)w;
|
||||||
|
if (y + (int)h > y1) y1 = y + (int)h;
|
||||||
|
s->ox[dp] = x0; s->oy[dp] = y0;
|
||||||
|
s->ow[dp] = (uint16_t)(x1 - x0); s->oh[dp] = (uint16_t)(y1 - y0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Добавить прямоугольник в окно fore-клипа слота (объединение «спрайт +
|
||||||
|
* накладные»): по нему fore-проход возвращает куски тайлов и по нему же
|
||||||
|
* расширяет перебор колонок/рядов. */
|
||||||
|
static void cd_clip_add(pop_cdraw_t *s, int x, int y, uint16_t w, uint16_t h)
|
||||||
|
{
|
||||||
|
if (!s->cw) {
|
||||||
|
s->cx = x; s->cy = y; s->cw = w; s->ch = h;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int x1 = s->cx + (int)s->cw, y1 = s->cy + (int)s->ch;
|
||||||
|
if (x < s->cx) s->cx = x;
|
||||||
|
if (y < s->cy) s->cy = y;
|
||||||
|
if (x + (int)w > x1) x1 = x + (int)w;
|
||||||
|
if (y + (int)h > y1) y1 = y + (int)h;
|
||||||
|
s->cw = (uint16_t)(x1 - s->cx);
|
||||||
|
s->ch = (uint16_t)(y1 - s->cy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стирание прошлого кадра персонажа и его накладных. pop_heal_fast, а не
|
||||||
|
* голый gfx_heal: стираем ровно то, что рисовали, а рисовали в подавляющем
|
||||||
|
* большинстве кадров noclip-ядром; клипающий путь остаётся для straddle и
|
||||||
|
* выхода за поле. */
|
||||||
|
void pop_char_heal(uint8_t who) __banked
|
||||||
|
{
|
||||||
|
pop_cdraw_t *s = &pop_cd[who];
|
||||||
|
uint8_t p = gfx_get_draw_page() & 1;
|
||||||
|
if (s->valid[p]) pop_heal_fast(s->x[p], s->y[p] + POP_YOFF, s->w[p], s->h[p]);
|
||||||
|
if (s->ovalid[p]) pop_heal_fast(s->ox[p], s->oy[p] + POP_YOFF, s->ow[p], s->oh[p]);
|
||||||
|
s->valid[p] = s->ovalid[p] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_char_set_render_dx(uint8_t who, int dx) __banked
|
||||||
|
{
|
||||||
|
pop_cd[who].render_dx = dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Брызги урона (draw_hurt_splash, seg006:2003) — ОДИН порт на всех: у Кида
|
||||||
|
* это image 218 своего chtab_2, у соперника image 1 его chtab_5 (у
|
||||||
|
* add_midtable аргумент `obj_id + 1`, а get_image вычитает единицу обратно
|
||||||
|
* — тот же off-by-one, что расписан в шапке pop_pack_guard.py). Подъём:
|
||||||
|
* `((charid == kid) << 2) + 11`, то есть 15 у Кида и 11 у остальных.
|
||||||
|
*
|
||||||
|
* Рисуются МЕЖДУ персонажем и клинком: draw_kid/draw_guard кладут их в
|
||||||
|
* objtable сразу после персонажа и до клинка (seg008:22A2/22C9), а порядок
|
||||||
|
* записей и есть порядок рисования. */
|
||||||
|
static void cd_splash(pop_cdraw_t *s, uint8_t who, const atlas_t *pages,
|
||||||
|
int fp_x, int obj_y)
|
||||||
|
{
|
||||||
|
const atlas_t *ap;
|
||||||
|
uint8_t aidx;
|
||||||
|
const uint8_t *img;
|
||||||
|
uint16_t w, h;
|
||||||
|
int qx = fp_x, qy = obj_y;
|
||||||
|
int fw = (Char.direction < 0) ? -5 : 5; /* obj_dx_forward(5) */
|
||||||
|
|
||||||
|
if (Char.frame == 178) return; /* chomped: брызг нет */
|
||||||
|
if (who == POP_CH_KID) {
|
||||||
|
if ((KID_SPLASH_IMAGE >> 3) >= kid_npages) return; /* страница не загружена */
|
||||||
|
ap = &pages[KID_SPLASH_IMAGE >> 3]; aidx = KID_SPLASH_IMAGE & 7;
|
||||||
|
} else {
|
||||||
|
ap = &pages[0]; aidx = 1;
|
||||||
|
}
|
||||||
|
if (Char.frame == 185 || /* мёртв */
|
||||||
|
(Char.frame >= 106 && Char.frame < 111)) { /* падение+посадка */
|
||||||
|
qy += 4;
|
||||||
|
} else if (Char.frame == 177) { /* напоролся на пики */
|
||||||
|
fw = -fw; /* obj_dx_forward(-5) */
|
||||||
|
} else {
|
||||||
|
qy -= (who == POP_CH_KID) ? 15 : 11;
|
||||||
|
}
|
||||||
|
qx = (qx + fw) * 8 / 7; /* calc_screen_x_coord */
|
||||||
|
img = (const uint8_t *)atlas_image(ap, aidx);
|
||||||
|
gfx_w0_map(ap->page);
|
||||||
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
|
if (w && h) {
|
||||||
|
int top = qy - (int)h + 1;
|
||||||
|
uint8_t flip = (uint8_t)(Char.direction >= 0);
|
||||||
|
int skip = 0, rows = (int)h; /* поле комнаты 0..192 */
|
||||||
|
uint8_t dp;
|
||||||
|
if (flip) qx -= (int)w;
|
||||||
|
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
||||||
|
if (top + rows > 192) rows = 192 - top;
|
||||||
|
if (rows <= 0) return;
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
if (pop_onscreen_cols(qx, top + POP_YOFF, w, (uint16_t)rows))
|
||||||
|
gfx_blit_cols_part_noclip(qx, top + POP_YOFF, img, flip,
|
||||||
|
(uint8_t)skip, (uint8_t)rows);
|
||||||
|
else
|
||||||
|
gfx_blit_cols_part(qx, top + POP_YOFF, img, flip, skip, rows);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
dp = gfx_get_draw_page() & 1;
|
||||||
|
cd_overlay_add(s, dp, qx, top, w, (uint16_t)rows);
|
||||||
|
cd_clip_add(s, qx, top, w, (uint16_t)rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_char_draw(uint8_t who) __banked
|
||||||
|
{
|
||||||
|
pop_cdraw_t *s = &pop_cd[who];
|
||||||
|
const pop_char_t *ch;
|
||||||
|
const kframe *fr;
|
||||||
|
const atlas_t *pages;
|
||||||
|
const uint8_t *img;
|
||||||
|
uint8_t npages, page, idx, dp, flip, hurt;
|
||||||
|
uint16_t w, h, vis_w;
|
||||||
|
int obj_x, obj_y, fp_x, fwd, top, bx, skip, rows, ct, cr;
|
||||||
|
|
||||||
|
s->cw = 0; /* окно fore-клипа: пусто, пока не нарисовали */
|
||||||
|
s->fpw = 0; /* габарит кадра: нечего перекрывать */
|
||||||
|
|
||||||
|
/* Окно Char (loadkid/loadshad) — как в оригинале: clip_char, футпринт и
|
||||||
|
* брызги работают с АКТИВНЫМ персонажем, а к этому моменту в Char лежит
|
||||||
|
* тот, кто тикал последним. Выгружать назад нечего: отрисовка только
|
||||||
|
* читает. */
|
||||||
|
if (who == POP_CH_KID) {
|
||||||
|
pop_loadkid();
|
||||||
|
ch = &Kid; fr = &kid_frame; pages = kidp; npages = kid_npages;
|
||||||
|
hurt = pop_kid_hurt;
|
||||||
|
} else {
|
||||||
|
pop_loadshad();
|
||||||
|
ch = &Guard; fr = &pop_gframe; pages = gp;
|
||||||
|
npages = g_ok ? GUARD_PAGES : 0;
|
||||||
|
hurt = pop_guard_hurt;
|
||||||
|
if (Guard.charid == 0) return; /* соперника на сцене нет */
|
||||||
|
}
|
||||||
|
if (fr->image == 255) return; /* пустой кадр */
|
||||||
|
|
||||||
|
/* load_frame_to_obj (seg008:1728): НИЗ спрайта на obj_y, левый край на
|
||||||
|
* obj_x. +render_dx — straddle-смещение (∓140), когда комната персонажа
|
||||||
|
* не совпадает с отрисованной: спрайт за кромкой. Коллизия НЕ смещается
|
||||||
|
* — она в кадре своей комнаты, смещение чисто рендерное. */
|
||||||
|
fwd = (int)ch->x + ((ch->direction < 0) ? -fr->dx : fr->dx) + s->render_dx;
|
||||||
|
obj_x = (fwd << 1) - 116;
|
||||||
|
obj_y = (int)ch->y + fr->dy;
|
||||||
|
if ((int8_t)(fr->flags ^ ch->direction) >= 0)
|
||||||
|
obj_x++; /* even/odd pixel (в ЛОГИЧЕСКОЙ X) */
|
||||||
|
fp_x = obj_x; /* ЛОГИЧЕСКАЯ X — для футпринта */
|
||||||
|
/* Позицию кадра публикуем СРАЗУ: её читает физика (check_spike_below —
|
||||||
|
* char_x_left активного персонажа), и она обязана обновиться даже в
|
||||||
|
* кадре, который рисовать нечем. Размер (fpw) — ниже, по факту кадра:
|
||||||
|
* он же служит признаком «есть что перекрывать» для fore-прохода. */
|
||||||
|
s->fpx = fp_x; s->fpy = obj_y;
|
||||||
|
/* seg008 calc_screen_x_coord: логическая X (280 шир = 28px/тайл) ->
|
||||||
|
* экран (320 шир = 32px/тайл), ×320/280 = ×8/7. БЕЗ этого персонаж
|
||||||
|
* уезжает влево от тайлов тем сильнее, чем правее. */
|
||||||
|
obj_x = obj_x * 8 / 7;
|
||||||
|
|
||||||
|
page = (uint8_t)(fr->image >> 3);
|
||||||
|
idx = (uint8_t)(fr->image & 7);
|
||||||
|
if (page >= npages) return; /* страница набора не загружена */
|
||||||
|
img = (const uint8_t *)atlas_image(&pages[page], idx);
|
||||||
|
gfx_w0_map(pages[page].page);
|
||||||
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
|
dp = gfx_get_draw_page() & 1;
|
||||||
|
if (w && h) {
|
||||||
|
/* Спрайты нарисованы ЛИЦОМ ВЛЕВО (как в оригинале); seg008:864 —
|
||||||
|
* флип, когда персонаж смотрит ВПРАВО, и тогда xpos -= image->w
|
||||||
|
* (draw_mid). Флип column-major бесплатен, но зеркалит ВНУТРИ
|
||||||
|
* [x,x+w), поэтому при флипе подаём x−w. */
|
||||||
|
flip = (uint8_t)(ch->direction >= 0);
|
||||||
|
bx = obj_x;
|
||||||
|
if (flip) bx -= (int)w;
|
||||||
|
top = obj_y - (int)h + 1;
|
||||||
|
vis_w = w;
|
||||||
|
skip = 0;
|
||||||
|
/* clip_char (seg006:1231/1749): тайл над головой — стена/пол → всё
|
||||||
|
* выше линии полосы своего ряда НЕ рисуется, иначе персонаж
|
||||||
|
* просвечивает сквозь щели кладки при спуске/висе. */
|
||||||
|
ct = pop_clip_char_top(fp_x, obj_y, w, h);
|
||||||
|
if (ct > top) {
|
||||||
|
skip = ct - top;
|
||||||
|
if (skip > (int)h) skip = (int)h; /* обрезан целиком */
|
||||||
|
top = ct;
|
||||||
|
}
|
||||||
|
/* Вторая половина clip_char: правый косяк двери уровня. Дверь
|
||||||
|
* рисуется ПОД персонажем (вся в backtable), поэтому спрятать
|
||||||
|
* поднимающегося по лестнице можно только срезав спрайт. */
|
||||||
|
cr = pop_clip_char_right();
|
||||||
|
if (cr) {
|
||||||
|
int avail = cr - bx;
|
||||||
|
if (avail <= 0) vis_w = 0; /* весь спрайт за косяком */
|
||||||
|
else if (avail < (int)w) vis_w = (uint16_t)avail;
|
||||||
|
}
|
||||||
|
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507:
|
||||||
|
* obj_clip_top = 0, obj_clip_bottom = 192). Персонаж, вышедший за
|
||||||
|
* него (скелет, падающий в пропасть), обязан обрезаться: ниже поля
|
||||||
|
* идёт полоса HP, и без клипа спрайт лез прямо на неё. */
|
||||||
|
rows = (int)h - skip;
|
||||||
|
if (top < 0) { skip += -top; rows += top; top = 0; }
|
||||||
|
if (top + rows > 192) rows = 192 - top;
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||||||
|
if (rows > 0 && vis_w) { /* +YOFF: центрирование */
|
||||||
|
/* noclip-ядро ширину не ограничивает, поэтому при обрезке справа
|
||||||
|
* идём общим путём: это редкие кадры (подъём в двери уровня раз
|
||||||
|
* за уровень), цена подготовки там не видна. */
|
||||||
|
if (vis_w == w && pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
||||||
|
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
|
||||||
|
(uint8_t)skip, (uint8_t)rows);
|
||||||
|
else
|
||||||
|
gfx_blit_cols_part_w(bx, top + POP_YOFF, img, flip, skip, rows,
|
||||||
|
(uint8_t)(vis_w == w ? 0 : vis_w));
|
||||||
|
} else rows = 0;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
|
||||||
|
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
|
||||||
|
s->x[dp] = bx; s->y[dp] = top;
|
||||||
|
s->w[dp] = vis_w; s->h[dp] = (uint16_t)rows;
|
||||||
|
s->valid[dp] = (uint8_t)(rows != 0);
|
||||||
|
if (rows) cd_clip_add(s, bx, top, vis_w, (uint16_t)rows);
|
||||||
|
s->fpw = w; s->fph = h; /* габарит КАДРА (не обрезанный): */
|
||||||
|
/* по нему считается футпринт */
|
||||||
|
}
|
||||||
|
if (hurt) cd_splash(s, who, pages, fp_x, obj_y);
|
||||||
|
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
||||||
|
* персонажа — со своим прямоугольником heal. */
|
||||||
|
{
|
||||||
|
int sx, sy; uint16_t sw, sh;
|
||||||
|
if (pop_sword_draw(ch, fr, obj_x, obj_y, &sx, &sy, &sw, &sh)) {
|
||||||
|
cd_overlay_add(s, dp, sx, sy, sw, sh);
|
||||||
|
cd_clip_add(s, sx, sy, sw, sh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_char_fore(uint8_t who) __banked
|
||||||
|
{
|
||||||
|
pop_cdraw_t *s = &pop_cd[who];
|
||||||
|
/* Окно fore-клипа — одно на всех, и ставит его КАЖДЫЙ, кто рисует
|
||||||
|
* персонажа. Пока соперник рисовался строго ДО Кида, к моменту прохода
|
||||||
|
* в окне оказывался Кид и всё сходилось само; как только порядок стал
|
||||||
|
* переменным (кто ниже — тот поверх), при «соперник поверх» окно
|
||||||
|
* оставалось ЕГО, и fore-проход Кида отсекался целиком — Кид рисовался
|
||||||
|
* поверх передних столбов. Поэтому окно ставится здесь, а не держится
|
||||||
|
* «по счастливому порядку вызовов». */
|
||||||
|
pop_fore_set_clip(s->cx, s->cy + POP_YOFF, (int)s->cw, (int)s->ch);
|
||||||
|
if (s->fpw)
|
||||||
|
pop_fore_over_char(who == POP_CH_KID ? &Kid : &Guard,
|
||||||
|
s->fpx, s->fpy, s->fpw, s->fph);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
|
||||||
|
* Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри
|
||||||
|
* тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража
|
||||||
|
* справа налево от x = 314. Пустые деления у Кида — свой спрайт (chtab_2
|
||||||
|
* id 217); у стража оригинал печатает тот же спрайт ЧЁРНЫМ силуэтом, а мы
|
||||||
|
* просто не печатаем — полоса и так на чёрном фоне.
|
||||||
|
*
|
||||||
|
* Спрайты Кида (216/217) лежат в его же атласах, поэтому блитим через
|
||||||
|
* pop_kid_img_blit; спрайт стража — image 0 его chtab (наша страница 0). */
|
||||||
|
#define HP_Y 194
|
||||||
|
#define HP_STEP 7
|
||||||
|
#define HP_KID_FULL 216
|
||||||
|
#define HP_KID_EMPTY 217
|
||||||
|
#define HP_MAXDRAW 10 /* больше в полосу всё равно не влезает */
|
||||||
|
|
||||||
|
/* Полоса перерисовывается ТОЛЬКО когда числа изменились — и тогда на ОБЕИХ
|
||||||
|
* страницах дабл-буфера (иначе на второй останется старое значение и его
|
||||||
|
* будет видно как мерцание через кадр). Отсюда счётчик страниц, а не флаг.
|
||||||
|
*
|
||||||
|
* Снимки прошлых значений пишем ДО сравнения: `if (n != s) s = n;` SDCC z80
|
||||||
|
* собирает неверно — SUB затирает A, и в s уезжает разность (memory
|
||||||
|
* sdcc_z80_cmp_store_a_bug). */
|
||||||
|
static uint8_t hp_todo; /* сколько страниц ещё обновить */
|
||||||
|
static uint8_t hp_kid_prev, hp_kidmax_prev, hp_gd_prev, hp_gdmax_prev;
|
||||||
|
|
||||||
|
/* Заставить перерисовать полосу на обеих страницах: звать при входе в
|
||||||
|
* комнату (фон перерисован целиком и стёр её) и при старте. */
|
||||||
|
void pop_hp_invalidate(void) __banked { hp_todo = 2; }
|
||||||
|
|
||||||
|
void pop_hp_draw(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t i, n;
|
||||||
|
uint8_t gd = (uint8_t)(Guard.charid ? guardhp_curr : 0);
|
||||||
|
uint8_t gm = (uint8_t)(Guard.charid ? guardhp_max : 0);
|
||||||
|
|
||||||
|
{ /* Изменилось хоть одно число — обновить обе страницы. */
|
||||||
|
uint8_t k = hitp_curr, km = hitp_max;
|
||||||
|
uint8_t changed = (uint8_t)(k != hp_kid_prev || km != hp_kidmax_prev ||
|
||||||
|
gd != hp_gd_prev || gm != hp_gdmax_prev);
|
||||||
|
hp_kid_prev = k; hp_kidmax_prev = km;
|
||||||
|
hp_gd_prev = gd; hp_gdmax_prev = gm;
|
||||||
|
if (changed) hp_todo = 2;
|
||||||
|
}
|
||||||
|
if (hp_todo == 0) return;
|
||||||
|
hp_todo--;
|
||||||
|
|
||||||
|
/* Стереть прошлую полосу: фон под ней — чёрный борт. */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6);
|
||||||
|
|
||||||
|
/* Кид: сначала пустые деления, поверх — полные (порядок оригинала). */
|
||||||
|
n = (uint8_t)(hitp_max > HP_MAXDRAW ? HP_MAXDRAW : hitp_max);
|
||||||
|
for (i = hitp_curr; i < n; i++)
|
||||||
|
pop_kid_img_blit(HP_KID_EMPTY, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||||||
|
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
||||||
|
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||||||
|
|
||||||
|
/* Страж: только полные деления, справа налево. Скелет полосы не имеет. */
|
||||||
|
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON && gd) {
|
||||||
|
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
||||||
|
uint16_t w, h;
|
||||||
|
gfx_w0_map(gp[0].page);
|
||||||
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
|
if (w && h) {
|
||||||
|
/* Атлас стража тоже column-major — блит колоночный. */
|
||||||
|
n = (uint8_t)(gd > HP_MAXDRAW ? HP_MAXDRAW : gd);
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
gfx_blit_cols_part_noclip(314 - (int)i * HP_STEP,
|
||||||
|
HP_Y + POP_YOFF, img, 0, 0, 0);
|
||||||
|
}
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* pop_cdraw.h — ОТРИСОВКА ПЕРСОНАЖА, одна на всех Char.
|
||||||
|
*
|
||||||
|
* В оригинале это два входа с ИДЕНТИЧНЫМ телом (seg008:22F0/2324):
|
||||||
|
*
|
||||||
|
* add_kid_to_objtable add_guard_to_objtable
|
||||||
|
* loadkid() loadshad()
|
||||||
|
* load_fram_det_col() load_fram_det_col()
|
||||||
|
* load_frame_to_obj() load_frame_to_obj()
|
||||||
|
* stuck_lower() stuck_lower()
|
||||||
|
* set_char_collision() set_char_collision()
|
||||||
|
* set_objtile_at_char() set_objtile_at_char()
|
||||||
|
* redraw_at_char() redraw_at_char()
|
||||||
|
* redraw_at_char2() redraw_at_char2()
|
||||||
|
* clip_char() clip_char()
|
||||||
|
* add_objtable(0) add_objtable(1 тень / 2 страж)
|
||||||
|
*
|
||||||
|
* — различаются ТОЛЬКО окном (loadkid/loadshad), набором спрайтов и типом
|
||||||
|
* объекта. У нас до DRAW-CHAR это были два независимых куска кода
|
||||||
|
* (kid_draw в резиденте и pop_guard_draw в банке), и расхождение уже стоило
|
||||||
|
* бага: падающий скелет рисовался поверх верхней грани пола соседней
|
||||||
|
* колонки, потому что порт redraw_at_char2 был только у Кида. Тем же путём
|
||||||
|
* пошли бы тень (ур. 4/5/6/12), визирь (13) и толстяк (12).
|
||||||
|
*
|
||||||
|
* Поэтому здесь ОДИН набор функций, а различия сведены к слоту:
|
||||||
|
* POP_CH_KID — Кид: окно loadkid, атласы chtab_2 (kidp), кадр kid_frame;
|
||||||
|
* POP_CH_OPP — соперник: окно loadshad, атласы chtab_5 (страж/скелет),
|
||||||
|
* кадр pop_gframe.
|
||||||
|
*
|
||||||
|
* Порядок в кадре (задаёт главный цикл, как draw_people):
|
||||||
|
* pop_char_heal(who) — стереть прошлый кадр ЭТОЙ страницы дабл-буфера;
|
||||||
|
* pop_char_draw(who) — спрайт + брызги урона + клинок;
|
||||||
|
* pop_char_fore(who) — передние грани и оверлеи тайлов ПОВЕРХ него.
|
||||||
|
*/
|
||||||
|
#ifndef POP_CDRAW_H
|
||||||
|
#define POP_CDRAW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define POP_CH_KID 0
|
||||||
|
#define POP_CH_OPP 1
|
||||||
|
#define POP_CH_N 2
|
||||||
|
|
||||||
|
/* Состояние отрисовки одного слота. Публичное, потому что метрики кадра
|
||||||
|
* (fp*) читает физика из ДРУГОГО банка (char_width_half в
|
||||||
|
* set_char_collision, порядок «кто поверх кого» в pop_bg): данные лежат в
|
||||||
|
* _DATA (W2) и видны всем без трамплина, а вызов аксессора из банка стоил
|
||||||
|
* бы дороже самого чтения. */
|
||||||
|
typedef struct {
|
||||||
|
/* прямоугольник спрайта по СТРАНИЦАМ дабл-буфера: у каждой своя
|
||||||
|
* видео-ОЗУ и своя ОЗУ-копия, поэтому heal обязан стирать спрайт именно
|
||||||
|
* той страницы, в которую сейчас рисуем */
|
||||||
|
int x[2], y[2];
|
||||||
|
uint16_t w[2], h[2];
|
||||||
|
uint8_t valid[2];
|
||||||
|
/* НАКЛАДНЫЕ спрайты (клинок + брызги урона) — свой прямоугольник, не
|
||||||
|
* объединение с персонажем: объединение сильно больше суммы двух
|
||||||
|
* (клинок уходит вперёд-вверх), а heal стоит ровно по площади */
|
||||||
|
int ox[2], oy[2];
|
||||||
|
uint16_t ow[2], oh[2];
|
||||||
|
uint8_t ovalid[2];
|
||||||
|
/* габарит кадра для fore-прохода: fpx — ЛОГИЧЕСКАЯ X (до ×8/7),
|
||||||
|
* fpy — низ спрайта; fpw == 0 — в этом кадре рисовать было нечего */
|
||||||
|
int fpx, fpy;
|
||||||
|
uint16_t fpw, fph;
|
||||||
|
/* окно fore-клипа слота (объединение «спрайт + накладные»), КОМНАТНЫЙ y */
|
||||||
|
int cx, cy;
|
||||||
|
uint16_t cw, ch;
|
||||||
|
/* straddle: рендерное смещение по ЛОГИЧЕСКОЙ X, когда комната персонажа
|
||||||
|
* не совпадает с отрисованной (порт xpos_in_drawn_room) */
|
||||||
|
int render_dx;
|
||||||
|
} pop_cdraw_t;
|
||||||
|
|
||||||
|
extern pop_cdraw_t pop_cd[POP_CH_N];
|
||||||
|
|
||||||
|
/* Стереть прошлый кадр персонажа (спрайт + накладные) на ТЕКУЩЕЙ странице. */
|
||||||
|
void pop_char_heal(uint8_t who) __banked;
|
||||||
|
|
||||||
|
/* Нарисовать текущий кадр: спрайт, брызги урона (по флагу pop_kid_hurt /
|
||||||
|
* pop_guard_hurt) и клинок. Загружает окно Char сам — clip_char и футпринт
|
||||||
|
* работают с АКТИВНЫМ персонажем, как в оригинале. */
|
||||||
|
void pop_char_draw(uint8_t who) __banked;
|
||||||
|
|
||||||
|
/* Передние грани и оверлеи тайлов ПОВЕРХ персонажа: ставит окно fore-клипа
|
||||||
|
* слота и зовёт общий проход pop_fore_over_char. Отдельным вызовом, а не
|
||||||
|
* хвостом pop_char_draw, потому что порядок в кадре задаёт главный цикл:
|
||||||
|
* между спрайтом и его fore-слоем успевает нарисоваться падающая плита. */
|
||||||
|
void pop_char_fore(uint8_t who) __banked;
|
||||||
|
|
||||||
|
/* Straddle-смещение спрайта (см. render_dx). */
|
||||||
|
void pop_char_set_render_dx(uint8_t who, int dx) __banked;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,13 +7,13 @@
|
|||||||
* CLIP-1 (2026-08-01), SDCC 4.5: `static inline` тут обходится втридорога —
|
* CLIP-1 (2026-08-01), SDCC 4.5: `static inline` тут обходится втридорога —
|
||||||
* компилятор И встраивает тело в каждый вызов (181 Б на место), И оставляет
|
* компилятор И встраивает тело в каждый вызов (181 Б на место), И оставляет
|
||||||
* отдельную мёртвую копию в каждом TU. У стража это раздуло
|
* отдельную мёртвую копию в каждом TU. У стража это раздуло
|
||||||
* `pop_guard_heal` с ~60 до 663 Б. Пяти местам вызова это стоило бы ~1.4 КБ
|
* тогдашний `pop_guard_heal` с ~60 до 663 Б. Пяти местам вызова это стоило бы ~1.4 КБ
|
||||||
* ради экономии ~50 тактов на вызов — при том что BANK2 занят на 86 %.
|
* ради экономии ~50 тактов на вызов — при том что BANK2 занят на 86 %.
|
||||||
*
|
*
|
||||||
* Резидент W1 (_CODE), а не банк: сюда зовут из ДВУХ разных банков
|
* Резидент W1 (_CODE), а не банк: сюда зовут из ДВУХ разных банков
|
||||||
* (pop_bg.c → BANK2, pop_gdraw.c → BANK4) и из резидента (pop_kid.c). W1
|
* (pop_bg.c → BANK2, pop_cdraw.c → BANK4). W1 замаплено всегда, поэтому из
|
||||||
* замаплено всегда, поэтому из банка это обычный `call` без трамплина —
|
* банка это обычный `call` без трамплина — тот же приём, что у
|
||||||
* тот же приём, что у pop_sword_draw.
|
* pop_sword_draw.
|
||||||
*/
|
*/
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
|
|||||||
@@ -1,371 +0,0 @@
|
|||||||
/*
|
|
||||||
* pop_gdraw.c — ОТРИСОВКА стража. Резидент окна W3 (--w3 pop_gdraw.c).
|
|
||||||
*
|
|
||||||
* Почему отдельным файлом, а не внутри pop_guard.c: --w3 забирает МОДУЛЬ
|
|
||||||
* целиком (код+rodata в 0xC000), а состояние стража и его логика обязаны
|
|
||||||
* остаться в W1/W2 — из банка (guards.c) резидент W3 недостижим ни прямо,
|
|
||||||
* ни транзитивно (docs/layout_plan_v2.md §2 R2). Значит граница проходит
|
|
||||||
* ровно между «что рисует» (зовёт только главный цикл → резидент) и «что
|
|
||||||
* знает» (зовёт и банк → W1/W2).
|
|
||||||
*
|
|
||||||
* Писучие статики резидента (--dataseg не трогаем) остаются в обычном
|
|
||||||
* _DATA, то есть в W2 — сюда переезжает КОД, не данные.
|
|
||||||
*/
|
|
||||||
#include <graphics.h>
|
|
||||||
#include <gfx.h>
|
|
||||||
#include <sprite.h>
|
|
||||||
#include "pop_guard.h"
|
|
||||||
#include "pop_kid.h" /* pop_sword_draw — клинок общий с Кидом */
|
|
||||||
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */
|
|
||||||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
|
||||||
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида */
|
|
||||||
#include "pop_level.h" /* тип соперника уровня (набор спрайтов) */
|
|
||||||
#include "kid_data.h"
|
|
||||||
#include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */
|
|
||||||
|
|
||||||
/* Цвет стража (set_chtab_palette, seg003:257). Оригинал держит ОДИН набор
|
|
||||||
* спрайтов и подменяет 16 цветов палитры chtab_5 по level.guards_color —
|
|
||||||
* так на уровне 2 стражи комнат 4/7/11/15/24 разного цвета (1/3/1/1/6).
|
|
||||||
* Полоса HP стража рисуется тем же атласом, поэтому меняется заодно.
|
|
||||||
* color = 0 — палитру не трогаем (оригинал в этом случае set_chtab_palette
|
|
||||||
* не зовёт: тип уровня не «обычный страж»).
|
|
||||||
* Грузим В ОБЕ палитры: у страниц дабл-буфера они раздельные. */
|
|
||||||
void pop_guard_set_palette(uint8_t color) __banked
|
|
||||||
{
|
|
||||||
/* Таблица — rodata ЭТОГО модуля, то есть страница банка (0xC000+), а
|
|
||||||
* gfx_pal_load отдаёт указатель в BIOS ($A4 через rst #0x08). BIOS
|
|
||||||
* читает только #4000-#BFFF (см. корневой CLAUDE.md), поэтому копируем
|
|
||||||
* запись в локальный буфер — стек гарантированно в W2. Без копии BIOS
|
|
||||||
* забирал мусор с чужой страницы и страж становился невидимым. */
|
|
||||||
uint8_t buf[64];
|
|
||||||
const uint8_t *p;
|
|
||||||
uint8_t i;
|
|
||||||
if (color == 0 || color > POP_GUARD_NPAL) return;
|
|
||||||
p = pop_guard_pal[color - 1];
|
|
||||||
for (i = 0; i < 64; i++) buf[i] = p[i];
|
|
||||||
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
|
||||||
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
|
|
||||||
#define GUARD_PAGES 5
|
|
||||||
static atlas_t gp[GUARD_PAGES];
|
|
||||||
static uint8_t g_ok;
|
|
||||||
|
|
||||||
/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid:
|
|
||||||
* heal обязан стирать спрайт именно той страницы, в которую рисуем). */
|
|
||||||
static int gx_l[2], gy_l[2];
|
|
||||||
static uint16_t gw_l[2], gh_l[2];
|
|
||||||
static uint8_t gvalid[2];
|
|
||||||
|
|
||||||
/* Клинок — ОТДЕЛЬНЫМ прямоугольником, не объединением со стражем: объединение
|
|
||||||
* почти вдвое больше суммы двух (клинок уходит вперёд-вверх), а heal стоит
|
|
||||||
* ровно по площади. Объединение считаем только для окна fore-клипа. */
|
|
||||||
static int sx_l[2], sy_l[2];
|
|
||||||
static uint16_t sw_l[2], sh_l[2];
|
|
||||||
static uint8_t svalid[2];
|
|
||||||
|
|
||||||
/* Брызги удара — тоже свой прямоугольник, по той же причине: спрайт мелкий и
|
|
||||||
* уезжает вперёд-вверх от стража. Живут они ОДИН кадр, но стереть их обязана
|
|
||||||
* та же страница, в которую рисовали (иначе остаток виден через кадр). */
|
|
||||||
static int qx_l[2], qy_l[2];
|
|
||||||
static uint16_t qw_l[2], qh_l[2];
|
|
||||||
static uint8_t qvalid[2];
|
|
||||||
|
|
||||||
/* Ширина спрайта последнего кадра стража — нужна set_char_collision
|
|
||||||
* (char_width_half) при вычислении его ТАЙЛА ОБЪЕКТА. Не по страницам:
|
|
||||||
* это свойство кадра, а не буфера. Отставание на кадр допустимо — так же
|
|
||||||
* работает kid_fp_width() у Кида. */
|
|
||||||
static uint16_t g_last_w;
|
|
||||||
|
|
||||||
uint16_t pop_guard_fp_width(void) __banked { return g_last_w; }
|
|
||||||
|
|
||||||
/* Набор спрайтов соперника выбирает ТИП УРОВНЯ (load_lev_spr, seg000:1092):
|
|
||||||
* tbl_guard_type = {0 guard, 1 fat, 2 skeleton, 3 vizier, 4 shadow, −1 нет}.
|
|
||||||
* У нас пока два набора: обычный страж (уровни 1-2) и скелет (уровень 3);
|
|
||||||
* у скелета и кадров меньше (28 против 34), поэтому пятую страницу он не
|
|
||||||
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
|
|
||||||
int pop_guard_load(void) __banked
|
|
||||||
{
|
|
||||||
static const char *const guard_names[GUARD_PAGES] = {
|
|
||||||
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
|
|
||||||
"GUARD\\g3.atl", "GUARD\\g4.atl"
|
|
||||||
};
|
|
||||||
static const char *const skel_names[GUARD_PAGES] = {
|
|
||||||
"SKEL\\g0.atl", "SKEL\\g1.atl", "SKEL\\g2.atl",
|
|
||||||
"SKEL\\g3.atl", 0
|
|
||||||
};
|
|
||||||
const char *const *names;
|
|
||||||
int8_t type = pop_level_guard_type(pop_current_level);
|
|
||||||
uint8_t i;
|
|
||||||
if (type < 0) return 0; /* соперников на уровне нет */
|
|
||||||
names = (type == 2) ? skel_names : guard_names;
|
|
||||||
for (i = 0; i < GUARD_PAGES; i++) {
|
|
||||||
if (names[i] == 0) break;
|
|
||||||
if (atlas_load(&gp[i], names[i]) != 0) return -1;
|
|
||||||
}
|
|
||||||
g_ok = 1;
|
|
||||||
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
|
|
||||||
* из res10.bin по guards_color — это делает pop_guard_set_palette при
|
|
||||||
* входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */
|
|
||||||
if (type == 2) {
|
|
||||||
uint8_t buf[64], k;
|
|
||||||
for (k = 0; k < 64; k++) buf[k] = pop_skel_pal[k]; /* BIOS: буфер в W2 */
|
|
||||||
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
|
||||||
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_guard_free(void) __banked
|
|
||||||
{
|
|
||||||
uint8_t i;
|
|
||||||
if (!g_ok) return;
|
|
||||||
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
|
|
||||||
g_ok = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стирание прошлого кадра стража и его клинка. pop_heal_fast — по той же
|
|
||||||
* причине, что в kid_heal: стираем ровно то, что рисовали, а рисовали в
|
|
||||||
* большинстве кадров noclip-ядром. Два прямоугольника, а не объединение
|
|
||||||
* (см. комментарий к sx_l выше). */
|
|
||||||
void pop_guard_heal(void) __banked
|
|
||||||
{
|
|
||||||
uint8_t p = gfx_get_draw_page() & 1;
|
|
||||||
if (gvalid[p]) pop_heal_fast(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
|
|
||||||
if (svalid[p]) pop_heal_fast(sx_l[p], sy_l[p] + POP_YOFF, sw_l[p], sh_l[p]);
|
|
||||||
if (qvalid[p]) pop_heal_fast(qx_l[p], qy_l[p] + POP_YOFF, qw_l[p], qh_l[p]);
|
|
||||||
gvalid[p] = svalid[p] = qvalid[p] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_guard_draw(void) __banked
|
|
||||||
{
|
|
||||||
int obj_x, obj_y, top, fwd, fp_x;
|
|
||||||
uint8_t page, idx, p, flip;
|
|
||||||
const uint8_t *img;
|
|
||||||
uint16_t w, h;
|
|
||||||
/* Окно fore-клипа — по ОБЪЕДИНЁННОМУ габариту «страж + клинок»; сам
|
|
||||||
* футпринт (pop_fore_over_char) считается по спрайту стража, поэтому
|
|
||||||
* w/h ниже НЕ трогаем. */
|
|
||||||
int clip_x = 0, clip_y = 0; uint16_t clip_w = 0, clip_h = 0;
|
|
||||||
|
|
||||||
if (!g_ok || Guard.charid == 0 || pop_gframe.image == 255) return;
|
|
||||||
/* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */
|
|
||||||
fwd = (int)Guard.x + ((Guard.direction < 0) ? -pop_gframe.dx : pop_gframe.dx);
|
|
||||||
obj_x = (fwd << 1) - 116;
|
|
||||||
obj_y = (int)Guard.y + pop_gframe.dy;
|
|
||||||
if ((int8_t)(pop_gframe.flags ^ Guard.direction) >= 0) obj_x++;
|
|
||||||
fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — футпринт */
|
|
||||||
obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */
|
|
||||||
|
|
||||||
page = (uint8_t)(pop_gframe.image >> 3);
|
|
||||||
idx = (uint8_t)(pop_gframe.image & 7);
|
|
||||||
if (page >= GUARD_PAGES) return;
|
|
||||||
img = (const uint8_t *)atlas_image(&gp[page], idx);
|
|
||||||
gfx_w0_map(gp[page].page);
|
|
||||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
|
||||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
|
||||||
if (w && h) {
|
|
||||||
int bx = obj_x;
|
|
||||||
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507:
|
|
||||||
* obj_clip_top = 0, obj_clip_bottom = 192). Персонаж, вышедший за
|
|
||||||
* него (скелет, падающий в пропасть), обязан обрезаться: ниже поля
|
|
||||||
* идёт полоса HP, и без клипа спрайт лез прямо на неё. */
|
|
||||||
int skip = 0, rows;
|
|
||||||
top = obj_y - (int)h + 1;
|
|
||||||
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
|
|
||||||
if (flip) bx -= (int)w;
|
|
||||||
rows = (int)h;
|
|
||||||
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
|
||||||
if (top + rows > 192) rows = 192 - top;
|
|
||||||
if (rows > 0) {
|
|
||||||
p = gfx_get_draw_page() & 1;
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
|
||||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
|
|
||||||
(uint8_t)skip, (uint8_t)rows);
|
|
||||||
else
|
|
||||||
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, skip, rows);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
/* heal — ровно по НАРИСОВАННОМУ (обрезанному) прямоугольнику. */
|
|
||||||
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = (uint16_t)rows;
|
|
||||||
gvalid[p] = 1;
|
|
||||||
clip_x = bx; clip_y = top; clip_w = w; clip_h = (uint16_t)rows;
|
|
||||||
}
|
|
||||||
g_last_w = w; /* для char_width_half, см. выше */
|
|
||||||
}
|
|
||||||
/* Брызги удара (draw_hurt_splash, seg006:16CE) — ровно здесь, между
|
|
||||||
* стражем и клинком: draw_guard кладёт их в objtable СРАЗУ после стража
|
|
||||||
* и ДО клинка (seg008:22C9), а порядок записей задаёт порядок рисования.
|
|
||||||
*
|
|
||||||
* Спрайт — chtab_5_guard `obj_id = 1`, то есть image 1 нашего атласа
|
|
||||||
* стража (страница 0, индекс 1): у add_midtable аргумент `obj_id + 1`, а
|
|
||||||
* get_image вычитает единицу обратно (тот же off-by-one, что расписан в
|
|
||||||
* шапке pop_pack_guard.py). У Кида это image 218 своего chtab.
|
|
||||||
*
|
|
||||||
* Смещения от кадра персонажа — по трём веткам оригинала; у стража
|
|
||||||
* подъём на 11 (у Кида 15: `((charid == kid) << 2) + 11`). */
|
|
||||||
if (pop_guard_hurt && Guard.frame != 178 /* chomped: брызг нет */) {
|
|
||||||
int qx = fp_x, qy = obj_y;
|
|
||||||
const uint8_t *qimg = (const uint8_t *)atlas_image(&gp[0], 1);
|
|
||||||
uint16_t qw, qh;
|
|
||||||
int fw = (Guard.direction < 0) ? -5 : 5; /* obj_dx_forward(5) */
|
|
||||||
if (Guard.frame == 185 || /* мёртв */
|
|
||||||
(Guard.frame >= 106 && Guard.frame < 111)) { /* падение+посадка */
|
|
||||||
qy += 4;
|
|
||||||
} else if (Guard.frame == 177) { /* напоролся на пики */
|
|
||||||
fw = -fw; /* obj_dx_forward(-5) */
|
|
||||||
} else {
|
|
||||||
qy -= 11;
|
|
||||||
}
|
|
||||||
qx = (qx + fw) * 8 / 7; /* calc_screen_x_coord */
|
|
||||||
gfx_w0_map(gp[0].page);
|
|
||||||
qw = (uint16_t)(qimg[0] | ((uint16_t)qimg[1] << 8));
|
|
||||||
qh = (uint16_t)(qimg[2] | ((uint16_t)qimg[3] << 8));
|
|
||||||
if (qw && qh) {
|
|
||||||
int qtop = qy - (int)qh + 1;
|
|
||||||
uint8_t qflip = (uint8_t)(Guard.direction >= 0);
|
|
||||||
int qskip = 0, qrows = (int)qh; /* клип полем 192, как у спрайта */
|
|
||||||
if (qflip) qx -= (int)qw;
|
|
||||||
if (qtop < 0) { qskip = -qtop; qrows -= qskip; qtop = 0; }
|
|
||||||
if (qtop + qrows > 192) qrows = 192 - qtop;
|
|
||||||
if (qrows <= 0) goto splash_done;
|
|
||||||
qh = (uint16_t)qrows;
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
if (pop_onscreen_cols(qx, qtop + POP_YOFF, qw, qh))
|
|
||||||
gfx_blit_cols_part_noclip(qx, qtop + POP_YOFF, qimg, qflip,
|
|
||||||
(uint8_t)qskip, (uint8_t)qrows);
|
|
||||||
else
|
|
||||||
gfx_blit_cols_part(qx, qtop + POP_YOFF, qimg, qflip, qskip, qrows);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
p = gfx_get_draw_page() & 1;
|
|
||||||
qx_l[p] = qx; qy_l[p] = qtop; qw_l[p] = qw; qh_l[p] = qh;
|
|
||||||
qvalid[p] = 1;
|
|
||||||
if (!clip_w) {
|
|
||||||
clip_x = qx; clip_y = qtop; clip_w = qw; clip_h = qh;
|
|
||||||
} else {
|
|
||||||
int x1 = clip_x + (int)clip_w, y1 = clip_y + (int)clip_h;
|
|
||||||
if (qx < clip_x) clip_x = qx;
|
|
||||||
if (qtop < clip_y) clip_y = qtop;
|
|
||||||
if (qx + (int)qw > x1) x1 = qx + (int)qw;
|
|
||||||
if (qtop + (int)qh > y1) y1 = qtop + (int)qh;
|
|
||||||
clip_w = (uint16_t)(x1 - clip_x);
|
|
||||||
clip_h = (uint16_t)(y1 - clip_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
splash_done: ;
|
|
||||||
}
|
|
||||||
/* Клинок стража — тот же chtab_0, что у Кида (add_sword_to_objtable,
|
|
||||||
* seg006:1798): у ЖИВОГО стража меч в руке всегда. Свой прямоугольник
|
|
||||||
* heal; окно fore-клипа — объединение (без него клинок полез бы поверх
|
|
||||||
* переднего столба). */
|
|
||||||
{
|
|
||||||
int sx, sy; uint16_t sw, sh;
|
|
||||||
if (pop_sword_draw(&Guard, &pop_gframe, obj_x, obj_y,
|
|
||||||
&sx, &sy, &sw, &sh)) {
|
|
||||||
p = gfx_get_draw_page() & 1;
|
|
||||||
sx_l[p] = sx; sy_l[p] = sy; sw_l[p] = sw; sh_l[p] = sh;
|
|
||||||
svalid[p] = 1;
|
|
||||||
if (!clip_w) {
|
|
||||||
clip_x = sx; clip_y = sy; clip_w = sw; clip_h = sh;
|
|
||||||
} else {
|
|
||||||
int x1 = clip_x + (int)clip_w, y1 = clip_y + (int)clip_h;
|
|
||||||
if (sx < clip_x) clip_x = sx;
|
|
||||||
if (sy < clip_y) clip_y = sy;
|
|
||||||
if (sx + (int)sw > x1) x1 = sx + (int)sw;
|
|
||||||
if (sy + (int)sh > y1) y1 = sy + (int)sh;
|
|
||||||
clip_w = (uint16_t)(x1 - clip_x);
|
|
||||||
clip_h = (uint16_t)(y1 - clip_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gfx_w0_unmap();
|
|
||||||
/* Окно fore-слоя = прямоугольник стража с клинком (см. pop_bg.c,
|
|
||||||
* fclip_on): без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов. */
|
|
||||||
pop_fore_set_clip(clip_x, clip_y + POP_YOFF, (int)clip_w, (int)clip_h);
|
|
||||||
/* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись
|
|
||||||
* midtable, а foretable рисуется после всех midtable-записей, поэтому
|
|
||||||
* колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid
|
|
||||||
* (seg008:690 draw_tile_fore). Без этого страж виден сквозь передний
|
|
||||||
* столб. */
|
|
||||||
if (w && h)
|
|
||||||
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction, Guard.sword,
|
|
||||||
clip_x, clip_y + POP_YOFF, clip_w, clip_h,
|
|
||||||
Guard.action, Guard.curr_col, Guard.curr_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
|
|
||||||
* Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри
|
|
||||||
* тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража
|
|
||||||
* справа налево от x = 314. Пустые деления у Кида — свой спрайт (chtab_2
|
|
||||||
* id 217); у стража оригинал печатает тот же спрайт ЧЁРНЫМ силуэтом, а мы
|
|
||||||
* просто не печатаем — полоса и так на чёрном фоне.
|
|
||||||
*
|
|
||||||
* Спрайты Кида (216/217) лежат в его же атласах, поэтому блитим через
|
|
||||||
* pop_kid_img_blit; спрайт стража — image 0 его chtab (наша страница 0). */
|
|
||||||
#define HP_Y 194
|
|
||||||
#define HP_STEP 7
|
|
||||||
#define HP_KID_FULL 216
|
|
||||||
#define HP_KID_EMPTY 217
|
|
||||||
#define HP_MAXDRAW 10 /* больше в полосу всё равно не влезает */
|
|
||||||
|
|
||||||
/* Полоса перерисовывается ТОЛЬКО когда числа изменились — и тогда на ОБЕИХ
|
|
||||||
* страницах дабл-буфера (иначе на второй останется старое значение и его
|
|
||||||
* будет видно как мерцание через кадр). Отсюда счётчик страниц, а не флаг.
|
|
||||||
*
|
|
||||||
* Снимки прошлых значений пишем ДО сравнения: `if (n != s) s = n;` SDCC z80
|
|
||||||
* собирает неверно — SUB затирает A, и в s уезжает разность (memory
|
|
||||||
* sdcc_z80_cmp_store_a_bug). */
|
|
||||||
static uint8_t hp_todo; /* сколько страниц ещё обновить */
|
|
||||||
static uint8_t hp_kid_prev, hp_kidmax_prev, hp_gd_prev, hp_gdmax_prev;
|
|
||||||
|
|
||||||
/* Заставить перерисовать полосу на обеих страницах: звать при входе в
|
|
||||||
* комнату (фон перерисован целиком и стёр её) и при старте. */
|
|
||||||
void pop_hp_invalidate(void) __banked { hp_todo = 2; }
|
|
||||||
|
|
||||||
void pop_hp_draw(void) __banked
|
|
||||||
{
|
|
||||||
uint8_t i, n;
|
|
||||||
uint8_t gd = (uint8_t)(Guard.charid ? guardhp_curr : 0);
|
|
||||||
uint8_t gm = (uint8_t)(Guard.charid ? guardhp_max : 0);
|
|
||||||
|
|
||||||
{ /* Изменилось хоть одно число — обновить обе страницы. */
|
|
||||||
uint8_t k = hitp_curr, km = hitp_max;
|
|
||||||
uint8_t changed = (uint8_t)(k != hp_kid_prev || km != hp_kidmax_prev ||
|
|
||||||
gd != hp_gd_prev || gm != hp_gdmax_prev);
|
|
||||||
hp_kid_prev = k; hp_kidmax_prev = km;
|
|
||||||
hp_gd_prev = gd; hp_gdmax_prev = gm;
|
|
||||||
if (changed) hp_todo = 2;
|
|
||||||
}
|
|
||||||
if (hp_todo == 0) return;
|
|
||||||
hp_todo--;
|
|
||||||
|
|
||||||
/* Стереть прошлую полосу: фон под ней — чёрный борт. */
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6);
|
|
||||||
|
|
||||||
/* Кид: сначала пустые деления, поверх — полные (порядок оригинала). */
|
|
||||||
n = (uint8_t)(hitp_max > HP_MAXDRAW ? HP_MAXDRAW : hitp_max);
|
|
||||||
for (i = hitp_curr; i < n; i++)
|
|
||||||
pop_kid_img_blit(HP_KID_EMPTY, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
|
||||||
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
|
||||||
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
|
||||||
|
|
||||||
/* Страж: только полные деления, справа налево. Скелет полосы не имеет. */
|
|
||||||
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON && gd) {
|
|
||||||
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
|
||||||
uint16_t w, h;
|
|
||||||
gfx_w0_map(gp[0].page);
|
|
||||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
|
||||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
|
||||||
if (w && h) {
|
|
||||||
/* Атлас стража тоже column-major — блит колоночный. */
|
|
||||||
n = (uint8_t)(gd > HP_MAXDRAW ? HP_MAXDRAW : gd);
|
|
||||||
for (i = 0; i < n; i++)
|
|
||||||
gfx_blit_cols_part_noclip(314 - (int)i * HP_STEP,
|
|
||||||
HP_Y + POP_YOFF, img, 0, 0, 0);
|
|
||||||
}
|
|
||||||
gfx_w0_unmap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Здесь только то, что должно оставаться достижимым из банка (guards.c):
|
* Здесь только то, что должно оставаться достижимым из банка (guards.c):
|
||||||
* поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы.
|
* поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы.
|
||||||
* ОТРИСОВКА — в pop_gdraw.c, резиденте W3 (её зовёт лишь главный цикл).
|
* ОТРИСОВКА — в pop_cdraw.c (банк 4), общая на всех Char.
|
||||||
*/
|
*/
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "pop_guard.h"
|
#include "pop_guard.h"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
|
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
|
||||||
* только код;
|
* только код;
|
||||||
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
|
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
|
||||||
* - ОТРИСОВКА стража — в pop_gdraw.c, РЕЗИДЕНТЕ W3 (--w3): её зовёт
|
* - ОТРИСОВКА стража — в pop_cdraw.c (банк 4), общая на всех Char: её зовёт
|
||||||
* только главный цикл, а из банка резидент недостижим. Так код
|
* только главный цикл, а из банка резидент недостижим. Так код
|
||||||
* отрисовки не занимает дефицитные W1/W2 (куча).
|
* отрисовки не занимает дефицитные W1/W2 (куча).
|
||||||
*
|
*
|
||||||
@@ -90,7 +90,7 @@ extern int8_t guardhp_delta; /* <0 = в этом кадре отняли
|
|||||||
uint8_t pop_take_hp(uint8_t count);
|
uint8_t pop_take_hp(uint8_t count);
|
||||||
|
|
||||||
/* Взводится в кадре, когда СТРАЖУ убавили HP (guardhp_delta < 0 оригинала):
|
/* Взводится в кадре, когда СТРАЖУ убавили HP (guardhp_delta < 0 оригинала):
|
||||||
* по нему pop_guard_draw рисует «брызги» удара, как draw_guard в seg008:1654.
|
* по нему pop_char_draw рисует «брызги» удара, как draw_guard в seg008:1654.
|
||||||
* Снимает главный цикл после отрисовки — стража могло не оказаться в
|
* Снимает главный цикл после отрисовки — стража могло не оказаться в
|
||||||
* отрисованной комнате, и тогда флаг обязан просто пропасть. */
|
* отрисованной комнате, и тогда флаг обязан просто пропасть. */
|
||||||
extern uint8_t pop_guard_hurt;
|
extern uint8_t pop_guard_hurt;
|
||||||
@@ -172,9 +172,6 @@ void pop_guard_leave(void);
|
|||||||
* side — куда ушёл КИД: 0 left, 1 right, 2 up, 3 down. */
|
* side — куда ушёл КИД: 0 left, 1 right, 2 up, 3 down. */
|
||||||
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side);
|
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side);
|
||||||
|
|
||||||
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
|
|
||||||
* как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
|
|
||||||
* (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
|
|
||||||
/* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл
|
/* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл
|
||||||
* ПОСЛЕ персонажей — она ничего не перекрывает. */
|
* ПОСЛЕ персонажей — она ничего не перекрывает. */
|
||||||
void pop_hp_draw(void) __banked;
|
void pop_hp_draw(void) __banked;
|
||||||
@@ -187,13 +184,9 @@ void pop_hp_invalidate(void) __banked;
|
|||||||
* отрисовки комнаты, как это делает redraw_screen. */
|
* отрисовки комнаты, как это делает redraw_screen. */
|
||||||
void pop_guard_set_palette(uint8_t color) __banked;
|
void pop_guard_set_palette(uint8_t color) __banked;
|
||||||
|
|
||||||
void pop_guard_heal(void) __banked;
|
/* Отрисовка соперника — общая на всех Char: pop_char_draw(POP_CH_OPP) и
|
||||||
void pop_guard_draw(void) __banked;
|
* компания, см. pop_cdraw.h. Метрики его последнего кадра (в т.ч. ширина
|
||||||
|
* спрайта для char_width_half) — pop_cd[POP_CH_OPP]. */
|
||||||
/* Ширина спрайта последнего нарисованного кадра стража (char_width_half
|
|
||||||
* в set_char_collision). Нужна для его ТАЙЛА ОБЪЕКТА — от него зависит
|
|
||||||
* порядок отрисовки относительно Кида. */
|
|
||||||
uint16_t pop_guard_fp_width(void) __banked;
|
|
||||||
|
|
||||||
/* Убрать стража со сцены (смена комнаты / инициализация). */
|
/* Убрать стража со сцены (смена комнаты / инициализация). */
|
||||||
void pop_guard_reset(void);
|
void pop_guard_reset(void);
|
||||||
|
|||||||
@@ -31,10 +31,21 @@ int8_t knock;
|
|||||||
uint8_t pop_next_level;
|
uint8_t pop_next_level;
|
||||||
|
|
||||||
|
|
||||||
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */
|
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ *
|
||||||
|
* НЕ static: набор спрайтов слота POP_CH_KID общей отрисовки персонажей
|
||||||
|
* (pop_cdraw.c, банк 4). Отдан как есть, а не через аксессор: читается раз
|
||||||
|
* в кадр из другого банка, и вызов стоил бы дороже самого чтения. */
|
||||||
#define KID_MAXPAGES 28
|
#define KID_MAXPAGES 28
|
||||||
static atlas_t kidp[KID_MAXPAGES];
|
atlas_t kidp[KID_MAXPAGES];
|
||||||
static uint8_t kid_npages;
|
uint8_t kid_npages;
|
||||||
|
|
||||||
|
/* Таблица «seq_id -> адрес в seqtbl» (original_seqtbl_offsets). Определена
|
||||||
|
* ЗДЕСЬ, а не в kid_data.h: заголовок включают почти все модули PoP, и
|
||||||
|
* `static const` в нём размножал таблицу мёртвой копией по каждому TU —
|
||||||
|
* 230 Б × 9 модулей, из них 460 Б в дефицитном резиденте W1/W2 (замер
|
||||||
|
* 2026-08-08). Наружу таблица не нужна: смену последовательности делают
|
||||||
|
* kid_set_seq / pop_char_set_seq. */
|
||||||
|
static const uint16_t kid_seq_off[KID_NSEQ] = KID_SEQ_OFF_INIT;
|
||||||
|
|
||||||
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): клинок —
|
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): клинок —
|
||||||
* ОТДЕЛЬНЫЙ спрайт поверх персонажа, один и тот же атлас у Кида и у стража.
|
* ОТДЕЛЬНЫЙ спрайт поверх персонажа, один и тот же атлас у Кида и у стража.
|
||||||
@@ -113,7 +124,7 @@ uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
|||||||
|
|
||||||
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
|
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
|
||||||
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
|
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
|
||||||
* Кида, а держать вторую копию раскладки в pop_gdraw ни к чему. */
|
* Кида, а держать вторую копию раскладки в pop_cdraw ни к чему. */
|
||||||
void pop_kid_img_blit(uint8_t image, int x, int y)
|
void pop_kid_img_blit(uint8_t image, int x, int y)
|
||||||
{
|
{
|
||||||
uint8_t pg = (uint8_t)(image >> 3); /* раскладка атласов: 8 спрайтов/стр. */
|
uint8_t pg = (uint8_t)(image >> 3); /* раскладка атласов: 8 спрайтов/стр. */
|
||||||
@@ -129,7 +140,7 @@ void pop_kid_img_blit(uint8_t image, int x, int y)
|
|||||||
* ради бесплатного горизонтального флипа), поэтому блит обязан быть
|
* ради бесплатного горизонтального флипа), поэтому блит обязан быть
|
||||||
* колоночным. Row-major gfx_blit_noclip выдал бы марку транспонированной.
|
* колоночным. Row-major gfx_blit_noclip выдал бы марку транспонированной.
|
||||||
* noclip БЕЗ проверки (CLIP-1): единственный вызывающий — полоса HP
|
* noclip БЕЗ проверки (CLIP-1): единственный вызывающий — полоса HP
|
||||||
* (pop_gdraw.c), у неё координаты фиксированные и заведомо на экране. */
|
* (pop_cdraw.c), у неё координаты фиксированные и заведомо на экране. */
|
||||||
if (w && h) gfx_blit_cols_part_noclip(x, y, img, 0, 0, 0);
|
if (w && h) gfx_blit_cols_part_noclip(x, y, img, 0, 0, 0);
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
}
|
}
|
||||||
@@ -207,9 +218,11 @@ static uint8_t kdat_blk, kdat_page, kdat_ok;
|
|||||||
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы.
|
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы.
|
||||||
* Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на
|
* Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на
|
||||||
* выходе из play_seq его надо сохранить в кэш своего персонажа: страж
|
* выходе из play_seq его надо сохранить в кэш своего персонажа: страж
|
||||||
* тикает ПОСЛЕ Кида, и без разделения kid_draw взял бы кадр стража. */
|
* тикает ПОСЛЕ Кида, и без разделения отрисовка Кида взяла бы кадр стража. */
|
||||||
static kframe cur_frame;
|
static kframe cur_frame;
|
||||||
static kframe kid_frame; /* кадр Kid для kid_draw */
|
/* Кадр Кида для отрисовки — пара к pop_gframe соперника; читает общая
|
||||||
|
* отрисовка (pop_cdraw.c), поэтому не static. */
|
||||||
|
kframe kid_frame;
|
||||||
|
|
||||||
/* Char (seg006): АКТИВНЫЙ персонаж. Интерпретатор последовательностей в
|
/* Char (seg006): АКТИВНЫЙ персонаж. Интерпретатор последовательностей в
|
||||||
* оригинале работает не с Kid и не с Guard, а с этой копией, а вокруг
|
* оригинале работает не с Kid и не с Guard, а с этой копией, а вокруг
|
||||||
@@ -349,7 +362,7 @@ static void load_frame(void)
|
|||||||
* персонажа, а не в save*-функциях. Раньше кэш писали pop_savekid/
|
* персонажа, а не в save*-функциях. Раньше кэш писали pop_savekid/
|
||||||
* pop_saveshad, и любое окно Char БЕЗ play_seq (окна боёвки
|
* pop_saveshad, и любое окно Char БЕЗ play_seq (окна боёвки
|
||||||
* check_sword_hurting / check_sword_hurt) записывало Киду кадр, который
|
* check_sword_hurting / check_sword_hurt) записывало Киду кадр, который
|
||||||
* на самом деле принадлежал СТРАЖУ: kid_draw брал его image из атласа
|
* на самом деле принадлежал СТРАЖУ: отрисовка брала его image из атласа
|
||||||
* Кида и рисовал произвольную позу. Теперь владелец один — тот, для
|
* Кида и рисовал произвольную позу. Теперь владелец один — тот, для
|
||||||
* кого кадр реально загружен. */
|
* кого кадр реально загружен. */
|
||||||
if (Char.charid == CHARID_0_KID) kid_frame = cur_frame;
|
if (Char.charid == CHARID_0_KID) kid_frame = cur_frame;
|
||||||
@@ -519,292 +532,6 @@ void kid_tick(void)
|
|||||||
pop_kid_play();
|
pop_kid_play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy
|
/* Отрисовка кадра (load_frame_to_obj, heal, клип, брызги, клинок) живёт в
|
||||||
* (seg008.c:1728 load_frame_to_obj). НИЗ спрайта на obj_y, левый край
|
* pop_cdraw.c — ОДНА на всех Char, см. DRAW-CHAR. Отсюда она получает
|
||||||
* на obj_x. Калибровка XOFF/YOFF — сверить с эталоном.
|
* атласы Кида (kidp/kid_npages) и его кадр (kid_frame). */
|
||||||
*
|
|
||||||
* Зеркалирование (K2a): спрайты Kid хранятся ЛИЦОМ ВЛЕВО (как в оригинале).
|
|
||||||
* seg008.c:864 — флип, когда персонаж смотрит ВПРАВО (direction==dir_0_right
|
|
||||||
* ==0), и тогда xpos -= image->w (draw_mid). Реализуем бесплатным
|
|
||||||
* горизонтальным флипом column-major спрайта (gfx_blit_cols flip=1):
|
|
||||||
* зеркало происходит ВНУТРИ [x,x+w), поэтому при флипе подаём x=obj_x-w,
|
|
||||||
* чтобы «hot-point» кадра остался на месте.
|
|
||||||
*
|
|
||||||
* Kid рисуется банком GFX_BANK_SPRITE (только видео-ОЗУ, НЕ теневая копия
|
|
||||||
* — 0xFF прозрачен), чтобы kid_heal() мог восстановить чистый фон из
|
|
||||||
* теневой копии (фон запёкся в неё при pop_room_draw). Прямоугольник
|
|
||||||
* последней отрисовки запоминается для heal. */
|
|
||||||
#define KID_XOFF 0
|
|
||||||
#define KID_YOFF 0
|
|
||||||
|
|
||||||
/* Прямоугольник последней отрисовки Kid — ПО СТРАНИЦЕ (0/1). У каждой
|
|
||||||
* графической страницы своя ОЗУ-копия/видео-ОЗУ, поэтому heal должен
|
|
||||||
* стирать пиксели Kid именно той страницы, в которую сейчас рисуем
|
|
||||||
* (при дабл-буфере страница чередуется — прошлый Kid на этой странице
|
|
||||||
* был 2 логических кадра назад). В однобуфере используется только [0]. */
|
|
||||||
static int kid_lx[2], kid_ly[2];
|
|
||||||
static uint16_t kid_lw[2], kid_lh[2];
|
|
||||||
static uint8_t kid_lvalid[2];
|
|
||||||
|
|
||||||
/* ОТДЕЛЬНЫЙ прямоугольник накладных спрайтов (клинок chtab_0, брызги урона).
|
|
||||||
* Раньше он объединялся с прямоугольником персонажа — но объединение сильно
|
|
||||||
* больше суммы двух: клинок уходит вперёд-вверх, и общий габарит выходил
|
|
||||||
* почти вдвое шире и выше самого Kid, а heal стоит ровно по площади.
|
|
||||||
* Поэтому heal делаем двумя вызовами по своим прямоугольникам, а объединение
|
|
||||||
* оставляем ТОЛЬКО для окна fore-клипа (там это 4 сравнения, без рисования —
|
|
||||||
* зато клинок/брызги честно уходят под передние грани тайлов). */
|
|
||||||
static int kid_ox[2], kid_oy[2];
|
|
||||||
static uint16_t kid_ow[2], kid_oh[2];
|
|
||||||
static uint8_t kid_ovalid[2];
|
|
||||||
|
|
||||||
/* Добавить накладной спрайт в overlay-прямоугольник страницы dp. Клинок и
|
|
||||||
* брызги в одном кадре встречаются редко и оба маленькие — их объединение
|
|
||||||
* дешевле третьего слота. */
|
|
||||||
static void kid_overlay_add(uint8_t dp, int x, int y, uint16_t w, uint16_t h)
|
|
||||||
{
|
|
||||||
if (!kid_ovalid[dp]) {
|
|
||||||
kid_ox[dp] = x; kid_oy[dp] = y; kid_ow[dp] = w; kid_oh[dp] = h;
|
|
||||||
kid_ovalid[dp] = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
int x0 = kid_ox[dp], y0 = kid_oy[dp];
|
|
||||||
int x1 = x0 + (int)kid_ow[dp], y1 = y0 + (int)kid_oh[dp];
|
|
||||||
if (x < x0) x0 = x;
|
|
||||||
if (y < y0) y0 = y;
|
|
||||||
if (x + (int)w > x1) x1 = x + (int)w;
|
|
||||||
if (y + (int)h > y1) y1 = y + (int)h;
|
|
||||||
kid_ox[dp] = x0; kid_oy[dp] = y0;
|
|
||||||
kid_ow[dp] = (uint16_t)(x1 - x0); kid_oh[dp] = (uint16_t)(y1 - y0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Окно fore-клипа = объединение «персонаж + накладные». Считается по
|
|
||||||
* прямоугольникам, ничего не рисует. */
|
|
||||||
static void kid_fore_clip(uint8_t dp)
|
|
||||||
{
|
|
||||||
int x0, y0, x1, y1;
|
|
||||||
if (!kid_lvalid[dp] && !kid_ovalid[dp]) { pop_fore_set_clip(0, 0, 0, 0); return; }
|
|
||||||
if (!kid_ovalid[dp]) {
|
|
||||||
pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF,
|
|
||||||
(int)kid_lw[dp], (int)kid_lh[dp]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!kid_lvalid[dp]) {
|
|
||||||
pop_fore_set_clip(kid_ox[dp], kid_oy[dp] + POP_YOFF,
|
|
||||||
(int)kid_ow[dp], (int)kid_oh[dp]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
x0 = kid_lx[dp] < kid_ox[dp] ? kid_lx[dp] : kid_ox[dp];
|
|
||||||
y0 = kid_ly[dp] < kid_oy[dp] ? kid_ly[dp] : kid_oy[dp];
|
|
||||||
x1 = kid_lx[dp] + (int)kid_lw[dp];
|
|
||||||
y1 = kid_ly[dp] + (int)kid_lh[dp];
|
|
||||||
if (kid_ox[dp] + (int)kid_ow[dp] > x1) x1 = kid_ox[dp] + (int)kid_ow[dp];
|
|
||||||
if (kid_oy[dp] + (int)kid_oh[dp] > y1) y1 = kid_oy[dp] + (int)kid_oh[dp];
|
|
||||||
pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg):
|
|
||||||
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
|
|
||||||
static int kid_fp_x, kid_fp_y;
|
|
||||||
static uint16_t kid_fp_w, kid_fp_h;
|
|
||||||
/* Рендер-смещение по ЛОГИЧЕСКОЙ X (straddle у шва, порт xpos_in_drawn_room):
|
|
||||||
* ∓140 когда комната Kid ≠ отрисованной; 0 обычно. */
|
|
||||||
static int kid_render_dx;
|
|
||||||
void pop_kid_set_render_dx(int dx) { kid_render_dx = dx; }
|
|
||||||
int kid_fp_obj_x(void) { return kid_fp_x; }
|
|
||||||
int kid_fp_obj_y(void) { return kid_fp_y; }
|
|
||||||
uint16_t kid_fp_width(void) { return kid_fp_w; }
|
|
||||||
uint16_t kid_fp_height(void) { return kid_fp_h; }
|
|
||||||
|
|
||||||
/* draw_hurt_splash (seg006:2003): «брызги» урона поверх персонажа — спрайт
|
|
||||||
* chtab_2_kid id 218 (res619.png; в наших атласах это image 218, стр. 27).
|
|
||||||
* Позиция от кадра Kid: obj_y −= 15, obj_x += 5 ВПЕРЁД (obj_dx_forward).
|
|
||||||
* Кадры смерти/падения (185, 106..110) и пик (177) в оригинале смещают
|
|
||||||
* иначе — тут только общий случай + «на пиках» (порт по мере надобности).
|
|
||||||
* Рисовать ПОСЛЕ kid_draw (splash кладётся в objtable позже Kid). */
|
|
||||||
#define KID_SPLASH_IMAGE 218
|
|
||||||
|
|
||||||
void kid_draw_splash(void)
|
|
||||||
{
|
|
||||||
int obj_x = kid_fp_x, obj_y = kid_fp_y - 15;
|
|
||||||
uint8_t page = KID_SPLASH_IMAGE >> 3, idx = KID_SPLASH_IMAGE & 7;
|
|
||||||
const uint8_t *img;
|
|
||||||
uint16_t w, h;
|
|
||||||
if (page >= kid_npages) return;
|
|
||||||
if (Kid.frame == 177) /* напоролся на пики */
|
|
||||||
obj_y = kid_fp_y, obj_x -= (Kid.direction < 0) ? -5 : 5;
|
|
||||||
else
|
|
||||||
obj_x += (Kid.direction < 0) ? -5 : 5;
|
|
||||||
obj_x = obj_x * 8 / 7; /* лог. X -> экран (calc_screen_x_coord) */
|
|
||||||
img = (const uint8_t *)atlas_image(&kidp[page], idx);
|
|
||||||
gfx_w0_map(kidp[page].page);
|
|
||||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
|
||||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
|
||||||
if (w && h) {
|
|
||||||
uint8_t flip = (Kid.direction >= 0);
|
|
||||||
int bx = obj_x;
|
|
||||||
int top = obj_y - (int)h + 1;
|
|
||||||
if (flip) bx -= (int)w;
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
|
||||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, 0, 0);
|
|
||||||
else
|
|
||||||
gfx_blit_cols(bx, top + POP_YOFF, img, flip);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
|
|
||||||
/* Брызги — накладной спрайт: свой прямоугольник heal (см.
|
|
||||||
* kid_overlay_add), окно fore пересчитываем объединением. */
|
|
||||||
{
|
|
||||||
uint8_t dp = gfx_get_draw_page();
|
|
||||||
kid_overlay_add(dp, bx, top, w, h);
|
|
||||||
kid_fore_clip(dp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gfx_w0_unmap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Восстановить фон из теневой копии на месте ПРЕДЫДУЩЕГО кадра Kid.
|
|
||||||
*
|
|
||||||
* pop_heal_fast, а не голый gfx_heal: прямоугольник тут — это ровно тот
|
|
||||||
* прямоугольник, который kid_draw нарисовал noclip-ядром, то есть в
|
|
||||||
* подавляющем большинстве кадров он заведомо на экране. Клипающий путь
|
|
||||||
* остаётся для straddle (kid_render_dx = ∓140) и падения ниже поля —
|
|
||||||
* там координата реально уходит за экран. */
|
|
||||||
void kid_heal(void)
|
|
||||||
{
|
|
||||||
uint8_t p = gfx_get_draw_page();
|
|
||||||
if (kid_lvalid[p]) /* +YOFF: центрирование */
|
|
||||||
pop_heal_fast(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]);
|
|
||||||
if (kid_ovalid[p])
|
|
||||||
pop_heal_fast(kid_ox[p], kid_oy[p] + POP_YOFF, kid_ow[p], kid_oh[p]);
|
|
||||||
kid_lvalid[p] = kid_ovalid[p] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void kid_draw(void)
|
|
||||||
{
|
|
||||||
kframe fr = kid_frame; /* кадр Kid, сохранённый savekid */
|
|
||||||
int fwd, obj_x, obj_y, top;
|
|
||||||
uint8_t page, idx;
|
|
||||||
const uint8_t *img;
|
|
||||||
uint16_t w, h;
|
|
||||||
|
|
||||||
/* Окно fore-слоя по умолчанию пустое: на любом раннем выходе (нет
|
|
||||||
* кадра / страница не загружена) перекрывать нечего, и fore-проход
|
|
||||||
* не должен трогать экран. */
|
|
||||||
pop_fore_set_clip(0, 0, 0, 0);
|
|
||||||
if (fr.image == 255)
|
|
||||||
return; /* пустой кадр */
|
|
||||||
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
|
|
||||||
* отрисованной — спрайт за кромкой. Коллизия (Kid.x в pop_map) НЕ
|
|
||||||
* смещается: она в кадре комнаты Kid; смещение только рендерное. */
|
|
||||||
fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx) + kid_render_dx;
|
|
||||||
obj_x = (fwd << 1) - 116;
|
|
||||||
obj_y = (int)Kid.y + fr.dy;
|
|
||||||
if ((int8_t)(fr.flags ^ Kid.direction) >= 0)
|
|
||||||
obj_x++; /* even/odd pixel (в ЛОГИЧЕСКОЙ координате) */
|
|
||||||
kid_fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — для футпринта fore */
|
|
||||||
kid_fp_y = obj_y;
|
|
||||||
/* seg008 calc_screen_x_coord: логическая X (280 шир = 28px/тайл) ->
|
|
||||||
* экран (320 шир = 32px/тайл), ×320/280 = ×8/7. БЕЗ этого Kid уезжает
|
|
||||||
* влево от тайлов тем сильнее, чем правее (col9 ~36px). Коллизия — в
|
|
||||||
* логической системе (не масштабируется). */
|
|
||||||
obj_x = obj_x * 8 / 7;
|
|
||||||
|
|
||||||
page = (uint8_t)(fr.image >> 3);
|
|
||||||
idx = (uint8_t)(fr.image & 7);
|
|
||||||
if (page >= kid_npages)
|
|
||||||
return; /* страница не загружена */
|
|
||||||
img = (const uint8_t *)atlas_image(&kidp[page], idx);
|
|
||||||
gfx_w0_map(kidp[page].page);
|
|
||||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
|
||||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
|
||||||
if (w && h) {
|
|
||||||
uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */
|
|
||||||
int bx = obj_x + KID_XOFF;
|
|
||||||
int skip = 0; /* строк спрайта срезано сверху */
|
|
||||||
int ct, cr;
|
|
||||||
uint16_t vis_w = w; /* видимая ширина (клип справа) */
|
|
||||||
if (flip)
|
|
||||||
bx -= (int)w; /* seg008: xpos -= image->w */
|
|
||||||
top = obj_y - (int)h + 1 + KID_YOFF;
|
|
||||||
uint8_t dp = gfx_get_draw_page(); /* в какую страницу рисуем */
|
|
||||||
/* clip_char (seg006:1749): тайл над головой = стена/пол → всё выше
|
|
||||||
* линии полосы своего ряда НЕ рисуется. Иначе Kid просвечивает
|
|
||||||
* сквозь щели кладки при спуске/висе.
|
|
||||||
*
|
|
||||||
* Окно Char обязательно: clip_char работает с АКТИВНЫМ персонажем
|
|
||||||
* (в оригинале он и зовётся внутри add_kid_to_objtable, то есть под
|
|
||||||
* loadkid), а к моменту отрисовки в Char лежит тот, кто тикал
|
|
||||||
* последним — страж. Выгружать назад нечего: clip_char только
|
|
||||||
* читает. */
|
|
||||||
pop_loadkid();
|
|
||||||
ct = pop_clip_char_top(kid_fp_x, obj_y, w, h);
|
|
||||||
if (ct > top) {
|
|
||||||
skip = ct - top;
|
|
||||||
if (skip >= (int)h) skip = (int)h; /* обрезан целиком */
|
|
||||||
}
|
|
||||||
/* clip_char, вторая половина: правый косяк двери уровня. Дверь
|
|
||||||
* рисуется ПОД персонажем (вся в backtable), поэтому спрятать его
|
|
||||||
* можно только срезав спрайт — см. pop_clip_char_right. */
|
|
||||||
cr = pop_clip_char_right();
|
|
||||||
if (cr) {
|
|
||||||
int avail = cr - bx;
|
|
||||||
if (avail <= 0) vis_w = 0; /* весь спрайт за косяком */
|
|
||||||
else if (avail < (int)w) vis_w = (uint16_t)avail;
|
|
||||||
}
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
|
||||||
if (skip < (int)h && vis_w) { /* +YOFF: центрирование */
|
|
||||||
/* noclip-ядро ширину не ограничивает, поэтому при обрезке справа
|
|
||||||
* идём общим путём: это редкие кадры (подъём в двери уровня раз
|
|
||||||
* за уровень), цена подготовки тут не видна. */
|
|
||||||
if (vis_w == w && pop_onscreen_cols(bx, top + skip + POP_YOFF, w,
|
|
||||||
(uint16_t)((int)h - skip)))
|
|
||||||
gfx_blit_cols_part_noclip(bx, top + skip + POP_YOFF, img, flip,
|
|
||||||
(uint8_t)skip, 0);
|
|
||||||
else
|
|
||||||
gfx_blit_cols_part_w(bx, top + skip + POP_YOFF, img, flip,
|
|
||||||
skip, 0, (uint8_t)(vis_w == w ? 0 : vis_w));
|
|
||||||
}
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
|
|
||||||
* узко, только по прямоугольнику Кида и только при выходе. */
|
|
||||||
pop_clip_sprite(bx, w, top + skip + POP_YOFF, (int)h - skip);
|
|
||||||
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
|
|
||||||
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
|
|
||||||
kid_lx[dp] = bx; kid_ly[dp] = top + skip;
|
|
||||||
kid_lw[dp] = vis_w; kid_lh[dp] = (uint16_t)((int)h - skip);
|
|
||||||
kid_lvalid[dp] = (skip < (int)h && vis_w != 0);
|
|
||||||
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
|
||||||
}
|
|
||||||
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
|
||||||
* Kid — со СВОИМ прямоугольником heal (kid_overlay_add). */
|
|
||||||
{
|
|
||||||
int sx, sy; uint16_t sw, sh;
|
|
||||||
if (pop_sword_draw(&Kid, &fr, obj_x, obj_y, &sx, &sy, &sw, &sh))
|
|
||||||
kid_overlay_add(gfx_get_draw_page() & 1, sx, sy, sw, sh);
|
|
||||||
}
|
|
||||||
gfx_w0_unmap();
|
|
||||||
/* Окно fore-слоя = объединение «Kid + клинок» (с обрезкой clip_char):
|
|
||||||
* fore-проход вернёт куски тайлов ТОЛЬКО поверх него, а не целыми
|
|
||||||
* тайлами — см. pop_bg.c, fclip_on. Ставим и когда рисовать нечего:
|
|
||||||
* нулевое окно = fore-проход не трогает экран, что и правильно. */
|
|
||||||
kid_fore_clip(gfx_get_draw_page() & 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Вернуть окно fore-клипа на прямоугольник ПОСЛЕДНЕГО кадра Kid.
|
|
||||||
*
|
|
||||||
* Окно (pop_fore_set_clip) — одно на всех, и его ставит КАЖДЫЙ, кто рисует
|
|
||||||
* персонажа: kid_draw в конце — своё, pop_guard_draw в конце — своё. Пока
|
|
||||||
* страж рисовался строго ДО Кида, к моменту pop_fore_over_kid в окне
|
|
||||||
* оказывался Kid, и всё сходилось само. Как только порядок стал
|
|
||||||
* переменным (guard_over_kid в roomtest.c — порядок задаёт обход тайлов),
|
|
||||||
* при «страж поверх» окно оставалось СТРАЖЬИМ и fore-проход Кида отсекался
|
|
||||||
* целиком: Кид рисовался поверх передних столбов.
|
|
||||||
*
|
|
||||||
* Поэтому окно восстанавливается явно перед pop_fore_over_kid, а не
|
|
||||||
* держится «по счастливому порядку вызовов». */
|
|
||||||
void kid_fore_clip_restore(void)
|
|
||||||
{
|
|
||||||
kid_fore_clip(gfx_get_draw_page() & 1);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "pop_char.h"
|
#include "pop_char.h"
|
||||||
#include "kid_data.h" /* kframe — тип кадра для pop_sword_draw */
|
#include "kid_data.h" /* kframe — тип кадра для pop_sword_draw */
|
||||||
|
#include <sprite.h> /* atlas_t — набор спрайтов Кида (см. kidp ниже) */
|
||||||
|
|
||||||
/* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */
|
/* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */
|
||||||
extern pop_char_t Kid;
|
extern pop_char_t Kid;
|
||||||
@@ -114,41 +115,16 @@ uint8_t kid_cur_flags(void);
|
|||||||
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
|
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
|
||||||
void kid_set_seq(uint8_t seq_id);
|
void kid_set_seq(uint8_t seq_id);
|
||||||
|
|
||||||
/* Рендер-смещение Kid по ЛОГИЧЕСКОЙ X (порт xpos_in_drawn_room, seg004):
|
|
||||||
* когда комната Kid ≠ отрисованной (straddle у шва), спрайт рисуется со
|
|
||||||
* сдвигом ∓140 (левый сосед −140 / правый +140), при этом Kid.x/коллизия
|
|
||||||
* остаются в кадре комнаты Kid. 0 — комнаты совпадают (обычный случай).
|
|
||||||
* Влияет на kid_draw и метрики футпринта (fore) — heal берёт запомненный
|
|
||||||
* прямоугольник, поэтому согласован автоматически. */
|
|
||||||
void pop_kid_set_render_dx(int dx);
|
|
||||||
|
|
||||||
/* Нарисовать текущий кадр Kid (банк GFX_BANK_SPRITE — видео-ОЗУ, без
|
|
||||||
* теневой копии; запоминает прямоугольник для kid_heal). */
|
|
||||||
void kid_draw(void);
|
|
||||||
|
|
||||||
/* Вернуть окно fore-клипа на прямоугольник последнего кадра Kid. Звать
|
|
||||||
* ПЕРЕД pop_fore_over_kid, если между ним и kid_draw кто-то ещё рисовал
|
|
||||||
* персонажа (pop_guard_draw ставит окно по стражу). */
|
|
||||||
void kid_fore_clip_restore(void);
|
|
||||||
|
|
||||||
/* draw_hurt_splash (seg006:2003): «брызги» урона (image 218) поверх Kid —
|
|
||||||
* звать СРАЗУ после kid_draw в кадре, когда HP убавилось (pop_kid_hurt). */
|
|
||||||
void kid_draw_splash(void);
|
|
||||||
|
|
||||||
/* Блит спрайта chtab_2 по id, левым верхним углом в (x,y) — для полосы HP
|
/* Блит спрайта chtab_2 по id, левым верхним углом в (x,y) — для полосы HP
|
||||||
* (id 216 полное деление / 217 пустое). */
|
* (id 216 полное деление / 217 пустое). */
|
||||||
void pop_kid_img_blit(uint8_t image, int x, int y);
|
void pop_kid_img_blit(uint8_t image, int x, int y);
|
||||||
|
|
||||||
/* Восстановить фон (из теневой копии) на месте ПРЕДЫДУЩЕГО кадра Kid —
|
/* Атласы Кида (chtab_2) и его текущий кадр — набор слота POP_CH_KID общей
|
||||||
* вместо перерисовки всего экрана. Звать ПЕРЕД kid_tick/kid_draw. */
|
* отрисовки персонажей (pop_cdraw.c). Наружу отданы как данные, а не через
|
||||||
void kid_heal(void);
|
* аксессоры: читаются раз в кадр из другого банка, где вызов стоил бы
|
||||||
|
* дороже самого чтения (данные в _DATA видны всем без трамплина). */
|
||||||
/* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg
|
extern atlas_t kidp[];
|
||||||
* pop_fore_over_kid): obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
extern uint8_t kid_npages;
|
||||||
* спрайта, w/h — размер кадра. */
|
extern kframe kid_frame;
|
||||||
int kid_fp_obj_x(void);
|
|
||||||
int kid_fp_obj_y(void);
|
|
||||||
uint16_t kid_fp_width(void);
|
|
||||||
uint16_t kid_fp_height(void);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -33,6 +33,14 @@
|
|||||||
#include "pop_level.h" /* pop_level_start_room — вход в дверь уровня */
|
#include "pop_level.h" /* pop_level_start_room — вход в дверь уровня */
|
||||||
#include "pop_guard.h" /* SWORD_2_DRAWN — ветка приземления с мечом */
|
#include "pop_guard.h" /* SWORD_2_DRAWN — ветка приземления с мечом */
|
||||||
#include "pop_tune.h" /* POP_ENABLE_JUMP_GRAB и прочие настройки */
|
#include "pop_tune.h" /* POP_ENABLE_JUMP_GRAB и прочие настройки */
|
||||||
|
#include "pop_cdraw.h" /* габарит кадра персонажа (pop_cd) */
|
||||||
|
|
||||||
|
/* Габарит последнего нарисованного кадра АКТИВНОГО персонажа (char_width_half
|
||||||
|
* и char_top_y в set_char_collision, seg006:0723). У каждого слота он свой:
|
||||||
|
* до DRAW-CHAR отрисовка была продублирована, метрики Кида лежали отдельно, и
|
||||||
|
* коллизия СТРАЖА считалась по ширине спрайта КИДА. Отставание на кадр —
|
||||||
|
* как в оригинале (cur_frame берётся тем же кадром, что рисуется). */
|
||||||
|
#define CD_ACT (&pop_cd[Char.charid == CHARID_0_KID ? POP_CH_KID : POP_CH_OPP])
|
||||||
|
|
||||||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||||||
#define TILE_EMPTY 0
|
#define TILE_EMPTY 0
|
||||||
@@ -307,7 +315,7 @@ static uint8_t gate_passable(int8_t col, int8_t row)
|
|||||||
{
|
{
|
||||||
uint8_t gm = gate_modif(col, row);
|
uint8_t gm = gate_modif(col, row);
|
||||||
if (gm == 0xFF) return 0;
|
if (gm == 0xFF) return 0;
|
||||||
return (uint8_t)((gm >> 2) + 6) >= (uint8_t)kid_fp_height();
|
return (uint8_t)((gm >> 2) + 6) >= (uint8_t)CD_ACT->fph;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t tile_is_floor(uint8_t t)
|
static uint8_t tile_is_floor(uint8_t t)
|
||||||
@@ -1368,7 +1376,7 @@ static void pop_coll_invalidate(void) { coll_prev_row = COLL_ROW_NONE; }
|
|||||||
static void set_char_collision(void)
|
static void set_char_collision(void)
|
||||||
{
|
{
|
||||||
int e = char_x_forward_edge();
|
int e = char_x_forward_edge();
|
||||||
int wh = ((int)kid_fp_width() + 1) >> 1; /* char_width_half */
|
int wh = ((int)CD_ACT->fpw + 1) >> 1; /* char_width_half */
|
||||||
if (Char.direction < 0) { coll_xl = e; coll_xr = e + wh; }
|
if (Char.direction < 0) { coll_xl = e; coll_xr = e + wh; }
|
||||||
else { coll_xr = e; coll_xl = e - wh; }
|
else { coll_xr = e; coll_xl = e - wh; }
|
||||||
if (kid_cur_flags() & FRAME_THIN) { coll_xl += 4; coll_xr -= 4; }
|
if (kid_cur_flags() & FRAME_THIN) { coll_xl += 4; coll_xr -= 4; }
|
||||||
@@ -2073,13 +2081,14 @@ static void check_spiked(void)
|
|||||||
* спрайта (char_x_left..char_x_right, порт set_char_collision) скан ВНИЗ от
|
* спрайта (char_x_left..char_x_right, порт set_char_collision) скан ВНИЗ от
|
||||||
* curr_row через НЕ-floor тайлы; встретил пику → start_anim_spike. Габарит,
|
* curr_row через НЕ-floor тайлы; встретил пику → start_anim_spike. Габарит,
|
||||||
* а не «curr_col + колонка спереди»: иначе пики выдвигаются на тайл раньше
|
* а не «curr_col + колонка спереди»: иначе пики выдвигаются на тайл раньше
|
||||||
* (когда тело Кида ещё не над шахтой). Метрики кадра — kid_fp (габарит
|
* (когда тело Кида ещё не над шахтой). Метрики кадра — pop_cd (габарит
|
||||||
* последнего kid_draw; отставание в 1 кадр несущественно, ошибка в позднюю
|
* последнего нарисованного кадра; отставание в 1 кадр несущественно, ошибка
|
||||||
* сторону). */
|
* в позднюю сторону). */
|
||||||
static void check_spike_below(void)
|
static void check_spike_below(void)
|
||||||
{
|
{
|
||||||
int wh = ((int)kid_fp_width() + 1) >> 1; /* char_width_half */
|
const pop_cdraw_t *cd = CD_ACT;
|
||||||
int xl = kid_fp_obj_x() / 2 + 58; /* char_x_left */
|
int wh = ((int)cd->fpw + 1) >> 1; /* char_width_half */
|
||||||
|
int xl = cd->fpx / 2 + 58; /* char_x_left */
|
||||||
int xr;
|
int xr;
|
||||||
int8_t c0, c1, col;
|
int8_t c0, c1, col;
|
||||||
if (Char.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
if (Char.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ extern uint8_t pop_ceil_fell;
|
|||||||
extern uint8_t pop_debris_at;
|
extern uint8_t pop_debris_at;
|
||||||
|
|
||||||
/* Взводится в кадре, когда Kid получил урон (hitp_delta<0 оригинала) —
|
/* Взводится в кадре, когда Kid получил урон (hitp_delta<0 оригинала) —
|
||||||
* приложение рисует «брызги» kid_draw_splash() поверх Kid и снимает флаг. */
|
* отрисовка персонажа рисует «брызги» поверх Kid, а главный цикл снимает
|
||||||
|
* флаг. */
|
||||||
extern uint8_t pop_kid_hurt;
|
extern uint8_t pop_kid_hurt;
|
||||||
|
|
||||||
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
|
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
|
||||||
@@ -158,7 +159,7 @@ void pop_guard_phys_tick(void) __banked;
|
|||||||
* над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid,
|
* над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid,
|
||||||
* ушедший головой под пол при спуске/висе, не просвечивает сквозь щели).
|
* ушедший головой под пол при спуске/висе, не просвечивает сквозь щели).
|
||||||
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер.
|
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер.
|
||||||
* Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */
|
* Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт pop_char_draw. */
|
||||||
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked;
|
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked;
|
||||||
|
|
||||||
/* Парная половина clip_char: ЭКРАННЫЙ X, правее которого спрайт не рисуется
|
/* Парная половина clip_char: ЭКРАННЫЙ X, правее которого спрайт не рисуется
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages);
|
|||||||
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages);
|
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages);
|
||||||
|
|
||||||
/* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и
|
/* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и
|
||||||
* уменьшить счётчики. Звать из главного цикла в слое ФОНА — до kid_draw. */
|
* уменьшить счётчики. Звать из главного цикла в слое ФОНА — до персонажей. */
|
||||||
void pop_redraw_needed(void);
|
void pop_redraw_needed(void);
|
||||||
|
|
||||||
/* Сбросить все пометки (смена комнаты/уровня). */
|
/* Сбросить все пометки (смена комнаты/уровня). */
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* pop_tile.c — общие ЛИСТЬЯ слоя фона: блит куска атласа, чтение тайла
|
||||||
|
* комнаты, таблица тайлов. РЕЗИДЕНТ W1 (_CODE).
|
||||||
|
*
|
||||||
|
* Мотивация раскладки (почему резидент, а не банк) — в шапке pop_tile.h:
|
||||||
|
* коротко, эти функции и таблицы нужны ОБЕИМ половинам слоя фона (горячей —
|
||||||
|
* fore-проход каждый кадр, холодной — полная отрисовка комнаты), а код и
|
||||||
|
* rodata банка видны только пока банк замаплен. W1 замаплено всегда.
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include "pop_bg.h" /* POP_YOFF */
|
||||||
|
#include "pop_tile.h"
|
||||||
|
#include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */
|
||||||
|
#include "_pop_draw.h" /* pop_heal_fast */
|
||||||
|
|
||||||
|
/* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */
|
||||||
|
atlas_t pop_env[5], pop_wall_at, pop_fore_at, pop_pot_at;
|
||||||
|
|
||||||
|
const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg;
|
||||||
|
const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod;
|
||||||
|
uint8_t pop_t_room;
|
||||||
|
pop_rnd_t pop_t_seed;
|
||||||
|
|
||||||
|
int pop_t_clip_top;
|
||||||
|
int pop_t_fclip_x0, pop_t_fclip_y0, pop_t_fclip_x1, pop_t_fclip_y1;
|
||||||
|
uint8_t pop_t_fclip_on;
|
||||||
|
uint8_t pop_t_bake_rest;
|
||||||
|
|
||||||
|
/* Позиция склянки внутри тайла зелья — поля fore_x/fore_y записи 0x0A той же
|
||||||
|
* таблицы. Вынесены МАКРОСАМИ (одно место правки, дублирования данных нет):
|
||||||
|
* пока таблица была `static const`, SDCC сворачивала `tile_table[10].fore_x`
|
||||||
|
* в константу, но глобальную (её читают оба банка) уже не сворачивает —
|
||||||
|
* чтение полей в рантайме стоило +150 Б и IX-кадр на 13 локалов. */
|
||||||
|
#define POT_FORE_X 2
|
||||||
|
#define POT_FORE_Y (-3)
|
||||||
|
|
||||||
|
/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */
|
||||||
|
const piece pop_tile_table[31] = {
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 00 empty */
|
||||||
|
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 01 floor */
|
||||||
|
{127, 1, 0, 133, 1, 2, 145, 0, 43, 0, 0, 0}, /* 02 spike */
|
||||||
|
{ 92, 1, 0, 93, 1, 2, 0, 94, 43, 95, 1, 0}, /* 03 pillar */
|
||||||
|
{ 46, 1, 0, 47, 1, 2, 0, 48, 43, 49, 3, 0}, /* 04 gate */
|
||||||
|
{ 41, 1, 1, 35, 1, 3, 145, 0, 36, 0, 0, 0}, /* 05 stuck floor */
|
||||||
|
{ 41, 1, 0, 42, 1, 2, 145, 0, 96, 0, 0, 0}, /* 06 close button */
|
||||||
|
{ 46, 1, 0, 0, 0, 2, 0, 0, 43, 49, 3, 0}, /* 07 door top w/floor */
|
||||||
|
{ 86, 1, 0, 87, 1, 2, 0, 0, 43, 88, 1, 0}, /* 08 big pillar bottom */
|
||||||
|
{ 0, 0, 0, 89, 0, 3, 0, 90, 0, 91, 1, 3}, /* 09 big pillar top */
|
||||||
|
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 12, POT_FORE_X, POT_FORE_Y}, /* 0A potion */
|
||||||
|
{ 0, 1, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0}, /* 0B loose floor */
|
||||||
|
{ 0, 0, 0, 0, 0, 2, 0, 0, 85, 49, 3, 0}, /* 0C door top */
|
||||||
|
{ 75, 1, 0, 42, 1, 2, 0, 0, 43, 77, 0, 0}, /* 0D mirror */
|
||||||
|
{ 97, 1, 0, 98, 1, 2, 145, 0, 43,100, 0, 0}, /* 0E debris */
|
||||||
|
{147, 1, 0, 42, 1, 1, 145, 0,149, 0, 0, 0}, /* 0F open button */
|
||||||
|
{ 41, 1, 0, 37, 0, 0, 0, 38, 43, 0, 0, 0}, /* 10 leveldoor left */
|
||||||
|
{ 0, 0, 0, 39, 1, 2, 0, 40, 43, 0, 0, 0}, /* 11 leveldoor right */
|
||||||
|
{ 0, 0, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 12 chomper */
|
||||||
|
{ 41, 1, 0, 42, 1, 2, 0, 0, 43, 0, 0, 0}, /* 13 torch */
|
||||||
|
{ 0, 0, 0, 1, 1, 2, 0, 2, 0, 0, 0, 0}, /* 14 wall */
|
||||||
|
{ 30, 1, 0, 31, 1, 2, 0, 0, 43, 0, 0, 0}, /* 15 skeleton */
|
||||||
|
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 16 sword */
|
||||||
|
{ 41, 1, 0, 10, 0, 0, 0, 11, 43, 0, 0, 0}, /* 17 balcony left */
|
||||||
|
{ 0, 0, 0, 12, 1, 2, 0, 13, 43, 0, 0, 0}, /* 18 balcony right */
|
||||||
|
{ 92, 1, 0, 42, 1, 2, 145, 0, 43, 95, 1, 0}, /* 19 lattice pillar */
|
||||||
|
{ 1, 0, 0, 0, 0, 0, 0, 0, 2, 9, 0,-53}, /* 1A lattice down */
|
||||||
|
{ 3, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1B lattice small */
|
||||||
|
{ 4, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1C lattice left */
|
||||||
|
{ 5, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1D lattice right */
|
||||||
|
{ 97, 1, 0, 98, 1, 2, 0, 0, 43,100, 0, 0}, /* 1E debris w/torch */
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t POP_COL_XH[10] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36};
|
||||||
|
const uint8_t POP_WALL_FRAM_BOTTOM[4] = {7, 9, 5, 3};
|
||||||
|
const uint8_t POP_WALL_FRAM_MAIN[4] = {8, 10, 6, 4};
|
||||||
|
const uint8_t POP_SPIKES_FRAM_LEFT[10] = {0, 128, 129, 130, 131, 132, 131, 129, 128, 0};
|
||||||
|
const uint8_t POP_LOOSE_FRAM_LEFT[12] = {41,69,41,70,70,41,41,41,70,70,70,0};
|
||||||
|
const uint8_t POP_LOOSE_FRAM_BOTTOM[12] = {43,73,43,74,74,43,43,43,74,74,74,0};
|
||||||
|
|
||||||
|
/* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору.
|
||||||
|
* В режиме ЗАПЕЧКИ фона отдаёт кадр ПОКОЯ — транзиентный кадр нельзя
|
||||||
|
* консервировать в ОЗУ-копию (см. pop_t_bake_rest в pop_bg.c). */
|
||||||
|
uint8_t pop_loose_frame(uint8_t m)
|
||||||
|
{
|
||||||
|
if (pop_t_bake_rest) return 0;
|
||||||
|
if (m & 0x80) { m &= 0x7F; if (m > 10) return 1; }
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Блит куска атласа ---------------------------------------------- */
|
||||||
|
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||||
|
{
|
||||||
|
const uint8_t *img;
|
||||||
|
uint16_t w, h;
|
||||||
|
int top, dx, dy, dw, dh, sx = 0, sy = 0;
|
||||||
|
if (idx >= a->count)
|
||||||
|
return;
|
||||||
|
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
|
||||||
|
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
|
||||||
|
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
|
||||||
|
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
|
||||||
|
* atlas_image, ни за map/unmap. */
|
||||||
|
if (pop_t_fclip_on &&
|
||||||
|
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 ||
|
||||||
|
ybottom + POP_YOFF + 1 <= pop_t_fclip_y0 ||
|
||||||
|
ybottom + POP_YOFF - 64 >= pop_t_fclip_y1))
|
||||||
|
return;
|
||||||
|
img = (const uint8_t *)atlas_image(a, idx);
|
||||||
|
gfx_w0_map(a->page);
|
||||||
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
|
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||||||
|
dx = x; dy = top; dw = (int)w; dh = (int)h;
|
||||||
|
if (pop_t_clip_top && dy < pop_t_clip_top) { /* полоса у потолка */
|
||||||
|
sy = pop_t_clip_top - dy; dy = pop_t_clip_top; dh -= sy;
|
||||||
|
}
|
||||||
|
if (pop_t_fclip_on) { /* окно fore-слоя */
|
||||||
|
if (dx < pop_t_fclip_x0) { int d = pop_t_fclip_x0 - dx; sx += d; dx += d; dw -= d; }
|
||||||
|
if (dy < pop_t_fclip_y0) { int d = pop_t_fclip_y0 - dy; sy += d; dy += d; dh -= d; }
|
||||||
|
if (dx + dw > pop_t_fclip_x1) dw = pop_t_fclip_x1 - dx;
|
||||||
|
if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
|
||||||
|
}
|
||||||
|
if (w && h && dw > 0 && dh > 0) {
|
||||||
|
/* Целиком на экране — линейное ядро без клипа: ~2.9× быстрее
|
||||||
|
* (замер: 4617 против 13288 тактов на спрайт 32×3; общее ядро
|
||||||
|
* gfx_blit платит за клип/16-бит/split, а не за пиксели).
|
||||||
|
* W3-скобку ставит сама libbgi: из кода, собранного с --w3, её
|
||||||
|
* вызывать нельзя (окно W3 уходит под видеобанк). */
|
||||||
|
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
|
||||||
|
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
|
||||||
|
if (sx == 0 && sy == 0 && dw == (int)w && dh == (int)h)
|
||||||
|
gfx_blit_noclip(dx, dy, img);
|
||||||
|
else
|
||||||
|
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
|
||||||
|
(uint8_t)dw, (uint8_t)dh);
|
||||||
|
} else {
|
||||||
|
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана — с клипом */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> 5], (uint8_t)(id & 31), x, yb); }
|
||||||
|
void pop_wall_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_wall_at, id, x, yb); }
|
||||||
|
void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); }
|
||||||
|
void pop_pot_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_pot_at, id, x, yb); }
|
||||||
|
|
||||||
|
void pop_heal_off(int x, int y, int w, int h)
|
||||||
|
{
|
||||||
|
pop_heal_fast(x, y + POP_YOFF, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Чтение тайла отрисованной комнаты ------------------------------ */
|
||||||
|
uint8_t pop_tile_code(int row, int col)
|
||||||
|
{
|
||||||
|
if (col < 0) {
|
||||||
|
if (pop_t_lfg && row >= 0 && row < 3) return pop_t_lfg[row];
|
||||||
|
return 20; /* уровневая кромка = стена */
|
||||||
|
}
|
||||||
|
if (row < 0) { /* верхняя полоса: ряд 2 комнаты сверху */
|
||||||
|
if (row == -1 && pop_t_above && col < 10) return pop_t_above[col];
|
||||||
|
return 1; /* нет комнаты сверху → пол (кромка) */
|
||||||
|
}
|
||||||
|
if (row > 2 || col > 9) return 20;
|
||||||
|
return pop_t_fg[row * 10 + col];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pop_tile_mod(int row, int col)
|
||||||
|
{
|
||||||
|
if (col < 0) {
|
||||||
|
if (pop_t_lbg && row >= 0 && row < 3) return pop_t_lbg[row];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (row == -1 && pop_t_abovemod && col < 10) return pop_t_abovemod[col];
|
||||||
|
if (row < 0 || row > 2 || col > 9) return 0;
|
||||||
|
return pop_t_bg[row * 10 + col];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Порт подстановки нажатой кнопки из get_tile_to_draw (seg008:240): opener →
|
||||||
|
* floor, closer → stuck, когда таймер связи > 1. Любой слой, который
|
||||||
|
* выбирает ветку отрисовки по типу тайла, обязан смотреть на ПОДСТАВЛЕННЫЙ
|
||||||
|
* тип (иначе кнопка, на которой стоит Kid, ведёт себя как «не пол» — напр.
|
||||||
|
* climb-оверлей уходил в draw_other_overlay и закрашивал Kid целым тайлом
|
||||||
|
* вместо узкой кромки floor_left_overlay). */
|
||||||
|
uint8_t pop_tile_code_drawn(int row, int col)
|
||||||
|
{
|
||||||
|
uint8_t code = pop_tile_code(row, col);
|
||||||
|
if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||||
|
else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pop_wall_modifier(int row, int col)
|
||||||
|
{
|
||||||
|
uint8_t lw = (col > 0) ? (pop_tile_code(row, col - 1) == 20) : 1;
|
||||||
|
uint8_t rw = (col < 9) ? (pop_tile_code(row, col + 1) == 20) : 1;
|
||||||
|
if (lw && rw) return 3; /* WWW */
|
||||||
|
if (lw) return 2; /* WWS */
|
||||||
|
if (rw) return 1; /* SWW */
|
||||||
|
return 0; /* SWS */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Зелье: склянка (tiles_10_potion, seg008 draw_tile_fore) --------- *
|
||||||
|
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
||||||
|
* склянка (id 13), остальные — маленькая (id 12). Спрайты из chtab_1 (атлас
|
||||||
|
* pop_pot), поэтому НЕ из env: раньше рисовали chtab_6 id 12 (res212),
|
||||||
|
* которого в VDUNGEON нет — оттуда и «мусорный объект». Позиция оригинала:
|
||||||
|
* xh = fore_x + draw_xh, xl = 6, ybottom = fore_y + draw_main_y.
|
||||||
|
*
|
||||||
|
* Здесь, а не в банке: склянку рисует и полная отрисовка тайла (холодная
|
||||||
|
* половина), и fore-проход поверх персонажа (горячая). */
|
||||||
|
void pop_potion_flask(int row, int col, uint8_t modif)
|
||||||
|
{
|
||||||
|
uint8_t type = (uint8_t)((modif & 0xF8) >> 3);
|
||||||
|
uint8_t id = 12;
|
||||||
|
if (type == 0) return;
|
||||||
|
if (type >= 2 && type < 5) id = 13; /* большая склянка */
|
||||||
|
pop_pot_b(id, (POT_FORE_X + POP_COL_XH[col]) * 8 + 6,
|
||||||
|
POT_FORE_Y + 63 * row + 62);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Окно fore-клипа: ставит тот, кто нарисовал персонажа (pop_cdraw, банк 4),
|
||||||
|
* читает blit_b. Резидент — чтобы вызов из банка шёл без трамплина. */
|
||||||
|
void pop_fore_set_clip(int x, int y, int w, int h)
|
||||||
|
{
|
||||||
|
pop_t_fclip_x0 = x; pop_t_fclip_y0 = y;
|
||||||
|
pop_t_fclip_x1 = x + w; pop_t_fclip_y1 = y + h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ряд 0 комнаты СНИЗУ (fg-коды) — нужен draw_tile_topright/floorright, когда
|
||||||
|
* тайл нижнего ряда стал видимым (empty после падения loose): «стена вниз» =
|
||||||
|
* топрайт тайла room_B(0, col-1). 0 — нет комнаты снизу (кромка = стена). */
|
||||||
|
void pop_room_set_below(const uint8_t *below_row0_fg) { pop_t_below = below_row0_fg; }
|
||||||
|
|
||||||
|
/* Ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки у
|
||||||
|
* потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
|
||||||
|
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
|
||||||
|
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
|
||||||
|
{ pop_t_above = above_row2_fg; pop_t_abovemod = above_row2_mod; }
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* pop_tile.h — ОБЩИЕ ЛИСТЬЯ отрисовки фона: блит куска атласа, чтение тайла
|
||||||
|
* комнаты, таблица тайлов. Внутренний заголовок слоя фона, НЕ публичный
|
||||||
|
* API (публичный — pop_bg.h).
|
||||||
|
*
|
||||||
|
* ПОЧЕМУ ОТДЕЛЬНЫЙ РЕЗИДЕНТНЫЙ МОДУЛЬ (pop_tile.c в _CODE / W1), а не часть
|
||||||
|
* банка 2. Слой фона режется надвое: горячая половина (fore-проход поверх
|
||||||
|
* персонажей, каждый кадр) и холодная (полная отрисовка комнаты и точечные
|
||||||
|
* перерисовки тайлов, раз на комнату/событие). Обе половины зовут одни и те
|
||||||
|
* же листья — blit_b, tile_code, tile_table. Разложить их по банкам нельзя:
|
||||||
|
*
|
||||||
|
* - код в чужом банке зовётся ТОЛЬКО через трамплин (по `__banked` в
|
||||||
|
* объявлении), и пометка бьёт по ВСЕМ вызывающим, включая горячих;
|
||||||
|
* - `const`-таблицы банка лежат в его СТРАНИЦЕ (0xC000+) и видны, лишь
|
||||||
|
* пока банк замаплен — из другого банка их не прочитать вовсе;
|
||||||
|
* - дублировать листья в оба банка — лишние килобайты и два места правки.
|
||||||
|
*
|
||||||
|
* Резидент W1 замаплен ВСЕГДА, поэтому и горячая, и холодная половина зовут
|
||||||
|
* их обычным `call` без трамплина, а таблицы читают напрямую. Тот же приём,
|
||||||
|
* что у pop_heal_fast (pop_draw.c) и pop_sword_draw (pop_kid.c).
|
||||||
|
*
|
||||||
|
* ПИСУЧИЕ глобалы (атласы, указатели на комнату, окно клипа) лежат в _DATA
|
||||||
|
* (W2) и видны отовсюду независимо от банка — они здесь ради одного места
|
||||||
|
* определения, а не ради доступности.
|
||||||
|
*/
|
||||||
|
#ifndef POP_TILE_H
|
||||||
|
#define POP_TILE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include "pop_geom.h" /* pop_rnd_t — сид кладки комнаты */
|
||||||
|
|
||||||
|
/* ---- Атласы фона (прямая адресация id) ------------------------------ *
|
||||||
|
* env[id>>5] idx id&31 — chtab_6 environment; wall — chtab_7; fore —
|
||||||
|
* передний слой; pot — chtab_1 (зелья, меч-предмет). */
|
||||||
|
extern atlas_t pop_env[5], pop_wall_at, pop_fore_at, pop_pot_at;
|
||||||
|
|
||||||
|
/* ---- Текущая отрисованная комната ----------------------------------- *
|
||||||
|
* fg — foretable-коды, bg — модификаторы; lfg/lbg — правый столбец ЛЕВОГО
|
||||||
|
* соседа (кромка); below — ряд 0 комнаты снизу (11 байт, см. pop_bg.h);
|
||||||
|
* above/abovemod — ряд 2 комнаты сверху (полоса кладки у потолка). */
|
||||||
|
extern const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg;
|
||||||
|
extern const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod;
|
||||||
|
extern uint8_t pop_t_room; /* номер комнаты — зерно раскладки стен */
|
||||||
|
extern pop_rnd_t pop_t_seed; /* сид кладки (seg009 PRNG) */
|
||||||
|
|
||||||
|
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра выше границы не
|
||||||
|
* рисуются. Нужен полосе кладки у потолка (ряд −1): высокие куски не должны
|
||||||
|
* лезть в верхний борт. 0 — без клипа. */
|
||||||
|
extern int pop_t_clip_top;
|
||||||
|
|
||||||
|
/* ОКНО FORE-СЛОЯ (экранные координаты, полуинтервал). Пока включено,
|
||||||
|
* blit_b рисует только ту часть куска, что попадает в окно.
|
||||||
|
*
|
||||||
|
* Зачем: fore-слой существует РОВНО для того, чтобы вернуть куски тайлов
|
||||||
|
* поверх нарисованного персонажа — за пределами его прямоугольника в
|
||||||
|
* видеопамяти и так лежит правильный фон. А рисовал он тайлы ЦЕЛИКОМ:
|
||||||
|
* стена (код 20) — блок 64×63 = 4032 пикселя, и таких кусков за проход до
|
||||||
|
* девяти. Замер в MAME 2026-07-29 (комната 3, Kid + страж): проход поверх
|
||||||
|
* Kid — 402 816 тактов, поверх стража — 431 964, вместе 78 % всей работы
|
||||||
|
* кадра при бюджете 430 000. С окном большинство кусков отсекается целиком,
|
||||||
|
* остальные режутся до пересечения. */
|
||||||
|
extern int pop_t_fclip_x0, pop_t_fclip_y0, pop_t_fclip_x1, pop_t_fclip_y1;
|
||||||
|
extern uint8_t pop_t_fclip_on;
|
||||||
|
|
||||||
|
/* ---- Таблица тайлов (piece, seg008.c:27) ---------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t base_id, floor_left; int8_t base_y;
|
||||||
|
uint8_t right_id, floor_right; int8_t right_y;
|
||||||
|
uint8_t stripe_id, topright_id, bottom_id, fore_id, fore_x; int8_t fore_y;
|
||||||
|
} piece;
|
||||||
|
|
||||||
|
extern const piece pop_tile_table[31];
|
||||||
|
|
||||||
|
/* Таблицы, нужные ОБЕИМ половинам слоя фона (односторонние живут по месту). */
|
||||||
|
extern const uint8_t POP_COL_XH[10]; /* x тайла = COL_XH[col]*8 */
|
||||||
|
extern const uint8_t POP_WALL_FRAM_BOTTOM[4]; /* нижняя грань стены по bg */
|
||||||
|
extern const uint8_t POP_WALL_FRAM_MAIN[4]; /* основная грань стены */
|
||||||
|
extern const uint8_t POP_SPIKES_FRAM_LEFT[10]; /* кадр пик в своей ячейке */
|
||||||
|
extern const uint8_t POP_LOOSE_FRAM_LEFT[12]; /* верх дрожащей плиты */
|
||||||
|
extern const uint8_t POP_LOOSE_FRAM_BOTTOM[12]; /* низ дрожащей плиты */
|
||||||
|
|
||||||
|
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
||||||
|
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
|
||||||
|
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
|
||||||
|
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
|
||||||
|
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Фон обязан быть
|
||||||
|
* состоянием ПОКОЯ; анимация каждый кадр кладётся поверх (банк SPRITE). */
|
||||||
|
extern uint8_t pop_t_bake_rest;
|
||||||
|
|
||||||
|
/* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */
|
||||||
|
uint8_t pop_loose_frame(uint8_t modif);
|
||||||
|
|
||||||
|
/* ---- Блит куска атласа ---------------------------------------------- *
|
||||||
|
* НИЗ спрайта на ybottom (room-space, POP_YOFF добавляется внутри), левый
|
||||||
|
* край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в
|
||||||
|
* W0). Режется окном fore-клипа и pop_t_clip_top. */
|
||||||
|
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom);
|
||||||
|
void pop_env_b(uint8_t id, int x, int yb);
|
||||||
|
void pop_wall_b(uint8_t id, int x, int yb);
|
||||||
|
void pop_fore_b(uint8_t id, int x, int yb);
|
||||||
|
void pop_pot_b(uint8_t id, int x, int yb);
|
||||||
|
|
||||||
|
/* heal в room-space (со сдвигом POP_YOFF) — все heal слоя фона/loose. */
|
||||||
|
void pop_heal_off(int x, int y, int w, int h);
|
||||||
|
|
||||||
|
/* ---- Чтение тайла отрисованной комнаты ------------------------------ */
|
||||||
|
uint8_t pop_tile_code(int row, int col);
|
||||||
|
uint8_t pop_tile_mod(int row, int col);
|
||||||
|
/* Тип «как он РИСУЕТСЯ» — подстановка нажатой кнопки (get_tile_to_draw,
|
||||||
|
* seg008:240): opener → floor, closer → stuck при таймере связи > 1. */
|
||||||
|
uint8_t pop_tile_code_drawn(int row, int col);
|
||||||
|
/* seg008.c:1255: сосед — стена, если его тип == 20. 0 SWS / 1 SWW / 2 WWS
|
||||||
|
* / 3 WWW. На кромке col0 сосед по умолчанию = стена (как render_room). */
|
||||||
|
uint8_t pop_wall_modifier(int row, int col);
|
||||||
|
|
||||||
|
/* Склянка зелья (draw_tile_fore, chtab_1): нужна и полной отрисовке тайла, и
|
||||||
|
* fore-проходу поверх персонажа. */
|
||||||
|
void pop_potion_flask(int row, int col, uint8_t modif);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -479,7 +479,7 @@ void pop_process_trobs(uint8_t cur_room) __banked
|
|||||||
/* ПИКИ — редрой КАЖДЫЙ кадр, пока trob жив: animate_spike
|
/* ПИКИ — редрой КАЖДЫЙ кадр, пока trob жив: animate_spike
|
||||||
* (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, а не по смене кадра.
|
* (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, а не по смене кадра.
|
||||||
* Это не «лишняя работа оригинала»: выдвинутая пика живёт только в
|
* Это не «лишняя работа оригинала»: выдвинутая пика живёт только в
|
||||||
* видео-ОЗУ (банк SPRITE, в фон не запечена), а kid_heal каждый кадр
|
* видео-ОЗУ (банк SPRITE, в фон не запечена), а heal персонажа каждый кадр
|
||||||
* возвращает под спрайтом Кида ПЕЧЁНЫЙ фон — то есть стирает те
|
* возвращает под спрайтом Кида ПЕЧЁНЫЙ фон — то есть стирает те
|
||||||
* части остриёв, что попали под его прямоугольник. При редрое
|
* части остриёв, что попали под его прямоугольник. При редрое
|
||||||
* по смене кадра пика держится 15 кадров (modif 0x8F..0x81) с
|
* по смене кадра пика держится 15 кадров (modif 0x8F..0x81) с
|
||||||
@@ -502,7 +502,7 @@ void pop_process_trobs(uint8_t cur_room) __banked
|
|||||||
if (room == cur_room && code == TILE_LEVELDOOR) {
|
if (room == cur_room && code == TILE_LEVELDOOR) {
|
||||||
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
|
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
|
||||||
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
|
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
|
||||||
* kid_heal вернул бы из фона закрытую створку. Куски двери
|
* heal персонажа вернул бы из фона закрытую створку. Куски двери
|
||||||
* непрозрачны, поэтому wipe не нужен. */
|
* непрозрачны, поэтому wipe не нужен. */
|
||||||
/* Створку рисует ПРАВАЯ половина двери; последний кадр анимации
|
/* Створку рисует ПРАВАЯ половина двери; последний кадр анимации
|
||||||
* помечаем на обе страницы, иначе дверь дрожит через кадр. */
|
* помечаем на обе страницы, иначе дверь дрожит через кадр. */
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "pop_redraw.h"
|
#include "pop_redraw.h"
|
||||||
#include "pop_geom.h" /* pop_y_to_row, TILE_SIZEX — тайл объекта */
|
#include "pop_geom.h" /* pop_y_to_row, TILE_SIZEX — тайл объекта */
|
||||||
#include "pop_guard.h"
|
#include "pop_guard.h"
|
||||||
|
#include "pop_cdraw.h" /* общая отрисовка персонажей (слоты Кид/соперник) */
|
||||||
#include "pop_cheat.h"
|
#include "pop_cheat.h"
|
||||||
#include "pop_tune.h"
|
#include "pop_tune.h"
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ __sfr __at (0xFE) io_border;
|
|||||||
|
|
||||||
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
||||||
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
||||||
* (коллизия/физика), 4 = pop_gdraw (отрисовка стража + полоса HP),
|
* (коллизия/физика), 4 = pop_cdraw (отрисовка персонажей + полоса HP),
|
||||||
* 5 = pop_ctrl (диспетчер управления).
|
* 5 = pop_ctrl (диспетчер управления).
|
||||||
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
||||||
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
||||||
@@ -127,7 +128,7 @@ static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_ro
|
|||||||
|
|
||||||
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
||||||
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
|
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
|
||||||
* же → 0. Звать каждый кадр перед kid_draw. */
|
* же → 0. Звать каждый кадр перед отрисовкой персонажей. */
|
||||||
static void update_kid_render_dx(void)
|
static void update_kid_render_dx(void)
|
||||||
{
|
{
|
||||||
int dx = 0;
|
int dx = 0;
|
||||||
@@ -135,7 +136,7 @@ static void update_kid_render_dx(void)
|
|||||||
if (kid_room == pop_room_link(cur_room, 0)) dx = -140; /* left neighbour */
|
if (kid_room == pop_room_link(cur_room, 0)) dx = -140; /* left neighbour */
|
||||||
else if (kid_room == pop_room_link(cur_room, 1)) dx = 140; /* right neighbour */
|
else if (kid_room == pop_room_link(cur_room, 1)) dx = 140; /* right neighbour */
|
||||||
}
|
}
|
||||||
pop_kid_set_render_dx(dx);
|
pop_char_set_render_dx(POP_CH_KID, dx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Порядок «Kid vs страж» (BUG-DRAWORDER-1) -----------------------
|
/* ---- Порядок «Kid vs страж» (BUG-DRAWORDER-1) -----------------------
|
||||||
@@ -211,14 +212,14 @@ static uint8_t guard_over_kid(void)
|
|||||||
int kk, gk;
|
int kk, gk;
|
||||||
if (Guard.charid == 0 || Guard.room != cur_room) return 0;
|
if (Guard.charid == 0 || Guard.room != cur_room) return 0;
|
||||||
objtile_at_char(&kr, &kc, &Kid, kid_cur_dx(), kid_cur_dy(),
|
objtile_at_char(&kr, &kc, &Kid, kid_cur_dx(), kid_cur_dy(),
|
||||||
kid_cur_flags(), kid_fp_width());
|
kid_cur_flags(), pop_cd[POP_CH_KID].fpw);
|
||||||
objtile_at_char(&gr, &gc, &Guard, pop_gframe.dx, pop_gframe.dy,
|
objtile_at_char(&gr, &gc, &Guard, pop_gframe.dx, pop_gframe.dy,
|
||||||
pop_gframe.flags, pop_guard_fp_width());
|
pop_gframe.flags, pop_cd[POP_CH_OPP].fpw);
|
||||||
kk = OBJ_ORDER_KEY(kr, kc);
|
kk = OBJ_ORDER_KEY(kr, kc);
|
||||||
gk = OBJ_ORDER_KEY(gr, gc);
|
gk = OBJ_ORDER_KEY(gr, gc);
|
||||||
if (gk != kk) return (uint8_t)(gk > kk);
|
if (gk != kk) return (uint8_t)(gk > kk);
|
||||||
/* один тайл — ниже по obj_y рисуется позже (sort_curr_objs) */
|
/* один тайл — ниже по obj_y рисуется позже (sort_curr_objs) */
|
||||||
return (uint8_t)((int)Guard.y + pop_gframe.dy > kid_fp_obj_y());
|
return (uint8_t)((int)Guard.y + pop_gframe.dy > pop_cd[POP_CH_KID].fpy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Change-driven редрой ЛЕВОГО шва (ворота соседа room8 видны в col0): тяжёлый
|
/* Change-driven редрой ЛЕВОГО шва (ворота соседа room8 видны в col0): тяжёлый
|
||||||
@@ -461,7 +462,6 @@ int main(void)
|
|||||||
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
||||||
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
||||||
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
|
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
|
||||||
uint8_t autofrz = 0; /* ВРЕМЕННО: автостоп на падении уже сработал */
|
|
||||||
uint16_t dead_frames = 0;
|
uint16_t dead_frames = 0;
|
||||||
#ifdef ROOMNAV
|
#ifdef ROOMNAV
|
||||||
uint8_t nav_prev = 0;
|
uint8_t nav_prev = 0;
|
||||||
@@ -502,7 +502,7 @@ int main(void)
|
|||||||
/* Фон комнаты рисуется в ОБЕ графические страницы: у каждой своя
|
/* Фон комнаты рисуется в ОБЕ графические страницы: у каждой своя
|
||||||
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
||||||
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
||||||
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
|
* ТОЛЬКО прямоугольник Кида (pop_char_heal), а не весь экран. */
|
||||||
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
||||||
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
||||||
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
|
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
|
||||||
@@ -510,10 +510,8 @@ int main(void)
|
|||||||
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
|
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
|
||||||
pop_start_level();
|
pop_start_level();
|
||||||
gfx_set_draw_page(0);
|
gfx_set_draw_page(0);
|
||||||
kid_draw();
|
pop_char_draw(POP_CH_KID);
|
||||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
pop_char_fore(POP_CH_KID);
|
||||||
kid_fp_width(), kid_fp_height(),
|
|
||||||
Kid.direction, Kid.frame, Kid.action);
|
|
||||||
|
|
||||||
if (pop_ctrl_init() != 0) { /* raw-канал клавиатуры (забирает ввод у DSS) */
|
if (pop_ctrl_init() != 0) { /* raw-канал клавиатуры (забирает ввод у DSS) */
|
||||||
closegraph();
|
closegraph();
|
||||||
@@ -543,22 +541,6 @@ int main(void)
|
|||||||
* стоит, можно спокойно разбирать позу Kid и окклюзию. */
|
* стоит, можно спокойно разбирать позу Kid и окклюзию. */
|
||||||
if (kbd_raw_down(0x16)) frozen = 1;
|
if (kbd_raw_down(0x16)) frozen = 1;
|
||||||
if (kbd_raw_down(0x1E)) frozen = 0;
|
if (kbd_raw_down(0x1E)) frozen = 0;
|
||||||
/* ВРЕМЕННО: автостоп на падении соперника в провал — сцену «скелет
|
|
||||||
* просвечивает сквозь кладку» руками не поймать (падение длится
|
|
||||||
* несколько кадров). Замирает сам, дальше снимок и чтение памяти.
|
|
||||||
*
|
|
||||||
* Срабатывает ОДИН раз на падение: пока стоит фриз, состояние
|
|
||||||
* персонажа не меняется, поэтому условие остаётся истинным — без
|
|
||||||
* этого флага снятие фриза клавишей '2' тут же гасилось повторным
|
|
||||||
* автостопом в той же итерации. Взводится заново, когда падение
|
|
||||||
* кончилось (страж убран или вышел из freefall).
|
|
||||||
* Снять вместе с разбором. */
|
|
||||||
if (Guard.charid != 0 && Guard.action == 4 /* in_freefall */ &&
|
|
||||||
Guard.y > 150) {
|
|
||||||
if (!autofrz) { frozen = 1; autofrz = 1; }
|
|
||||||
} else {
|
|
||||||
autofrz = 0;
|
|
||||||
}
|
|
||||||
{ /* Читы (seg000: switch под if (cheats_enabled)). Все — по
|
{ /* Читы (seg000: switch под if (cheats_enabled)). Все — по
|
||||||
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
|
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
|
||||||
* K — убить стража;
|
* K — убить стража;
|
||||||
@@ -661,8 +643,8 @@ int main(void)
|
|||||||
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
|
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
|
||||||
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||||
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
|
pop_char_heal(POP_CH_KID); /* стереть прошлый кадр ЭТОЙ страницы */
|
||||||
pop_guard_heal(); /* и прошлый кадр стража */
|
pop_char_heal(POP_CH_OPP); /* и прошлый кадр соперника */
|
||||||
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
|
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
|
||||||
PROF(2); /* логика (тот же синий) */
|
PROF(2); /* логика (тот же синий) */
|
||||||
kid_tick(); /* play_seq: следующий кадр */
|
kid_tick(); /* play_seq: следующий кадр */
|
||||||
@@ -680,7 +662,7 @@ int main(void)
|
|||||||
pop_fell_out = 0;
|
pop_fell_out = 0;
|
||||||
if (down) { /* L2: переход в комнату снизу — продолжает падать */
|
if (down) { /* L2: переход в комнату снизу — продолжает падать */
|
||||||
enter_room(down); /* координаты Kid уже репроецированы pop_map */
|
enter_room(down); /* координаты Kid уже репроецированы pop_map */
|
||||||
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
|
gfx_set_draw_page(back); /* вернуть текущую back для отрисовки */
|
||||||
} else { /* выпал из уровня (нет комнаты снизу) — рестарт */
|
} else { /* выпал из уровня (нет комнаты снизу) — рестарт */
|
||||||
pop_start_level();
|
pop_start_level();
|
||||||
gfx_set_draw_page(back);
|
gfx_set_draw_page(back);
|
||||||
@@ -832,35 +814,31 @@ int main(void)
|
|||||||
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
||||||
PROF(6); /* спрайты: страж, Kid, fore поверх */
|
PROF(6); /* спрайты: страж, Kid, fore поверх */
|
||||||
{ /* Кто позже — тот поверх; порядок задаёт обход тайлов, а не роль
|
{ /* Кто позже — тот поверх; порядок задаёт обход тайлов, а не роль
|
||||||
* персонажа (см. guard_over_kid). */
|
* персонажа (см. guard_over_kid). Отрисовка одна на всех Char
|
||||||
|
* (pop_cdraw.c), различает их только слот. */
|
||||||
uint8_t g_after = guard_over_kid();
|
uint8_t g_after = guard_over_kid();
|
||||||
if (!g_after) pop_guard_draw();
|
if (!g_after) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
|
||||||
PROF(6); /* kid_draw (тот же циан) */
|
PROF(6); /* кадр Кида (тот же циан) */
|
||||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
pop_char_draw(POP_CH_KID); /* спрайт + брызги + клинок */
|
||||||
if (g_after) pop_guard_draw();
|
if (g_after) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
|
||||||
/* Брызги стража рисует сам pop_guard_draw (порядок objtable), а
|
/* Брызги урона рисует сама отрисовка персонажа (порядок objtable:
|
||||||
* флаг снимаем здесь: стража могло не оказаться в отрисованной
|
* сразу после него и до клинка), а флаги снимаем здесь: соперника
|
||||||
* комнате — тогда брызги просто пропадают, как в оригинале.
|
* могло не оказаться в отрисованной комнате — тогда брызги просто
|
||||||
* Снимаем ПО ФАКТУ, а не безусловно: запись в кадре удара —
|
* пропадают, как в оригинале. Снимаем ПО ФАКТУ, а не безусловно:
|
||||||
* готовая точка останова для отладки в MAME (watchpoint), и
|
* запись в кадре удара — готовая точка останова для отладки в
|
||||||
* лишней записи каждый кадр нет. */
|
* MAME (watchpoint), и лишней записи каждый кадр нет. */
|
||||||
if (pop_guard_hurt) pop_guard_hurt = 0;
|
if (pop_guard_hurt) pop_guard_hurt = 0;
|
||||||
|
if (pop_kid_hurt) pop_kid_hurt = 0;
|
||||||
}
|
}
|
||||||
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
|
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
|
||||||
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
|
|
||||||
kid_draw_splash();
|
|
||||||
pop_kid_hurt = 0;
|
|
||||||
}
|
|
||||||
pop_hp_draw(); /* полоса HP — только при изменении */
|
pop_hp_draw(); /* полоса HP — только при изменении */
|
||||||
PROF(6); /* fore поверх Kid + борта */
|
PROF(6); /* fore поверх Kid + борта */
|
||||||
/* Окно fore-клипа общее, и pop_guard_draw ставит своё. При «страж
|
/* Fore-слой Кида — В КОНЦЕ, после плиты и полосы HP: окно fore-клипа
|
||||||
* поверх» он отрабатывает ПОСЛЕ kid_draw, поэтому окно Кида
|
* одно на всех, и его ставит каждый, кто рисовал персонажа, поэтому
|
||||||
* восстанавливаем явно — иначе передние грани у Кида отсекаются и
|
* pop_char_fore(KID) заодно возвращает окно Кида (при «соперник
|
||||||
* он рисуется поверх столбов. */
|
* поверх» в нём остался бы соперник, и передние грани у Кида
|
||||||
kid_fore_clip_restore();
|
* отсеклись бы целиком — он рисовался бы поверх столбов). */
|
||||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
pop_char_fore(POP_CH_KID);
|
||||||
kid_fp_width(), kid_fp_height(),
|
|
||||||
Kid.direction, Kid.frame, Kid.action);
|
|
||||||
pop_room_clip_borders(); /* почистить борта — только в кадрах падения (гейт) */
|
pop_room_clip_borders(); /* почистить борта — только в кадрах падения (гейт) */
|
||||||
|
|
||||||
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "pop_level.h"
|
#include "pop_level.h"
|
||||||
#include "pop_redraw.h"
|
#include "pop_redraw.h"
|
||||||
#include "_pop_draw.h"
|
#include "_pop_draw.h"
|
||||||
|
#include "pop_cdraw.h"
|
||||||
#include "stubs.h"
|
#include "stubs.h"
|
||||||
#include "kidblob.h"
|
#include "kidblob.h"
|
||||||
|
|
||||||
@@ -195,10 +196,16 @@ void pop_heal_fast(int x, int y, int w, int h)
|
|||||||
|
|
||||||
uint8_t pop_current_level; /* pop_level.c под тест не линкуется; sc_init ставит 1 */
|
uint8_t pop_current_level; /* pop_level.c под тест не линкуется; sc_init ставит 1 */
|
||||||
|
|
||||||
/* Цвет стража — подмена палитры, живёт в pop_gdraw (отрисовка, в набор не
|
/* Цвет стража — подмена палитры, живёт в pop_cdraw (отрисовка, в набор не
|
||||||
* линкуется). Здесь важен только факт вызова из pop_guard_enter. */
|
* линкуется). Здесь важен только факт вызова из pop_guard_enter. */
|
||||||
void pop_guard_set_palette(uint8_t color) __banked { (void)color; }
|
void pop_guard_set_palette(uint8_t color) __banked { (void)color; }
|
||||||
|
|
||||||
|
/* Габарит последнего нарисованного кадра по слотам (pop_cdraw.c): физика
|
||||||
|
* читает из него char_width_half. Под тестом никто не рисует, поэтому все
|
||||||
|
* поля остаются нулями — ровно как было до DRAW-CHAR, когда kid_fp_width()
|
||||||
|
* возвращал 0. Тест, которому ширина важна, выставляет её сам. */
|
||||||
|
pop_cdraw_t pop_cd[POP_CH_N];
|
||||||
|
|
||||||
/* Связи комнат: нужны check_guard_fallout (возрождение скелета в комнате
|
/* Связи комнат: нужны check_guard_fallout (возрождение скелета в комнате
|
||||||
* снизу). Тест задаёт их через tk_link_down. */
|
* снизу). Тест задаёт их через tk_link_down. */
|
||||||
uint8_t tk_link_down;
|
uint8_t tk_link_down;
|
||||||
|
|||||||
@@ -112,9 +112,14 @@ def main():
|
|||||||
f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))
|
f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))
|
||||||
|
|
||||||
# seq offsets
|
# seq offsets
|
||||||
|
# kid_seq_off — МАКРО-инициализатором, не готовым массивом: заголовок
|
||||||
|
# включают почти все модули PoP, и `static const` размножал таблицу по
|
||||||
|
# каждому TU мёртвой копией (замер 2026-08-08: 230 Б × 9 модулей =
|
||||||
|
# 1840 Б впустую, в т.ч. 460 Б резидента W1/W2). Сам массив
|
||||||
|
# определяет ОДИН pop_kid.c — та же причина, что у sword_tbl ниже.
|
||||||
f.write("#define KID_NSEQ %d\n" % len(offs))
|
f.write("#define KID_NSEQ %d\n" % len(offs))
|
||||||
f.write("static const uint16_t kid_seq_off[KID_NSEQ] = {\n")
|
f.write("#define KID_SEQ_OFF_INIT { \\\n")
|
||||||
f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")
|
f.write(emit_c(offs, 8, "0x%04X").replace("\n", " \\\n") + " \\\n}\n\n")
|
||||||
|
|
||||||
# sword_tbl — макро-инициализаторами, а не готовыми массивами: так
|
# sword_tbl — макро-инициализаторами, а не готовыми массивами: так
|
||||||
# 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто
|
# 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто
|
||||||
|
|||||||
Reference in New Issue
Block a user